Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
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.",
);
});
});