diff --git a/origin-table.html b/origin-table.html new file mode 100644 index 0000000000..bd36eef202 --- /dev/null +++ b/origin-table.html @@ -0,0 +1,796 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
descriptioncurrentafternotes
basic
+
+const a = 1;
+return a;
+
[..., i32, 'constant']
+
[..., i32, snippet(1, i32, 'constant')]
+
+
It is required for the d.ref case mentioned above
+
+
+const a = 1;
+const b = a;
+return b;
+
[..., i32, 'constant']
+
[..., i32, snippet(1, i32, 'constant')]
+
+
+ Note that b 'references' a in the code, but the origin points
+ to the highest snippet in the chain.
+
fn([i32])((a) => return a);
[..., i32, 'argument']
[..., i32, 'argument']
+
+Non-ref arguments are copied, so a is the highest origin
+
+
+fn([i32])((a) => {
+  const b = a;
+  return b;
+})
+
[..., i32, 'runtime']
[..., i32, snippet(a, i32, 'argument')]
return getOne();
[..., i32, 'runtime']
[..., i32, 'runtime']
+
+Here, we can potentially run into issue with getById, calls should be represented by separate snippets
+
+
+const b = getOne();
+return b;
+
[..., i32, 'runtime']
+
[..., i32, snippet('getOne()', i32, 'runtime')]
+
+
return vec2i(7);
+
+
[..., vec2i, 'constant']
+
+
[..., vec2i, 'constant']
+
+
+const a = vec2i(7);
+return a;
+
+
[..., vec2i, 'this-function']
+
+
+[..., vec2i, snippet(vec2i(7), vec2i, 'constant')]
+
+
+const a = vec2i(7);
+const b = a;
+return b;
+
+
+[..., iptr<function,vec2i,rw>, 'this-function']
+
+
+[..., iptr<function,vec2i,rw>, snippet(vec2i(7), vec2i, 'constant')]
+
Note, how datatype has changed
fn([vec2i])((a) => return a);
[..., vec2i, 'argument']
[..., vec2i, 'argument']
Uninterrupted flow :)
+
+fn([vec2i])((a) => {
+  const b = a;
+  return b;
+})
+
[..., vec2i, 'argument']
+
[..., vec2i, snippet(a, vec2i, 'argument')]
+
+
+Implicit pointer btw, but currently cannot take pointer to te argument. TODO: carefully handle this case
+
fn([vec2i])((a) => return a.x);
[..., i32, 'argument']
[..., i32, 'argument']
Uninterrupted flow :)
+
+fn([vec2i])((a) => {
+  const b = a.x;
+  return b;
+})
+
[..., i32, 'runtime']
[..., i32, snippet('a.x', i32, 'argument')]
+
+Comparing to the above flow, origin has changed. Maybe it should remain 'runtime'
+
return getV();
[..., vec3f, 'runtime']
[..., vec3f, 'runtime']
+
+const b = getV();
+return b;
+
+
[..., vec3f, 'this-function']
+
+
[..., vec3f, snippet('getV()', vec3f, 'runtime')]
+
+ Cannot really put 'this-function' origin here, what if we + assign the result to a buffer? +
uniform
return buf.$;
[..., f32, 'runtime']
[..., f32, 'runtime']
Buffer element is naturally ephemeral
+
+const b = buf.$;
+return b;
+
[..., f32, 'runtime']
[..., f32, snippet('buf', f32, 'runtime')]
+
+On the contrary to the function call, we should distribute only
+1 reference to buffer access snippet
+
return buf.$;
[..., vec3f, 'uniform']
[..., vec3f, 'uniform']
Buffer element is NOT naturally ephemeral
+
+const b = buf.$;
+return b;
+
+
[..., iptr<uniform,vec3f,r>, 'uniform']
+
+
+[..., iptr<uniform,vec3f,r>, snippet('buf', vec3f, 'uniform')]
+
readonly
return buf.$;
[..., f32, 'runtime']
[..., f32, 'runtime']
+
+const b = buf.$;
+return b;
+
[..., f32, 'runtime']
[..., f32, snippet('buf', f32, 'runtime')]
return buf.$;
[..., vec3f, 'readonly']
[..., vec3f, 'readonly']
+
+const b = buf.$;
+return b;
+
+
[..., iptr<storage,vec3f,r>, 'readonly']
+
+
+[..., iptr<storage,vec3f,r>, snippet('buf', vec3f, 'readonly')]
+
mutable
return buf.$;
[..., f32, 'runtime']
[..., f32, 'runtime']
+
+const b = buf.$;
+return b;
+
[..., f32, 'runtime']
[..., f32, snippet('buf', f32, 'runtime')]
return buf.$;
[..., vec3f, 'mutable']
[..., vec3f, 'mutable']
+
+const b = buf.$;
+return b;
+
+
[..., iptr<storage,vec3f,rw>, 'mutable']
+
+
+[..., iptr<storage,vec3f,rw>, snippet('buf', vec3f, 'mutable')]
+
private
return pv.$;
[..., f32, 'runtime']
[..., f32, 'runtime']
+
+const b = pv.$;
+return b;
+
[..., f32, 'runtime']
[..., f32, snippet('pv', f32, 'runtime')]
return pv.$;
[..., vec3f, 'private']
[..., vec3f, 'private']
+
+const b = pv.$;
+return b;
+
+
[..., iptr<private,vec3f,rw>, 'private']
+
+
+[..., iptr<private,vec3f,rw>, snippet('pv', vec3f, 'private')]
+
workgroup
return wgv.$;
[..., f32, 'runtime']
[..., f32, 'runtime']
+
+const b = wgv.$;
+return b;
+
[..., f32, 'runtime']
[..., f32, snippet('wgv', f32, 'runtime')]
return wgv.$;
[..., vec3f, 'workgroup']
[..., vec3f, 'workgroup']
+
+const b = wgv.$;
+return b;
+
+
+[..., iptr<workgroup,vec3f,rw>, 'workgroup']
+
+
+[..., iptr<workgroup,vec3f,rw>, snippet('wgv', vec3f, 'workgroup')]
+
handle
return layout.$.s;
[..., sampler, 'handle']
[..., sampler, 'handle']
+
+For now, you cannot assign sampler or texture to a variable
+
tgpu.const
return c.$;
[..., u32, 'constant']
[..., u32, 'constant']
+
+const b = c.$;
+return b;
+
[..., u32, 'constant']
[..., u32, snippet('c', u32, 'constant')]
return c.$;
+
[..., vec3f, 'constant-tgpu-const-ref']
+
+
[..., vec3f, 'constant-tgpu-const-ref']
+
+
+const b = c.$;
+return b;
+
+
[..., vec3f, 'constant-tgpu-const-ref']
+
+
+[..., vec3f, snippet('c', vec3f, 'constant-tgpu-const-ref')]
+
fn([i32])((i) => return arr.$[i]);
[..., f32, 'runtime']
[..., f32, 'runtime']
+
+fn([i32])((i) => {
+  const b = arr.$[i];
+  return b;
+})
+
[..., f32, 'runtime']
+
[..., f32, snippet('arr[i]', f32, 'runtime')]
+
fn([i32])((i) => return arr.$[i]);
+
[..., vec3f, 'runtime-tgpu-const-ref']
+
+
[..., vec3f, 'runtime-tgpu-const-ref']
+
+
+fn([i32])((i) => {
+  const b = arr.$[i];
+  return b;
+})
+
+
[..., vec3f, 'runtime-tgpu-const-ref']
+
+
+[..., vec3f, snippet('arr[i]', vec3f, 'runtime-tgpu-const-ref')]
+
struct
return buf.$.count;
[..., u32, 'runtime']
[..., u32, 'runtime']
+
+const b = buf.$.count;
+return b;
+
[..., u32, 'runtime']
+
[..., u32, snippet('buf.count', u32, 'runtime')]
+
+
+Access to struct should be distributed as reference to one unique snippet
+
return buf.$.pos;
[..., vec3f, 'uniform']
[..., vec3f, 'uniform']
+
+const b = buf.$.pos;
+return b;
+
+
[..., iptr<uniform,vec3f,r>, 'uniform']
+
+
+[..., iptr<uniform,vec3f,r>, snippet('buf.$.pos', vec3f, 'uniform')]
+
swizzle
fn([vec3f])((v) => return v.x);
[..., f32, 'argument']
[..., f32, 'argument']
+
+fn([vec3f])((v) => {
+  const b = v.x;
+  return b;
+})
+
[..., f32, 'runtime']
[..., f32, snippet('v.x', f32, 'runtime')]
fn([vec3f])((v) => return v.xy);
[..., vec2f, 'runtime']
[..., vec2f, 'runtime']
+
+fn([vec3f])((v) => {
+  const b = v.xy;
+  return b;
+})
+
+
[..., vec2f, 'this-function']
+
+
[..., vec2f, snippet('v.xy', vec2f, 'runtime')]
+
d.ref
+
+const b = d.ref(u32(0));
+return b;
+
+
[..., ptr<function,u32,rw>, 'function']
+
+
+[..., ptr<function,u32,rw>, snippet(u32(0), u32, 'constant')]
+
Not sure about that
+
+const b = d.ref(vec2f());
+return b;
+
+
[..., ptr<function,vec2f,rw>, 'function']
+
+
+[..., ptr<function,vec2f,rw>, snippet(vec2f(0), vec2f, 'constant')]
+
+ mixed origins in complex types +
return vec4f(7).xz;
[..., vec2f, 'constant']
[..., vec2f, 'constant']
+
+const b = vec4f(7).xz;
+return b;
+
[..., vec2f, 'this-function']
+
+[..., vec2f, snippet(vec4f(7).xz, vec2f, 'constant')]
+
return vec4f(buf.$, 1).xw;
[..., vec2f, 'runtime']
+
[..., vec2f, 'runtime']
+
+
+const b = vec4f(buf.$, 1).xw;
+return b;
+
[..., vec2f, 'this-function']
+
+[..., vec2f, snippet(vec4f(buf.$, 1), vec2f, 'runtime')]
+
Constructors perform deep copy :))
+
+const a = Boid();
+return a.pos;
+
[..., vec2f, 'this-function']
[..., vec2f, 'this-function']
+
+Not sure about that because there is nothing to be referenced
+
+
+const a = Boid();
+const b = a.pos;
+return b;
+
+
+[..., iptr<function,vec2f,rw>, 'this-function']
+
+
+[..., iptr<function,vec2f,rw>, snippet(a.pos, vec2f, 'this-function')]
+
+
+const a = Boid({ pos: buf.$, ... });
+return a.pos;
+
[..., vec2f, 'this-function']
[..., vec2f, 'this-function']
Again, constructors perform deep copy
+
+const a = Boid({ pos: buf.$, ... });
+const b = a.pos;
+return b;
+
+
+[..., iptr<function,vec2f,rw>, 'this-function']
+
+
+[..., iptr<function,vec2f,rw>, snippet(a.pos, vec2f, 'this-function')]
+
+
diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index ed1e76eade..b290bbe26c 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -29,6 +29,10 @@ export function ptrHandle(inner: T): Ptr<'handle', T, 'r return INTERNAL_createPtr('handle', inner, 'read'); } +function ptrToString(this: Ptr): string { + return `ptr<${this.addressSpace}, ${this.inner}, ${this.access}>`; +} + export function INTERNAL_createPtr< TAddressSpace extends AddressSpace, TInner extends BaseData, @@ -46,13 +50,12 @@ export function INTERNAL_createPtr< inner, access, implicit, - toString: () => `ptr<${addressSpace}, ${inner}, ${access}>`, + toString: ptrToString, // `toStrictEqual` fails if we create a new function for each `Ptr` instance } as Ptr; } export function createPtrFromOrigin(origin: Origin, innerDataType: StorableData): Ptr | undefined { const ptrParams = originToPtrParams[origin as keyof OriginToPtrParams]; - if (ptrParams) { return INTERNAL_createPtr(ptrParams.space, innerDataType, ptrParams.access); } diff --git a/packages/typegpu/tests/tgsl/origin.test.ts b/packages/typegpu/tests/tgsl/origin.test.ts new file mode 100644 index 0000000000..4a6943eda1 --- /dev/null +++ b/packages/typegpu/tests/tgsl/origin.test.ts @@ -0,0 +1,889 @@ +import { describe, expect } from 'vitest'; +import { it } from 'typegpu-testing-utility'; +import tgpu, { d } from '../../src/index.js'; +import { extractSnippetFromFn } from '../utils/parseResolved.ts'; + +const implicitFnPtr = (type: d.StorableData) => { + const ptr = d.ptrFn(type); + // @ts-expect-error + ptr.implicit = true; + return ptr; +}; + +const implicitUniformPtr = (type: d.StorableData) => { + const ptr = d.ptrUniform(type); + // @ts-expect-error + ptr.implicit = true; + return ptr; +}; + +const implicitReadonlyPtr = (type: d.StorableData) => { + const ptr = d.ptrStorage(type); + // @ts-expect-error + ptr.implicit = true; + return ptr; +}; + +const implicitMutablePtr = (type: d.StorableData) => { + const ptr = d.ptrStorage(type, 'read-write'); + // @ts-expect-error + ptr.implicit = true; + return ptr; +}; + +const implicitPrivatePtr = (type: d.StorableData) => { + const ptr = d.ptrPrivate(type); + // @ts-expect-error + ptr.implicit = true; + return ptr; +}; + +const implicitWorkgroupPtr = (type: d.StorableData) => { + const ptr = d.ptrWorkgroup(type); + // @ts-expect-error + ptr.implicit = true; + return ptr; +}; + +describe('origin tracking', () => { + describe('basic', () => { + it('i32 has constant origin', () => { + const f = () => { + 'use gpu'; + return d.i32(1); + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('constant'); + }); + + it('i32 assignment has constant origin', () => { + const f = () => { + 'use gpu'; + const b = 1; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('constant'); + }); + + it('i32 argument has argument origin', () => { + const f = tgpu.fn([d.i32])((a) => { + return a; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('argument'); + }); + + it('i32 argument assignment has runtime origin', () => { + const f = tgpu.fn([d.i32])((a) => { + const b = a; + return b; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('runtime'); + }); + + it('i32 return from function has runtime origin', () => { + const getOne = tgpu.fn( + [], + d.i32, + )(() => { + 'use gpu'; + return 1; + }); + + const f = () => { + 'use gpu'; + return getOne(); + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('runtime'); + }); + + it('i32 return from function assignment has runtime origin', () => { + const getOne = tgpu.fn( + [], + d.i32, + )(() => { + 'use gpu'; + return 1; + }); + + const f = () => { + 'use gpu'; + const b = getOne(); + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec2i has constant origin', () => { + const f = () => { + 'use gpu'; + return d.vec2i(7); + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2i); + expect(snippet.origin).toBe('constant'); + }); + + it('vec2i assignment has this-function origin', () => { + const f = () => { + 'use gpu'; + const a = d.vec2i(7); + return a; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2i); + expect(snippet.origin).toBe('this-function'); + }); + + it('vec2i double assignment has this-function origin', () => { + const f = () => { + 'use gpu'; + const a = d.vec2i(7); + const b = a; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitFnPtr(d.vec2i)); + expect(snippet.origin).toBe('this-function'); + }); + + it('vec2i argument has argument origin', () => { + const f = tgpu.fn([d.vec2i])((a) => { + return a; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2i); + expect(snippet.origin).toBe('argument'); + }); + + it('vec2i argument assignment has argument origin', () => { + const f = tgpu.fn([d.vec2i])((a) => { + const b = a; + return b; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2i); + expect(snippet.origin).toBe('argument'); + }); + + it('vec2i argument component has argument origin', () => { + const f = tgpu.fn([d.vec2i])((a) => { + return a.x; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('argument'); // whyyy ??? + }); + + it('vec2i argument component assignment has runtime origin', () => { + const f = tgpu.fn([d.vec2i])((a) => { + const b = a.x; + return b; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.i32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f return from function has runtime origin', () => { + const getV = () => { + 'use gpu'; + return d.vec3f(1, 2, 3); + }; + + const f = () => { + 'use gpu'; + return getV(); + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f return from function assignment to variable has this-function origin', () => { + const getV = () => { + 'use gpu'; + return d.vec3f(1, 2, 3); + }; + + const f = () => { + 'use gpu'; + const b = getV(); + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('this-function'); + }); + }); + + describe('uniform', () => { + it('f32 uniform buffer has runtime origin', ({ root }) => { + const buf = root.createUniform(d.f32); + + const f = () => { + 'use gpu'; + return buf.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('f32 uniform buffer assignment has runtime origin', ({ root }) => { + const buf = root.createUniform(d.f32); + + const f = () => { + 'use gpu'; + const b = buf.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f uniform buffer has uniform origin', ({ root }) => { + const buf = root.createUniform(d.vec3f); + + const f = () => { + 'use gpu'; + return buf.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('uniform'); + }); + + it('vec3f uniform buffer assignment has uniform origin', ({ root }) => { + const buf = root.createUniform(d.vec3f); + + const f = () => { + 'use gpu'; + const b = buf.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitUniformPtr(d.vec3f)); + expect(snippet.origin).toBe('uniform'); + }); + }); + + describe('readonly', () => { + it('f32 readonly buffer has runtime origin', ({ root }) => { + const buf = root.createReadonly(d.f32); + + const f = () => { + 'use gpu'; + return buf.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('f32 readonly buffer assignment has runtime origin', ({ root }) => { + const buf = root.createReadonly(d.f32); + + const f = () => { + 'use gpu'; + const b = buf.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f readonly buffer has readonly origin', ({ root }) => { + const buf = root.createReadonly(d.vec3f); + + const f = () => { + 'use gpu'; + return buf.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('readonly'); + }); + + it('vec3f readonly buffer assignment has readonly origin', ({ root }) => { + const buf = root.createReadonly(d.vec3f); + + const f = () => { + 'use gpu'; + const b = buf.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitReadonlyPtr(d.vec3f)); + expect(snippet.origin).toBe('readonly'); + }); + }); + + describe('mutable', () => { + it('f32 mutable buffer has runtime origin', ({ root }) => { + const buf = root.createMutable(d.f32); + + const f = () => { + 'use gpu'; + return buf.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('f32 mutable buffer assignment has runtime origin', ({ root }) => { + const buf = root.createMutable(d.f32); + + const f = () => { + 'use gpu'; + const b = buf.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f mutable buffer has mutable origin', ({ root }) => { + const buf = root.createMutable(d.vec3f); + + const f = () => { + 'use gpu'; + return buf.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('mutable'); + }); + + it('vec3f mutable buffer assignment has mutable origin', ({ root }) => { + const buf = root.createMutable(d.vec3f); + + const f = () => { + 'use gpu'; + const b = buf.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitMutablePtr(d.vec3f)); + expect(snippet.origin).toBe('mutable'); + }); + }); + + describe('private', () => { + it('f32 private var has runtime origin', () => { + const pv = tgpu.privateVar(d.f32); + + const f = () => { + 'use gpu'; + return pv.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('f32 private var assignment has runtime origin', () => { + const pv = tgpu.privateVar(d.f32); + + const f = () => { + 'use gpu'; + const b = pv.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f private var has private origin', () => { + const pv = tgpu.privateVar(d.vec3f); + + const f = () => { + 'use gpu'; + return pv.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('private'); + }); + + it('vec3f private var assignment has private origin', () => { + const pv = tgpu.privateVar(d.vec3f); + + const f = () => { + 'use gpu'; + const b = pv.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitPrivatePtr(d.vec3f)); + expect(snippet.origin).toBe('private'); + }); + }); + + describe('workgroup', () => { + it('f32 workgroup var has runtime origin', () => { + const wgv = tgpu.workgroupVar(d.f32); + + const f = () => { + 'use gpu'; + return wgv.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('f32 workgroup var assignment has runtime origin', () => { + const wgv = tgpu.workgroupVar(d.f32); + + const f = () => { + 'use gpu'; + const b = wgv.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f workgroup var has workgroup origin', () => { + const wgv = tgpu.workgroupVar(d.vec3f); + + const f = () => { + 'use gpu'; + return wgv.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('workgroup'); + }); + + it('vec3f workgroup var assignment has workgroup origin', () => { + const wgv = tgpu.workgroupVar(d.vec3f); + + const f = () => { + 'use gpu'; + const b = wgv.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitWorkgroupPtr(d.vec3f)); + expect(snippet.origin).toBe('workgroup'); + }); + }); + + describe('handle', () => { + it('sampler binding has handle origin', () => { + const layout = tgpu.bindGroupLayout({ + s: { sampler: 'filtering' }, + }); + + const f = () => { + 'use gpu'; + return layout.$.s; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.sampler()); + expect(snippet.origin).toBe('handle'); + }); + }); + + describe('tgpu.const', () => { + it('tgpu.const u32 value has constant origin', () => { + const c = tgpu.const(d.u32, 42); + + const f = () => { + 'use gpu'; + return c.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.u32); + expect(snippet.origin).toBe('constant'); + }); + + it('tgpu.const u32 value assignment to variable has constant origin', () => { + const c = tgpu.const(d.u32, 42); + + const f = () => { + 'use gpu'; + const b = c.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.u32); + expect(snippet.origin).toBe('constant'); + }); + + it('tgpu.const vec3f value has constant-tgpu-const-ref origin', () => { + const c = tgpu.const(d.vec3f, d.vec3f(1, 2, 3)); + + const f = () => { + 'use gpu'; + return c.$; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('constant-tgpu-const-ref'); + }); + + it('tgpu.const vec3f value assignment to variable has constant-tgpu-const-ref origin', () => { + const c = tgpu.const(d.vec3f, d.vec3f(1, 2, 3)); + + const f = () => { + 'use gpu'; + const b = c.$; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('constant-tgpu-const-ref'); + }); + + it('tgpu.const array of f32 indexed by runtime value has runtime origin', () => { + const arr = tgpu.const(d.arrayOf(d.f32, 3), [0, 1, 2]); + + const f = tgpu.fn([d.i32])((i) => { + return arr.$[i]; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('tgpu.const array of f32 indexed by runtime value assignment has runtime origin', () => { + const arr = tgpu.const(d.arrayOf(d.f32, 3), [0, 1, 2]); + + const f = tgpu.fn([d.i32])((i) => { + const b = arr.$[i]; + return b; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('tgpu.const array of vec3f indexed by runtime value has runtime-tgpu-const-ref origin', () => { + const arr = tgpu.const(d.arrayOf(d.vec3f, 3), [d.vec3f(0), d.vec3f(1), d.vec3f(2)]); + + const f = tgpu.fn([d.i32])((i) => { + return arr.$[i]; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('runtime-tgpu-const-ref'); + }); + + it('tgpu.const array of vec3f indexed by runtime value assignment has runtime-tgpu-const-ref origin', () => { + const arr = tgpu.const(d.arrayOf(d.vec3f, 3), [d.vec3f(0), d.vec3f(1), d.vec3f(2)]); + + const f = tgpu.fn([d.i32])((i) => { + const b = arr.$[i]; + return b; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('runtime-tgpu-const-ref'); + }); + }); + + describe('struct', () => { + it('u32 struct field from uniform buffer has runtime origin', ({ root }) => { + const Particle = d.struct({ pos: d.vec3f, count: d.u32 }); + const buf = root.createUniform(Particle); + + const f = () => { + 'use gpu'; + return buf.$.count; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.u32); + expect(snippet.origin).toBe('runtime'); + }); + + it('u32 struct field from uniform buffer assignment has runtime origin', ({ root }) => { + const Particle = d.struct({ pos: d.vec3f, count: d.u32 }); + const buf = root.createUniform(Particle); + + const f = () => { + 'use gpu'; + const b = buf.$.count; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.u32); + expect(snippet.origin).toBe('runtime'); + }); + + it('vec3f struct field from uniform buffer preserves uniform origin', ({ root }) => { + const Particle = d.struct({ pos: d.vec3f, count: d.u32 }); + const buf = root.createUniform(Particle); + + const f = () => { + 'use gpu'; + return buf.$.pos; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec3f); + expect(snippet.origin).toBe('uniform'); + }); + + it('vec3f struct field from uniform buffer assignment preserves uniform origin', ({ root }) => { + const Particle = d.struct({ pos: d.vec3f, count: d.u32 }); + const buf = root.createUniform(Particle); + + const f = () => { + 'use gpu'; + const b = buf.$.pos; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitUniformPtr(d.vec3f)); + expect(snippet.origin).toBe('uniform'); + }); + }); + + describe('swizzle', () => { + it('single-component swizzle from argument preserves argument origin', () => { + const f = tgpu.fn([d.vec3f])((v) => { + return v.x; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('argument'); + }); + + it('single-component swizzle from argument assignment has runtime origin', () => { + const f = tgpu.fn([d.vec3f])((v) => { + const b = v.x; + return b; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.f32); + expect(snippet.origin).toBe('runtime'); + }); + + it('multi-component swizzle from argument has runtime origin', () => { + const f = tgpu.fn([d.vec3f])((v) => { + return v.xy; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('runtime'); + }); + + it('multi-component swizzle from argument assignment has this-function origin', () => { + const f = tgpu.fn([d.vec3f])((v) => { + const b = v.xy; + return b; + }); + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('this-function'); + }); + }); + + describe('d.ref', () => { + it('d.ref of u32 has function origin', () => { + const f = () => { + 'use gpu'; + const b = d.ref(d.u32(0)); + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.ptrFn(d.u32)); + expect(snippet.origin).toBe('function'); + }); + + it('d.ref of vec2f has function origin', () => { + const f = () => { + 'use gpu'; + const b = d.ref(d.vec2f()); + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.ptrFn(d.vec2f)); + expect(snippet.origin).toBe('function'); + }); + }); + + describe('mixed origins in complex types', () => { + it('constant swizzle has constant origin', () => { + const f = () => { + 'use gpu'; + return d.vec4f(7).xz; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('constant'); + }); + + it('constant swizzle assignment has this-function origin', () => { + const f = () => { + 'use gpu'; + const b = d.vec4f(7).xz; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('this-function'); + }); + + it('mixed swizzle has runtime origin', ({ root }) => { + const buf = root.createMutable(d.vec3f); + + const f = () => { + 'use gpu'; + return d.vec4f(buf.$, 1).xw; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('runtime'); + }); + + it('mixed swizzle assignment has this-function origin', ({ root }) => { + const buf = root.createMutable(d.vec3f); + + const f = () => { + 'use gpu'; + const b = d.vec4f(buf.$, 1).xw; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('this-function'); + }); + + it('constant struct field access has this-function origin', () => { + const Boid = d.struct({ pos: d.vec2f }); + + const f = () => { + 'use gpu'; + const a = Boid(); + return a.pos; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('this-function'); + }); + + it('constant struct field access assignment has this-function origin', () => { + const Boid = d.struct({ pos: d.vec2f }); + + const f = () => { + 'use gpu'; + const a = Boid(); + const b = a.pos; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitFnPtr(d.vec2f)); + expect(snippet.origin).toBe('this-function'); + }); + + it('mixed struct field access has this-function origin', ({ root }) => { + const buf = root.createMutable(d.vec2f); + const Boid = d.struct({ pos: d.vec2f, health: d.u32 }); + + const f = () => { + 'use gpu'; + const a = Boid({ pos: buf.$, health: 7 }); + return a.pos; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(d.vec2f); + expect(snippet.origin).toBe('this-function'); + }); + + it('mixed struct field access assignment has this-function origin', ({ root }) => { + const buf = root.createMutable(d.vec2f); + const Boid = d.struct({ pos: d.vec2f, health: d.u32 }); + + const f = () => { + 'use gpu'; + const a = Boid({ pos: buf.$, health: 7 }); + const b = a.pos; + return b; + }; + + const snippet = extractSnippetFromFn(f); + expect(snippet.dataType).toStrictEqual(implicitFnPtr(d.vec2f)); + expect(snippet.origin).toBe('this-function'); + }); + }); +}); diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index f6dffc8bdd..09985762f7 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -43,25 +43,24 @@ class ExtractingGenerator extends WgslGenerator { } } -export function extractSnippetFromFn(cb: () => unknown): Snippet { +export function extractSnippetFromFn(cb: (...args: never[]) => unknown): Snippet { const generator = new ExtractingGenerator(); - tgpu.resolve([cb], { unstable_shaderGenerator: generator }); - if (!generator.returnedSnippet) { throw new Error('Something must be returned to be inspected'); } - return generator.returnedSnippet; } export function expectSnippetOf( - cb: () => unknown, + cb: (...args: never[]) => unknown, ): Assertion<[unknown, d.BaseData | UnknownData, Origin]> { const snippet = extractSnippetFromFn(cb); return expect([snippet.value, snippet.dataType, snippet.origin]); } -export function expectDataTypeOf(cb: () => unknown): Assertion { +export function expectDataTypeOf( + cb: (...args: never[]) => unknown, +): Assertion { return expect(extractSnippetFromFn(cb).dataType); }