Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e9af65e
Add tests
Oct 23, 2025
14e34af
Add a semi-working implementation
Oct 23, 2025
7d8b469
Push the slot usage down to the stack
Oct 23, 2025
dc99c35
Remove unused slot from double-buffering
Oct 23, 2025
2cb06eb
Fix the message
Oct 23, 2025
a298ddb
Update packages/typegpu/src/resolutionCtx.ts
aleksanderkatan Oct 23, 2025
512637a
Fix repeated logs (and repeated slot refills)
Oct 23, 2025
52ccf52
Move types to, well, types.ts
Oct 24, 2025
e6ddefd
Merge pop methods into one
Oct 24, 2025
3fcea18
Merge remote-tracking branch 'origin/main' into impr/warn-when-slot-i…
Oct 24, 2025
614c03c
Change functions to keep all of their slots and `innerFn` instead of …
Oct 28, 2025
d86ee04
Add a test for nice object stringify
Oct 28, 2025
d107e53
Merge remote-tracking branch 'origin/main' into impr/warn-when-slot-i…
Oct 28, 2025
9d6febd
Merge branch 'main' into impr/warn-when-slot-is-unused
aleksanderkatan Oct 30, 2025
d0048c8
Merge branch 'main' into impr/warn-when-slot-is-unused
aleksanderkatan Oct 30, 2025
a5df776
Merge branch 'main' into impr/warn-when-slot-is-unused
aleksanderkatan Oct 30, 2025
5ab45ed
Merge branch 'main' into impr/warn-when-slot-is-unused
aleksanderkatan Nov 3, 2025
792e046
Merge branch 'main' into impr/warn-when-slot-is-unused
aleksanderkatan Nov 14, 2025
e19e70e
Merge remote-tracking branch 'origin/main' into impr/warn-when-slot-i…
Jan 14, 2026
d140f06
Remove the warn
Jan 14, 2026
f615b05
Remove unused variable
Jan 14, 2026
e806bac
nr fix
Jan 14, 2026
534bed3
Merge branch 'main' into impr/warn-when-slot-is-unused
aleksanderkatan Jan 15, 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
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,6 @@ function makePipelines(
outputGridMutable: TgpuBufferMutable<GridData>,
) {
const initWorldAction = root['~unstable']
.with(inputGridSlot, outputGridMutable)
.with(outputGridSlot, outputGridMutable)
.prepareDispatch((xu, yu) => {
'use gpu';
Expand Down
22 changes: 17 additions & 5 deletions packages/typegpu/src/resolutionCtx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import type {
} from './types.ts';
import { CodegenState, isSelfResolvable, NormalState } from './types.ts';
import type { WgslExtension } from './wgslExtensions.ts';
import { hasTinyestMetadata } from './shared/meta.ts';
import { getName, hasTinyestMetadata } from './shared/meta.ts';

/**
* Inserted into bind group entry definitions that belong
Expand All @@ -93,6 +93,7 @@ export type ResolutionCtxImplOptions = {
type SlotBindingLayer = {
type: 'slotBinding';
bindingMap: WeakMap<TgpuSlot<unknown>, unknown>;
usedSet: WeakSet<TgpuSlot<unknown>>;
};

type BlockScopeLayer = {
Expand Down Expand Up @@ -141,11 +142,13 @@ class ItemStateStackImpl implements ItemStateStack {
this._stack.push({
type: 'slotBinding',
bindingMap: new WeakMap(pairs),
usedSet: new WeakSet(),
});
}

popSlotBindings() {
this.pop('slotBinding');
popSlotBindings(): WeakSet<TgpuSlot<unknown>> {
const slotBindings = this.pop('slotBinding') as SlotBindingLayer;
return slotBindings.usedSet;
}

pushFunctionScope(
Expand Down Expand Up @@ -188,10 +191,11 @@ class ItemStateStackImpl implements ItemStateStack {
throw new Error(`Internal error, expected a ${type} layer to be on top.`);
}

this._stack.pop();
const poppedValue = this._stack.pop();
if (type === 'item') {
this._itemDepth--;
}
return poppedValue;
}

readSlot<T>(slot: TgpuSlot<T>): T | undefined {
Expand All @@ -202,6 +206,7 @@ class ItemStateStackImpl implements ItemStateStack {
layer.usedSlots.add(slot);
} else if (layer?.type === 'slotBinding') {
const boundValue = layer.bindingMap.get(slot);
layer.usedSet.add(slot);

if (boundValue !== undefined) {
return boundValue as T;
Expand Down Expand Up @@ -537,7 +542,14 @@ export class ResolutionCtxImpl implements ResolutionCtx {
try {
return callback();
} finally {
this._itemStateStack.popSlotBindings();
const usedSlots = this._itemStateStack.popSlotBindings();
pairs.forEach((pair) => {
!usedSlots.has(pair[0]) && console.warn(
`Slot '${getName(pair[0])}' with value '${
pair[1]
}' was provided in a 'with' method despite not being utilized during resolution. Please verify that this slot was intended for use and that, in case of WGSL-implemented functions, it is properly declared with the '$uses' method.`,
);
});
}
}

Expand Down
110 changes: 108 additions & 2 deletions packages/typegpu/tests/slot.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect } from 'vitest';
import { describe, expect, vi } from 'vitest';
import * as d from '../src/data/index.ts';
import * as std from '../src/std/index.ts';
import tgpu from '../src/index.ts';
import * as std from '../src/std/index.ts';
import { it } from './utils/extendedIt.ts';
import { asWgsl } from './utils/parseResolved.ts';

Expand Down Expand Up @@ -363,4 +363,110 @@ describe('tgpu.slot', () => {
}"
`);
});

it('warns when the slot is unused in WGSL-implemented functions', () => {
using warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

const colorSlot = tgpu.slot<d.v3f>();

const getColor = tgpu.fn([], d.vec3f)`() { return vec3f(); }`;

const main = getColor.with(colorSlot, d.vec3f(0, 1, 0));

tgpu.resolve({ externals: { main } });

expect(warnSpy).toHaveBeenCalledWith(
"Slot 'colorSlot' with value 'vec3f(0, 1, 0)' was provided in a 'with' method despite not being utilized during resolution. Please verify that this slot was intended for use and that, in case of WGSL-implemented functions, it is properly declared with the '$uses' method.",
);
});

it('warns when the slot is unused in TGSL-implemented functions', () => {
using warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

const colorSlot = tgpu.slot<d.v3f>();

const getColor = tgpu.fn([], d.vec3f)(() => {
return d.vec3f();
});

const main = getColor.with(colorSlot, d.vec3f(0, 1, 0));

tgpu.resolve({ externals: { main } });

expect(warnSpy).toHaveBeenCalledWith(
"Slot 'colorSlot' with value 'vec3f(0, 1, 0)' was provided in a 'with' method despite not being utilized during resolution. Please verify that this slot was intended for use and that, in case of WGSL-implemented functions, it is properly declared with the '$uses' method.",
);
});

it('warns when the slot is unused in WGSL-implemented pipeline', ({ root }) => {
using warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

const colorSlot = tgpu.slot<d.v3f>();

const computeFn = tgpu['~unstable'].computeFn({
workgroupSize: [1, 1, 1],
in: { gid: d.builtin.globalInvocationId },
})`{ }`;

const pipeline = root['~unstable']
.with(colorSlot, d.vec3f(1, 0, 1))
.withCompute(computeFn)
.createPipeline();

tgpu.resolve({ externals: { pipeline } });

expect(warnSpy).toHaveBeenCalledWith(
"Slot 'colorSlot' with value 'vec3f(1, 0, 1)' was provided in a 'with' method despite not being utilized during resolution. Please verify that this slot was intended for use and that, in case of WGSL-implemented functions, it is properly declared with the '$uses' method.",
);
});

it('warns when the slot is unused in TGSL-implemented pipeline', ({ root }) => {
using warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

const colorSlot = tgpu.slot<d.v3f>();

const computeFn = tgpu['~unstable'].computeFn({
workgroupSize: [1, 1, 1],
in: { gid: d.builtin.globalInvocationId },
})(() => {});

const pipeline = root['~unstable']
.with(colorSlot, d.vec3f(1, 0, 1))
.withCompute(computeFn)
.createPipeline();

tgpu.resolve({ externals: { pipeline } });

expect(warnSpy).toHaveBeenCalledWith(
"Slot 'colorSlot' with value 'vec3f(1, 0, 1)' was provided in a 'with' method despite not being utilized during resolution. Please verify that this slot was intended for use and that, in case of WGSL-implemented functions, it is properly declared with the '$uses' method.",
);
});

it('distinguishes different slot usages', () => {
using warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

const colorSlot = tgpu.slot<d.v3f>();

const getColorUsingSlot = tgpu
.fn([], d.vec3f)(() => {
return colorSlot.$;
})
.with(colorSlot, d.vec3f(0, 1, 0));

const getColor = tgpu
.fn([], d.vec3f)(() => {
return d.vec3f();
})
.with(colorSlot, d.vec3f(2, 1, 0));

const main = tgpu.fn([])(() => {
getColorUsingSlot();
getColor();
});
tgpu.resolve({ externals: { main } });

expect(warnSpy).toHaveBeenCalledWith(
"Slot 'colorSlot' with value 'vec3f(2, 1, 0)' was provided in a 'with' method despite not being utilized during resolution. Please verify that this slot was intended for use and that, in case of WGSL-implemented functions, it is properly declared with the '$uses' method.",
);
});
});