Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f7c6a76
Replace warn with throw
aleksanderkatan Jul 20, 2026
e2ce177
Add logger, replace console.warns with logger warns
aleksanderkatan Jul 20, 2026
c0a42d0
Rename warning types
aleksanderkatan Jul 20, 2026
1e567ec
Split apis into two
aleksanderkatan Jul 20, 2026
b82d259
Rename warning types again
aleksanderkatan Jul 20, 2026
5cae02c
Add custom warn message, change log tests to snapshots
aleksanderkatan Jul 20, 2026
8a8d1ed
Change `enable` to `reset`, add tests
aleksanderkatan Jul 20, 2026
7e1368b
Pass prod mode to logger
aleksanderkatan Jul 20, 2026
1c4bde9
Change warn to invariant
aleksanderkatan Jul 21, 2026
d7ce0dc
Merge remote-tracking branch 'origin/main' into feat/logger
aleksanderkatan Jul 21, 2026
b4b2ba7
Change remaining calls to warns
aleksanderkatan Jul 21, 2026
5932429
Add docs
aleksanderkatan Jul 21, 2026
24dfc22
nr fix
aleksanderkatan Jul 21, 2026
46fc32e
Add tests
aleksanderkatan Jul 21, 2026
bf5b386
Finish warn implementation
aleksanderkatan Jul 22, 2026
90ff2ec
Report when giving usage
aleksanderkatan Jul 22, 2026
664d90f
Remove 'uniform' usage from confetti examples
aleksanderkatan Jul 22, 2026
742c695
Self review
aleksanderkatan Jul 22, 2026
7357da4
Review fixes
aleksanderkatan Jul 22, 2026
521ca90
Unused import
aleksanderkatan Jul 22, 2026
a4207b5
Merge branch 'main' into feat/logger
aleksanderkatan Jul 22, 2026
7b8f4d2
Review fixes
aleksanderkatan Jul 22, 2026
25ed48e
Merge branch 'feat/logger' into impr/warn-when-schema-is-not-uniform-…
aleksanderkatan Jul 22, 2026
7d87abc
Update snapshots
aleksanderkatan Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion apps/typegpu-docs/src/content/docs/apis/utils.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ If what the expression is a direct reference to an existing value (e.g. a unifor
storage binding, ...), then choose from `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`,
`'private'` or `'handle'` depending on the address space of the referred value.

### `possibleSideEffects`
### *possibleSideEffects*
The fourth optional parameter `possibleSideEffects` indicates, whether generating this snippet may produce a WGSL expression with
observable side-effects (e.g. calling a barrier, discarding a fragment,
or writing to memory).
Expand All @@ -557,3 +557,21 @@ Snippets with `possibleSideEffects: true` cannot appear in ternary
branches that get compiled to `select()`, because `select()` evaluates
both branches unconditionally - a side-effect meant to be conditional
would execute regardless of the condition.

## *warn*

TypeGPU warns you about a lot of potential problems or API misuses.
Although it is almost always better addressed properly, the `warn` object lets you silence specific warning types.

```ts twoslash
import { tgpu, d, warn } from 'typegpu';

const fn = tgpu.fn([], d.u32)(() => {
'use gpu';
return 1.5; // <- AbstractFloat needs an implicit cast to u32
});

warn.disable('implicit-conversion');
console.log(tgpu.resolve([fn])); // resolves without warning about the cast
warn.reset();
```
2 changes: 1 addition & 1 deletion apps/typegpu-docs/src/examples/react/confetti/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function App() {

const particleDataBuffer = useBuffer(d.arrayOf(ParticleData, PARTICLE_AMOUNT), {
initial: writeRandomPositions,
}).$usage('storage', 'uniform', 'vertex');
}).$usage('storage', 'vertex');

const aspectRatio = useUniform(d.f32, { initial: 1 });
const deltaTime = useUniform(d.f32);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const particleGeometryBuffer = root

const particleDataBuffer = root
.createBuffer(d.arrayOf(ParticleData, PARTICLE_AMOUNT))
.$usage('storage', 'uniform', 'vertex');
.$usage('storage', 'vertex');

let elapsedTime = 0;
const aspectRatio = root.createUniform(d.f32, canvas.width / canvas.height);
Expand Down
5 changes: 5 additions & 0 deletions packages/typegpu/src/core/buffer/buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
type TgpuReadonly,
type TgpuUniform,
} from './bufferBinding.ts';
import { warnIfNotUniformAligned } from '../pipeline/webgpuLimitations.ts';

// ----------
// Public API
Expand Down Expand Up @@ -292,6 +293,10 @@ class TgpuBufferImpl<TData extends BaseData> implements TgpuBuffer<TData> {
throw new Error(`Buffer of type ${this.dataType.type} cannot be used as ${usage}`);
}

if (usage === 'uniform') {
warnIfNotUniformAligned(this.dataType);
}

this.flags |= usage === 'uniform' ? GPUBufferUsage.UNIFORM : 0;
this.flags |= usage === 'storage' ? GPUBufferUsage.STORAGE : 0;
this.flags |= usage === 'vertex' ? GPUBufferUsage.VERTEX : 0;
Expand Down
2 changes: 1 addition & 1 deletion packages/typegpu/src/core/pipeline/applyPipelineState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { BaseData } from '../../data/wgslTypes.ts';

import type { TgpuVertexLayout } from '../vertexLayout/vertexLayout.ts';
import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
import { warnIfOverflow } from './limitsOverflow.ts';
import { warnIfOverflow } from './webgpuLimitations.ts';

// -----------------------------------------------
// shared helpers for applying pipeline state to render/compute pass encoders
Expand Down
4 changes: 3 additions & 1 deletion packages/typegpu/src/core/pipeline/computePipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
PerformanceTrackerImpl,
type PerformanceTracker,
} from './performanceTracker.ts';
import { logger } from '../../tgpuLogger.ts';

interface ComputePipelineInternals {
readonly rawPipeline: GPUComputePipeline;
Expand Down Expand Up @@ -227,7 +228,8 @@ class TgpuComputePipelineImpl implements TgpuComputePipeline {

const querySet = this.#core.performanceCallbackQuerySet;
if (!querySet) {
console.warn(
logger.warn(
'webgpu-feature-missing',
'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.',
);
return this;
Expand Down
24 changes: 0 additions & 24 deletions packages/typegpu/src/core/pipeline/limitsOverflow.ts

This file was deleted.

4 changes: 3 additions & 1 deletion packages/typegpu/src/core/pipeline/pipelineUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { memoryLayoutOf, type PrimitiveOffsetInfo } from '../../data/offsetUtils
import { sizeOf } from '../../data/sizeOf.ts';
import type { BaseData } from '../../data/wgslTypes.ts';
import { isGPUBuffer } from '../../types.ts';
import { logger } from '../../tgpuLogger.ts';

type IndirectOperation = 'dispatchWorkgroupsIndirect' | 'drawIndirect' | 'drawIndexedIndirect';
const IndirectOperationToRequiredData = {
Expand Down Expand Up @@ -51,7 +52,8 @@ export function resolveIndirectOffset(
validateIndirectBufferSize(sizeOf(indirectBuffer.dataType), offset, requiredSize, operation);

if (contiguous < requiredSize) {
console.warn(
logger.warn(
'suspicious',
`${operation}: Starting at offset ${offset}, only ${contiguous} contiguous bytes are available before padding. '${operation}' requires ${requiredSize} bytes (${IndirectOperationToRequiredData[operation]}). Reading across padding may result in undefined behavior.`,
);
}
Expand Down
7 changes: 5 additions & 2 deletions packages/typegpu/src/core/pipeline/renderPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import {
PerformanceTrackerImpl,
type PerformanceTracker,
} from './performanceTracker.ts';
import { logger } from '../../tgpuLogger.ts';

const DRAW_INDIRECT_SIZE = 16; // 4 x 4
const DRAW_INDEXED_INDIRECT_SIZE = 20; // 5 x 4
Expand Down Expand Up @@ -601,7 +602,8 @@ class TgpuRenderPipelineImpl implements TgpuRenderPipeline {

const querySet = internals.core.performanceCallbackQuerySet;
if (!querySet) {
console.warn(
logger.warn(
'webgpu-feature-missing',
'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.',
);
return this;
Expand Down Expand Up @@ -1288,7 +1290,8 @@ export function matchUpVaryingLocations(
if (locations[key] === undefined) {
saveLocation(key, customLocation);
} else if (locations[key] !== customLocation) {
console.warn(
logger.warn(
'locations-mismatched',
`Mismatched location between vertexFn (${vertexFnName}) output (${
locations[key]
}) and fragmentFn (${fragmentFnName}) input (${customLocation}) for the key "${key}", using the location set on vertex output.`,
Expand Down
111 changes: 111 additions & 0 deletions packages/typegpu/src/core/pipeline/webgpuLimitations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { alignmentOf } from '../../data/alignmentOf.ts';
import { memoryLayoutOf } from '../../data/offsetUtils.ts';
import { sizeOf } from '../../data/sizeOf.ts';
import { isWgslArray, isWgslStruct, type BaseData } from '../../data/wgslTypes.ts';
import { invariant } from '../../errors.ts';
import { getName } from '../../internal.ts';
import { roundUp } from '../../mathUtils.ts';
import type { TgpuBindGroupLayout } from '../../tgpuBindGroupLayout.ts';
import { logger } from '../../tgpuLogger.ts';

/**
* Warns if layout exceeds supported buffer count limits.
*/
export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSupportedLimits) {
const entries = Object.values(layouts)
.flatMap((layout) => Object.values(layout.entries))
.filter((entry) => entry !== null);

const uniform = entries.filter((entry) => 'uniform' in entry).length;
const storage = entries.filter((entry) => 'storage' in entry).length;

if (uniform > limits.maxUniformBuffersPerShaderStage) {
logger.warn(
'webgpu-limits-exceeded',
`Total number of uniform buffers (${uniform}) exceeds maxUniformBuffersPerShaderStage (${limits.maxUniformBuffersPerShaderStage}). Consider:
1. Grouping some of the uniforms into one using 'd.struct',
2. Increasing the limit when requesting a device or creating a root.`,
);
}

if (storage > limits.maxStorageBuffersPerShaderStage) {
logger.warn(
'webgpu-limits-exceeded',
`Total number of storage buffers (${storage}) exceeds maxStorageBuffersPerShaderStage (${limits.maxStorageBuffersPerShaderStage}).`,
);
}
}

function requiredAlignOf(schema: BaseData) {
if (isWgslStruct(schema) || isWgslArray(schema)) {
return roundUp(alignmentOf(schema), 16);
}
return alignmentOf(schema);
}

/**
* See https://www.w3.org/TR/WGSL/#address-space-layout-constraints
*/
export function warnIfNotUniformAligned(schema: BaseData) {
if (isWgslArray(schema)) {
warnIfNotUniformAligned(schema.elementType);

const stride = roundUp(sizeOf(schema.elementType), alignmentOf(schema.elementType));
if (stride % 16) {
logger.warn(
'uniform-schema-misaligned',
`\
Schema '${getName(schema.elementType) ?? '<unnamed>'}' is used in an array in an uniform buffer, and its stride (${stride}) is not a multiple of 16.
This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices.
To address this, wrap the element in 'd.align(16, ...)'.`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot wrap array element with d.align

);
}
}
if (isWgslStruct(schema)) {
Object.values(schema.propTypes).forEach(warnIfNotUniformAligned);

Object.entries(schema.propTypes).forEach(([key, value]) => {
const offset = memoryLayoutOf(schema, (schema) => schema[key]).offset;
const requiredAlignment = requiredAlignOf(value);

if (offset % requiredAlignment) {
logger.warn(
'uniform-schema-misaligned',
`\
Schema '${getName(schema) ?? '<unnamed>'}' is used in an uniform buffer, and its property '${key}' does not meet required alignment (offset is ${offset}, required alignment is ${requiredAlignment}).
This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices.
To address this, wrap the property '${key}' in 'd.align(${requiredAlignment}, ...)'.`,
);
}
});

const keys = Object.keys(schema.propTypes);
for (let i = 0; i < keys.length - 1; i++) {
const thisKey = keys[i];
const nextKey = keys[i + 1];
invariant(thisKey && nextKey);

const thisValue = schema.propTypes[thisKey];
invariant(thisValue);

if (!isWgslStruct(thisValue)) {
continue;
}

const minimumDifference = roundUp(16, sizeOf(thisValue));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const minimumDifference = roundUp(16, sizeOf(thisValue));
const minimumDifference = roundUp(sizeOf(thisValue), 16);

const thisKeyOffset = memoryLayoutOf(schema, (schema) => schema[thisKey]).offset;
const nextKeyOffset = memoryLayoutOf(schema, (schema) => schema[nextKey]).offset;
const difference = nextKeyOffset - thisKeyOffset;

if (minimumDifference > difference) {
logger.warn(
'uniform-schema-misaligned',
`\
Schema '${getName(schema) ?? '<unnamed>'}' is used in an uniform buffer, and the difference between memory offsets of '${thisKey}' and '${nextKey}' props (${difference}) is less than recommended (${minimumDifference}).
This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices.
To address this, wrap the '${thisKey}' prop in 'd.size(${minimumDifference}, ...)'.`,
);
}
}
}
}
4 changes: 3 additions & 1 deletion packages/typegpu/src/core/resolve/externals.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isLooseData } from '../../data/dataTypes.ts';
import { isWgslStruct } from '../../data/wgslTypes.ts';
import { getName, hasTinyestMetadata, isNamable, setName } from '../../shared/meta.ts';
import { logger } from '../../tgpuLogger.ts';
import { isWgsl, type ResolutionCtx } from '../../types.ts';
import type { FnExternals } from '../function/fnCore.ts';

Expand Down Expand Up @@ -124,7 +125,8 @@ export function replaceExternalsInWgsl(
}

if (typeof currentItem !== 'object' || currentItem === null || i === chain.length - 1) {
console.warn(
logger.warn(
'external-omitted',
`During resolution, the external '${chain.slice(0, i + 1).join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`,
);
return match;
Expand Down
4 changes: 3 additions & 1 deletion packages/typegpu/src/core/resolve/tgpuResolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Void } from '../../data/wgslTypes.ts';
import { type ResolutionResult, resolve as resolveImpl } from '../../resolutionCtx.ts';
import { $internal, $resolve } from '../../shared/symbols.ts';
import { isBindGroupLayout } from '../../tgpuBindGroupLayout.ts';
import { logger } from '../../tgpuLogger.ts';
import type { ShaderGenerator } from '../../tgsl/shaderGenerator.ts';
import type { ResolvableObject, SelfResolvable, Wgsl } from '../../types.ts';
import type { WgslEnableExtension } from '../../wgslExtensions.ts';
Expand Down Expand Up @@ -190,7 +191,8 @@ function resolveFromTemplate(options: TgpuExtendedResolveOptions): ResolutionRes
} = options;

if (!template) {
console.warn(
logger.warn(
'deprecated',
"Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.",
);
}
Expand Down
8 changes: 6 additions & 2 deletions packages/typegpu/src/core/root/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import { u32 } from '../../data/numeric.ts';
import { ceil } from '../../std/numeric.ts';
import { allEq } from '../../std/boolean.ts';
import { getName, setName } from '../../shared/meta.ts';
import { logger } from '../../tgpuLogger.ts';

/**
* Changes the given array to a vec of 3 numbers, filling missing values with 1.
Expand Down Expand Up @@ -659,7 +660,7 @@ class TgpuRootImpl extends WithBindingImpl implements TgpuRoot, ExperimentalTgpu
}

flush() {
console.warn('flush() has been deprecated, and has no effect.');
logger.warn('deprecated', 'flush() has been deprecated, and has no effect.');
}
}

Expand Down Expand Up @@ -741,7 +742,10 @@ export async function init(options?: InitOptions): Promise<TgpuRoot> {
if (adapter.features.has(feature)) {
availableFeatures.push(feature);
} else {
console.warn(`Optional feature "${feature}" is not supported by the adapter.`);
logger.warn(
'webgpu-feature-missing',
`Optional feature "${feature}" is not supported by the adapter.`,
);
}
}

Expand Down
7 changes: 5 additions & 2 deletions packages/typegpu/src/core/texture/texture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
SampledFlag,
} from './usageExtension.ts';
import { generateTextureMipmaps, getImageSourceDimensions, resampleImage } from './textureUtils.ts';
import { logger } from '../../tgpuLogger.ts';

export type TextureInternals = {
unwrap(): GPUTexture;
Expand Down Expand Up @@ -394,7 +395,8 @@ class TgpuTextureImpl<TProps extends TextureProps> implements TgpuTexture<TProps
const actualMipLevels = mipLevels ?? (this.props.mipLevelCount ?? 1) - baseMipLevel;

if (actualMipLevels <= 1) {
console.warn(
logger.warn(
'suspicious',
`generateMipmaps is a no-op: would generate ${actualMipLevels} mip levels (base: ${baseMipLevel}, total: ${
this.props.mipLevelCount ?? 1
})`,
Expand Down Expand Up @@ -439,7 +441,8 @@ class TgpuTextureImpl<TProps extends TextureProps> implements TgpuTexture<TProps

const layerCount = this.props.size[2] ?? 1;
if (source.length > layerCount) {
console.warn(
logger.warn(
'suspicious',
`Too many image sources provided. Expected ${layerCount} layers, got ${source.length}. Extra sources will be ignored.`,
);
}
Expand Down
9 changes: 7 additions & 2 deletions packages/typegpu/src/data/compiledIO.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { roundUp } from '../mathUtils.ts';
import { logger } from '../tgpuLogger.ts';
import { alignmentOf } from './alignmentOf.ts';
import { isDisarray, isUnstruct } from './dataTypes.ts';
import { offsetsForProps } from './offsets.ts';
Expand Down Expand Up @@ -289,7 +290,10 @@ export function buildWriter(

export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undefined {
if (!EVAL_ALLOWED_IN_ENV) {
console.warn('This environment does not allow eval - using default writer as fallback');
logger.warn(
'fallback',
'This environment does not allow eval - using default writer as fallback',
);
return undefined;
}

Expand All @@ -316,7 +320,8 @@ export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undef
compiledWriters.set(schema, fn);
return fn;
} catch (error) {
console.warn(
logger.warn(
'fallback',
`Failed to compile writer for schema: ${schema}\nReason: ${
error instanceof Error ? error.message : String(error)
}\nFalling back to default writer`,
Expand Down
Loading
Loading