From ac2d19290770bea5a249783effa5cf459a4fabad Mon Sep 17 00:00:00 2001 From: Konrad Reczko Date: Tue, 26 May 2026 16:53:55 +0200 Subject: [PATCH 1/8] fix --- packages/typegpu/src/core/slot/accessor.ts | 6 +-- packages/typegpu/tests/tgsl/comptime.test.ts | 42 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 321f866b29..846dab496f 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -2,7 +2,7 @@ import { type AnyData, isData } from '../../data/dataTypes.ts'; import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { isSnippet, type ResolvedSnippet, snip } from '../../data/snippet.ts'; import type { BaseData } from '../../data/wgslTypes.ts'; -import { getResolutionCtx, inCodegenMode } from '../../execMode.ts'; +import { getResolutionCtx } from '../../execMode.ts'; import { getName, hasTinyestMetadata, setName } from '../../shared/meta.ts'; import type { InferGPU } from '../../shared/repr.ts'; import { @@ -174,7 +174,7 @@ export class TgpuAccessorImpl } get $(): InferGPU { - if (inCodegenMode()) { + if (getResolutionCtx()) { return this[$gpuValueOf]; } @@ -198,7 +198,7 @@ export class TgpuMutableAccessorImpl } get $(): InferGPU { - if (inCodegenMode()) { + if (getResolutionCtx()) { return this[$gpuValueOf]; } diff --git a/packages/typegpu/tests/tgsl/comptime.test.ts b/packages/typegpu/tests/tgsl/comptime.test.ts index f4fc57f42c..dbe857c305 100644 --- a/packages/typegpu/tests/tgsl/comptime.test.ts +++ b/packages/typegpu/tests/tgsl/comptime.test.ts @@ -75,4 +75,46 @@ describe('comptime', () => { }" `); }); + + it('can read accessors during shader resolution', () => { + const value = tgpu.accessor(d.f32, 1); + const readValue = tgpu.comptime(() => value.$); + + const myFn = tgpu.fn( + [], + d.f32, + )(() => { + return readValue(); + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return 1f; + }" + `); + + expect(tgpu.resolve([myFn.with(value, 2)])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return 2f; + }" + `); + }); + + it('still throws when a comptime-read accessor has no value', () => { + const value = tgpu.accessor(d.f32); + const readValue = tgpu.comptime(() => value.$); + const myFn = tgpu.fn( + [], + d.f32, + )(() => { + return readValue(); + }); + + expect(() => tgpu.resolve([myFn])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn:myFn + - fn:readValue: Missing value for 'slot:value'] + `); + }); }); From 829c75b1fe70b8091fd047c38d5fd242bbe37121 Mon Sep 17 00:00:00 2001 From: Konrad Reczko Date: Tue, 26 May 2026 16:56:27 +0200 Subject: [PATCH 2/8] nit --- packages/typegpu/tests/tgsl/comptime.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/tests/tgsl/comptime.test.ts b/packages/typegpu/tests/tgsl/comptime.test.ts index dbe857c305..236719e821 100644 --- a/packages/typegpu/tests/tgsl/comptime.test.ts +++ b/packages/typegpu/tests/tgsl/comptime.test.ts @@ -100,7 +100,7 @@ describe('comptime', () => { `); }); - it('still throws when a comptime-read accessor has no value', () => { + it('throws when a comptime-read accessor has no value', () => { const value = tgpu.accessor(d.f32); const readValue = tgpu.comptime(() => value.$); const myFn = tgpu.fn( From 2f3a40df87fa64c5de1c29738820e9b2b0b80c88 Mon Sep 17 00:00:00 2001 From: Konrad Reczko Date: Tue, 26 May 2026 16:57:48 +0200 Subject: [PATCH 3/8] ban turtles --- packages/typegpu/tests/tgsl/comptime.test.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/typegpu/tests/tgsl/comptime.test.ts b/packages/typegpu/tests/tgsl/comptime.test.ts index 236719e821..c179d45433 100644 --- a/packages/typegpu/tests/tgsl/comptime.test.ts +++ b/packages/typegpu/tests/tgsl/comptime.test.ts @@ -103,17 +103,16 @@ describe('comptime', () => { it('throws when a comptime-read accessor has no value', () => { const value = tgpu.accessor(d.f32); const readValue = tgpu.comptime(() => value.$); - const myFn = tgpu.fn( - [], - d.f32, - )(() => { + const myFn = () => { + 'use gpu'; return readValue(); - }); + }; expect(() => tgpu.resolve([myFn])).toThrowErrorMatchingInlineSnapshot(` [Error: Resolution of the following tree failed: - - - fn:myFn + - fn*:myFn + - fn*:myFn() - fn:readValue: Missing value for 'slot:value'] `); }); From 63373b251c26333dbce6f06db763a074c409c8cc Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 26 May 2026 17:56:43 +0200 Subject: [PATCH 4/8] Failing test --- packages/typegpu/tests/tgsl/comptime.test.ts | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/typegpu/tests/tgsl/comptime.test.ts b/packages/typegpu/tests/tgsl/comptime.test.ts index c179d45433..9bc061c97b 100644 --- a/packages/typegpu/tests/tgsl/comptime.test.ts +++ b/packages/typegpu/tests/tgsl/comptime.test.ts @@ -100,6 +100,30 @@ describe('comptime', () => { `); }); + it('can read and work with accessors in comptime', () => { + const valueAccess = tgpu.accessor(d.f32, 1); + const doubleValue = tgpu.comptime(() => valueAccess.$ * 2); + + const myFn = tgpu.fn( + [], + d.f32, + )(() => { + return doubleValue(); + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return NaNf; + }" + `); + + expect(tgpu.resolve([myFn.with(valueAccess, 2)])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return NaNf; + }" + `); + }); + it('throws when a comptime-read accessor has no value', () => { const value = tgpu.accessor(d.f32); const readValue = tgpu.comptime(() => value.$); From e8d1c2d98cbed037f6b343c75db26f23ed768cbe Mon Sep 17 00:00:00 2001 From: Szymon Szulc <103948576+cieplypolar@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:26:45 +0200 Subject: [PATCH 5/8] fix: comptime can read and work with accessors (#2571) --- packages/typegpu/src/core/slot/accessor.ts | 33 ++++++-- packages/typegpu/tests/tgsl/comptime.test.ts | 82 +++++++++++++++++++- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 846dab496f..611990c766 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -2,7 +2,7 @@ import { type AnyData, isData } from '../../data/dataTypes.ts'; import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { isSnippet, type ResolvedSnippet, snip } from '../../data/snippet.ts'; import type { BaseData } from '../../data/wgslTypes.ts'; -import { getResolutionCtx } from '../../execMode.ts'; +import { getExecMode, getResolutionCtx } from '../../execMode.ts'; import { getName, hasTinyestMetadata, setName } from '../../shared/meta.ts'; import type { InferGPU } from '../../shared/repr.ts'; import { @@ -14,6 +14,7 @@ import { } from '../../shared/symbols.ts'; import type { UnwrapRuntimeConstructor } from '../../tgpuBindGroupLayout.ts'; import { + CodegenState, getOwnSnippet, NormalState, type ResolutionCtx, @@ -174,13 +175,33 @@ export class TgpuAccessorImpl } get $(): InferGPU { - if (getResolutionCtx()) { - return this[$gpuValueOf]; + const ctx = getResolutionCtx(); + if (!ctx) { + throw new Error( + '`tgpu.accessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call', + ); } - throw new Error( - '`tgpu.accessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call', - ); + if (ctx.mode.type !== 'codegen') { + const slotValue = ctx.unwrap(this.slot); + + if ( + typeof slotValue !== 'function' && + !hasTinyestMetadata(slotValue) && + !isTgpuFn(slotValue) + ) { + return slotValue as unknown as InferGPU; + } + + ctx.pushMode(new CodegenState()); + try { + return this[$gpuValueOf]; + } finally { + ctx.popMode('codegen'); + } + } + + return this[$gpuValueOf]; } } diff --git a/packages/typegpu/tests/tgsl/comptime.test.ts b/packages/typegpu/tests/tgsl/comptime.test.ts index 9bc061c97b..341e3d577e 100644 --- a/packages/typegpu/tests/tgsl/comptime.test.ts +++ b/packages/typegpu/tests/tgsl/comptime.test.ts @@ -100,7 +100,7 @@ describe('comptime', () => { `); }); - it('can read and work with accessors in comptime', () => { + it('can read and work with accessors', () => { const valueAccess = tgpu.accessor(d.f32, 1); const doubleValue = tgpu.comptime(() => valueAccess.$ * 2); @@ -113,17 +113,93 @@ describe('comptime', () => { expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` "fn myFn() -> f32 { - return NaNf; + return 2f; }" `); expect(tgpu.resolve([myFn.with(valueAccess, 2)])).toMatchInlineSnapshot(` "fn myFn() -> f32 { - return NaNf; + return 4f; + }" + `); + }); + + it('can read "use gpu" callback accessors', () => { + const colorAccess = tgpu.accessor(d.vec3f, () => { + 'use gpu'; + return d.vec3f(0, 1, 0); + }); + const readColor = tgpu.comptime(() => colorAccess.$); + + const myFn = tgpu.fn( + [], + d.vec3f, + )(() => { + return readColor(); + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn colorAccess() -> vec3f { + return vec3f(0, 1, 0); + } + + fn myFn() -> vec3f { + return colorAccess(); }" `); }); + it('can read GPU-resource accessors', ({ root }) => { + const Camera = d.struct({ pos: d.vec3f }); + const camera = root.createUniform(Camera); + + const posAccess = tgpu.accessor(d.vec3f, () => camera.$.pos); + const readPos = tgpu.comptime(() => posAccess.$); + + const myFn = tgpu.fn( + [], + d.vec3f, + )(() => { + return readPos(); + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "struct Camera { + pos: vec3f, + } + + @group(0) @binding(0) var camera: Camera; + + fn myFn() -> vec3f { + return camera.pos; + }" + `); + }); + + it('throws when reading "use gpu" callback accessor in js', () => { + const colorAccess = tgpu.accessor(d.vec3f, () => { + 'use gpu'; + return d.vec3f(0, 1, 0); + }); + const readColor = tgpu.comptime(() => colorAccess.$); + + expect(() => readColor()).toThrowErrorMatchingInlineSnapshot( + `[Error: \`tgpu.accessor\` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call]`, + ); + }); + + it('throws when reading GPU-resource accessor in js', ({ root }) => { + const Camera = d.struct({ pos: d.vec3f }); + const camera = root.createUniform(Camera); + + const posAccess = tgpu.accessor(d.vec3f, () => camera.$.pos); + const readPos = tgpu.comptime(() => posAccess.$); + + expect(() => readPos()).toThrowErrorMatchingInlineSnapshot( + `[Error: \`tgpu.accessor\` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call]`, + ); + }); + it('throws when a comptime-read accessor has no value', () => { const value = tgpu.accessor(d.f32); const readValue = tgpu.comptime(() => value.$); From 5ec546fa8156ff16e33424484929586a3b26bacd Mon Sep 17 00:00:00 2001 From: Konrad Reczko Date: Mon, 1 Jun 2026 13:38:49 +0200 Subject: [PATCH 6/8] fix --- packages/typegpu/src/core/slot/accessor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 611990c766..907de61bf5 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -2,7 +2,7 @@ import { type AnyData, isData } from '../../data/dataTypes.ts'; import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { isSnippet, type ResolvedSnippet, snip } from '../../data/snippet.ts'; import type { BaseData } from '../../data/wgslTypes.ts'; -import { getExecMode, getResolutionCtx } from '../../execMode.ts'; +import { getResolutionCtx } from '../../execMode.ts'; import { getName, hasTinyestMetadata, setName } from '../../shared/meta.ts'; import type { InferGPU } from '../../shared/repr.ts'; import { From ff9b6c1c3b91511201b66a29ecb897c39e21cdbc Mon Sep 17 00:00:00 2001 From: Konrad Reczko <66403540+reczkok@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:21:17 +0200 Subject: [PATCH 7/8] Update packages/typegpu/src/core/slot/accessor.ts Co-authored-by: Szymon Szulc <103948576+cieplypolar@users.noreply.github.com> --- packages/typegpu/src/core/slot/accessor.ts | 28 +++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 907de61bf5..3cb54fb079 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -219,7 +219,33 @@ export class TgpuMutableAccessorImpl } get $(): InferGPU { - if (getResolutionCtx()) { + const ctx = getResolutionCtx(); + if (!ctx) { + throw new Error( + '`tgpu.mutableAccessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call', + ); + } + + if (ctx.mode.type !== 'codegen') { + const slotValue = ctx.unwrap(this.slot); + + if ( + typeof slotValue !== 'function' && + !hasTinyestMetadata(slotValue) && + !isTgpuFn(slotValue) + ) { + return slotValue as unknown as InferGPU; + } + + ctx.pushMode(new CodegenState()); + try { + return this[$gpuValueOf]; + } finally { + ctx.popMode('codegen'); + } + } + + return this[$gpuValueOf]; return this[$gpuValueOf]; } From af46d66d409ffd7caef7d463328b18c9a78c28d6 Mon Sep 17 00:00:00 2001 From: Konrad Reczko Date: Tue, 9 Jun 2026 15:25:16 +0200 Subject: [PATCH 8/8] review fixes --- packages/typegpu/setupVitest.ts | 5 +++++ packages/typegpu/src/core/slot/accessor.ts | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/typegpu/setupVitest.ts b/packages/typegpu/setupVitest.ts index b4f12cf76f..e93275d37d 100644 --- a/packages/typegpu/setupVitest.ts +++ b/packages/typegpu/setupVitest.ts @@ -1,5 +1,9 @@ import { setup } from '@ark/attest'; import { type } from 'arktype'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const packageDir = dirname(fileURLToPath(import.meta.url)); const truthyString = type('"0"|"1"').pipe.try((value) => Boolean(Number.parseInt(value))); @@ -12,6 +16,7 @@ const env = ProcessEnvType.assert(process.env); export default () => setup({ formatCmd: 'pnpm fix', + tsconfig: join(packageDir, 'tsconfig.json'), // Skipping type tests by default skipTypes: !env.ENABLE_ATTEST, }); diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 3cb54fb079..5e3e7b6708 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -246,11 +246,5 @@ export class TgpuMutableAccessorImpl } return this[$gpuValueOf]; - return this[$gpuValueOf]; - } - - throw new Error( - '`tgpu.mutableAccessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call', - ); } }