Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 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
12d7796
Merge remote-tracking branch 'origin/main' into impr/warn-when-schema…
aleksanderkatan Aug 10, 2026
cc70e4f
Merge branch 'main' into impr/warn-when-schema-is-not-uniform-aligned
aleksanderkatan Aug 11, 2026
9eeba98
Merge origin/main
aleksanderkatan Aug 14, 2026
595acc1
Review fix argument order
aleksanderkatan Aug 14, 2026
805423f
Fix wrong suggestion
aleksanderkatan Aug 14, 2026
88b89eb
Merge remote-tracking branch 'origin/main' into impr/warn-when-schema…
aleksanderkatan Aug 14, 2026
2dfb69d
Potential fix for pull request finding
aleksanderkatan Aug 14, 2026
c140c5f
Merge fix
aleksanderkatan Aug 14, 2026
877ca1d
Update snapshots
aleksanderkatan Aug 14, 2026
bd348fc
Fix circular dependency
aleksanderkatan Aug 17, 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
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 @@ -34,6 +34,7 @@ import {
type TgpuReadonly,
type TgpuUniform,
} from './bufferBinding.ts';
import { warnIfNotUniformAligned } from '../pipeline/webgpuLimitations.ts';

// ----------
// Public API
Expand Down Expand Up @@ -350,6 +351,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/computePipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
import type { TgpuSlot } from '../slot/slotTypes.ts';

import type { PrimitiveOffsetInfo } from '../../data/offsetUtils.ts';
import { warnIfOverflow } from './limitsOverflow.ts';
import { warnIfOverflow } from './webgpuLimitations.ts';
import {
collectBindGroupPairs,
DISPATCH_INDIRECT_SIZE,
Expand Down
27 changes: 0 additions & 27 deletions packages/typegpu/src/core/pipeline/limitsOverflow.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/typegpu/src/core/pipeline/renderPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ import {
} from './timeable.ts';
import { nonTransferablePriorsOf } from './priors.ts';
import { type PrimitiveOffsetInfo } from '../../data/offsetUtils.ts';
import { warnIfOverflow } from './limitsOverflow.ts';
import { warnIfOverflow } from './webgpuLimitations.ts';
import {
collectBindGroupPairs,
collectVertexBufferPairs,
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 { roundUp } from '../../mathUtils.ts';
import { getName } from '../../shared/meta.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 a 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, put the element schema in a struct and wrap the prop in 'd.align(16, ...)', or use other schema like 'vec4f'.`,
);
}
}
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(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}, ...)'.`,
);
}
}
}
}
1 change: 1 addition & 0 deletions packages/typegpu/src/tgpuLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const warningTypes = [
'locations-mismatched',
'log-limit-exceeded',
'external-omitted',
'uniform-schema-misaligned',
] as const;
type WarningType = (typeof warningTypes)[number];

Expand Down
161 changes: 161 additions & 0 deletions packages/typegpu/tests/buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1773,3 +1773,164 @@ describe('ValidateBufferSchema', () => {
);
});
});

describe('Uniform alignment', () => {
it('does not report legit schemas', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createUniform(d.u32);
root.createUniform(d.struct({ p: d.u32 }));
root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.vec4f }));
root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.align(16, d.u32) }));
root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.align(32, d.u32) }));
root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.vec3f }));
root.createUniform(d.struct({ p: d.size(16, d.struct({ p: d.u32 })), q: d.u32 }));
root.createUniform(d.struct({ p: d.size(32, d.struct({ p: d.u32 })), q: d.u32 }));
root.createUniform(d.arrayOf(d.vec4f, 3));
root.createUniform(d.arrayOf(d.vec3f, 3));
root.createUniform(d.arrayOf(d.struct({ p: d.vec3f }), 3));

expect(consoleWarnSpy).not.toHaveBeenCalled();
});

it('reports props not meeting requiredAlignOf in structs', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createUniform(d.struct({ q: d.u32, p: d.struct({ p: d.u32 }) }));

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema '<unnamed>' is used in an uniform buffer, and its property 'p' does not meet required alignment (offset is 4, required alignment is 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 property 'p' in 'd.align(16, ...)'.",
]
`);
});

it('reports unaligned props in structs', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.u32 }));

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema '<unnamed>' is used in an uniform buffer, and the difference between memory offsets of 'p' and 'q' props (4) is less than recommended (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 'p' prop in 'd.size(16, ...)'.",
]
`);
});

it('reports further unaligned props in structs', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createUniform(d.struct({ p: d.vec4f, q: d.struct({ p: d.u32 }), r: d.u32 }));

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema '<unnamed>' is used in an uniform buffer, and the difference between memory offsets of 'q' and 'r' props (4) is less than recommended (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 'q' prop in 'd.size(16, ...)'.",
]
`);
});

it('reports unaligned props in structs of size greater than 16', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

// The struct has size 20, therefore the next member must start at 32 or later.
root.createUniform(
d.struct({ p: d.struct({ p: d.u32, q: d.u32, r: d.u32, s: d.u32, t: d.u32 }), q: d.u32 }),
);

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema '<unnamed>' is used in an uniform buffer, and the difference between memory offsets of 'p' and 'q' props (20) is less than recommended (32).
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 'p' prop in 'd.size(32, ...)'.",
]
`);
});

it('reports nested unaligned props in structs', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createUniform(
d.struct({ p: d.vec4f, q: d.struct({ p: d.struct({ p: d.u32 }), q: d.u32 }) }),
);

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema 'q' is used in an uniform buffer, and the difference between memory offsets of 'p' and 'q' props (4) is less than recommended (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 'p' prop in 'd.size(16, ...)'.",
]
`);
});

it('reports unaligned arrays', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createUniform(d.arrayOf(d.u32, 3));

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema 'u32' is used in an array in a uniform buffer, and its stride (4) 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, put the element schema in a struct and wrap the prop in 'd.align(16, ...)', or use other schema like 'vec4f'.",
]
`);
});

it('reports nested unaligned arrays', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createUniform(d.arrayOf(d.arrayOf(d.u32, 4), 4));

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema 'u32' is used in an array in a uniform buffer, and its stride (4) 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, put the element schema in a struct and wrap the prop in 'd.align(16, ...)', or use other schema like 'vec4f'.",
]
`);
});

it('reports when giving usage', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createBuffer(d.arrayOf(d.u32, 2)).$usage('uniform');

expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema 'u32' is used in an array in a uniform buffer, and its stride (4) 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, put the element schema in a struct and wrap the prop in 'd.align(16, ...)', or use other schema like 'vec4f'.",
]
`);
});

it('does not report twice', ({ root }) => {
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

root.createBuffer(d.arrayOf(d.u32, 2)).$usage('uniform').as('uniform');

expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
"⚠️ [uniform-schema-misaligned] ",
"Schema 'u32' is used in an array in a uniform buffer, and its stride (4) 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, put the element schema in a struct and wrap the prop in 'd.align(16, ...)', or use other schema like 'vec4f'.",
]
`);
});
});
2 changes: 1 addition & 1 deletion packages/typegpu/tests/internal/limitsOverflow.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, vi } from 'vitest';
import { it } from 'typegpu-testing-utility';
import { tgpu, d } from 'typegpu';
import { warnIfOverflow } from '../../src/core/pipeline/limitsOverflow.ts';
import { warnIfOverflow } from '../../src/core/pipeline/webgpuLimitations.ts';

describe('warnIfOverflow', () => {
const limits = {
Expand Down
Loading