From d2d8a83a32dfca146889d48de6bdab25ef77ba53 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 15 Dec 2025 00:55:54 +0100 Subject: [PATCH 01/67] basic for ... of ... loop compilation --- packages/tinyest-for-wgsl/src/parsers.ts | 28 +++++++++ packages/tinyest/src/nodes.ts | 11 +++- packages/typegpu/src/tgsl/wgslGenerator.ts | 28 +++++++++ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 62 +++++++++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) diff --git a/packages/tinyest-for-wgsl/src/parsers.ts b/packages/tinyest-for-wgsl/src/parsers.ts index 8ec4fe9933..901185a0e4 100644 --- a/packages/tinyest-for-wgsl/src/parsers.ts +++ b/packages/tinyest-for-wgsl/src/parsers.ts @@ -369,6 +369,34 @@ const Transpilers: Partial< return [NODE.while, condition, body]; }, + ForOfStatement(ctx, node) { + if ( + !(node.left.type === 'VariableDeclaration' && + node.left.declarations.length === 1 && + node.left.declarations[0]?.id.type === 'Identifier') + ) { + throw new Error( + '"for ... of ..." loops only support simple variable declarations', + ); + } + + if (node.right.type !== 'Identifier') { + throw new Error( + '"for ... of ..." loops only support iterables stored in variables', + ); + } + + // TODO: I can distinguish let and const there, idk how yet + + ctx.ignoreExternalDepth++; // left side variable is not an external + const loopVarName = transpile(ctx, node.left.declarations[0].id) as string; + ctx.ignoreExternalDepth--; + const iterable = transpile(ctx, node.right) as tinyest.Expression; // need that for info during generation + const body = transpile(ctx, node.body) as tinyest.Statement; + + return [NODE.forOf, loopVarName, iterable, body]; + }, + ContinueStatement() { return [NODE.continue]; }, diff --git a/packages/tinyest/src/nodes.ts b/packages/tinyest/src/nodes.ts index 6f073ff321..bf4be85f0e 100644 --- a/packages/tinyest/src/nodes.ts +++ b/packages/tinyest/src/nodes.ts @@ -23,6 +23,7 @@ export const NodeTypeCatalog = { while: 15, continue: 16, break: 17, + forOf: 18, // rare arrayExpr: 100, @@ -96,6 +97,13 @@ export type Continue = readonly [type: NodeTypeCatalog['continue']]; export type Break = readonly [type: NodeTypeCatalog['break']]; +export type ForOf = readonly [ + type: NodeTypeCatalog['forOf'], + left: string, + right: Expression, + body: Statement, +]; + /** * A union type of all statements */ @@ -109,7 +117,8 @@ export type Statement = | For | While | Continue - | Break; + | Break + | ForOf; // // Expression diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 0267213e65..5408c66166 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -974,6 +974,34 @@ ${this.ctx.pre}else ${alternate}`; return `${this.ctx.pre}while (${conditionStr}) ${bodyStr}`; } + if (statement[0] === NODE.forOf) { + const [_, loopVarName, iterableExpr, body] = statement; + const iterable = this.expression(iterableExpr); + if (!wgsl.isWgslArray(iterable.dataType)) { + throw new WgslTypeError('for-of loops only support array iteration'); + } + + const arrayLength = iterable.dataType.elementCount; + const elementType = iterable.dataType.elementType; // will be used later + + const indexVar = this.ctx.makeNameValid('i'); + const loopVarNameValid = this.ctx.makeNameValid(loopVarName); + + const iterableStr = + this.ctx.resolve(iterable.value, iterable.dataType).value; + + const forStr = + `${this.ctx.pre}for (var ${indexVar} = 0; ${indexVar} < ${arrayLength}; ${indexVar}++) {`; + this.ctx.indent(); + const loopVarDecl = + `${this.ctx.pre}var ${loopVarNameValid} = ${iterableStr}[${indexVar}];`; + const bodyStr = `${this.ctx.pre}${ + this.block(blockifySingleStatement(body)) + }`; + this.ctx.dedent(); + return stitch`${forStr}\n${loopVarDecl}\n${bodyStr}\n${this.ctx.pre}}`; + } + if (statement[0] === NODE.continue) { return `${this.ctx.pre}continue;`; } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 0559e29931..1efc33cd08 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -449,6 +449,68 @@ describe('wgslGenerator', () => { `); }); + it('for ... of ...', () => { + const main = () => { + 'use gpu'; + const arr = d.arrayOf(d.f32, 3)([1, 2, 3]); + for (const foo of arr) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; + + const astInfo = getMetaData(main); + + if (!astInfo) { + throw new Error('Expected prebuilt AST to be present'); + } + + expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","f32"],[5,"3"]]],[[100,[[5,"1"],[5,"2"],[5,"3"]]]]]],[18,"foo","arr",[0,[[16]]]]]]"`, + ); + + provideCtx( + ctx, + () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.Void, + (astInfo.externals as () => Record)() ?? {}, + ); + + wgslGenerator.initGenerator(ctx); + const gen = wgslGenerator.functionDefinition( + astInfo.ast?.body as tinyest.Block, + ); + expect(gen).toMatchInlineSnapshot(` + "{ + var arr = array(1f, 2f, 3f); + for (var i = 0; i < 3; i++) { + var foo = arr[i]; + { + continue; + } + } + }" + `); + }, + ); + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var arr = array(1f, 2f, 3f); + for (var i = 0; i < 3; i++) { + var foo = arr[i]; + { + continue; + } + } + }" + `); + }); + it('creates correct resources for derived values and slots', () => { const testFn = tgpu.fn([], d.vec4u)(() => { return derivedV4u.value; From a2bb1869adf241c8a2a39d7dea4e87604c22210f Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 15 Dec 2025 01:02:17 +0100 Subject: [PATCH 02/67] naming convention fix --- packages/typegpu/src/tgsl/wgslGenerator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 5408c66166..32b88ac4ba 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -993,13 +993,13 @@ ${this.ctx.pre}else ${alternate}`; const forStr = `${this.ctx.pre}for (var ${indexVar} = 0; ${indexVar} < ${arrayLength}; ${indexVar}++) {`; this.ctx.indent(); - const loopVarDecl = + const loopVarDeclStr = `${this.ctx.pre}var ${loopVarNameValid} = ${iterableStr}[${indexVar}];`; const bodyStr = `${this.ctx.pre}${ this.block(blockifySingleStatement(body)) }`; this.ctx.dedent(); - return stitch`${forStr}\n${loopVarDecl}\n${bodyStr}\n${this.ctx.pre}}`; + return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; } if (statement[0] === NODE.continue) { From edfaea7731e9c1e222243434528cc2faabbafbf2 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 16 Dec 2025 18:37:48 +0100 Subject: [PATCH 03/67] refactor, var vs let, support for vectors --- packages/tinyest-for-wgsl/src/parsers.ts | 34 +-- packages/tinyest/src/nodes.ts | 14 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 64 ++++- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 242 +++++++++++++++--- 4 files changed, 268 insertions(+), 86 deletions(-) diff --git a/packages/tinyest-for-wgsl/src/parsers.ts b/packages/tinyest-for-wgsl/src/parsers.ts index 901185a0e4..e81a20ed03 100644 --- a/packages/tinyest-for-wgsl/src/parsers.ts +++ b/packages/tinyest-for-wgsl/src/parsers.ts @@ -291,12 +291,7 @@ const Transpilers: Partial< } if (node.kind === 'const') { - if (init === undefined) { - throw new Error( - 'Did not provide initial value in `const` declaration.', - ); - } - return [NODE.const, id, init]; + return init !== undefined ? [NODE.const, id, init] : [NODE.const, id]; } return init !== undefined ? [NODE.let, id, init] : [NODE.let, id]; @@ -370,31 +365,10 @@ const Transpilers: Partial< }, ForOfStatement(ctx, node) { - if ( - !(node.left.type === 'VariableDeclaration' && - node.left.declarations.length === 1 && - node.left.declarations[0]?.id.type === 'Identifier') - ) { - throw new Error( - '"for ... of ..." loops only support simple variable declarations', - ); - } - - if (node.right.type !== 'Identifier') { - throw new Error( - '"for ... of ..." loops only support iterables stored in variables', - ); - } - - // TODO: I can distinguish let and const there, idk how yet - - ctx.ignoreExternalDepth++; // left side variable is not an external - const loopVarName = transpile(ctx, node.left.declarations[0].id) as string; - ctx.ignoreExternalDepth--; - const iterable = transpile(ctx, node.right) as tinyest.Expression; // need that for info during generation + const loopVar = transpile(ctx, node.left) as tinyest.Const | tinyest.Let; + const iterable = transpile(ctx, node.right) as tinyest.Expression; const body = transpile(ctx, node.body) as tinyest.Statement; - - return [NODE.forOf, loopVarName, iterable, body]; + return [NODE.forOf, loopVar, iterable, body]; }, ContinueStatement() { diff --git a/packages/tinyest/src/nodes.ts b/packages/tinyest/src/nodes.ts index bf4be85f0e..2c6e2336ce 100644 --- a/packages/tinyest/src/nodes.ts +++ b/packages/tinyest/src/nodes.ts @@ -73,11 +73,13 @@ export type Let = /** * Represents a const statement */ -export type Const = readonly [ - type: NodeTypeCatalog['const'], - identifier: string, - value: Expression, -]; +export type Const = + | readonly [type: NodeTypeCatalog['const'], identifier: string] + | readonly [ + type: NodeTypeCatalog['const'], + identifier: string, + value: Expression, + ]; export type For = readonly [ type: NodeTypeCatalog['for'], @@ -99,7 +101,7 @@ export type Break = readonly [type: NodeTypeCatalog['break']]; export type ForOf = readonly [ type: NodeTypeCatalog['forOf'], - left: string, + left: Const | Let, right: Expression, body: Statement, ]; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 32b88ac4ba..5a95eacbf1 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -33,6 +33,7 @@ import { tryConvertSnippet, } from './conversion.ts'; import { + coerceToSnippet, concretize, type GenerationCtx, numericLiteralToSnippet, @@ -975,30 +976,69 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.forOf) { - const [_, loopVarName, iterableExpr, body] = statement; - const iterable = this.expression(iterableExpr); - if (!wgsl.isWgslArray(iterable.dataType)) { - throw new WgslTypeError('for-of loops only support array iteration'); + const [_, loopVar, iterable, body] = statement; + const iterableSnippet = this.expression(iterable); + + if (isEphemeralSnippet(iterableSnippet)) { + throw new Error( + '`for ... of ...` loops only support iterables stored in variables', + ); } - const arrayLength = iterable.dataType.elementCount; - const elementType = iterable.dataType.elementType; // will be used later + const iterableDataType = iterableSnippet.dataType; - const indexVar = this.ctx.makeNameValid('i'); - const loopVarNameValid = this.ctx.makeNameValid(loopVarName); + let elementCount; + let elementType; + if (wgsl.isWgslArray(iterableDataType)) { + elementCount = iterableDataType.elementCount; + elementType = iterableDataType.elementType; + } else if (wgsl.isVec(iterableDataType)) { + elementType = iterableDataType.primitive; + elementCount = Number(iterableDataType.type.match(/\d/)); + } else { + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + } + const loopVarKind = loopVar[0] === tinyest.NodeTypeCatalog.const && + wgsl.isNaturallyEphemeral(elementType) + ? 'let' + : 'var'; + + /* + * if user defines a variable named 'i', it will be scoped to a new block, + * shadowing our 'i' + */ + const index = this.ctx.makeNameValid('i'); + + const loopVarName = this.ctx.makeNameValid(loopVar[1]); + const loopVarSnippet = snip( + loopVarName, + elementType as AnyData, + 'runtime', + ); + this.ctx.defineVariable(loopVarName, loopVarSnippet); - const iterableStr = - this.ctx.resolve(iterable.value, iterable.dataType).value; + const iterableStr = this.ctx.resolve( + iterableSnippet.value, + // it's vector or wgslArray + iterableSnippet.dataType as AnyData, + ).value; const forStr = - `${this.ctx.pre}for (var ${indexVar} = 0; ${indexVar} < ${arrayLength}; ${indexVar}++) {`; + `${this.ctx.pre}for (var ${index} = 0; ${index} < ${elementCount}; ${index}++) {`; + this.ctx.indent(); + const loopVarDeclStr = - `${this.ctx.pre}var ${loopVarNameValid} = ${iterableStr}[${indexVar}];`; + `${this.ctx.pre}${loopVarKind} ${loopVarName} = ${iterableStr}[${index}];`; + const bodyStr = `${this.ctx.pre}${ this.block(blockifySingleStatement(body)) }`; + this.ctx.dedent(); + return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 1efc33cd08..6a5ce18fa3 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -326,7 +326,7 @@ describe('wgslGenerator', () => { // Check for: const value = std.atomicLoad(testUsage.value.b.aa[idx]!.y); // ^ this part should be a i32 const res = wgslGenerator.expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2], + (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, ); expect(res.dataType).toStrictEqual(d.i32); @@ -336,7 +336,7 @@ describe('wgslGenerator', () => { ctx[$internal].itemStateStack.pushBlockScope(); wgslGenerator.blockVariable('var', 'value', d.i32, 'runtime'); const res2 = wgslGenerator.expression( - (astInfo.ast?.body[1][1] as tinyest.Const)[2], + (astInfo.ast?.body[1][1] as tinyest.Const)[2] as tinyest.Expression, ); ctx[$internal].itemStateStack.popBlockScope(); @@ -449,60 +449,185 @@ describe('wgslGenerator', () => { `); }); - it('for ... of ...', () => { + it('creates correct code for for ... of ... statement with array of primitives', () => { const main = () => { 'use gpu'; const arr = d.arrayOf(d.f32, 3)([1, 2, 3]); + let res = d.f32(); + for (const foo of arr) { + res += foo; + const i = res; + } + }; + + const parsed = getMetaData(main)?.ast?.body; + + expect(JSON.stringify(parsed)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","f32"],[5,"3"]]],[[100,[[5,"1"],[5,"2"],[5,"3"]]]]]],[12,"res",[6,[7,"d","f32"],[]]],[18,[13,"foo"],"arr",[0,[[2,"res","+=","foo"],[13,"i","res"]]]]]]"`, + ); + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var arr = array(1f, 2f, 3f); + var res = 0f; + for (var i = 0; i < 3; i++) { + let foo = arr[i]; + { + res += foo; + let i = res; + } + } + }" + `); + }); + + it('creates correct code for for ... of ... statement derived, comptime iterables', () => { + const comptimeVec = tgpu['~unstable'].comptime(() => d.vec4f(1, 8, 8, 2)); + + const main = () => { + 'use gpu'; + const arr = derivedV4u.$; for (const foo of arr) { // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } - }; - const astInfo = getMetaData(main); + const v = comptimeVec(); + for (const foo of v) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; - if (!astInfo) { - throw new Error('Expected prebuilt AST to be present'); - } + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var arr = vec4u(44, 88, 132, 176); + for (var i = 0; i < 4; i++) { + let foo = arr[i]; + { + continue; + } + } + var v = vec4f(1, 8, 8, 2); + for (var i = 0; i < 4; i++) { + let foo = v[i]; + { + continue; + } + } + }" + `); + }); - expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( - `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","f32"],[5,"3"]]],[[100,[[5,"1"],[5,"2"],[5,"3"]]]]]],[18,"foo","arr",[0,[[16]]]]]]"`, - ); + it('creates correct code for for ... of ... statement with array of non-primitives', () => { + const main = () => { + 'use gpu'; + const arr = d.arrayOf(d.vec2f, 3)([d.vec2f(1), d.vec2f(2), d.vec2f(3)]); + let res = 0; + for (const foo of arr) { + res += foo.x; + const i = res; + } + }; - provideCtx( - ctx, - () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.Void, - (astInfo.externals as () => Record)() ?? {}, - ); + const parsed = getMetaData(main)?.ast?.body; - wgslGenerator.initGenerator(ctx); - const gen = wgslGenerator.functionDefinition( - astInfo.ast?.body as tinyest.Block, - ); - expect(gen).toMatchInlineSnapshot(` - "{ - var arr = array(1f, 2f, 3f); - for (var i = 0; i < 3; i++) { - var foo = arr[i]; - { - continue; - } - } - }" - `); - }, + expect(JSON.stringify(parsed)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","vec2f"],[5,"3"]]],[[100,[[6,[7,"d","vec2f"],[[5,"1"]]],[6,[7,"d","vec2f"],[[5,"2"]]],[6,[7,"d","vec2f"],[[5,"3"]]]]]]]],[12,"res",[5,"0"]],[18,[13,"foo"],"arr",[0,[[2,"res","+=",[7,"foo","x"]],[13,"i","res"]]]]]]"`, ); expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var arr = array(1f, 2f, 3f); + var arr = array(vec2f(1), vec2f(2), vec2f(3)); + var res = 0; for (var i = 0; i < 3; i++) { var foo = arr[i]; + { + res += i32(foo.x); + let i = res; + } + } + }" + `); + }); + + it('creates correct code for for ... of ... statement with vector iterables', () => { + const main = () => { + 'use gpu'; + const v1 = d.vec4f(1, 2, 3, 4); + const v2 = d.vec3u(5, 6, 7); + const v3 = d.vec2b(true, false); + + let res1 = d.f32(); + let res2 = d.u32(); + let res3 = d.bool(); + + for (const foo of v1) { + res1 += foo; + } + + for (const foo of v2) { + res2 *= foo; + } + + for (const foo of v3) { + res3 = foo != res3; + } + }; + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var v1 = vec4f(1, 2, 3, 4); + var v2 = vec3u(5, 6, 7); + var v3 = vec2(true, false); + var res1 = 0f; + var res2 = 0u; + var res3 = false; + for (var i = 0; i < 4; i++) { + let foo = v1[i]; + { + res1 += foo; + } + } + for (var i = 0; i < 3; i++) { + let foo = v2[i]; + { + res2 *= foo; + } + } + for (var i = 0; i < 2; i++) { + let foo = v3[i]; + { + res3 = (foo != res3); + } + } + }" + `); + }); + + it('creates correct code for for ... of ... statement with struct member iterable', () => { + const TestStruct = d.struct({ + arr: d.arrayOf(d.f32, 4), + }); + + const main = () => { + 'use gpu'; + const testStruct = TestStruct({ arr: [1, 8, 8, 2] }); + for (const foo of testStruct.arr) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "struct TestStruct { + arr: array, + } + + fn main() { + var testStruct = TestStruct(array(1f, 8f, 8f, 2f)); + for (var i = 0; i < 4; i++) { + let foo = testStruct.arr[i]; { continue; } @@ -511,6 +636,47 @@ describe('wgslGenerator', () => { `); }); + it('throws error when for ... of ... iterable is ephemeral', () => { + const main = () => { + 'use gpu'; + for (const foo of [1, 2, 3]) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; + + expect(() => tgpu.resolve([main])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main(): \`for ... of ...\` loops only support iterables stored in variables] + `); + }); + + it('throws error when for ... of ... iterable is not an array or a vector', () => { + const TestStruct = d.struct({ + x: d.u32, + y: d.f32, + }); + + const main = () => { + 'use gpu'; + const testStruct = TestStruct({ x: 1, y: 2 }); + //@ts-ignore: let's assume it has an iterator + for (const foo of testStruct) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; + + expect(() => tgpu.resolve([main])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main(): \`for ... of ...\` loops only support array or vector iterables] + `); + }); + it('creates correct resources for derived values and slots', () => { const testFn = tgpu.fn([], d.vec4u)(() => { return derivedV4u.value; From f8ac4b80feb07610b2692ee55e0e392a9c75d8e2 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 16 Dec 2025 18:44:59 +0100 Subject: [PATCH 04/67] biome fixes --- packages/typegpu/src/tgsl/wgslGenerator.ts | 5 ++--- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 2c023f7755..3c289a9b44 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -33,7 +33,6 @@ import { tryConvertSnippet, } from './conversion.ts'; import { - coerceToSnippet, concretize, type GenerationCtx, numericLiteralToSnippet, @@ -1056,8 +1055,8 @@ ${this.ctx.pre}else ${alternate}`; const iterableDataType = iterableSnippet.dataType; - let elementCount; - let elementType; + let elementCount: number; + let elementType: wgsl.BaseData; if (wgsl.isWgslArray(iterableDataType)) { elementCount = iterableDataType.elementCount; elementType = iterableDataType.elementType; diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 6a5ce18fa3..f304bee586 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -571,7 +571,7 @@ describe('wgslGenerator', () => { } for (const foo of v3) { - res3 = foo != res3; + res3 = foo !== res3; } }; @@ -662,7 +662,7 @@ describe('wgslGenerator', () => { const main = () => { 'use gpu'; const testStruct = TestStruct({ x: 1, y: 2 }); - //@ts-ignore: let's assume it has an iterator + //@ts-expect-error: let's assume it has an iterator for (const foo of testStruct) { // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; From 8be4dd7c797fb9da577deb93ad4e844fec93d966 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 16 Dec 2025 19:00:03 +0100 Subject: [PATCH 05/67] for of in some examples --- .../src/examples/rendering/point-light-shadow/index.ts | 4 ++-- .../examples/simulation/fluid-double-buffering/index.ts | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts index 3db845e1f9..79d6c59301 100644 --- a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts @@ -182,8 +182,8 @@ const fragmentMain = tgpu['~unstable'].fragmentFn({ const diskRadius = shadowParams.$.diskRadius; let visibilityAcc = 0.0; - for (let i = 0; i < PCF_SAMPLES; i++) { - const o = samplesUniform.$[i].xy.mul(diskRadius); + for (const sample of samplesUniform.$) { + const o = sample.xy.mul(diskRadius); const sampleDir = dir .add(right.mul(o.x)) diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index fb47dade46..cde9d100ae 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -103,9 +103,7 @@ const time = root.createUniform(d.f32); const isInsideObstacle = (x: number, y: number): boolean => { 'use gpu'; - for (let obsIdx = 0; obsIdx < MAX_OBSTACLES; obsIdx++) { - const obs = obstacles.$[obsIdx]; - + for (const obs of obstacles.$) { if (obs.enabled === 0) { continue; } @@ -158,8 +156,7 @@ const computeVelocity = (x: number, y: number): d.v2f => { ]; let dirChoiceCount = 1; - for (let i = 0; i < 4; i++) { - const offset = neighborOffsets[i]; + for (const offset of neighborOffsets) { const neighborDensity = getCell(x + offset.x, y + offset.y); const cost = neighborDensity.z + d.f32(offset.y) * gravityCost; From d74b998446a9b47345925e2ea301795caf959b6c Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 16 Dec 2025 19:00:52 +0100 Subject: [PATCH 06/67] updated wgsl tests --- .../individual/fluid-double-buffering.test.ts | 164 ++++++++++-------- .../individual/point-light-shadow.test.ts | 11 +- 2 files changed, 95 insertions(+), 80 deletions(-) diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 3b5f09e290..dd1f9c3ee2 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -36,17 +36,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var obstacles_7: array; fn isInsideObstacle_6(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles_7[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + var obs = obstacles_7[i]; + { + if ((obs.enabled == 0u)) { + continue; + } + let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); + let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); + let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); + let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; @@ -129,17 +131,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var obstacles_14: array; fn isInsideObstacle_13(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles_14[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + var obs = obstacles_14[i]; + { + if ((obs.enabled == 0u)) { + continue; + } + let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); + let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); + let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); + let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; @@ -174,22 +178,24 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; - for (var i = 0; (i < 4i); i++) { - let offset = (&neighborOffsets[i]); - var neighborDensity = getCell_8((x + (*offset).x), (y + (*offset).y)); - let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); - if (!isValidFlowOut_11((x + (*offset).x), (y + (*offset).y))) { - continue; - } - if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount++; - } - else { - if ((cost < leastCost)) { - leastCost = cost; - dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount = 1i; + for (var i = 0; i < 4; i++) { + var offset = neighborOffsets[i]; + { + var neighborDensity = getCell_8((x + offset.x), (y + offset.y)); + let cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); + if (!isValidFlowOut_11((x + offset.x), (y + offset.y))) { + continue; + } + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(offset.x), f32(offset.y)); + dirChoiceCount = 1i; + } } } } @@ -305,17 +311,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var obstacles_14: array; fn isInsideObstacle_13(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles_14[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + var obs = obstacles_14[i]; + { + if ((obs.enabled == 0u)) { + continue; + } + let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); + let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); + let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); + let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; @@ -350,22 +358,24 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; - for (var i = 0; (i < 4i); i++) { - let offset = (&neighborOffsets[i]); - var neighborDensity = getCell_8((x + (*offset).x), (y + (*offset).y)); - let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); - if (!isValidFlowOut_11((x + (*offset).x), (y + (*offset).y))) { - continue; - } - if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount++; - } - else { - if ((cost < leastCost)) { - leastCost = cost; - dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount = 1i; + for (var i = 0; i < 4; i++) { + var offset = neighborOffsets[i]; + { + var neighborDensity = getCell_8((x + offset.x), (y + offset.y)); + let cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); + if (!isValidFlowOut_11((x + offset.x), (y + offset.y))) { + continue; + } + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(offset.x), f32(offset.y)); + dirChoiceCount = 1i; + } } } } @@ -474,17 +484,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var obstacles_7: array; fn isInsideObstacle_6(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles_7[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + var obs = obstacles_7[i]; + { + if ((obs.enabled == 0u)) { + continue; + } + let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); + let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); + let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); + let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; diff --git a/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts index 4f107f2ade..632738ad0f 100644 --- a/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts +++ b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts @@ -132,10 +132,13 @@ describe('point light shadow example', () => { let PCF_SAMPLES = shadowParams_7.pcfSamples; let diskRadius = shadowParams_7.diskRadius; var visibilityAcc = 0; - for (var i = 0; (i < i32(PCF_SAMPLES)); i++) { - var o = (samplesUniform_9[i].xy * diskRadius); - var sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); - visibilityAcc += i32(textureSampleCompare(shadowDepthCube_10, shadowSampler_11, sampleDir, depthRef)); + for (var i = 0; i < 64; i++) { + var sample = samplesUniform_9[i]; + { + var o = (sample.xy * diskRadius); + var sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); + visibilityAcc += i32(textureSampleCompare(shadowDepthCube_10, shadowSampler_11, sampleDir, depthRef)); + } } let rawNdotl = dot(_arg_0.normal, lightDir); let visibility = select((f32(visibilityAcc) / f32(PCF_SAMPLES)), 0f, (rawNdotl < 0f)); From 934bacfd5343101a6092edbb411a575bc3bd467c Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 16 Dec 2025 19:44:33 +0100 Subject: [PATCH 07/67] cleanup --- packages/typegpu/src/tgsl/wgslGenerator.ts | 18 ++- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 111 +++++++++++------- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 3c289a9b44..4f21c0a730 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1054,12 +1054,11 @@ ${this.ctx.pre}else ${alternate}`; } const iterableDataType = iterableSnippet.dataType; - let elementCount: number; - let elementType: wgsl.BaseData; + let elementType: wgsl.AnyWgslData; if (wgsl.isWgslArray(iterableDataType)) { elementCount = iterableDataType.elementCount; - elementType = iterableDataType.elementType; + elementType = iterableDataType.elementType as wgsl.AnyWgslData; } else if (wgsl.isVec(iterableDataType)) { elementType = iterableDataType.primitive; elementCount = Number(iterableDataType.type.match(/\d/)); @@ -1068,10 +1067,6 @@ ${this.ctx.pre}else ${alternate}`; '`for ... of ...` loops only support array or vector iterables', ); } - const loopVarKind = loopVar[0] === tinyest.NodeTypeCatalog.const && - wgsl.isNaturallyEphemeral(elementType) - ? 'let' - : 'var'; /* * if user defines a variable named 'i', it will be scoped to a new block, @@ -1079,18 +1074,21 @@ ${this.ctx.pre}else ${alternate}`; */ const index = this.ctx.makeNameValid('i'); + const loopVarKind = loopVar[0] === tinyest.NodeTypeCatalog.const && + wgsl.isNaturallyEphemeral(elementType) + ? 'let' + : 'var'; const loopVarName = this.ctx.makeNameValid(loopVar[1]); const loopVarSnippet = snip( loopVarName, - elementType as AnyData, + elementType, 'runtime', ); this.ctx.defineVariable(loopVarName, loopVarSnippet); const iterableStr = this.ctx.resolve( iterableSnippet.value, - // it's vector or wgslArray - iterableSnippet.dataType as AnyData, + iterableDataType, ).value; const forStr = diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index f304bee586..853d176070 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -449,7 +449,38 @@ describe('wgslGenerator', () => { `); }); - it('creates correct code for for ... of ... statement with array of primitives', () => { + it('parses correctly "for ... of ..." statements', () => { + const main1 = () => { + 'use gpu'; + const arr = [1, 2, 3]; + for (const foo of arr) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; + + const main2 = () => { + 'use gpu'; + const arr = [1, 2, 3]; + // biome-ignore lint/style/useConst: it's a part of the test + for (let foo of arr) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; + + const parsed1 = getMetaData(main1)?.ast?.body; + expect(JSON.stringify(parsed1)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[100,[[5,"1"],[5,"2"],[5,"3"]]]],[18,[13,"foo"],"arr",[0,[[16]]]]]]"`, + ); + + const parsed2 = getMetaData(main2)?.ast?.body; + expect(JSON.stringify(parsed2)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[100,[[5,"1"],[5,"2"],[5,"3"]]]],[18,[12,"foo"],"arr",[0,[[16]]]]]]"`, + ); + }); + + it('creates correct code for "for ... of ..." statement using array of primitives', () => { const main = () => { 'use gpu'; const arr = d.arrayOf(d.f32, 3)([1, 2, 3]); @@ -460,20 +491,46 @@ describe('wgslGenerator', () => { } }; + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var arr = array(1f, 2f, 3f); + var res = 0f; + for (var i = 0; i < 3; i++) { + let foo = arr[i]; + { + res += foo; + let i = res; + } + } + }" + `); + }); + + it('creates correct code for "for ... of ..." statement using array of non-primitives', () => { + const main = () => { + 'use gpu'; + const arr = d.arrayOf(d.vec2f, 3)([d.vec2f(1), d.vec2f(2), d.vec2f(3)]); + let res = 0; + for (const foo of arr) { + res += foo.x; + const i = res; + } + }; + const parsed = getMetaData(main)?.ast?.body; expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","f32"],[5,"3"]]],[[100,[[5,"1"],[5,"2"],[5,"3"]]]]]],[12,"res",[6,[7,"d","f32"],[]]],[18,[13,"foo"],"arr",[0,[[2,"res","+=","foo"],[13,"i","res"]]]]]]"`, + `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","vec2f"],[5,"3"]]],[[100,[[6,[7,"d","vec2f"],[[5,"1"]]],[6,[7,"d","vec2f"],[[5,"2"]]],[6,[7,"d","vec2f"],[[5,"3"]]]]]]]],[12,"res",[5,"0"]],[18,[13,"foo"],"arr",[0,[[2,"res","+=",[7,"foo","x"]],[13,"i","res"]]]]]]"`, ); expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var arr = array(1f, 2f, 3f); - var res = 0f; + var arr = array(vec2f(1), vec2f(2), vec2f(3)); + var res = 0; for (var i = 0; i < 3; i++) { - let foo = arr[i]; + var foo = arr[i]; { - res += foo; + res += i32(foo.x); let i = res; } } @@ -481,7 +538,7 @@ describe('wgslGenerator', () => { `); }); - it('creates correct code for for ... of ... statement derived, comptime iterables', () => { + it('creates correct code for "for ... of ..." statements using derived and comptime iterables', () => { const comptimeVec = tgpu['~unstable'].comptime(() => d.vec4f(1, 8, 8, 2)); const main = () => { @@ -519,39 +576,7 @@ describe('wgslGenerator', () => { `); }); - it('creates correct code for for ... of ... statement with array of non-primitives', () => { - const main = () => { - 'use gpu'; - const arr = d.arrayOf(d.vec2f, 3)([d.vec2f(1), d.vec2f(2), d.vec2f(3)]); - let res = 0; - for (const foo of arr) { - res += foo.x; - const i = res; - } - }; - - const parsed = getMetaData(main)?.ast?.body; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","vec2f"],[5,"3"]]],[[100,[[6,[7,"d","vec2f"],[[5,"1"]]],[6,[7,"d","vec2f"],[[5,"2"]]],[6,[7,"d","vec2f"],[[5,"3"]]]]]]]],[12,"res",[5,"0"]],[18,[13,"foo"],"arr",[0,[[2,"res","+=",[7,"foo","x"]],[13,"i","res"]]]]]]"`, - ); - - expect(tgpu.resolve([main])).toMatchInlineSnapshot(` - "fn main() { - var arr = array(vec2f(1), vec2f(2), vec2f(3)); - var res = 0; - for (var i = 0; i < 3; i++) { - var foo = arr[i]; - { - res += i32(foo.x); - let i = res; - } - } - }" - `); - }); - - it('creates correct code for for ... of ... statement with vector iterables', () => { + it('creates correct code for "for ... of ..." statements using vector iterables', () => { const main = () => { 'use gpu'; const v1 = d.vec4f(1, 2, 3, 4); @@ -605,7 +630,7 @@ describe('wgslGenerator', () => { `); }); - it('creates correct code for for ... of ... statement with struct member iterable', () => { + it('creates correct code for "for ... of ..." statement using a struct member iterable', () => { const TestStruct = d.struct({ arr: d.arrayOf(d.f32, 4), }); @@ -636,7 +661,7 @@ describe('wgslGenerator', () => { `); }); - it('throws error when for ... of ... iterable is ephemeral', () => { + it('throws error when "for ... of ..." statement uses an ephemeral iterable', () => { const main = () => { 'use gpu'; for (const foo of [1, 2, 3]) { @@ -653,7 +678,7 @@ describe('wgslGenerator', () => { `); }); - it('throws error when for ... of ... iterable is not an array or a vector', () => { + it('throws error when "for ... of ..." statement uses iterable that is not an array or a vector', () => { const TestStruct = d.struct({ x: d.u32, y: d.f32, From 38bee477a56c36290ea8b3f47c0a2e36053f43c9 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 17 Dec 2025 10:31:14 +0100 Subject: [PATCH 08/67] minor test cleanup --- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 853d176070..3c080eb821 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -517,12 +517,6 @@ describe('wgslGenerator', () => { } }; - const parsed = getMetaData(main)?.ast?.body; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[13,"arr",[6,[6,[7,"d","arrayOf"],[[7,"d","vec2f"],[5,"3"]]],[[100,[[6,[7,"d","vec2f"],[[5,"1"]]],[6,[7,"d","vec2f"],[[5,"2"]]],[6,[7,"d","vec2f"],[[5,"3"]]]]]]]],[12,"res",[5,"0"]],[18,[13,"foo"],"arr",[0,[[2,"res","+=",[7,"foo","x"]],[13,"i","res"]]]]]]"`, - ); - expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { var arr = array(vec2f(1), vec2f(2), vec2f(3)); @@ -539,18 +533,18 @@ describe('wgslGenerator', () => { }); it('creates correct code for "for ... of ..." statements using derived and comptime iterables', () => { - const comptimeVec = tgpu['~unstable'].comptime(() => d.vec4f(1, 8, 8, 2)); + const comptimeVec = tgpu['~unstable'].comptime(() => d.vec2f(1, 2)); const main = () => { 'use gpu'; - const arr = derivedV4u.$; - for (const foo of arr) { + const v1 = derivedV4u.$; + for (const foo of v1) { // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } - const v = comptimeVec(); - for (const foo of v) { + const v2 = comptimeVec(); + for (const foo of v2) { // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } @@ -558,16 +552,16 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var arr = vec4u(44, 88, 132, 176); + var v1 = vec4u(44, 88, 132, 176); for (var i = 0; i < 4; i++) { - let foo = arr[i]; + let foo = v1[i]; { continue; } } - var v = vec4f(1, 8, 8, 2); - for (var i = 0; i < 4; i++) { - let foo = v[i]; + var v2 = vec2f(1, 2); + for (var i = 0; i < 2; i++) { + let foo = v2[i]; { continue; } From 581f7d2352a5cc620b98f412c9428e7adfcf1348 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 18 Dec 2025 15:29:24 +0100 Subject: [PATCH 09/67] Taking a reference to the iterated value if it's a non-ephemeral element --- packages/typegpu/src/tgsl/wgslGenerator.ts | 65 +++++++++++++---- .../individual/fluid-double-buffering.test.ts | 72 +++++++++---------- .../individual/point-light-shadow.test.ts | 4 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +- 4 files changed, 91 insertions(+), 54 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 4f21c0a730..8ec63fda59 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1053,14 +1053,26 @@ ${this.ctx.pre}else ${alternate}`; ); } + // if user defines a variable named 'i', it will be scoped to a new block, + // shadowing our 'i' + const index = this.ctx.makeNameValid('i'); + + const elementSnippet = accessIndex( + iterableSnippet, + snip(index, u32, 'runtime'), + ); + if (!elementSnippet) { + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + } + const iterableDataType = iterableSnippet.dataType; let elementCount: number; - let elementType: wgsl.AnyWgslData; + let elementType = elementSnippet?.dataType; if (wgsl.isWgslArray(iterableDataType)) { elementCount = iterableDataType.elementCount; - elementType = iterableDataType.elementType as wgsl.AnyWgslData; } else if (wgsl.isVec(iterableDataType)) { - elementType = iterableDataType.primitive; elementCount = Number(iterableDataType.type.match(/\d/)); } else { throw new WgslTypeError( @@ -1068,21 +1080,44 @@ ${this.ctx.pre}else ${alternate}`; ); } - /* - * if user defines a variable named 'i', it will be scoped to a new block, - * shadowing our 'i' - */ - const index = this.ctx.makeNameValid('i'); + if (loopVar[0] !== NODE.const) { + throw new WgslTypeError( + 'Only `for (const ... of ... )` loops are supported', + ); + } - const loopVarKind = loopVar[0] === tinyest.NodeTypeCatalog.const && - wgsl.isNaturallyEphemeral(elementType) - ? 'let' - : 'var'; + // If it's ephemeral, it's a value that cannot change. If it's a reference, we take + // an implicit pointer to it + let loopVarKind = 'let'; const loopVarName = this.ctx.makeNameValid(loopVar[1]); + + if (!isEphemeralSnippet(elementSnippet)) { + if (elementSnippet.origin === 'constant-tgpu-const-ref') { + loopVarKind = 'const'; + } else if (elementSnippet.origin === 'runtime-tgpu-const-ref') { + loopVarKind = 'let'; + } else { + loopVarKind = 'let'; + if (!wgsl.isPtr(elementType)) { + const ptrType = createPtrFromOrigin( + elementSnippet.origin, + concretize(elementType as wgsl.AnyWgslData) as wgsl.StorableData, + ); + invariant( + ptrType !== undefined, + `Creating pointer type from origin ${elementSnippet.origin}`, + ); + elementType = ptrType; + } + + elementType = implicitFrom(elementType); + } + } + const loopVarSnippet = snip( loopVarName, elementType, - 'runtime', + elementSnippet.origin, ); this.ctx.defineVariable(loopVarName, loopVarSnippet); @@ -1097,7 +1132,9 @@ ${this.ctx.pre}else ${alternate}`; this.ctx.indent(); const loopVarDeclStr = - `${this.ctx.pre}${loopVarKind} ${loopVarName} = ${iterableStr}[${index}];`; + stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ + tryConvertSnippet(elementSnippet, elementType as AnyData, false) + };`; const bodyStr = `${this.ctx.pre}${ this.block(blockifySingleStatement(body)) diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index dd1f9c3ee2..00d80f81c9 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -37,15 +37,15 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_6(x: i32, y: i32) -> bool { for (var i = 0; i < 4; i++) { - var obs = obstacles_7[i]; + let obs = (&obstacles_7[i]); { - if ((obs.enabled == 0u)) { + if (((*obs).enabled == 0u)) { continue; } - let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -132,15 +132,15 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_13(x: i32, y: i32) -> bool { for (var i = 0; i < 4; i++) { - var obs = obstacles_14[i]; + let obs = (&obstacles_14[i]); { - if ((obs.enabled == 0u)) { + if (((*obs).enabled == 0u)) { continue; } - let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -179,21 +179,21 @@ describe('fluid double buffering example', () => { var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; i < 4; i++) { - var offset = neighborOffsets[i]; + let offset = (&neighborOffsets[i]); { - var neighborDensity = getCell_8((x + offset.x), (y + offset.y)); - let cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); - if (!isValidFlowOut_11((x + offset.x), (y + offset.y))) { + var neighborDensity = getCell_8((x + (*offset).x), (y + (*offset).y)); + let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut_11((x + (*offset).x), (y + (*offset).y))) { continue; } if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0i] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount = 1i; } } @@ -312,15 +312,15 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_13(x: i32, y: i32) -> bool { for (var i = 0; i < 4; i++) { - var obs = obstacles_14[i]; + let obs = (&obstacles_14[i]); { - if ((obs.enabled == 0u)) { + if (((*obs).enabled == 0u)) { continue; } - let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -359,21 +359,21 @@ describe('fluid double buffering example', () => { var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; i < 4; i++) { - var offset = neighborOffsets[i]; + let offset = (&neighborOffsets[i]); { - var neighborDensity = getCell_8((x + offset.x), (y + offset.y)); - let cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); - if (!isValidFlowOut_11((x + offset.x), (y + offset.y))) { + var neighborDensity = getCell_8((x + (*offset).x), (y + (*offset).y)); + let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut_11((x + (*offset).x), (y + (*offset).y))) { continue; } if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0i] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount = 1i; } } @@ -485,15 +485,15 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_6(x: i32, y: i32) -> bool { for (var i = 0; i < 4; i++) { - var obs = obstacles_7[i]; + let obs = (&obstacles_7[i]); { - if ((obs.enabled == 0u)) { + if (((*obs).enabled == 0u)) { continue; } - let minX = max(0i, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - let maxX = min(256i, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - let minY = max(0i, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - let maxY = min(256i, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } diff --git a/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts index 632738ad0f..30ba948545 100644 --- a/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts +++ b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts @@ -133,9 +133,9 @@ describe('point light shadow example', () => { let diskRadius = shadowParams_7.diskRadius; var visibilityAcc = 0; for (var i = 0; i < 64; i++) { - var sample = samplesUniform_9[i]; + let sample = (&samplesUniform_9[i]); { - var o = (sample.xy * diskRadius); + var o = ((*sample).xy * diskRadius); var sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); visibilityAcc += i32(textureSampleCompare(shadowDepthCube_10, shadowSampler_11, sampleDir, depthRef)); } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 3c080eb821..fce60b9fee 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -522,9 +522,9 @@ describe('wgslGenerator', () => { var arr = array(vec2f(1), vec2f(2), vec2f(3)); var res = 0; for (var i = 0; i < 3; i++) { - var foo = arr[i]; + let foo = (&arr[i]); { - res += i32(foo.x); + res += i32((*foo).x); let i = res; } } From 5e8cfe0675f51da345315195ac599202489acf19 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 18 Dec 2025 15:39:52 +0100 Subject: [PATCH 10/67] Revert example change --- .../examples/rendering/point-light-shadow/index.ts | 4 ++-- .../examples/individual/point-light-shadow.test.ts | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts index 79d6c59301..3db845e1f9 100644 --- a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts @@ -182,8 +182,8 @@ const fragmentMain = tgpu['~unstable'].fragmentFn({ const diskRadius = shadowParams.$.diskRadius; let visibilityAcc = 0.0; - for (const sample of samplesUniform.$) { - const o = sample.xy.mul(diskRadius); + for (let i = 0; i < PCF_SAMPLES; i++) { + const o = samplesUniform.$[i].xy.mul(diskRadius); const sampleDir = dir .add(right.mul(o.x)) diff --git a/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts index 30ba948545..4f107f2ade 100644 --- a/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts +++ b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts @@ -132,13 +132,10 @@ describe('point light shadow example', () => { let PCF_SAMPLES = shadowParams_7.pcfSamples; let diskRadius = shadowParams_7.diskRadius; var visibilityAcc = 0; - for (var i = 0; i < 64; i++) { - let sample = (&samplesUniform_9[i]); - { - var o = ((*sample).xy * diskRadius); - var sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); - visibilityAcc += i32(textureSampleCompare(shadowDepthCube_10, shadowSampler_11, sampleDir, depthRef)); - } + for (var i = 0; (i < i32(PCF_SAMPLES)); i++) { + var o = (samplesUniform_9[i].xy * diskRadius); + var sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); + visibilityAcc += i32(textureSampleCompare(shadowDepthCube_10, shadowSampler_11, sampleDir, depthRef)); } let rawNdotl = dot(_arg_0.normal, lightDir); let visibility = select((f32(visibilityAcc) / f32(PCF_SAMPLES)), 0f, (rawNdotl < 0f)); From c386c459bc2680fcaa9102c91f1c227e90c80bf0 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Thu, 18 Dec 2025 15:47:31 +0100 Subject: [PATCH 11/67] Free test! --- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index fce60b9fee..d3e0d7263b 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -696,6 +696,23 @@ describe('wgslGenerator', () => { `); }); + it('throws error when "for ... of ..." statement uses let declarator', () => { + const main = () => { + 'use gpu'; + const arr = [1, 2, 3]; + // biome-ignore lint/style/useConst: it's a part of the test + for (let foo of arr) { + } + }; + + expect(() => tgpu.resolve([main])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main(): Only \`for (const ... of ... )\` loops are supported] + `); + }); + it('creates correct resources for derived values and slots', () => { const testFn = tgpu.fn([], d.vec4u)(() => { return derivedV4u.value; From 9b0da3d8c1b8d6b4d12d0d1f006902a87bbc3835 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 19 Dec 2025 00:15:16 +0100 Subject: [PATCH 12/67] more review changes + vector type guard impr --- packages/typegpu/src/data/compiledIO.ts | 4 ++ packages/typegpu/src/data/wgslTypes.ts | 14 +++++- packages/typegpu/src/tgsl/wgslGenerator.ts | 19 ++++---- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 45 +++++++++++++++++++ 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/typegpu/src/data/compiledIO.ts b/packages/typegpu/src/data/compiledIO.ts index 746f79e411..2668489717 100644 --- a/packages/typegpu/src/data/compiledIO.ts +++ b/packages/typegpu/src/data/compiledIO.ts @@ -196,6 +196,10 @@ export function buildWriter( } if (wgsl.isVec(node)) { + if (wgsl.isVecBool(node)) { + throw new Error('Compiled writers do not support boolean vectors'); + } + const primitive = typeToPrimitive[node.type]; let code = ''; const writeFunc = primitiveToWriteFunction[primitive]; diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 7e9fef27b2..942bf1d72c 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1688,17 +1688,29 @@ export function isVec( | Vec2h | Vec2i | Vec2u + | Vec2b | Vec3f | Vec3h | Vec3i | Vec3u + | Vec3b | Vec4f | Vec4h | Vec4i - | Vec4u { + | Vec4u + | Vec4b { return isVec2(value) || isVec3(value) || isVec4(value); } +export function isVecBool( + value: unknown, +): value is + | Vec2b + | Vec3b + | Vec4b { + return isVec(value) && value.type.includes('b'); +} + export function isMatInstance(value: unknown): value is AnyMatInstance { const v = value as AnyMatInstance | undefined; return isMarkedInternal(v) && diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 8ec63fda59..41c36b8cf0 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1053,9 +1053,15 @@ ${this.ctx.pre}else ${alternate}`; ); } - // if user defines a variable named 'i', it will be scoped to a new block, - // shadowing our 'i' - const index = this.ctx.makeNameValid('i'); + // Our index name will be some element from infinite sequence (i, ii, iii, ...). + // If user defines `i` and `ii` before `for ... of ...` loop, then our index name will be `iii`. + // If user defines `i` inside `for ... of ...` then it will be scoped to a new block, + // so we can safely use `i`. + let index = 'i'; // it will be valid name, no need to call this.ctx.makeNameValid + while (this.ctx.getById(index) !== null) { + console.log(this.ctx.getById(index)); + index += 'i'; + } const elementSnippet = accessIndex( iterableSnippet, @@ -1069,7 +1075,7 @@ ${this.ctx.pre}else ${alternate}`; const iterableDataType = iterableSnippet.dataType; let elementCount: number; - let elementType = elementSnippet?.dataType; + let elementType = elementSnippet.dataType; if (wgsl.isWgslArray(iterableDataType)) { elementCount = iterableDataType.elementCount; } else if (wgsl.isVec(iterableDataType)) { @@ -1121,11 +1127,6 @@ ${this.ctx.pre}else ${alternate}`; ); this.ctx.defineVariable(loopVarName, loopVarSnippet); - const iterableStr = this.ctx.resolve( - iterableSnippet.value, - iterableDataType, - ).value; - const forStr = `${this.ctx.pre}for (var ${index} = 0; ${index} < ${elementCount}; ${index}++) {`; diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index d3e0d7263b..f824467785 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -713,6 +713,51 @@ describe('wgslGenerator', () => { `); }); + it('handles "for ... of ..." internal index variable when "i" is used by user', () => { + const f1 = () => { + 'use gpu'; + const arr = [1, 2, 3]; + for (const foo of arr) { + const i = foo; + } + }; + + expect(tgpu.resolve([f1])).toMatchInlineSnapshot(` + "fn f1() { + var arr = array(1, 2, 3); + for (var i = 0; i < 3; i++) { + let foo = arr[i]; + { + let i = foo; + } + } + }" + `); + + const f2 = () => { + 'use gpu'; + const i = 7; + const arr = [1, 2, 3]; + for (const foo of arr) { + // biome-ignore lint/complexity/noUselessContinue: it's a part of the test + continue; + } + }; + + expect(tgpu.resolve([f2])).toMatchInlineSnapshot(` + "fn f2() { + const i = 7; + var arr = array(1, 2, 3); + for (var ii = 0; ii < 3; ii++) { + let foo = arr[ii]; + { + continue; + } + } + }" + `); + }); + it('creates correct resources for derived values and slots', () => { const testFn = tgpu.fn([], d.vec4u)(() => { return derivedV4u.value; From 17c34dcd42553f93ccc02d22d5f2f4492525299e Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Sat, 20 Dec 2025 03:32:57 +0100 Subject: [PATCH 13/67] review changes --- packages/typegpu/src/tgsl/wgslGenerator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 41c36b8cf0..cd62967a1f 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1059,7 +1059,6 @@ ${this.ctx.pre}else ${alternate}`; // so we can safely use `i`. let index = 'i'; // it will be valid name, no need to call this.ctx.makeNameValid while (this.ctx.getById(index) !== null) { - console.log(this.ctx.getById(index)); index += 'i'; } From 41a0165ae723fdd7b82a352e8f95551703a6e57e Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Sun, 21 Dec 2025 22:45:50 +0100 Subject: [PATCH 14/67] nested forOf test --- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index f824467785..defa7c2d53 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -506,6 +506,37 @@ describe('wgslGenerator', () => { `); }); + it('creates correct code for "for ... of ..." nested statements', () => { + const main = () => { + 'use gpu'; + const arr = d.arrayOf(d.f32, 3)([1, 2, 3]); + let res = d.f32(); + for (const foo of arr) { + for (const boo of arr) { + res += foo * boo; + } + } + }; + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var arr = array(1f, 2f, 3f); + var res = 0f; + for (var i = 0; i < 3; i++) { + let foo = arr[i]; + { + for (var i = 0; i < 3; i++) { + let boo = arr[i]; + { + res += (foo * boo); + } + } + } + } + }" + `); + }); + it('creates correct code for "for ... of ..." statement using array of non-primitives', () => { const main = () => { 'use gpu'; From e47a2ccdba9a2f3c67297586393fa7fb80aac4bd Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Sun, 21 Dec 2025 23:12:34 +0100 Subject: [PATCH 15/67] the last test, I promise --- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index defa7c2d53..f4eb47a3de 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -537,6 +537,37 @@ describe('wgslGenerator', () => { `); }); + it('creates correct code for "for ... of ..." nested statements that use the same variable name', () => { + const main = () => { + 'use gpu'; + const arr = d.arrayOf(d.f32, 3)([1, 2, 3]); + let res = d.f32(); + for (const foo of arr) { + for (const foo of arr) { + res += foo * foo; + } + } + }; + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var arr = array(1f, 2f, 3f); + var res = 0f; + for (var i = 0; i < 3; i++) { + let foo = arr[i]; + { + for (var i = 0; i < 3; i++) { + let foo2 = arr[i]; + { + res += (foo2 * foo2); + } + } + } + } + }" + `); + }); + it('creates correct code for "for ... of ..." statement using array of non-primitives', () => { const main = () => { 'use gpu'; From 80e60bd7f6105c057c9033e7635f867db41bb49c Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 22 Dec 2025 09:46:06 +0100 Subject: [PATCH 16/67] test cleanup --- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index f4eb47a3de..cacc42e8b1 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -487,7 +487,7 @@ describe('wgslGenerator', () => { let res = d.f32(); for (const foo of arr) { res += foo; - const i = res; + const i = res; // this is intentional, scoping `i` to a block separate from `arr` index } }; @@ -575,7 +575,6 @@ describe('wgslGenerator', () => { let res = 0; for (const foo of arr) { res += foo.x; - const i = res; } }; @@ -587,7 +586,6 @@ describe('wgslGenerator', () => { let foo = (&arr[i]); { res += i32((*foo).x); - let i = res; } } }" From 26e0f987d11c5c0be9189623ac95ed7d5fcba660 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 22 Dec 2025 13:04:29 +0100 Subject: [PATCH 17/67] I lied --- packages/typegpu/src/tgsl/wgslGenerator.ts | 22 +++++++++++---- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 28 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 7dc0487b0d..6c4a5cd09e 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -44,6 +44,7 @@ import type { DualFn } from '../data/dualFn.ts'; import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; +import { arrayLength } from 'src/std/array.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -1088,12 +1089,22 @@ ${this.ctx.pre}else ${alternate}`; } const iterableDataType = iterableSnippet.dataType; - let elementCount: number; + let elementCountSnippet: Snippet; let elementType = elementSnippet.dataType; if (wgsl.isWgslArray(iterableDataType)) { - elementCount = iterableDataType.elementCount; + elementCountSnippet = iterableDataType.elementCount > 0 + ? snip( + `${iterableDataType.elementCount}`, + u32, + 'constant', + ) + : arrayLength[$internal].gpuImpl(iterableSnippet); } else if (wgsl.isVec(iterableDataType)) { - elementCount = Number(iterableDataType.type.match(/\d/)); + elementCountSnippet = snip( + `${Number(iterableDataType.type.match(/\d/))}`, + u32, + 'constant', + ); } else { throw new WgslTypeError( '`for ... of ...` loops only support array or vector iterables', @@ -1141,8 +1152,9 @@ ${this.ctx.pre}else ${alternate}`; ); this.ctx.defineVariable(loopVarName, loopVarSnippet); - const forStr = - `${this.ctx.pre}for (var ${index} = 0; ${index} < ${elementCount}; ${index}++) {`; + const forStr = stitch`${this.ctx.pre}for (var ${index} = 0; ${index} < ${ + tryConvertSnippet(elementCountSnippet, u32, false) + }; ${index}++) {`; this.ctx.indent(); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index cacc42e8b1..92455a3b4e 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -592,6 +592,34 @@ describe('wgslGenerator', () => { `); }); + it('creates correct code for "for ... of ..." statement using runtime size array', ({ root }) => { + const layout = tgpu.bindGroupLayout({ + arr: { storage: d.arrayOf(d.f32) }, + }); + + const main = () => { + 'use gpu'; + let res = d.f32(0); + for (const foo of layout.$.arr) { + res += foo; + } + }; + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var arr: array; + + fn main() { + var res = 0f; + for (var i = 0; i < arrayLength((&arr)); i++) { + let foo = arr[i]; + { + res += foo; + } + } + }" + `); + }); + it('creates correct code for "for ... of ..." statements using derived and comptime iterables', () => { const comptimeVec = tgpu['~unstable'].comptime(() => d.vec2f(1, 2)); From 46ef92a80752988e72412f606ff1ba388313c712 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 23 Dec 2025 15:22:20 +0100 Subject: [PATCH 18/67] merge main --- packages/typegpu/src/tgsl/wgslGenerator.ts | 5 +- .../individual/fluid-double-buffering.test.ts | 156 ++++++++++-------- 2 files changed, 86 insertions(+), 75 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 6c4a5cd09e..47c8f7313b 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -21,9 +21,8 @@ import { import * as wgsl from '../data/wgslTypes.ts'; import { invariant, ResolutionError, WgslTypeError } from '../errors.ts'; import { getName } from '../shared/meta.ts'; -import { isMarkedInternal } from '../shared/symbols.ts'; +import { $internal, isMarkedInternal } from '../shared/symbols.ts'; import { safeStringify } from '../shared/stringify.ts'; -import { $internal } from '../shared/symbols.ts'; import { pow } from '../std/numeric.ts'; import { add, div, mul, neg, sub } from '../std/operators.ts'; import { type FnArgsConversionHint, isKnownAtComptime } from '../types.ts'; @@ -44,7 +43,7 @@ import type { DualFn } from '../data/dualFn.ts'; import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; -import { arrayLength } from 'src/std/array.ts'; +import { arrayLength } from '../std/array.ts'; const { NodeTypeCatalog: NODE } = tinyest; diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 53e71f8eb0..9826333df6 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -36,17 +36,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + let obs = (&obstacles[i]); + { + if (((*obs).enabled == 0u)) { + continue; + } + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; @@ -129,17 +131,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + let obs = (&obstacles[i]); + { + if (((*obs).enabled == 0u)) { + continue; + } + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; @@ -176,20 +180,22 @@ describe('fluid double buffering example', () => { var dirChoiceCount = 1; for (var i = 0; i < 4; i++) { let offset = (&neighborOffsets[i]); - var neighborDensity = getCell((x + (*offset).x), (y + (*offset).y)); - let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); - if (!isValidFlowOut((x + (*offset).x), (y + (*offset).y))) { - continue; - } - if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount++; - } - else { - if ((cost < leastCost)) { - leastCost = cost; - dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount = 1i; + { + var neighborDensity = getCell((x + (*offset).x), (y + (*offset).y)); + let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut((x + (*offset).x), (y + (*offset).y))) { + continue; + } + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoiceCount = 1i; + } } } } @@ -305,17 +311,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + let obs = (&obstacles[i]); + { + if (((*obs).enabled == 0u)) { + continue; + } + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; @@ -352,20 +360,22 @@ describe('fluid double buffering example', () => { var dirChoiceCount = 1; for (var i = 0; i < 4; i++) { let offset = (&neighborOffsets[i]); - var neighborDensity = getCell((x + (*offset).x), (y + (*offset).y)); - let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); - if (!isValidFlowOut((x + (*offset).x), (y + (*offset).y))) { - continue; - } - if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount++; - } - else { - if ((cost < leastCost)) { - leastCost = cost; - dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); - dirChoiceCount = 1i; + { + var neighborDensity = getCell((x + (*offset).x), (y + (*offset).y)); + let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut((x + (*offset).x), (y + (*offset).y))) { + continue; + } + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoiceCount = 1i; + } } } } @@ -474,17 +484,19 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var obsIdx = 0; (obsIdx < 4i); obsIdx++) { - let obs = (&obstacles[obsIdx]); - if (((*obs).enabled == 0u)) { - continue; - } - let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); - if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { - return true; + for (var i = 0; i < 4; i++) { + let obs = (&obstacles[i]); + { + if (((*obs).enabled == 0u)) { + continue; + } + let minX = max(0i, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256i, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0i, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256i, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { + return true; + } } } return false; From a1addca09fc27c429f830c31a9cf9c34a2e9b883 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 8 Jan 2026 16:44:35 +0100 Subject: [PATCH 19/67] intermediate representation of array expression --- .../typegpu/src/tgsl/generationHelpers.ts | 35 +++++++- packages/typegpu/src/tgsl/wgslGenerator.ts | 17 ++-- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 80 +++++++++++++++++++ 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 3b5061e24c..90e18a0c1d 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -1,7 +1,14 @@ +import { arrayOf } from '../../src/data/array.ts'; +import { $internal, $resolve } from '../../src/shared/symbols.ts'; import { type AnyData, UnknownData } from '../data/dataTypes.ts'; import { abstractFloat, abstractInt, bool, f32, i32 } from '../data/numeric.ts'; import { isRef } from '../data/ref.ts'; -import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { + isSnippet, + ResolvedSnippet, + snip, + type Snippet, +} from '../data/snippet.ts'; import { type AnyWgslData, type F32, @@ -14,8 +21,10 @@ import { type FunctionScopeLayer, getOwnSnippet, type ResolutionCtx, + SelfResolvable, } from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; +import { stitch } from '../../src/core/resolve/stitch.ts'; export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { @@ -127,3 +136,27 @@ export function coerceToSnippet(value: unknown): Snippet { return snip(value, UnknownData, /* origin */ 'constant'); } + +// defers the resolution of array expressions +export class ArrayExpression implements SelfResolvable { + readonly [$internal] = true; + + constructor( + public readonly elementType: AnyWgslData, + public readonly type: AnyWgslData, + public readonly elements: Snippet[], + ) { + } + + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + const arrayType = `array<${ + ctx.resolve(this.elementType).value + }, ${this.elements.length}>`; + + return snip( + stitch`${arrayType}(${this.elements})`, + this.type, + 'runtime', + ); + } +} diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 11d6b703d6..71cd12b27d 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -33,6 +33,7 @@ import { tryConvertSnippet, } from './conversion.ts'; import { + ArrayExpression, concretize, type GenerationCtx, numericLiteralToSnippet, @@ -756,16 +757,18 @@ ${this.ctx.pre}}`; elemType = concretize(values[0]?.dataType as wgsl.AnyWgslData); } - const arrayType = `array<${ - this.ctx.resolve(elemType).value - }, ${values.length}>`; + const arrayType = arrayOf[$internal].jsImpl( + elemType as wgsl.AnyWgslData, + values.length, + ); return snip( - stitch`${arrayType}(${values})`, - arrayOf[$internal].jsImpl( + new ArrayExpression( elemType as wgsl.AnyWgslData, - values.length, - ) as wgsl.AnyWgslData, + arrayType, + values, + ), + arrayType, /* origin */ 'runtime', ); } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 74f04f5a9b..30041006e0 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -14,6 +14,7 @@ import * as std from '../../src/std/index.ts'; import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from '../utils/extendedIt.ts'; +import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -575,6 +576,12 @@ describe('wgslGenerator', () => { expect(d.isWgslArray(res.dataType)).toBe(true); expect((res.dataType as unknown as WgslArray).elementCount).toBe(3); expect((res.dataType as unknown as WgslArray).elementType).toBe(d.u32); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); }); }); @@ -594,6 +601,48 @@ describe('wgslGenerator', () => { return arr[1i].x; }" `); + + const astInfo = getMetaData( + testFn[$internal].implementation as (...args: unknown[]) => unknown, + ); + + if (!astInfo) { + throw new Error('Expected prebuilt AST to be present'); + } + + expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[100,[[6,[7,"d","vec2u"],[[5,"1"],[5,"2"]]],[6,[7,"d","vec2u"],[[5,"3"],[5,"4"]]],[6,[7,"std","min"],[[6,[7,"d","vec2u"],[[5,"5"],[5,"8"]]],[6,[7,"d","vec2u"],[[5,"7"],[5,"6"]]]]]]]],[10,[7,[8,"arr",[5,"1"]],"x"]]]]"`, + ); + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.u32, + (astInfo.externals as () => Record)() ?? {}, + ); + + // Check for: const arr = [1, 2, 3] + // ^ this should be an array + wgslGenerator.initGenerator(ctx); + const res = wgslGenerator.expression( + // deno-fmt-ignore: it's better that way + ( + astInfo.ast?.body[1][0] as tinyest.Const + )[2] as unknown as tinyest.Expression, + ); + + expect(d.isWgslArray(res.dataType)).toBe(true); + expect((res.dataType as unknown as WgslArray).elementCount).toBe(3); + expect((res.dataType as unknown as WgslArray).elementType).toBe(d.vec2u); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); + }); }); it('does not autocast lhs of an assignment', () => { @@ -670,6 +719,12 @@ describe('wgslGenerator', () => { expect(d.isWgslArray(res.dataType)).toBe(true); expect((res.dataType as unknown as WgslArray).elementCount).toBe(2); expect((res.dataType as unknown as WgslArray).elementType).toBe(TestStruct); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); }); it('generates correct code for array expressions with derived elements', () => { @@ -696,6 +751,31 @@ describe('wgslGenerator', () => { expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( `"[0,[[13,"arr",[100,[[7,"derivedV2f","$"],[6,[7,"std","mul"],[[7,"derivedV2f","$"],[6,[7,"d","vec2f"],[[5,"2"],[5,"2"]]]]]]]],[10,[7,[8,"arr",[5,"1"]],"y"]]]]"`, ); + + const res = provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.f32, + (astInfo.externals as () => Record)() ?? {}, + ); + + wgslGenerator.initGenerator(ctx); + return wgslGenerator.expression( + (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, + ); + }); + + expect(d.isWgslArray(res.dataType)).toBe(true); + expect((res.dataType as unknown as WgslArray).elementCount).toBe(2); + expect((res.dataType as unknown as WgslArray).elementType).toBe(d.vec2f); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); }); it('allows for member access on values returned from function calls', () => { From 807b8812533e9e6a35f0c7e6e9d1b7ff77795546 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 8 Jan 2026 16:54:49 +0100 Subject: [PATCH 20/67] missing origin comment --- packages/typegpu/src/tgsl/generationHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 90e18a0c1d..a4dba6f113 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -156,7 +156,7 @@ export class ArrayExpression implements SelfResolvable { return snip( stitch`${arrayType}(${this.elements})`, this.type, - 'runtime', + /* origin */ 'runtime', ); } } From 243d9e6cb9402dd27e0e28dd306969e753e23215 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 8 Jan 2026 17:07:11 +0100 Subject: [PATCH 21/67] unused import --- packages/typegpu/src/tgsl/generationHelpers.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index a4dba6f113..8f6408e320 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -1,11 +1,10 @@ -import { arrayOf } from '../../src/data/array.ts'; import { $internal, $resolve } from '../../src/shared/symbols.ts'; import { type AnyData, UnknownData } from '../data/dataTypes.ts'; import { abstractFloat, abstractInt, bool, f32, i32 } from '../data/numeric.ts'; import { isRef } from '../data/ref.ts'; import { isSnippet, - ResolvedSnippet, + type ResolvedSnippet, snip, type Snippet, } from '../data/snippet.ts'; @@ -21,7 +20,7 @@ import { type FunctionScopeLayer, getOwnSnippet, type ResolutionCtx, - SelfResolvable, + type SelfResolvable, } from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; import { stitch } from '../../src/core/resolve/stitch.ts'; From 81e8e6439e000dc452bac3f627fc743b4f5e5ff4 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 8 Jan 2026 16:44:35 +0100 Subject: [PATCH 22/67] intermediate representation of array expression missing origin comment unused import --- .../typegpu/src/tgsl/generationHelpers.ts | 34 +++++++- packages/typegpu/src/tgsl/wgslGenerator.ts | 17 ++-- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 80 +++++++++++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 3b5061e24c..8f6408e320 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -1,7 +1,13 @@ +import { $internal, $resolve } from '../../src/shared/symbols.ts'; import { type AnyData, UnknownData } from '../data/dataTypes.ts'; import { abstractFloat, abstractInt, bool, f32, i32 } from '../data/numeric.ts'; import { isRef } from '../data/ref.ts'; -import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { + isSnippet, + type ResolvedSnippet, + snip, + type Snippet, +} from '../data/snippet.ts'; import { type AnyWgslData, type F32, @@ -14,8 +20,10 @@ import { type FunctionScopeLayer, getOwnSnippet, type ResolutionCtx, + type SelfResolvable, } from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; +import { stitch } from '../../src/core/resolve/stitch.ts'; export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { @@ -127,3 +135,27 @@ export function coerceToSnippet(value: unknown): Snippet { return snip(value, UnknownData, /* origin */ 'constant'); } + +// defers the resolution of array expressions +export class ArrayExpression implements SelfResolvable { + readonly [$internal] = true; + + constructor( + public readonly elementType: AnyWgslData, + public readonly type: AnyWgslData, + public readonly elements: Snippet[], + ) { + } + + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + const arrayType = `array<${ + ctx.resolve(this.elementType).value + }, ${this.elements.length}>`; + + return snip( + stitch`${arrayType}(${this.elements})`, + this.type, + /* origin */ 'runtime', + ); + } +} diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 8d8bca327c..b2e3f97005 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -32,6 +32,7 @@ import { tryConvertSnippet, } from './conversion.ts'; import { + ArrayExpression, concretize, type GenerationCtx, numericLiteralToSnippet, @@ -756,16 +757,18 @@ ${this.ctx.pre}}`; elemType = concretize(values[0]?.dataType as wgsl.AnyWgslData); } - const arrayType = `array<${ - this.ctx.resolve(elemType).value - }, ${values.length}>`; + const arrayType = arrayOf[$internal].jsImpl( + elemType as wgsl.AnyWgslData, + values.length, + ); return snip( - stitch`${arrayType}(${values})`, - arrayOf[$internal].jsImpl( + new ArrayExpression( elemType as wgsl.AnyWgslData, - values.length, - ) as wgsl.AnyWgslData, + arrayType, + values, + ), + arrayType, /* origin */ 'runtime', ); } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 331e579a4a..8ec7dc6e2f 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -14,6 +14,7 @@ import * as std from '../../src/std/index.ts'; import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from '../utils/extendedIt.ts'; +import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -972,6 +973,12 @@ describe('wgslGenerator', () => { expect(d.isWgslArray(res.dataType)).toBe(true); expect((res.dataType as unknown as WgslArray).elementCount).toBe(3); expect((res.dataType as unknown as WgslArray).elementType).toBe(d.u32); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); }); }); @@ -991,6 +998,48 @@ describe('wgslGenerator', () => { return arr[1i].x; }" `); + + const astInfo = getMetaData( + testFn[$internal].implementation as (...args: unknown[]) => unknown, + ); + + if (!astInfo) { + throw new Error('Expected prebuilt AST to be present'); + } + + expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[100,[[6,[7,"d","vec2u"],[[5,"1"],[5,"2"]]],[6,[7,"d","vec2u"],[[5,"3"],[5,"4"]]],[6,[7,"std","min"],[[6,[7,"d","vec2u"],[[5,"5"],[5,"8"]]],[6,[7,"d","vec2u"],[[5,"7"],[5,"6"]]]]]]]],[10,[7,[8,"arr",[5,"1"]],"x"]]]]"`, + ); + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.u32, + (astInfo.externals as () => Record)() ?? {}, + ); + + // Check for: const arr = [1, 2, 3] + // ^ this should be an array + wgslGenerator.initGenerator(ctx); + const res = wgslGenerator.expression( + // deno-fmt-ignore: it's better that way + ( + astInfo.ast?.body[1][0] as tinyest.Const + )[2] as unknown as tinyest.Expression, + ); + + expect(d.isWgslArray(res.dataType)).toBe(true); + expect((res.dataType as unknown as WgslArray).elementCount).toBe(3); + expect((res.dataType as unknown as WgslArray).elementType).toBe(d.vec2u); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); + }); }); it('does not autocast lhs of an assignment', () => { @@ -1067,6 +1116,12 @@ describe('wgslGenerator', () => { expect(d.isWgslArray(res.dataType)).toBe(true); expect((res.dataType as unknown as WgslArray).elementCount).toBe(2); expect((res.dataType as unknown as WgslArray).elementType).toBe(TestStruct); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); }); it('generates correct code for array expressions with derived elements', () => { @@ -1093,6 +1148,31 @@ describe('wgslGenerator', () => { expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( `"[0,[[13,"arr",[100,[[7,"derivedV2f","$"],[6,[7,"std","mul"],[[7,"derivedV2f","$"],[6,[7,"d","vec2f"],[[5,"2"],[5,"2"]]]]]]]],[10,[7,[8,"arr",[5,"1"]],"y"]]]]"`, ); + + const res = provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.f32, + (astInfo.externals as () => Record)() ?? {}, + ); + + wgslGenerator.initGenerator(ctx); + return wgslGenerator.expression( + (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, + ); + }); + + expect(d.isWgslArray(res.dataType)).toBe(true); + expect((res.dataType as unknown as WgslArray).elementCount).toBe(2); + expect((res.dataType as unknown as WgslArray).elementType).toBe(d.vec2f); + + // intermediate representation + expect(res.value instanceof ArrayExpression).toBe(true); + expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); + expect((res.value as unknown as ArrayExpression).elementType) + .toBe((res.dataType as unknown as WgslArray).elementType); }); it('allows for member access on values returned from function calls', () => { From d6c6823145d29b27a2b3fefa68d2ca7de311dd07 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 8 Jan 2026 11:36:59 +0100 Subject: [PATCH 23/67] unroll initial commit --- .../typegpu/src/core/unroll/tgpuUnroll.ts | 59 ++++++++++ packages/typegpu/src/index.ts | 3 + packages/typegpu/src/tgsl/wgslGenerator.ts | 51 +++++---- packages/typegpu/tests/unroll.test.ts | 101 ++++++++++++++++++ 4 files changed, 192 insertions(+), 22 deletions(-) create mode 100644 packages/typegpu/src/core/unroll/tgpuUnroll.ts create mode 100644 packages/typegpu/tests/unroll.test.ts diff --git a/packages/typegpu/src/core/unroll/tgpuUnroll.ts b/packages/typegpu/src/core/unroll/tgpuUnroll.ts new file mode 100644 index 0000000000..84b1f35dee --- /dev/null +++ b/packages/typegpu/src/core/unroll/tgpuUnroll.ts @@ -0,0 +1,59 @@ +import { stitch } from '../resolve/stitch.ts'; +import { $internal, $resolve } from '../../../src/shared/symbols.ts'; +import { WgslTypeError } from '../../../src/errors.ts'; +import { inCodegenMode } from '../../../src/execMode.ts'; +import { setName } from '../../../src/shared/meta.ts'; +import { DualFn } from '../../../src/data/dualFn.ts'; + +import { AnyData } from '../../../src/data/dataTypes.ts'; +import { ResolvedSnippet, snip, Snippet } from '../../../src/data/snippet.ts'; +import { ResolutionCtx, SelfResolvable } from 'src/types.ts'; + +/** + * The result of calling `tgpu.unroll(...)`. The code responsible for + * generating shader code can check if the value of a snippet is + * an instance of `UnrolledIterable`, and act accordingly. + */ +export class UnrolledIterable implements SelfResolvable { + readonly [$internal] = true; + + constructor(public readonly snippet: Snippet) {} + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + return snip( + stitch`${this.snippet}`, + this.snippet.dataType as AnyData, + this.snippet.origin, + ); + } +} + +/** + * Marks an iterable to be unrolled by the wgslGenerator. + */ +export const unroll = (() => { + const gpuImpl = (value: Snippet) => { + return snip(new UnrolledIterable(value), value.dataType, value.origin); + }; + + const jsImpl = >(value: T) => value; + + const impl = >(value: T) => { + if (inCodegenMode()) { + return gpuImpl(value as unknown as Snippet); + } + return jsImpl(value); + }; + + setName(impl, 'unroll'); + impl.toString = () => 'unroll'; + Object.defineProperty(impl, $internal, { + value: { + jsImpl, + gpuImpl, + }, + }); + + return impl as unknown as DualFn< + >(value: T) => T + >; +})(); diff --git a/packages/typegpu/src/index.ts b/packages/typegpu/src/index.ts index 92a2b15d7f..b2c6675ab0 100644 --- a/packages/typegpu/src/index.ts +++ b/packages/typegpu/src/index.ts @@ -21,6 +21,7 @@ import { privateVar, workgroupVar } from './core/variable/tgpuVariable.ts'; import { vertexLayout } from './core/vertexLayout/vertexLayout.ts'; import { bindGroupLayout } from './tgpuBindGroupLayout.ts'; import { namespace } from './core/resolve/namespace.ts'; +import { unroll } from './core/unroll/tgpuUnroll.ts'; export const tgpu = { fn, @@ -74,6 +75,8 @@ export const tgpu = { rawCodeSnippet, simulate, + + unroll, }, }; export default tgpu; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index b2e3f97005..9d72941dc2 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -45,6 +45,7 @@ import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; import { arrayLength } from '../std/array.ts'; +import { UnrolledIterable } from '../../src/core/unroll/tgpuUnroll.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -1063,36 +1064,22 @@ ${this.ctx.pre}else ${alternate}`; if (statement[0] === NODE.forOf) { const [_, loopVar, iterable, body] = statement; - const iterableSnippet = this.expression(iterable); - if (isEphemeralSnippet(iterableSnippet)) { - throw new Error( - '`for ... of ...` loops only support iterables stored in variables', - ); - } + const iterableExpr = this.expression(iterable); - // Our index name will be some element from infinite sequence (i, ii, iii, ...). - // If user defines `i` and `ii` before `for ... of ...` loop, then our index name will be `iii`. - // If user defines `i` inside `for ... of ...` then it will be scoped to a new block, - // so we can safely use `i`. - let index = 'i'; // it will be valid name, no need to call this.ctx.makeNameValid - while (this.ctx.getById(index) !== null) { - index += 'i'; - } + const shouldUnroll = iterableExpr.value instanceof UnrolledIterable; + const iterableSnippet = shouldUnroll + ? iterableExpr.value.snippet + : iterableExpr; - const elementSnippet = accessIndex( - iterableSnippet, - snip(index, u32, 'runtime'), - ); - if (!elementSnippet) { - throw new WgslTypeError( - '`for ... of ...` loops only support array or vector iterables', + if (isEphemeralSnippet(iterableSnippet) && !shouldUnroll) { + throw new Error( + '`for ... of ...` loops only support iterables stored in variables', ); } const iterableDataType = iterableSnippet.dataType; let elementCountSnippet: Snippet; - let elementType = elementSnippet.dataType; if (wgsl.isWgslArray(iterableDataType)) { elementCountSnippet = iterableDataType.elementCount > 0 ? snip( @@ -1124,6 +1111,26 @@ ${this.ctx.pre}else ${alternate}`; let loopVarKind = 'let'; const loopVarName = this.ctx.makeNameValid(loopVar[1]); + // Our index name will be some element from infinite sequence (i, ii, iii, ...). + // If user defines `i` and `ii` before `for ... of ...` loop, then our index name will be `iii`. + // If user defines `i` inside `for ... of ...` then it will be scoped to a new block, + // so we can safely use `i`. + let index = 'i'; // it will be valid name, no need to call this.ctx.makeNameValid + while (this.ctx.getById(index) !== null) { + index += 'i'; + } + + const elementSnippet = accessIndex( + iterableSnippet, + snip(index, u32, 'runtime'), + ); + if (!elementSnippet) { + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + } + let elementType = elementSnippet.dataType; + if (!isEphemeralSnippet(elementSnippet)) { if (elementSnippet.origin === 'constant-tgpu-const-ref') { loopVarKind = 'const'; diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts new file mode 100644 index 0000000000..0c299a88d3 --- /dev/null +++ b/packages/typegpu/tests/unroll.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest'; +import * as d from '../src/data/index.ts'; +import tgpu from '../src/index.ts'; + +describe('tgpu.unroll', () => { + it('called outside shader and outside forOf returns passed iterable', () => { + const arr = [1, 2, 3]; + + const x = tgpu['~unstable'].unroll(arr); + + expect(x).toBe(arr); + }); + + it('called inside shader but outside forOf returns passed iterable', () => { + const layout = tgpu.bindGroupLayout({ + arr: { storage: d.arrayOf(d.f32) }, + }); + + const f = () => { + 'use gpu'; + const a = tgpu['~unstable'].unroll([1, 2, 3]); + + const v1 = d.vec2f(7); + const v2 = tgpu['~unstable'].unroll(v1); // this should return a pointer + const arr = tgpu['~unstable'].unroll(layout.$.arr); // this should return a pointer + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var arr: array; + + fn f() { + var a = array(1, 2, 3); + var v1 = vec2f(7); + let v2 = (&v1); + let arr = (&arr); + }" + `); + }); + + it.skip('unrolls forOf of comptime array', () => { + const f = () => { + 'use gpu'; + let result = d.f32(0); + for (const item of tgpu['~unstable'].unroll([1, 2, 3])) { // should operate on values + result += d.f32(item); + } + + const arr = [1, 2, 3]; + for (const item of tgpu['~unstable'].unroll(arr)) { // should operate on indices + result -= item; + } + + const v = d.vec2f(); + for (const item of tgpu['~unstable'].unroll(v)) { // should operate on indices + result *= item; + } + + return result; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(); + }); + + it.skip('unrolls forOf of iterable provided by comptime', () => { + const getArr = tgpu['~unstable'].comptime(() => [1, 2, 3]); + // add basic external, derived, maybe slot and accessor + + const f = () => { + 'use gpu'; + let result = d.f32(0); + for (const item of tgpu['~unstable'].unroll(getArr())) { + result += d.f32(item); + } + + return result; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(); + }); + + it.skip('throws error when number of iterations is unknown at comptime', () => { + const layout = tgpu.bindGroupLayout({ + arr: { storage: d.arrayOf(d.f32) }, + }); + + const f = () => { + 'use gpu'; + let res = d.f32(0); + for (const foo of tgpu['~unstable'].unroll(layout.$.arr)) { + res += foo; + } + }; + + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f(): Cannot unroll loop. Number of iterations unknown at compile time] + `); + }); +}); From 0f54edd1e1544080ce08afe4d69753b76c21fb5a Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 8 Jan 2026 12:45:31 +0100 Subject: [PATCH 24/67] very minor cleanup --- packages/typegpu/src/core/unroll/tgpuUnroll.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/typegpu/src/core/unroll/tgpuUnroll.ts b/packages/typegpu/src/core/unroll/tgpuUnroll.ts index 84b1f35dee..9cf40d80f4 100644 --- a/packages/typegpu/src/core/unroll/tgpuUnroll.ts +++ b/packages/typegpu/src/core/unroll/tgpuUnroll.ts @@ -18,7 +18,8 @@ export class UnrolledIterable implements SelfResolvable { readonly [$internal] = true; constructor(public readonly snippet: Snippet) {} - [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + + [$resolve](_ctx: ResolutionCtx): ResolvedSnippet { return snip( stitch`${this.snippet}`, this.snippet.dataType as AnyData, From d65a6c1f3bd394e56a1c6519e4e6ecb5b968f7ac Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 9 Jan 2026 01:50:19 +0100 Subject: [PATCH 25/67] trying to make it readable --- .../typegpu/src/core/unroll/tgpuUnroll.ts | 15 +- .../typegpu/src/tgsl/generationHelpers.ts | 93 ++++++++++++ packages/typegpu/src/tgsl/wgslGenerator.ts | 143 ++++++------------ packages/typegpu/tests/unroll.test.ts | 33 ++-- 4 files changed, 171 insertions(+), 113 deletions(-) diff --git a/packages/typegpu/src/core/unroll/tgpuUnroll.ts b/packages/typegpu/src/core/unroll/tgpuUnroll.ts index 9cf40d80f4..72f0dab2c9 100644 --- a/packages/typegpu/src/core/unroll/tgpuUnroll.ts +++ b/packages/typegpu/src/core/unroll/tgpuUnroll.ts @@ -1,13 +1,16 @@ import { stitch } from '../resolve/stitch.ts'; import { $internal, $resolve } from '../../../src/shared/symbols.ts'; -import { WgslTypeError } from '../../../src/errors.ts'; import { inCodegenMode } from '../../../src/execMode.ts'; import { setName } from '../../../src/shared/meta.ts'; -import { DualFn } from '../../../src/data/dualFn.ts'; - -import { AnyData } from '../../../src/data/dataTypes.ts'; -import { ResolvedSnippet, snip, Snippet } from '../../../src/data/snippet.ts'; -import { ResolutionCtx, SelfResolvable } from 'src/types.ts'; +import type { DualFn } from '../../../src/data/dualFn.ts'; + +import type { AnyData } from '../../../src/data/dataTypes.ts'; +import { + type ResolvedSnippet, + snip, + type Snippet, +} from '../../../src/data/snippet.ts'; +import type { ResolutionCtx, SelfResolvable } from 'src/types.ts'; /** * The result of calling `tgpu.unroll(...)`. The code responsible for diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 8f6408e320..5942316358 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -3,6 +3,7 @@ import { type AnyData, UnknownData } from '../data/dataTypes.ts'; import { abstractFloat, abstractInt, bool, f32, i32 } from '../data/numeric.ts'; import { isRef } from '../data/ref.ts'; import { + isEphemeralSnippet, isSnippet, type ResolvedSnippet, snip, @@ -24,6 +25,12 @@ import { } from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; import { stitch } from '../../src/core/resolve/stitch.ts'; +import * as wgsl from '../data/wgslTypes.ts'; +import { u32 } from '../data/numeric.ts'; +import { invariant, WgslTypeError } from '../../src/errors.ts'; +import { arrayLength } from '../std/array.ts'; +import { accessIndex } from './accessIndex.ts'; +import { createPtrFromOrigin, implicitFrom } from '../../src/data/ptr.ts'; export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { @@ -159,3 +166,89 @@ export class ArrayExpression implements SelfResolvable { ); } } + +export const forOfHelpers = { + getLoopVarKind(elementSnippet: Snippet) { + // If it's ephemeral, it's a value that cannot change. If it's a reference, we take + // an implicit pointer to it + return elementSnippet.origin === 'constant-tgpu-const-ref' + ? 'const' + : 'let'; + }, + getValidIndexName(ctx: GenerationCtx) { + // Our index name will be some element from infinite sequence (i, ii, iii, ...). + // If user defines `i` and `ii` before `for ... of ...` loop, then our index name will be `iii`. + // If user defines `i` inside `for ... of ...` then it will be scoped to a new block, + // so we can safely use `i`. + let index = 'i'; // it will be valid name, no need to call this.ctx.makeNameValid + while (ctx.getById(index) !== null) { + index += 'i'; + } + + return index; + }, + getElementSnippet(iterableSnippet: Snippet, index: string) { + const elementSnippet = accessIndex( + iterableSnippet, + snip(index, u32, 'runtime'), + ); + + if (!elementSnippet) { + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + } + + return elementSnippet; + }, + getElementType(elementSnippet: Snippet) { + let elementType = elementSnippet.dataType; + + if ( + isEphemeralSnippet(elementSnippet) || + elementSnippet.origin === 'constant-tgpu-const-ref' || + elementSnippet.origin === 'runtime-tgpu-const-ref' + ) { + return elementType; + } + + if (!wgsl.isPtr(elementType)) { + const ptrType = createPtrFromOrigin( + elementSnippet.origin, + concretize(elementType as wgsl.AnyWgslData) as wgsl.StorableData, + ); + invariant( + ptrType !== undefined, + `Creating pointer type from origin ${elementSnippet.origin}`, + ); + elementType = ptrType; + } + + return implicitFrom(elementType); + }, + getElementCountSnippet(iterableSnippet: Snippet) { + const iterableDataType = iterableSnippet.dataType; + + if (wgsl.isWgslArray(iterableDataType)) { + return iterableDataType.elementCount > 0 + ? snip( + `${iterableDataType.elementCount}`, + u32, + 'constant', + ) + : arrayLength[$internal].gpuImpl(iterableSnippet); + } + + if (wgsl.isVec(iterableDataType)) { + return snip( + `${Number(iterableDataType.type.match(/\d/))}`, + u32, + 'constant', + ); + } + + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + }, +} as const; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 9d72941dc2..3a347827cc 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -34,6 +34,7 @@ import { import { ArrayExpression, concretize, + forOfHelpers, type GenerationCtx, numericLiteralToSnippet, } from './generationHelpers.ts'; @@ -44,7 +45,6 @@ import type { DualFn } from '../data/dualFn.ts'; import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; -import { arrayLength } from '../std/array.ts'; import { UnrolledIterable } from '../../src/core/unroll/tgpuUnroll.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -1065,120 +1065,71 @@ ${this.ctx.pre}else ${alternate}`; if (statement[0] === NODE.forOf) { const [_, loopVar, iterable, body] = statement; - const iterableExpr = this.expression(iterable); + if (loopVar[0] !== NODE.const) { + throw new WgslTypeError( + 'Only `for (const ... of ... )` loops are supported', + ); + } + const iterableExpr = this.expression(iterable); const shouldUnroll = iterableExpr.value instanceof UnrolledIterable; const iterableSnippet = shouldUnroll ? iterableExpr.value.snippet : iterableExpr; + const ephemeralIterable = isEphemeralSnippet(iterableSnippet); - if (isEphemeralSnippet(iterableSnippet) && !shouldUnroll) { - throw new Error( - '`for ... of ...` loops only support iterables stored in variables', - ); + if (shouldUnroll && ephemeralIterable) { + throw new Error('Not implemented'); } - const iterableDataType = iterableSnippet.dataType; - let elementCountSnippet: Snippet; - if (wgsl.isWgslArray(iterableDataType)) { - elementCountSnippet = iterableDataType.elementCount > 0 - ? snip( - `${iterableDataType.elementCount}`, - u32, - 'constant', - ) - : arrayLength[$internal].gpuImpl(iterableSnippet); - } else if (wgsl.isVec(iterableDataType)) { - elementCountSnippet = snip( - `${Number(iterableDataType.type.match(/\d/))}`, - u32, - 'constant', - ); - } else { - throw new WgslTypeError( - '`for ... of ...` loops only support array or vector iterables', - ); + if (shouldUnroll && !ephemeralIterable) { + throw new Error('Not implemented'); } - if (loopVar[0] !== NODE.const) { - throw new WgslTypeError( - 'Only `for (const ... of ... )` loops are supported', + if (!shouldUnroll && !ephemeralIterable) { + const index = forOfHelpers.getValidIndexName(this.ctx); + const elementSnippet = forOfHelpers.getElementSnippet( + iterableSnippet, + index, ); - } - - // If it's ephemeral, it's a value that cannot change. If it's a reference, we take - // an implicit pointer to it - let loopVarKind = 'let'; - const loopVarName = this.ctx.makeNameValid(loopVar[1]); - - // Our index name will be some element from infinite sequence (i, ii, iii, ...). - // If user defines `i` and `ii` before `for ... of ...` loop, then our index name will be `iii`. - // If user defines `i` inside `for ... of ...` then it will be scoped to a new block, - // so we can safely use `i`. - let index = 'i'; // it will be valid name, no need to call this.ctx.makeNameValid - while (this.ctx.getById(index) !== null) { - index += 'i'; - } - - const elementSnippet = accessIndex( - iterableSnippet, - snip(index, u32, 'runtime'), - ); - if (!elementSnippet) { - throw new WgslTypeError( - '`for ... of ...` loops only support array or vector iterables', + const elementCountSnippet = forOfHelpers.getElementCountSnippet( + iterableSnippet, ); - } - let elementType = elementSnippet.dataType; - - if (!isEphemeralSnippet(elementSnippet)) { - if (elementSnippet.origin === 'constant-tgpu-const-ref') { - loopVarKind = 'const'; - } else if (elementSnippet.origin === 'runtime-tgpu-const-ref') { - loopVarKind = 'let'; - } else { - loopVarKind = 'let'; - if (!wgsl.isPtr(elementType)) { - const ptrType = createPtrFromOrigin( - elementSnippet.origin, - concretize(elementType as wgsl.AnyWgslData) as wgsl.StorableData, - ); - invariant( - ptrType !== undefined, - `Creating pointer type from origin ${elementSnippet.origin}`, - ); - elementType = ptrType; - } - - elementType = implicitFrom(elementType); - } - } + const loopVarName = this.ctx.makeNameValid(loopVar[1]); + const loopVarKind = forOfHelpers.getLoopVarKind(elementSnippet); + const elementType = forOfHelpers.getElementType(elementSnippet); + + const loopVarSnippet = snip( + loopVarName, + elementType, + elementSnippet.origin, + ); + this.ctx.defineVariable(loopVarName, loopVarSnippet); - const loopVarSnippet = snip( - loopVarName, - elementType, - elementSnippet.origin, - ); - this.ctx.defineVariable(loopVarName, loopVarSnippet); + const forStr = + stitch`${this.ctx.pre}for (var ${index} = 0; ${index} < ${ + tryConvertSnippet(elementCountSnippet, u32, false) + }; ${index}++) {`; - const forStr = stitch`${this.ctx.pre}for (var ${index} = 0; ${index} < ${ - tryConvertSnippet(elementCountSnippet, u32, false) - }; ${index}++) {`; + this.ctx.indent(); - this.ctx.indent(); + const loopVarDeclStr = + stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ + tryConvertSnippet(elementSnippet, elementType as AnyData, false) + };`; - const loopVarDeclStr = - stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ - tryConvertSnippet(elementSnippet, elementType as AnyData, false) - };`; + const bodyStr = `${this.ctx.pre}${ + this.block(blockifySingleStatement(body)) + }`; - const bodyStr = `${this.ctx.pre}${ - this.block(blockifySingleStatement(body)) - }`; + this.ctx.dedent(); - this.ctx.dedent(); + return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; + } - return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; + throw new Error( + '`for ... of ...` loops only support iterables stored in variables', + ); } if (statement[0] === NODE.continue) { diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 0c299a88d3..6dc9907857 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -26,13 +26,13 @@ describe('tgpu.unroll', () => { }; expect(tgpu.resolve([f])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var arr: array; + "@group(0) @binding(0) var arr_1: array; fn f() { var a = array(1, 2, 3); var v1 = vec2f(7); let v2 = (&v1); - let arr = (&arr); + let arr = (&arr_1); }" `); }); @@ -45,20 +45,31 @@ describe('tgpu.unroll', () => { result += d.f32(item); } - const arr = [1, 2, 3]; - for (const item of tgpu['~unstable'].unroll(arr)) { // should operate on indices - result -= item; - } + // const arr = [1, 2, 3]; + // for (const item of tgpu['~unstable'].unroll(arr)) { // should operate on indices + // result -= item; + // } - const v = d.vec2f(); - for (const item of tgpu['~unstable'].unroll(v)) { // should operate on indices - result *= item; - } + // const v = d.vec2f(); + // for (const item of tgpu['~unstable'].unroll(v)) { // should operate on indices + // result *= item; + // } return result; }; - expect(tgpu.resolve([f])).toMatchInlineSnapshot(); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> f32 { + var result = 0f; + for (var i = 0; i < 3; i++) { + let item = array(1, 2, 3)[i]; + { + result += f32(item); + } + } + return result; + }" + `); }); it.skip('unrolls forOf of iterable provided by comptime', () => { From 182a8d036034f7e47e8c387e92dab45c547cc365 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 13 Jan 2026 19:47:35 +0100 Subject: [PATCH 26/67] fix: array expression resolves references correctly --- .../typegpu/src/tgsl/generationHelpers.ts | 23 ++++ packages/typegpu/src/tgsl/wgslGenerator.ts | 79 +++++++++----- packages/typegpu/tests/array.test.ts | 101 +++++++++++++++++- packages/typegpu/tests/struct.test.ts | 23 ++++ 4 files changed, 197 insertions(+), 29 deletions(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 8f6408e320..8dd19df4bf 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -3,6 +3,7 @@ import { type AnyData, UnknownData } from '../data/dataTypes.ts'; import { abstractFloat, abstractInt, bool, f32, i32 } from '../data/numeric.ts'; import { isRef } from '../data/ref.ts'; import { + isEphemeralSnippet, isSnippet, type ResolvedSnippet, snip, @@ -13,6 +14,7 @@ import { type F32, type I32, isMatInstance, + isNaturallyEphemeral, isVecInstance, WORKAROUND_getSchema, } from '../data/wgslTypes.ts'; @@ -24,6 +26,7 @@ import { } from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; import { stitch } from '../../src/core/resolve/stitch.ts'; +import { WgslTypeError } from '../../src/errors.ts'; export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { @@ -147,7 +150,27 @@ export class ArrayExpression implements SelfResolvable { ) { } + toString(): string { + return 'ArrayExpression'; + } + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + for (const elem of this.elements) { + // We check if there are no references among the elements + if ( + (elem.origin === 'argument' && + !isNaturallyEphemeral(elem.dataType)) || + !isEphemeralSnippet(elem) + ) { + const snippetStr = ctx.resolve(elem.value, elem.dataType).value; + const snippetType = + ctx.resolve(concretize(elem.dataType as AnyData)).value; + throw new WgslTypeError( + `'${snippetStr}' reference cannot be used in an array constructor.\n-----\nTry '${snippetType}(${snippetStr})' or 'arrayOf(${snippetType}, count)([...])' to copy the value instead.\n-----`, + ); + } + } + const arrayType = `array<${ ctx.resolve(this.elementType).value }, ${this.elements.length}>`; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 71cd12b27d..1814b86a4a 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -499,21 +499,21 @@ ${this.ctx.pre}}`; const [_, calleeNode, argNodes] = expression; const callee = this.expression(calleeNode); - if (wgsl.isWgslStruct(callee.value) || wgsl.isWgslArray(callee.value)) { - // Struct/array schema call. + if (wgsl.isWgslStruct(callee.value)) { + // Struct schema call. if (argNodes.length > 1) { throw new WgslTypeError( - 'Array and struct schemas should always be called with at most 1 argument', + 'Struct schemas should always be called with at most 1 argument', ); } // No arguments `Struct()`, resolve struct name and return. if (!argNodes[0]) { - // the schema becomes the data type + // The schema becomes the data type. return snip( `${this.ctx.resolve(callee.value).value}()`, callee.value, - // A new struct, so not a reference + // A new struct, so not a reference. /* origin */ 'runtime', ); } @@ -528,7 +528,53 @@ ${this.ctx.pre}}`; return snip( this.ctx.resolve(arg.value, callee.value).value, callee.value, - // A new struct, so not a reference + // A new struct, so not a reference. + /* origin */ 'runtime', + ); + } + + if (wgsl.isWgslArray(callee.value)) { + // Array schema call. + if (argNodes.length > 1) { + throw new WgslTypeError( + 'Array schemas should always be called with at most 1 argument', + ); + } + + // No arguments `array<...>()`, resolve array type and return. + if (!argNodes[0]) { + // The schema becomes the data type. + return snip( + `${this.ctx.resolve(callee.value).value}()`, + callee.value, + // A new array, so not a reference. + /* origin */ 'runtime', + ); + } + + const arg = this.typedExpression( + argNodes[0], + callee.value, + ); + + // `d.arrayOf(...)([...])`. + // We resolve each element separately. + if (arg.value instanceof ArrayExpression) { + return snip( + stitch`${ + this.ctx.resolve(callee.value).value + }(${arg.value.elements})`, + arg.dataType, + /* origin */ 'runtime', + ); + } + + // `d.arrayOf(...)(otherArr)`. + // We just let the argument resolve everything. + return snip( + this.ctx.resolve(arg.value, callee.value).value, + callee.value, + // A new array, so not a reference. /* origin */ 'runtime', ); } @@ -721,24 +767,9 @@ ${this.ctx.pre}}`; } } else { // The array is not typed, so we try to guess the types. - const valuesSnippets = valueNodes.map((value) => { - const snippet = this.expression(value as tinyest.Expression); - // We check if there are no references among the elements - if ( - (snippet.origin === 'argument' && - !wgsl.isNaturallyEphemeral(snippet.dataType)) || - !isEphemeralSnippet(snippet) - ) { - const snippetStr = - this.ctx.resolve(snippet.value, snippet.dataType).value; - const snippetType = - this.ctx.resolve(concretize(snippet.dataType as AnyData)).value; - throw new WgslTypeError( - `'${snippetStr}' reference cannot be used in an array constructor.\n-----\nTry '${snippetType}(${snippetStr})' or 'arrayOf(${snippetType}, count)([...])' to copy the value instead.\n-----`, - ); - } - return snippet; - }); + const valuesSnippets = valueNodes.map((value) => + this.expression(value as tinyest.Expression) + ); if (valuesSnippets.length === 0) { throw new WgslTypeError( diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index e8220da979..b193c85a20 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -140,6 +140,24 @@ describe('array', () => { ); }); + it('throws when invalid number of arguments during code generation', () => { + const ArraySchema = d.arrayOf(d.u32, 2); + + const f = () => { + 'use gpu'; + // @ts-expect-error + const arr = ArraySchema([1, 1], [6, 7]); + return; + }; + + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f(): Array schemas should always be called with at most 1 argument] + `); + }); + it('can be called to create a default value', () => { const ArraySchema = d.arrayOf(d.vec3f, 2); @@ -188,16 +206,28 @@ describe('array', () => { it('generates correct code when array clone is used', () => { const ArraySchema = d.arrayOf(d.u32, 1); - const testFn = tgpu.fn([])(() => { + const f = (arr: d.Infer) => { + 'use gpu'; + const clone = ArraySchema(arr); + }; + + const testFn = () => { + 'use gpu'; const myArray = ArraySchema([d.u32(10)]); const myClone = ArraySchema(myArray); + f(myArray); return; - }); + }; expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` - "fn testFn() { + "fn f(arr: array) { + var clone = arr; + } + + fn testFn() { var myArray = array(10u); var myClone = myArray; + f(myArray); return; }" `); @@ -221,6 +251,65 @@ describe('array', () => { `); }); + it('generates correct code when array expression with ephemeral element type clone is used', () => { + const f = () => { + 'use gpu'; + const arr = d.arrayOf(d.f32, 2)([6, 7]); + return; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var arr = array(6f, 7f); + return; + }" + `); + }); + + it('generates correct code when array expression with reference element type clone is used', () => { + const f = (v: d.v4f) => { + 'use gpu'; + const v2 = d.vec4f(3); + const v3 = v2; + const arr = d.arrayOf(d.vec4f, 3)([v, v2, v3]); + }; + + const main = tgpu.fn([])(() => { + const v1 = d.vec4f(7); + f(v1); + return; + }); + + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn f(v: vec4f) { + var v2 = vec4f(3); + let v3 = (&v2); + var arr = array(v, v2, (*v3)); + } + + fn main() { + var v1 = vec4f(7); + f(v1); + return; + }" + `); + }); + + it('generates correct code when array expression with mixed element types clone is used', () => { + const f = () => { + 'use gpu'; + const arr = d.arrayOf(d.f32, 3)([5, 6.7, 8.0]); + return; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var arr = array(5f, 6.7f, 8f); + return; + }" + `); + }); + it('can be immediately-invoked in TGSL', () => { const foo = tgpu.fn([])(() => { const result = d.arrayOf(d.f32, 4)(); @@ -339,7 +428,8 @@ describe('array', () => { expect(() => tgpu.resolve([foo])).toThrowErrorMatchingInlineSnapshot(` [Error: Resolution of the following tree failed: - - - fn:foo: 'myVec' reference cannot be used in an array constructor. + - fn:foo + - ArrayExpression: 'myVec' reference cannot be used in an array constructor. ----- Try 'vec2f(myVec)' or 'arrayOf(vec2f, count)([...])' to copy the value instead. -----] @@ -354,7 +444,8 @@ describe('array', () => { expect(() => tgpu.resolve([foo])).toThrowErrorMatchingInlineSnapshot(` [Error: Resolution of the following tree failed: - - - fn:foo: 'myVec' reference cannot be used in an array constructor. + - fn:foo + - ArrayExpression: 'myVec' reference cannot be used in an array constructor. ----- Try 'vec2f(myVec)' or 'arrayOf(vec2f, count)([...])' to copy the value instead. -----] diff --git a/packages/typegpu/tests/struct.test.ts b/packages/typegpu/tests/struct.test.ts index b7fe34dfb2..54d644c4a5 100644 --- a/packages/typegpu/tests/struct.test.ts +++ b/packages/typegpu/tests/struct.test.ts @@ -390,6 +390,29 @@ describe('struct', () => { ); }); + it('throws when invalid number of arguments during code generation', () => { + const Boid = struct({ + pos: vec2f, + vel: vec2f, + }); + + const f = () => { + 'use gpu'; + const b1 = Boid({ pos: vec2f(6), vel: vec2f(7) }); + + // @ts-expect-error + const b2 = Boid(b1, b1); + return; + }; + + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f(): Struct schemas should always be called with at most 1 argument] + `); + }); + it('allows builtin names as struct props', () => { const myStruct = struct({ min: u32, From e30a98f852d2abe972b1296e2ee6f875549fc662 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 13 Jan 2026 23:18:41 +0100 Subject: [PATCH 27/67] more work on unroll --- packages/typegpu/src/tgsl/wgslGenerator.ts | 57 ++++++- packages/typegpu/tests/unroll.test.ts | 183 ++++++++++++++++++--- 2 files changed, 214 insertions(+), 26 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 272b2f532d..f31cffba43 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1110,7 +1110,62 @@ ${this.ctx.pre}else ${alternate}`; const ephemeralIterable = isEphemeralSnippet(iterableSnippet); if (shouldUnroll && ephemeralIterable) { - throw new Error('Not implemented'); + if (iterableSnippet.value instanceof ArrayExpression) { + const elementSnippet = forOfHelpers.getElementSnippet( + iterableSnippet, + 'i', // never will use that, can be arbitrary + ); + + // we need dummy variable to resolve body of the loop + const loopVarName = this.ctx.makeNameValid(loopVar[1]); + const elementType = forOfHelpers.getElementType(elementSnippet); + + const loopVarSnippet = snip( + loopVarName, + elementType, + elementSnippet.origin, + ); + this.ctx.defineVariable(loopVarName, loopVarSnippet); + + const baseIterationStr = `${this.ctx.pre}${ + this.block(blockifySingleStatement(body)) + }`; + + const values = Array.isArray(iterableSnippet.value) + ? iterableSnippet.value + : iterableSnippet.value.elements; + + const re = new RegExp(`\\b${loopVarName}\\b`, 'g'); + const iterations = values.map((e) => + baseIterationStr.replace(re, stitch`${e}`) + ); + + return iterations.join('\n'); + } + + // if (Array.isArray(iterableSnippet.value)) { + // const elementSnippet = forOfHelpers.getElementSnippet( + // iterableSnippet, + // 'i', never will use that, can be arbitrary + // ); + + // console.log({ iterableSnippet, elementSnippet }); + + // // we need dummy variable to resolve body of the loop + // const loopVarName = this.ctx.makeNameValid(loopVar[1]); + // const elementType = forOfHelpers.getElementType(elementSnippet); + + // const loopVarSnippet = snip( + // loopVarName, + // elementType, + // elementSnippet.origin, + // ); + // this.ctx.defineVariable(loopVarName, loopVarSnippet); + + // return ''; + // } + + throw new WgslTypeError('Cannot unroll. Unsupported iterable.'); } if (shouldUnroll && !ephemeralIterable) { diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 6dc9907857..3c35dabbca 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -5,7 +5,6 @@ import tgpu from '../src/index.ts'; describe('tgpu.unroll', () => { it('called outside shader and outside forOf returns passed iterable', () => { const arr = [1, 2, 3]; - const x = tgpu['~unstable'].unroll(arr); expect(x).toBe(arr); @@ -37,50 +36,160 @@ describe('tgpu.unroll', () => { `); }); - it.skip('unrolls forOf of comptime array', () => { + it('unrolls forOf of array expression of primitives', () => { const f = () => { 'use gpu'; - let result = d.f32(0); - for (const item of tgpu['~unstable'].unroll([1, 2, 3])) { // should operate on values - result += d.f32(item); + let res = 0; + for (const foo of tgpu['~unstable'].unroll([1, 2, 3])) { + res += foo; } + return res; + }; - // const arr = [1, 2, 3]; - // for (const item of tgpu['~unstable'].unroll(arr)) { // should operate on indices - // result -= item; - // } + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var res = 0; + { + res += 1; + } + { + res += 2; + } + { + res += 3; + } + return res; + }" + `); + }); - // const v = d.vec2f(); - // for (const item of tgpu['~unstable'].unroll(v)) { // should operate on indices - // result *= item; - // } + it('unrolls forOf with variable shadowing', () => { + const f = () => { + 'use gpu'; + let fooResult = d.f32(0); + for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { + const boo = foo; + { // broken indenting + let foo = boo.x; + fooResult += foo; + } + } - return result; + return fooResult; }; expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> f32 { - var result = 0f; - for (var i = 0; i < 3; i++) { - let item = array(1, 2, 3)[i]; + var fooResult = 0f; + { + var boo = vec3f(7); { - result += f32(item); + var foo2 = boo.x; + fooResult += foo2; } } - return result; + { + var boo = vec3f(3); + { + var foo2 = boo.x; + fooResult += foo2; + } + } + return fooResult; }" `); }); - it.skip('unrolls forOf of iterable provided by comptime', () => { - const getArr = tgpu['~unstable'].comptime(() => [1, 2, 3]); - // add basic external, derived, maybe slot and accessor + it('unrolls forOf of array expression of complex types', () => { + const Boid = d.struct({ + pos: d.vec2i, + vel: d.vec2f, + }); const f = () => { 'use gpu'; - let result = d.f32(0); - for (const item of tgpu['~unstable'].unroll(getArr())) { - result += d.f32(item); + let res = d.vec2f(); + for (const foo of tgpu['~unstable'].unroll([d.vec2f(7), d.vec2f(3)])) { + for (const boo of tgpu['~unstable'].unroll([Boid(), Boid()])) { + res = res.add(foo).add(boo.vel); + } + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "struct Boid { + pos: vec2i, + vel: vec2f, + } + + fn f() -> vec2f { + var res = vec2f(); + { + { + res = ((res + vec2f(7)) + Boid().vel); + } + { + res = ((res + vec2f(7)) + Boid().vel); + } + } + { + { + res = ((res + vec2f(3)) + Boid().vel); + } + { + res = ((res + vec2f(3)) + Boid().vel); + } + } + return res; + }" + `); + }); + + it('unrolls forOf of array expression of copied values', () => { + const f = () => { + 'use gpu'; + let res = d.vec2f(); + const v1 = d.vec2f(7); + const v2 = d.vec2f(3); + for (const foo of tgpu['~unstable'].unroll([d.vec2f(v1), d.vec2f(v2)])) { + res = res.add(foo); + const boo = foo; + boo.x = 0; + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> vec2f { + var res = vec2f(); + var v1 = vec2f(7); + var v2 = vec2f(3); + { + res = (res + v1); + var boo = v1; + boo.x = 0f; + } + { + res = (res + v2); + var boo = v2; + boo.x = 0f; + } + return res; + }" + `); + }); + + it.skip('unrolls forOf of external comptime iterable', () => { + const arr = [1, 2, 3]; + + const f = () => { + 'use gpu'; + let result = 0; + for (const foo of tgpu['~unstable'].unroll(arr)) { + result += foo; } return result; @@ -109,4 +218,28 @@ describe('tgpu.unroll', () => { - fn*:f(): Cannot unroll loop. Number of iterations unknown at compile time] `); }); + + // TODO + // + // add basic external, derived, maybe slot and accessor + // + // const arr = [1, 2, 3]; + // for (const foo of tgpu['~unstable'].unroll(arr)) { // should operate on indices + // result -= foo; + // } + + // const v = d.vec2f(); + // for (const foo of tgpu['~unstable'].unroll(v)) { // should operate on indices + // result *= foo; + // } + + // for (const foo of tgpu['~unstable'].unroll([1, 2, 3])) { // should operate on values + // const foo = 1; + // result += d.f32(foo); + // } + + // const foo = 1; + // for (const foo of tgpu['~unstable'].unroll([1, 2, 3])) { // should operate on values + // result += d.f32(foo); + // } }); From d3a19ea9c9598dc62e1a4d32c884787f0429bb7b Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 16 Jan 2026 14:32:36 +0100 Subject: [PATCH 28/67] block with externals --- packages/typegpu/src/resolutionCtx.ts | 36 +++++++++- .../typegpu/src/tgsl/generationHelpers.ts | 2 + packages/typegpu/src/tgsl/shaderGenerator.ts | 3 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 13 ++++ packages/typegpu/src/types.ts | 2 + .../typegpu/tests/tgsl/wgslGenerator.test.ts | 69 +++++++++++++++++++ packages/typegpu/tests/unroll.test.ts | 6 +- 7 files changed, 126 insertions(+), 5 deletions(-) diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 4b13aa8afb..6fba6eb071 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -111,6 +111,7 @@ type SlotBindingLayer = { type BlockScopeLayer = { type: 'blockScope'; declarations: Map; + externals: Map; }; class ItemStateStackImpl implements ItemStateStack { @@ -190,6 +191,7 @@ class ItemStateStackImpl implements ItemStateStack { this._stack.push({ type: 'blockScope', declarations: new Map(), + externals: new Map(), }); } @@ -260,7 +262,8 @@ class ItemStateStackImpl implements ItemStateStack { } if (layer?.type === 'blockScope') { - const snippet = layer.declarations.get(id); + // the order matters + const snippet = layer.declarations.get(id) ?? layer.externals.get(id); if (snippet !== undefined) { return snippet; } @@ -288,6 +291,30 @@ class ItemStateStackImpl implements ItemStateStack { throw new Error('No block scope found to define a variable in.'); } + + pushBlockExternals(externals: Record): void { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + if (layer?.type === 'blockScope') { + Object.entries(externals).forEach(([id, snippet]) => { + layer.externals.set(id, snippet); + }); + return; + } + } + throw new Error('No block scope found to push externals in.'); + } + + popBlockExternals(): void { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + if (layer?.type === 'blockScope') { + layer.externals.clear(); + return; + } + } + throw new Error('No block scope found to pop overrides from.'); + } } const INDENT = [ @@ -411,6 +438,13 @@ export class ResolutionCtxImpl implements ResolutionCtx { return scope.returnType; } + pushBlockExternals(externals: Record) { + this._itemStateStack.pushBlockExternals(externals); + } + popBlockExternals() { + this._itemStateStack.popBlockExternals(); + } + get shelllessRepo() { return this.#namespaceInternal.shelllessRepo; } diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 7b3a6d6391..80e7fbde8c 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -93,6 +93,8 @@ export type GenerationCtx = ResolutionCtx & { generateLog(op: string, args: Snippet[]): Snippet; getById(id: string): Snippet | null; defineVariable(id: string, snippet: Snippet): void; + pushBlockExternals(externals: Record): void; + popBlockExternals(): void; /** * Types that are used in `return` statements are diff --git a/packages/typegpu/src/tgsl/shaderGenerator.ts b/packages/typegpu/src/tgsl/shaderGenerator.ts index 7dc6502a53..3f99c0c5f4 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator.ts @@ -2,10 +2,11 @@ import type { Block, Expression, Statement } from 'tinyest'; import type { AnyData } from '../data/dataTypes.ts'; import type { Snippet } from '../data/snippet.ts'; import type { GenerationCtx } from './generationHelpers.ts'; +import type { ExternalMap } from '../../src/core/resolve/externals.ts'; export interface ShaderGenerator { initGenerator(ctx: GenerationCtx): void; - block(body: Block): string; + block(body: Block, externalMap: ExternalMap): string; identifier(id: string): Snippet; typedExpression(expression: Expression, expectedType: AnyData): Snippet; expression(expression: Expression): Snippet; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index f31cffba43..4321b0a5f4 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -33,6 +33,7 @@ import { } from './conversion.ts'; import { ArrayExpression, + coerceToSnippet, concretize, forOfHelpers, type GenerationCtx, @@ -46,6 +47,7 @@ import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; import { UnrolledIterable } from '../../src/core/unroll/tgpuUnroll.ts'; +import type { ExternalMap } from '../../src/core/resolve/externals.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -194,8 +196,19 @@ class WgslGenerator implements ShaderGenerator { public block( [_, statements]: tinyest.Block, + externalMap?: ExternalMap, ): string { this.ctx.pushBlockScope(); + + if (externalMap) { + const externals = Object.fromEntries( + Object.entries(externalMap).map(( + [id, value], + ) => [id, coerceToSnippet(value)]), + ); + this.ctx.pushBlockExternals(externals); + } + try { this.ctx.indent(); const body = statements.map((statement) => this.statement(statement)) diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 239ea5ad46..9114ceae2f 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -136,6 +136,8 @@ export interface ItemStateStack { readSlot(slot: TgpuSlot): T | undefined; getSnippetById(id: string): Snippet | undefined; defineBlockVariable(id: string, snippet: Snippet): void; + pushBlockExternals(externals: Record): void; + popBlockExternals(): void; } /** diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 8ec7dc6e2f..1d6208d022 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -15,6 +15,8 @@ import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from '../utils/extendedIt.ts'; import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; +import { resolutionAccess } from 'apps/typegpu-docs/src/examples/rendering/disco/consts.ts'; +import { ResultsTable } from 'apps/typegpu-docs/src/pages/benchmark/components/benchmark-results.tsx'; const { NodeTypeCatalog: NODE } = tinyest; @@ -1667,4 +1669,71 @@ describe('wgslGenerator', () => { }" `); }); + + it('block externals do not override identifiers', () => { + const f = () => { + 'use gpu'; + const y = 100; + const x = y; + return x; + }; + + const parsed = getMetaData(f)?.ast?.body as tinyest.Block; + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.u32, + {}, + ); + + const res = wgslGenerator.block( + parsed, + { x: 42 }, + ); + + expect(res).toMatchInlineSnapshot(` + "{ + const y = 100; + const x = y; + return u32(x); + }" + `); + }); + }); + + it('block externals are injected correctly', () => { + const f = () => { + 'use gpu'; + for (const x of []) { + const y = x; + } + }; + + const parsed = getMetaData(f)?.ast?.body as tinyest.Block; + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.Void, + {}, + ); + + const res = wgslGenerator.block( + // @ts-ignore + parsed[1][0][3] as tinyest.Block, + { x: 67 }, + ); + + expect(res).toMatchInlineSnapshot(` + "{ + const y = 67; + }" + `); + }); + }); }); diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 3c35dabbca..dc0911ba4d 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -70,7 +70,7 @@ describe('tgpu.unroll', () => { for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { const boo = foo; { // broken indenting - let foo = boo.x; + const foo = boo.x; fooResult += foo; } } @@ -84,14 +84,14 @@ describe('tgpu.unroll', () => { { var boo = vec3f(7); { - var foo2 = boo.x; + let foo2 = boo.x; fooResult += foo2; } } { var boo = vec3f(3); { - var foo2 = boo.x; + let foo2 = boo.x; fooResult += foo2; } } From d64a71f8633c8cda69ed30a6913d35b27400558e Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 16 Jan 2026 14:36:25 +0100 Subject: [PATCH 29/67] nr fix --- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 1d6208d022..ebb473e261 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -15,8 +15,6 @@ import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from '../utils/extendedIt.ts'; import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; -import { resolutionAccess } from 'apps/typegpu-docs/src/examples/rendering/disco/consts.ts'; -import { ResultsTable } from 'apps/typegpu-docs/src/pages/benchmark/components/benchmark-results.tsx'; const { NodeTypeCatalog: NODE } = tinyest; @@ -1724,7 +1722,7 @@ describe('wgslGenerator', () => { ); const res = wgslGenerator.block( - // @ts-ignore + // @ts-expect-error it's not undefined parsed[1][0][3] as tinyest.Block, { x: 67 }, ); From 91715f52f98fb9da5ab11efd3d163a3e3f41d539 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 16 Jan 2026 15:00:04 +0100 Subject: [PATCH 30/67] nested block test --- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index ebb473e261..a09cf9ec32 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1734,4 +1734,43 @@ describe('wgslGenerator', () => { `); }); }); + + it('block externals are injected correctly into nested block', () => { + const f = () => { + 'use gpu'; + for (const x of []) { + const z = x; + { + const y = x; + } + } + }; + + const parsed = getMetaData(f)?.ast?.body as tinyest.Block; + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.Void, + {}, + ); + + const res = wgslGenerator.block( + // @ts-expect-error it's not undefined + parsed[1][0][3] as tinyest.Block, + { x: 67 }, + ); + + expect(res).toMatchInlineSnapshot(` + "{ + const z = 67; + { + const y = 67; + } + }" + `); + }); + }); }); From e21cda1775ae933b6f7cef180a34eb9851de69be Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 16 Jan 2026 15:09:56 +0100 Subject: [PATCH 31/67] no ts-expect-error --- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index a09cf9ec32..bea48ecf02 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1722,8 +1722,7 @@ describe('wgslGenerator', () => { ); const res = wgslGenerator.block( - // @ts-expect-error it's not undefined - parsed[1][0][3] as tinyest.Block, + ((parsed[1][0] as tinyest.ForOf)[3]) as tinyest.Block, { x: 67 }, ); @@ -1758,8 +1757,7 @@ describe('wgslGenerator', () => { ); const res = wgslGenerator.block( - // @ts-expect-error it's not undefined - parsed[1][0][3] as tinyest.Block, + ((parsed[1][0] as tinyest.ForOf)[3]) as tinyest.Block, { x: 67 }, ); From 0ed7d326527d150628cc76dcfc64ae2027cd58a9 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 20 Jan 2026 12:49:09 +0100 Subject: [PATCH 32/67] wip --- .../typegpu/src/core/unroll/tgpuUnroll.ts | 2 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 57 ++++---------- packages/typegpu/tests/unroll.test.ts | 77 +++++++++++++++++-- 3 files changed, 84 insertions(+), 52 deletions(-) diff --git a/packages/typegpu/src/core/unroll/tgpuUnroll.ts b/packages/typegpu/src/core/unroll/tgpuUnroll.ts index 72f0dab2c9..15871762a1 100644 --- a/packages/typegpu/src/core/unroll/tgpuUnroll.ts +++ b/packages/typegpu/src/core/unroll/tgpuUnroll.ts @@ -10,7 +10,7 @@ import { snip, type Snippet, } from '../../../src/data/snippet.ts'; -import type { ResolutionCtx, SelfResolvable } from 'src/types.ts'; +import type { ResolutionCtx, SelfResolvable } from '../../../src/types.ts'; /** * The result of calling `tgpu.unroll(...)`. The code responsible for diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 63ea818fa1..12ff30d720 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1124,60 +1124,31 @@ ${this.ctx.pre}else ${alternate}`; if (shouldUnroll && ephemeralIterable) { if (iterableSnippet.value instanceof ArrayExpression) { - const elementSnippet = forOfHelpers.getElementSnippet( - iterableSnippet, - 'i', // never will use that, can be arbitrary - ); - - // we need dummy variable to resolve body of the loop const loopVarName = this.ctx.makeNameValid(loopVar[1]); - const elementType = forOfHelpers.getElementType(elementSnippet); - const loopVarSnippet = snip( - loopVarName, - elementType, - elementSnippet.origin, + const blockified = blockifySingleStatement(body); + const iterations = iterableSnippet.value.elements.map((e) => + `${this.ctx.pre}${ + this.block(blockified, { [`${loopVarName}`]: e }) + }` ); - this.ctx.defineVariable(loopVarName, loopVarSnippet); - const baseIterationStr = `${this.ctx.pre}${ - this.block(blockifySingleStatement(body)) - }`; + return iterations.join('\n'); + } - const values = Array.isArray(iterableSnippet.value) - ? iterableSnippet.value - : iterableSnippet.value.elements; + if (Array.isArray(iterableSnippet.value)) { + const loopVarName = this.ctx.makeNameValid(loopVar[1]); - const re = new RegExp(`\\b${loopVarName}\\b`, 'g'); - const iterations = values.map((e) => - baseIterationStr.replace(re, stitch`${e}`) + const blockified = blockifySingleStatement(body); + const iterations = iterableSnippet.value.map((e) => + `${this.ctx.pre}${ + this.block(blockified, { [`${loopVarName}`]: e }) + }` ); return iterations.join('\n'); } - // if (Array.isArray(iterableSnippet.value)) { - // const elementSnippet = forOfHelpers.getElementSnippet( - // iterableSnippet, - // 'i', never will use that, can be arbitrary - // ); - - // console.log({ iterableSnippet, elementSnippet }); - - // // we need dummy variable to resolve body of the loop - // const loopVarName = this.ctx.makeNameValid(loopVar[1]); - // const elementType = forOfHelpers.getElementType(elementSnippet); - - // const loopVarSnippet = snip( - // loopVarName, - // elementType, - // elementSnippet.origin, - // ); - // this.ctx.defineVariable(loopVarName, loopVarSnippet); - - // return ''; - // } - throw new WgslTypeError('Cannot unroll. Unsupported iterable.'); } diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index dc0911ba4d..9904ea571a 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -50,29 +50,74 @@ describe('tgpu.unroll', () => { "fn f() -> i32 { var res = 0; { - res += 1; + res += 1i; } { - res += 2; + res += 2i; } { - res += 3; + res += 3i; } return res; }" `); }); - it('unrolls forOf with variable shadowing', () => { + it('unrolls forOf with variable override', () => { + const f = () => { + 'use gpu'; + const foo = d.vec3f(6); + for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { // this foo is in different block layer, it will be found first + const boo = foo; + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var foo = vec3f(6); + { + var boo = vec3f(7); + } + { + var boo = vec3f(3); + } + }" + `); + }); + + it('unrolls forOf with variable from different scope override', () => { + const foo = 2; + + const f = () => { + 'use gpu'; + for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { // this foo is in different block layer, it will be found first + const boo = foo; + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + { + var boo = vec3f(7); + } + { + var boo = vec3f(3); + } + }" + `); + }); + + it('unrolls forOf with variable being overriden', () => { const f = () => { 'use gpu'; let fooResult = d.f32(0); for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { const boo = foo; - { // broken indenting - const foo = boo.x; + { + const foo = boo.x; // this foo is in different block layer, it will see the previous foo fooResult += foo; } + const bar = foo; } return fooResult; @@ -87,6 +132,7 @@ describe('tgpu.unroll', () => { let foo2 = boo.x; fooResult += foo2; } + var bar = vec3f(7); } { var boo = vec3f(3); @@ -94,6 +140,7 @@ describe('tgpu.unroll', () => { let foo2 = boo.x; fooResult += foo2; } + var bar = vec3f(3); } return fooResult; }" @@ -182,7 +229,7 @@ describe('tgpu.unroll', () => { `); }); - it.skip('unrolls forOf of external comptime iterable', () => { + it('unrolls forOf of external comptime iterable', () => { const arr = [1, 2, 3]; const f = () => { @@ -195,7 +242,21 @@ describe('tgpu.unroll', () => { return result; }; - expect(tgpu.resolve([f])).toMatchInlineSnapshot(); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var result = 0; + { + result += 1i; + } + { + result += 2i; + } + { + result += 3i; + } + return result; + }" + `); }); it.skip('throws error when number of iterations is unknown at comptime', () => { From 43e4f427919cd1a574dc17288316209cd7ec66a9 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 11 Feb 2026 11:26:35 +0100 Subject: [PATCH 33/67] new name registry --- packages/typegpu/src/nameRegistry.ts | 98 +++++++++++++++++++++------ packages/typegpu/src/resolutionCtx.ts | 2 + 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameRegistry.ts index 05319a7d66..3c6b668900 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameRegistry.ts @@ -1,3 +1,5 @@ +import { invariant } from './errors.ts'; + const bannedTokens = new Set([ // keywords 'alias', @@ -385,6 +387,8 @@ export interface NameRegistry { pushFunctionScope(): void; popFunctionScope(): void; + pushBlockScope(): void; + popBlockScope(): void; } function sanitizePrimer(primer: string | undefined) { @@ -429,69 +433,121 @@ export function isValidProp(ident: string): boolean { const prefix = ident.split('_')[0] as string; return !bannedTokens.has(prefix); } +type FunctionScopeLayer = { + type: 'functionScope'; +}; + +type BlockScopeLayer = { + type: 'blockScope'; + usedBlockScopeNames: Set; +}; + +type ScopeLayer = FunctionScopeLayer | BlockScopeLayer; abstract class NameRegistryImpl implements NameRegistry { - abstract getUniqueVariant(base: string): string; + abstract getUniqueVariant(base: string, global: boolean): string; readonly #usedNames: Set; - readonly #usedFunctionScopeNamesStack: Set[]; + readonly #usedAllTimeBlockScopeNames: Set; + readonly #scopeStack: ScopeLayer[]; constructor() { this.#usedNames = new Set([ ...bannedTokens, ...builtins, ]); - this.#usedFunctionScopeNamesStack = []; + this.#usedAllTimeBlockScopeNames = new Set(); + this.#scopeStack = []; } - get usedFunctionScopeNames(): Set | undefined { - return this - .#usedFunctionScopeNamesStack[ - this.#usedFunctionScopeNamesStack.length - 1 - ]; + get #usedBlockScopeNames(): Set | undefined { + return this.#scopeStack.findLast((scope) => scope.type === 'blockScope') + ?.usedBlockScopeNames; } makeUnique(primer: string | undefined, global: boolean): string { const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer); + const name = this.getUniqueVariant(sanitizedPrimer, global); if (global) { this.#usedNames.add(name); } else { - this.usedFunctionScopeNames?.add(name); + this.#usedAllTimeBlockScopeNames.add(name); + this.#usedBlockScopeNames?.add(name); } return name; } + #isUsedInBlocksBefore(name: string): boolean { + const functionScopeIndex = this.#scopeStack.findLastIndex((scope) => + scope.type === 'functionScope' + ); + return this.#scopeStack.slice(functionScopeIndex + 1).some((scope) => + (scope as BlockScopeLayer).usedBlockScopeNames.has(name) + ); + } + makeValid(primer: string): string { - if (isValidIdentifier(primer) && !this.#usedNames.has(primer)) { - this.usedFunctionScopeNames?.add(primer); + if ( + isValidIdentifier(primer) && !this.#usedNames.has(primer) && + !this.#isUsedInBlocksBefore(primer) + ) { + this.#usedAllTimeBlockScopeNames.add(primer); + this.#usedBlockScopeNames?.add(primer); return primer; } return this.makeUnique(primer, false); } - isUsed(name: string): boolean { - return this.#usedNames.has(name) || - !!this.usedFunctionScopeNames?.has(name); + isUsed(name: string, global: boolean): boolean { + const varyingCond = global + ? this.#usedAllTimeBlockScopeNames.has(name) + : this.#isUsedInBlocksBefore(name); + return this.#usedNames.has(name) || varyingCond; } pushFunctionScope(): void { - this.#usedFunctionScopeNamesStack.push(new Set()); + this.#scopeStack.push({ type: 'functionScope' }); + this.#scopeStack.push({ + type: 'blockScope', + usedBlockScopeNames: new Set(), + }); } popFunctionScope(): void { - this.#usedFunctionScopeNamesStack.pop(); + const functionScopeIndex = this.#scopeStack.findLastIndex((scope) => + scope.type === 'functionScope' + ); + + if (functionScopeIndex === -1) { + return; + } + + this.#scopeStack.splice(functionScopeIndex); + } + + pushBlockScope(): void { + this.#scopeStack.push({ + type: 'blockScope', + usedBlockScopeNames: new Set(), + }); + } + popBlockScope(): void { + invariant( + this.#scopeStack[this.#scopeStack.length - 1]?.type === 'blockScope', + 'Tried to pop block scope, but it is not present', + ); + this.#scopeStack.pop(); } } export class RandomNameRegistry extends NameRegistryImpl { #lastUniqueId = 0; - getUniqueVariant(base: string): string { + getUniqueVariant(base: string, global: boolean): string { let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name)) { + while (this.isUsed(name, global)) { name = `${base}_${this.#lastUniqueId++}`; } return name; @@ -499,10 +555,10 @@ export class RandomNameRegistry extends NameRegistryImpl { } export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string): string { + getUniqueVariant(base: string, global: boolean): string { let index = 0; let name = base; - while (this.isUsed(name)) { + while (this.isUsed(name, global)) { index++; name = `${base}_${index}`; } diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index af8302a3b8..f797d304be 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -420,10 +420,12 @@ export class ResolutionCtxImpl implements ResolutionCtx { } pushBlockScope() { + this.#namespaceInternal.nameRegistry.pushBlockScope(); this._itemStateStack.pushBlockScope(); } popBlockScope() { + this.#namespaceInternal.nameRegistry.popBlockScope(); this._itemStateStack.pop('blockScope'); } From 03aaab5a86a2560ce019616fa2faaa46f5d5edbe Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 18 Feb 2026 12:03:16 +0100 Subject: [PATCH 34/67] test align --- .../algorithms/mnist-inference/index.ts | 4 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 108 ++++++----- .../tests/examples/individual/3d-fish.test.ts | 22 +-- .../tests/examples/individual/blur.test.ts | 6 +- .../examples/individual/caustics.test.ts | 14 +- .../tests/examples/individual/clouds.test.ts | 14 +- .../individual/fluid-double-buffering.test.ts | 28 +-- .../examples/individual/jelly-slider.test.ts | 16 +- .../examples/individual/jelly-switch.test.ts | 6 +- .../individual/jump-flood-voronoi.test.ts | 14 +- .../examples/individual/perlin-noise.test.ts | 14 +- .../examples/individual/probability.test.ts | 182 +++++++++--------- .../examples/individual/ripple-cube.test.ts | 46 ++--- .../examples/individual/slime-mold-3d.test.ts | 42 ++-- .../examples/individual/slime-mold.test.ts | 28 +-- .../individual/smoky-triangle.test.ts | 14 +- .../individual/tgsl-parsing-test.test.ts | 6 +- .../examples/individual/uniformity.test.ts | 26 +-- .../examples/individual/vaporrave.test.ts | 14 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 19 +- 20 files changed, 325 insertions(+), 298 deletions(-) diff --git a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts index 49dfd54ac0..68d105fd72 100644 --- a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts @@ -19,7 +19,7 @@ const hasTimestampQuery = root.enabledFeatures.has('timestamp-query'); const hasSubgroups = root.enabledFeatures.has('subgroups'); let useSubgroups = hasSubgroups; -const canvasData = Array.from({ length: (SIZE ** 2) }, () => 0); +const canvasData = Array.from({ length: SIZE ** 2 }, () => 0); // Shaders @@ -313,7 +313,7 @@ function centerImage(data: number[]) { const offsetX = Math.round(SIZE / 2 - x); const offsetY = Math.round(SIZE / 2 - y); - const newData = Array.from({ length: (SIZE * SIZE) }, () => 0); + const newData = Array.from({ length: SIZE * SIZE }, () => 0); for (let i = 0; i < SIZE; i++) { for (let j = 0; j < SIZE; j++) { const index = i * SIZE + j; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 409ba2d7c3..32acbeebaf 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1228,64 +1228,74 @@ ${this.ctx.pre}else ${alternate}`; ); } - // If it's ephemeral, it's a value that cannot change. If it's a reference, we take - // an implicit pointer to it - let loopVarKind = 'let'; - const loopVarName = this.ctx.makeNameValid(loopVar[1]); - - if (!isEphemeralSnippet(elementSnippet)) { - if (elementSnippet.origin === 'constant-tgpu-const-ref') { - loopVarKind = 'const'; - } else if (elementSnippet.origin === 'runtime-tgpu-const-ref') { - loopVarKind = 'let'; - } else { - loopVarKind = 'let'; - if (!wgsl.isPtr(elementType)) { - const ptrType = createPtrFromOrigin( - elementSnippet.origin, - concretize(elementType as wgsl.AnyWgslData) as wgsl.StorableData, - ); - invariant( - ptrType !== undefined, - `Creating pointer type from origin ${elementSnippet.origin}`, - ); - elementType = ptrType; - } + try { + this.ctx.pushBlockScope(); + + // If it's ephemeral, it's a value that cannot change. If it's a reference, we take + // an implicit pointer to it + let loopVarKind = 'let'; + const loopVarName = this.ctx.makeNameValid(loopVar[1]); + + if (!isEphemeralSnippet(elementSnippet)) { + if (elementSnippet.origin === 'constant-tgpu-const-ref') { + loopVarKind = 'const'; + } else if (elementSnippet.origin === 'runtime-tgpu-const-ref') { + loopVarKind = 'let'; + } else { + loopVarKind = 'let'; + if (!wgsl.isPtr(elementType)) { + const ptrType = createPtrFromOrigin( + elementSnippet.origin, + concretize( + elementType as wgsl.AnyWgslData, + ) as wgsl.StorableData, + ); + invariant( + ptrType !== undefined, + `Creating pointer type from origin ${elementSnippet.origin}`, + ); + elementType = ptrType; + } - elementType = implicitFrom(elementType as wgsl.Ptr); + elementType = implicitFrom(elementType as wgsl.Ptr); + } } - } - const loopVarSnippet = snip( - loopVarName, - elementType, - elementSnippet.origin, - ); - this.ctx.defineVariable(loopVarName, loopVarSnippet); + const loopVarSnippet = snip( + loopVarName, + elementType, + elementSnippet.origin, + ); - const forStr = stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ - tryConvertSnippet(this.ctx, elementCountSnippet, u32, false) - }; ${index}++) {`; + this.ctx.defineVariable(loopVar[1], loopVarSnippet); - this.ctx.indent(); + const forStr = + stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ + tryConvertSnippet(this.ctx, elementCountSnippet, u32, false) + }; ${index}++) {`; - const loopVarDeclStr = - stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ - tryConvertSnippet( - this.ctx, - elementSnippet, - elementType, - false, - ) - };`; + this.ctx.indent(); - const bodyStr = `${this.ctx.pre}${ - this.block(blockifySingleStatement(body)) - }`; + const loopVarDeclStr = + stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ + tryConvertSnippet( + this.ctx, + elementSnippet, + elementType, + false, + ) + };`; - this.ctx.dedent(); + const bodyStr = `${this.ctx.pre}${ + this.block(blockifySingleStatement(body)) + }`; - return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; + this.ctx.dedent(); + + return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; + } finally { + this.ctx.popBlockScope(); + } } if (statement[0] === NODE.continue) { diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 4c75b546b6..107a074ec6 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -31,10 +31,10 @@ describe('3d fish example', () => { @group(0) @binding(1) var seedUniform: f32; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -42,11 +42,11 @@ describe('3d fish example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { @@ -160,11 +160,11 @@ describe('3d fish example', () => { if ((cohesionCount > 0i)) { cohesion = (((1f / f32(cohesionCount)) * cohesion) - (*fishData).position); } - for (var i = 0; (i < 3i); i += 1i) { + for (var i_1 = 0; (i_1 < 3i); i_1 += 1i) { var repulsion = vec3f(); - repulsion[i] = 1f; - let axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); - let axisPosition = (*fishData).position[i]; + repulsion[i_1] = 1f; + let axisAquariumSize = (vec3f(10, 4, 10)[i_1] / 2f); + let axisPosition = (*fishData).position[i_1]; const distance_1 = 0.1; if ((axisPosition > (axisAquariumSize - distance_1))) { let str2 = (axisPosition - (axisAquariumSize - distance_1)); diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index 137c90c309..8732ae8e21 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -62,9 +62,9 @@ describe('blur example', () => { } } workgroupBarrier(); - for (var r = 0; (r < 4i); r++) { + for (var r_1 = 0; (r_1 < 4i); r_1++) { for (var c = 0; (c < 4i); c++) { - var writeIndex = (baseIndex + vec2i(c, r)); + var writeIndex = (baseIndex + vec2i(c, r_1)); if ((flip != 0u)) { writeIndex = writeIndex.yx; } @@ -73,7 +73,7 @@ describe('blur example', () => { var acc = vec3f(); for (var f = 0; (f < (*settings2).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[r][i] * (1f / f32((*settings2).filterDim)))); + acc = (acc + (tileData[r_1][i] * (1f / f32((*settings2).filterDim)))); } textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } diff --git a/packages/typegpu/tests/examples/individual/caustics.test.ts b/packages/typegpu/tests/examples/individual/caustics.test.ts index cac8c27b83..33cd236b8e 100644 --- a/packages/typegpu/tests/examples/individual/caustics.test.ts +++ b/packages/typegpu/tests/examples/individual/caustics.test.ts @@ -43,10 +43,10 @@ describe('caustics example', () => { @group(0) @binding(1) var time: f32; - var seed: vec2f; + var seed_1: vec2f; fn seed3(value: vec3f) { - seed = (value.xy + vec2f(value.z)); + seed_1 = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -54,11 +54,11 @@ describe('caustics example', () => { } fn sample_1() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randOnUnitSphere() -> vec3f { diff --git a/packages/typegpu/tests/examples/individual/clouds.test.ts b/packages/typegpu/tests/examples/individual/clouds.test.ts index df70cf0435..e64f32f7de 100644 --- a/packages/typegpu/tests/examples/individual/clouds.test.ts +++ b/packages/typegpu/tests/examples/individual/clouds.test.ts @@ -43,10 +43,10 @@ describe('clouds example', () => { @group(1) @binding(0) var params: CloudsParams; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -56,11 +56,11 @@ describe('clouds example', () => { @group(0) @binding(0) var resolutionUniform: vec2f; fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 1bc06bccb2..db9423ce72 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -102,10 +102,10 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var time: f32; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -160,11 +160,11 @@ describe('fluid double buffering example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { @@ -282,10 +282,10 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var time: f32; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -340,11 +340,11 @@ describe('fluid double buffering example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index 544d5b5ae7..8b600d72c2 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -69,10 +69,10 @@ describe('jelly-slider example', () => { @group(0) @binding(0) var randomUniform: vec2f; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -166,11 +166,11 @@ describe('jelly-slider example', () => { @group(0) @binding(6) var bezierTexture: texture_2d; fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { @@ -533,7 +533,7 @@ describe('jelly-slider example', () => { return background; } var distanceFromOrigin = max(0f, intersection.tMin); - for (var i = 0; (i < 64i); i++) { + for (var i_1 = 0; (i_1 < 64i); i_1++) { if ((totalSteps >= 64u)) { break; } diff --git a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts index 8b4760c223..62a934e3de 100644 --- a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts @@ -43,10 +43,10 @@ describe('jelly switch example', () => { @group(0) @binding(0) var randomUniform: vec2f; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -337,7 +337,7 @@ describe('jelly switch example', () => { return background; } var distanceFromOrigin = max(0f, intersection.tMin); - for (var i = 0; (i < 64i); i++) { + for (var i_1 = 0; (i_1 < 64i); i_1++) { if ((totalSteps >= 64u)) { break; } diff --git a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts index fbb318b98c..9987c5e464 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts @@ -25,10 +25,10 @@ describe('jump flood (voronoi) example', () => { @group(0) @binding(1) var timeUniform: f32; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -36,11 +36,11 @@ describe('jump flood (voronoi) example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts index cc90153c0c..daa5f527af 100644 --- a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts +++ b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts @@ -23,10 +23,10 @@ describe('perlin noise example', () => { @group(1) @binding(1) var memory: array; - var seed: vec2f; + var seed_1: vec2f; fn seed3(value: vec3f) { - seed = (value.xy + vec2f(value.z)); + seed_1 = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -34,11 +34,11 @@ describe('perlin noise example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randOnUnitSphere() -> vec3f { diff --git a/packages/typegpu/tests/examples/individual/probability.test.ts b/packages/typegpu/tests/examples/individual/probability.test.ts index 13a01bb952..ddb31bd409 100644 --- a/packages/typegpu/tests/examples/individual/probability.test.ts +++ b/packages/typegpu/tests/examples/individual/probability.test.ts @@ -22,10 +22,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -33,11 +33,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randUniformExclusive() -> f32 { @@ -74,10 +74,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -85,11 +85,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randOnUnitSphere() -> vec3f { @@ -118,10 +118,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -129,11 +129,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randInUnitCircle() -> vec2f { @@ -163,10 +163,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -174,11 +174,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randOnUnitCircle() -> vec2f { @@ -207,10 +207,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -218,11 +218,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randInUnitCube() -> vec3f { @@ -246,10 +246,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -257,11 +257,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randOnUnitCube() -> vec3f { @@ -291,10 +291,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -302,11 +302,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randUniformExclusive() -> f32 { @@ -353,10 +353,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -364,11 +364,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randOnUnitSphere() -> vec3f { @@ -407,10 +407,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -418,11 +418,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randBernoulli(p: f32) -> f32 { @@ -451,10 +451,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -462,11 +462,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randFloat01() -> f32 { @@ -494,10 +494,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -505,11 +505,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randUniformExclusive() -> f32 { @@ -542,10 +542,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -553,11 +553,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randUniformExclusive() -> f32 { @@ -591,10 +591,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -602,11 +602,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randUniformExclusive() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts index dbe13f72fd..4cd0057425 100644 --- a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts +++ b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts @@ -23,10 +23,10 @@ describe('ripple-cube example', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed: vec2f; + var seed_1: vec2f; fn seed3(value: vec3f) { - seed = (value.xy + vec2f(value.z)); + seed_1 = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -34,11 +34,11 @@ describe('ripple-cube example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randOnUnitSphere() -> vec3f { @@ -75,10 +75,10 @@ describe('ripple-cube example', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed: vec2f; + var seed_1: vec2f; fn seed3(value: vec3f) { - seed = (value.xy + vec2f(value.z)); + seed_1 = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -86,11 +86,11 @@ describe('ripple-cube example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randOnUnitSphere() -> vec3f { @@ -190,10 +190,10 @@ describe('ripple-cube example', () => { return color; } - var seed: vec2f; + var seed_1: vec2f; fn seed3(value: vec3f) { - seed = (value.xy + vec2f(value.z)); + seed_1 = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -201,11 +201,11 @@ describe('ripple-cube example', () => { } fn sample_1() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { @@ -305,10 +305,10 @@ describe('ripple-cube example', () => { @group(0) @binding(1) var timeUniform: f32; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index 0f1b46e97e..a4b16a680a 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -19,10 +19,10 @@ describe('slime mold 3d example', () => { expect(shaderCodes).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -30,11 +30,11 @@ describe('slime mold 3d example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randUniformExclusive() -> f32 { @@ -126,10 +126,10 @@ describe('slime mold 3d example', () => { textureStore(newState, _arg_0.gid.xyz, vec4f(newValue, 0f, 0f, 1f)); } - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -199,11 +199,11 @@ describe('slime mold 3d example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randOnUnitSphere() -> vec3f { @@ -318,10 +318,10 @@ describe('slime mold 3d example', () => { return fullScreenTriangle_Output(vec4f(pos[in.vertexIndex], 0, 1), uv[in.vertexIndex]); } - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -355,11 +355,11 @@ describe('slime mold 3d example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index 9cdf638876..2aadb83f25 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -19,10 +19,10 @@ describe('slime mold example', () => { expect(shaderCodes).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -30,11 +30,11 @@ describe('slime mold example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randInUnitCircle() -> vec2f { @@ -109,10 +109,10 @@ describe('slime mold example', () => { textureStore(newState, _arg_0.gid.xy, vec4f(newColor, 1f)); } - var seed: vec2f; + var seed_2: vec2f; fn seed_1(value: f32) { - seed = vec2f(value, 0f); + seed_2 = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -129,11 +129,11 @@ describe('slime mold example', () => { @group(0) @binding(0) var agentsData: array; fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); + seed_2.x = fract((cos(a) * 136.8168f)); + seed_2.y = fract((cos(b) * 534.7645f)); + return seed_2.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts b/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts index c5251d3c79..ed7b70f221 100644 --- a/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts +++ b/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts @@ -21,10 +21,10 @@ describe('smoky triangle', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed: vec2f; + var seed_1: vec2f; fn seed3(value: vec3f) { - seed = (value.xy + vec2f(value.z)); + seed_1 = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -32,11 +32,11 @@ describe('smoky triangle', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randOnUnitSphere() -> vec3f { diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index 40a3b2632d..5d72e81ea4 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -208,7 +208,7 @@ describe('tgsl parsing test example', () => { return s; } - @group(0) @binding(0) var result: i32; + @group(0) @binding(0) var result_1: i32; @compute @workgroup_size(1) fn computeRunTests() { var s = true; @@ -218,10 +218,10 @@ describe('tgsl parsing test example', () => { s = (s && arrayAndStructConstructorsTest()); s = (s && pointersTest()); if (s) { - result = 1i; + result_1 = 1i; } else { - result = 0i; + result_1 = 0i; } }" `); diff --git a/packages/typegpu/tests/examples/individual/uniformity.test.ts b/packages/typegpu/tests/examples/individual/uniformity.test.ts index 33b84bd3a4..a135dfd7ff 100644 --- a/packages/typegpu/tests/examples/individual/uniformity.test.ts +++ b/packages/typegpu/tests/examples/individual/uniformity.test.ts @@ -40,10 +40,10 @@ describe('uniformity test example', () => { @group(0) @binding(1) var gridSizeUniform: f32; - var seed: vec2f; + var seed_1: vec2f; fn seed2(value: vec2f) { - seed = value; + seed_1 = value; } fn randSeed2(seed: vec2f) { @@ -51,11 +51,11 @@ describe('uniformity test example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randFloat01() -> f32 { @@ -73,14 +73,14 @@ describe('uniformity test example', () => { return vec4f(vec3f(randFloat01()), 1f); } - var seed_1: u32; + var seed_2: u32; fn seed2_1(value: vec2f) { - seed_1 = u32(((value.x * 32768f) + (value.y * 1024f))); + seed_2 = u32(((value.x * 32768f) + (value.y * 1024f))); } - fn randSeed2_1(seed_1: vec2f) { - seed2_1(seed_1); + fn randSeed2_1(seed: vec2f) { + seed2_1(seed); } fn u32To01Float(value: u32) -> f32 { @@ -91,8 +91,8 @@ describe('uniformity test example', () => { } fn sample_1() -> f32 { - seed_1 = ((seed_1 * 1664525u) + 1013904223u); - return u32To01Float(seed_1); + seed_2 = ((seed_2 * 1664525u) + 1013904223u); + return u32To01Float(seed_2); } fn randFloat01_1() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/vaporrave.test.ts b/packages/typegpu/tests/examples/individual/vaporrave.test.ts index c39d076d81..1ddc9d276b 100644 --- a/packages/typegpu/tests/examples/individual/vaporrave.test.ts +++ b/packages/typegpu/tests/examples/individual/vaporrave.test.ts @@ -21,10 +21,10 @@ describe('vaporrave example', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed: vec2f; + var seed_1: vec2f; fn seed3(value: vec3f) { - seed = (value.xy + vec2f(value.z)); + seed_1 = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -32,11 +32,11 @@ describe('vaporrave example', () => { } fn sample() -> f32 { - let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); - seed.x = fract((cos(a) * 136.8168f)); - seed.y = fract((cos(b) * 534.7645f)); - return seed.y; + let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); + seed_1.x = fract((cos(a) * 136.8168f)); + seed_1.y = fract((cos(b) * 534.7645f)); + return seed_1.y; } fn randOnUnitSphere() -> vec3f { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index bbcba1dfe5..0b65bafd66 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -617,7 +617,7 @@ describe('wgslGenerator', () => { }); it('creates correct code for "for ... of ..." statements using lazy and comptime iterables', () => { - const comptimeVec = tgpu['~unstable'].comptime(() => d.vec2f(1, 2)); + const comptimeVec = tgpu.comptime(() => d.vec2f(1, 2)); const main = () => { 'use gpu'; @@ -797,6 +797,23 @@ describe('wgslGenerator', () => { `); }); + it('throws error when "for ... of ..." loop variable name is not correct in wgsl', () => { + const main = () => { + 'use gpu'; + const arr = [1, 2, 3]; + for (const __foo of arr) { + continue; + } + }; + + expect(() => tgpu.resolve([main])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main(): Invalid identifier '__foo'. Choose an identifier without whitespaces or leading underscores.] + `); + }); + it('handles "for ... of ..." internal index variable when "i" is used by user', () => { const f1 = () => { 'use gpu'; From ad27b4355a73fa5d19fd32a52db7668f9a6fccac Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 18 Feb 2026 13:52:19 +0100 Subject: [PATCH 35/67] align with new apis --- .../algorithms/mnist-inference/index.ts | 4 +- .../typegpu/src/core/unroll/tgpuUnroll.ts | 38 ++++++++----------- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 6 --- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts index 49dfd54ac0..68d105fd72 100644 --- a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts @@ -19,7 +19,7 @@ const hasTimestampQuery = root.enabledFeatures.has('timestamp-query'); const hasSubgroups = root.enabledFeatures.has('subgroups'); let useSubgroups = hasSubgroups; -const canvasData = Array.from({ length: (SIZE ** 2) }, () => 0); +const canvasData = Array.from({ length: SIZE ** 2 }, () => 0); // Shaders @@ -313,7 +313,7 @@ function centerImage(data: number[]) { const offsetX = Math.round(SIZE / 2 - x); const offsetY = Math.round(SIZE / 2 - y); - const newData = Array.from({ length: (SIZE * SIZE) }, () => 0); + const newData = Array.from({ length: SIZE * SIZE }, () => 0); for (let i = 0; i < SIZE; i++) { for (let j = 0; j < SIZE; j++) { const index = i * SIZE + j; diff --git a/packages/typegpu/src/core/unroll/tgpuUnroll.ts b/packages/typegpu/src/core/unroll/tgpuUnroll.ts index 15871762a1..853b560865 100644 --- a/packages/typegpu/src/core/unroll/tgpuUnroll.ts +++ b/packages/typegpu/src/core/unroll/tgpuUnroll.ts @@ -1,8 +1,11 @@ import { stitch } from '../resolve/stitch.ts'; -import { $internal, $resolve } from '../../../src/shared/symbols.ts'; -import { inCodegenMode } from '../../../src/execMode.ts'; +import { + $gpuCallable, + $internal, + $resolve, +} from '../../../src/shared/symbols.ts'; import { setName } from '../../../src/shared/meta.ts'; -import type { DualFn } from '../../../src/data/dualFn.ts'; +import type { DualFn } from '../../../src/types.ts'; import type { AnyData } from '../../../src/data/dataTypes.ts'; import { @@ -35,29 +38,18 @@ export class UnrolledIterable implements SelfResolvable { * Marks an iterable to be unrolled by the wgslGenerator. */ export const unroll = (() => { - const gpuImpl = (value: Snippet) => { - return snip(new UnrolledIterable(value), value.dataType, value.origin); - }; - - const jsImpl = >(value: T) => value; - - const impl = >(value: T) => { - if (inCodegenMode()) { - return gpuImpl(value as unknown as Snippet); - } - return jsImpl(value); - }; + const impl = (>(value: T) => value) as unknown as + & DualFn<(>(value: T) => T)> + & { [$internal]: true }; setName(impl, 'unroll'); impl.toString = () => 'unroll'; - Object.defineProperty(impl, $internal, { - value: { - jsImpl, - gpuImpl, + impl[$internal] = true; + impl[$gpuCallable] = { + call(_ctx, [value]) { + return snip(new UnrolledIterable(value), value.dataType, value.origin); }, - }); + }; - return impl as unknown as DualFn< - >(value: T) => T - >; + return impl; })(); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 66d85518eb..cfaa2a34b2 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1062,12 +1062,6 @@ describe('wgslGenerator', () => { expect(d.isWgslArray(res.dataType)).toBe(true); expect((res.dataType as unknown as WgslArray).elementCount).toBe(2); expect((res.dataType as unknown as WgslArray).elementType).toBe(TestStruct); - - // intermediate representation - expect(res.value instanceof ArrayExpression).toBe(true); - expect((res.value as unknown as ArrayExpression).type).toBe(res.dataType); - expect((res.value as unknown as ArrayExpression).elementType) - .toBe((res.dataType as unknown as WgslArray).elementType); }); it('generates correct code for array expressions with lazy elements', () => { From 5f3448376794e46a10936c2843d416e5be61d0fa Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 18 Feb 2026 13:55:37 +0100 Subject: [PATCH 36/67] use vector components field instead of regex match --- packages/typegpu/src/tgsl/generationHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 640ef6cc0c..1118ebf3fa 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -276,7 +276,7 @@ export const forOfHelpers = { if (wgsl.isVec(iterableDataType)) { return snip( - `${Number(iterableDataType.type.match(/\d/))}`, + `${iterableDataType.componentCount}`, u32, 'constant', ); From a89d840ec750e0c70c9717c0ba7ff64b7e9d9bfa Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 19 Feb 2026 12:54:07 +0100 Subject: [PATCH 37/67] we don't need allTimeUsedNames --- packages/typegpu/src/nameRegistry.ts | 23 +-- .../tests/examples/individual/3d-fish.test.ts | 14 +- .../examples/individual/caustics.test.ts | 14 +- .../tests/examples/individual/clouds.test.ts | 14 +- .../individual/fluid-double-buffering.test.ts | 28 +-- .../examples/individual/game-of-life.test.ts | 74 +------ .../examples/individual/jelly-slider.test.ts | 14 +- .../examples/individual/jelly-switch.test.ts | 4 +- .../individual/jump-flood-voronoi.test.ts | 14 +- .../examples/individual/perlin-noise.test.ts | 14 +- .../examples/individual/probability.test.ts | 182 +++++++++--------- .../examples/individual/ripple-cube.test.ts | 46 ++--- .../examples/individual/slime-mold-3d.test.ts | 42 ++-- .../examples/individual/slime-mold.test.ts | 28 +-- .../individual/smoky-triangle.test.ts | 14 +- .../individual/tgsl-parsing-test.test.ts | 6 +- .../examples/individual/uniformity.test.ts | 26 +-- .../examples/individual/vaporrave.test.ts | 14 +- 18 files changed, 246 insertions(+), 325 deletions(-) diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameRegistry.ts index 3c6b668900..db5f121752 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameRegistry.ts @@ -445,10 +445,9 @@ type BlockScopeLayer = { type ScopeLayer = FunctionScopeLayer | BlockScopeLayer; abstract class NameRegistryImpl implements NameRegistry { - abstract getUniqueVariant(base: string, global: boolean): string; + abstract getUniqueVariant(base: string): string; readonly #usedNames: Set; - readonly #usedAllTimeBlockScopeNames: Set; readonly #scopeStack: ScopeLayer[]; constructor() { @@ -456,7 +455,6 @@ abstract class NameRegistryImpl implements NameRegistry { ...bannedTokens, ...builtins, ]); - this.#usedAllTimeBlockScopeNames = new Set(); this.#scopeStack = []; } @@ -467,12 +465,11 @@ abstract class NameRegistryImpl implements NameRegistry { makeUnique(primer: string | undefined, global: boolean): string { const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer, global); + const name = this.getUniqueVariant(sanitizedPrimer); if (global) { this.#usedNames.add(name); } else { - this.#usedAllTimeBlockScopeNames.add(name); this.#usedBlockScopeNames?.add(name); } @@ -493,18 +490,14 @@ abstract class NameRegistryImpl implements NameRegistry { isValidIdentifier(primer) && !this.#usedNames.has(primer) && !this.#isUsedInBlocksBefore(primer) ) { - this.#usedAllTimeBlockScopeNames.add(primer); this.#usedBlockScopeNames?.add(primer); return primer; } return this.makeUnique(primer, false); } - isUsed(name: string, global: boolean): boolean { - const varyingCond = global - ? this.#usedAllTimeBlockScopeNames.has(name) - : this.#isUsedInBlocksBefore(name); - return this.#usedNames.has(name) || varyingCond; + isUsed(name: string): boolean { + return this.#usedNames.has(name) || this.#isUsedInBlocksBefore(name); } pushFunctionScope(): void { @@ -545,9 +538,9 @@ abstract class NameRegistryImpl implements NameRegistry { export class RandomNameRegistry extends NameRegistryImpl { #lastUniqueId = 0; - getUniqueVariant(base: string, global: boolean): string { + getUniqueVariant(base: string): string { let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name, global)) { + while (this.isUsed(name)) { name = `${base}_${this.#lastUniqueId++}`; } return name; @@ -555,10 +548,10 @@ export class RandomNameRegistry extends NameRegistryImpl { } export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string, global: boolean): string { + getUniqueVariant(base: string): string { let index = 0; let name = base; - while (this.isUsed(name, global)) { + while (this.isUsed(name)) { index++; name = `${base}_${index}`; } diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 107a074ec6..0f5f2cb656 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -31,10 +31,10 @@ describe('3d fish example', () => { @group(0) @binding(1) var seedUniform: f32; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -42,11 +42,11 @@ describe('3d fish example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/caustics.test.ts b/packages/typegpu/tests/examples/individual/caustics.test.ts index 33cd236b8e..cac8c27b83 100644 --- a/packages/typegpu/tests/examples/individual/caustics.test.ts +++ b/packages/typegpu/tests/examples/individual/caustics.test.ts @@ -43,10 +43,10 @@ describe('caustics example', () => { @group(0) @binding(1) var time: f32; - var seed_1: vec2f; + var seed: vec2f; fn seed3(value: vec3f) { - seed_1 = (value.xy + vec2f(value.z)); + seed = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -54,11 +54,11 @@ describe('caustics example', () => { } fn sample_1() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { diff --git a/packages/typegpu/tests/examples/individual/clouds.test.ts b/packages/typegpu/tests/examples/individual/clouds.test.ts index e64f32f7de..df70cf0435 100644 --- a/packages/typegpu/tests/examples/individual/clouds.test.ts +++ b/packages/typegpu/tests/examples/individual/clouds.test.ts @@ -43,10 +43,10 @@ describe('clouds example', () => { @group(1) @binding(0) var params: CloudsParams; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -56,11 +56,11 @@ describe('clouds example', () => { @group(0) @binding(0) var resolutionUniform: vec2f; fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index db9423ce72..1bc06bccb2 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -102,10 +102,10 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var time: f32; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -160,11 +160,11 @@ describe('fluid double buffering example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { @@ -282,10 +282,10 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var time: f32; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -340,11 +340,11 @@ describe('fluid double buffering example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/game-of-life.test.ts b/packages/typegpu/tests/examples/individual/game-of-life.test.ts index fd3be0ded4..2703291472 100644 --- a/packages/typegpu/tests/examples/individual/game-of-life.test.ts +++ b/packages/typegpu/tests/examples/individual/game-of-life.test.ts @@ -16,78 +16,6 @@ describe('game of life example', () => { expectedCalls: 2, }, device); - expect(shaderCodes).toMatchInlineSnapshot(` - "@group(0) @binding(0) var sizeUniform: vec3u; - - fn getIndex(x: u32, y: u32) -> u32 { - return (((y % 64u) * 64u) + (x % 64u)); - } - - @group(1) @binding(0) var current: array; - - fn getCell(x: u32, y: u32) -> u32 { - return current[getIndex(x, y)]; - } - - fn countNeighbors(x: u32, y: u32) -> u32 { - return (((((((getCell((x - 1u), (y - 1u)) + getCell(x, (y - 1u))) + getCell((x + 1u), (y - 1u))) + getCell((x - 1u), y)) + getCell((x + 1u), y)) + getCell((x - 1u), (y + 1u))) + getCell(x, (y + 1u))) + getCell((x + 1u), (y + 1u))); - } - - @group(1) @binding(1) var next: array; - - fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - let n = countNeighbors(x, y); - next[getIndex(x, y)] = u32(select((n == 3u), ((n == 2u) || (n == 3u)), (getCell(x, y) == 1u))); - } - - struct mainCompute_Input { - @builtin(global_invocation_id) id: vec3u, - } - - @compute @workgroup_size(16, 16, 1) fn mainCompute(in: mainCompute_Input) { - if (any(in.id >= sizeUniform)) { - return; - } - wrappedCallback(in.id.x, in.id.y, in.id.z); - } - - struct vertexFn_Output { - @builtin(position) pos: vec4f, - @location(0) @interpolate(flat) cell: u32, - @location(1) uv: vec2f, - } - - struct vertexFn_Input { - @builtin(instance_index) iid: u32, - @location(0) cell: u32, - @location(1) pos: vec2u, - } - - @vertex fn vertexFn(_arg_0: vertexFn_Input) -> vertexFn_Output { - const w = 64u; - const h = 64u; - let col = (_arg_0.iid % w); - let row = u32((f32(_arg_0.iid) / f32(w))); - let gx = (col + _arg_0.pos.x); - let gy = (row + _arg_0.pos.y); - let maxWH = f32(max(w, h)); - let x = (((f32(gx) * 2f) - f32(w)) / maxWH); - let y = (((f32(gy) * 2f) - f32(h)) / maxWH); - return vertexFn_Output(vec4f(x, y, 0f, 1f), _arg_0.cell, vec2f(((x + 1f) * 0.5f), ((y + 1f) * 0.5f))); - } - - struct fragmentFn_Input { - @location(0) @interpolate(flat) cell: u32, - @location(1) uv: vec2f, - } - - @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { - if ((_arg_0.cell == 0u)) { - discard;; - } - var u = (_arg_0.uv / 1.5f); - return vec4f(u.x, u.y, (1f - u.x), 0.8f); - }" - `); + expect(shaderCodes).toMatchInlineSnapshot(`""`); }); }); diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index 8b600d72c2..4d8f6242eb 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -69,10 +69,10 @@ describe('jelly-slider example', () => { @group(0) @binding(0) var randomUniform: vec2f; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -166,11 +166,11 @@ describe('jelly-slider example', () => { @group(0) @binding(6) var bezierTexture: texture_2d; fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts index 62a934e3de..061c3e8411 100644 --- a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts @@ -43,10 +43,10 @@ describe('jelly switch example', () => { @group(0) @binding(0) var randomUniform: vec2f; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { diff --git a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts index 9987c5e464..fbb318b98c 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts @@ -25,10 +25,10 @@ describe('jump flood (voronoi) example', () => { @group(0) @binding(1) var timeUniform: f32; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -36,11 +36,11 @@ describe('jump flood (voronoi) example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts index daa5f527af..cc90153c0c 100644 --- a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts +++ b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts @@ -23,10 +23,10 @@ describe('perlin noise example', () => { @group(1) @binding(1) var memory: array; - var seed_1: vec2f; + var seed: vec2f; fn seed3(value: vec3f) { - seed_1 = (value.xy + vec2f(value.z)); + seed = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -34,11 +34,11 @@ describe('perlin noise example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { diff --git a/packages/typegpu/tests/examples/individual/probability.test.ts b/packages/typegpu/tests/examples/individual/probability.test.ts index ddb31bd409..13a01bb952 100644 --- a/packages/typegpu/tests/examples/individual/probability.test.ts +++ b/packages/typegpu/tests/examples/individual/probability.test.ts @@ -22,10 +22,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -33,11 +33,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randUniformExclusive() -> f32 { @@ -74,10 +74,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -85,11 +85,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { @@ -118,10 +118,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -129,11 +129,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randInUnitCircle() -> vec2f { @@ -163,10 +163,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -174,11 +174,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitCircle() -> vec2f { @@ -207,10 +207,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -218,11 +218,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randInUnitCube() -> vec3f { @@ -246,10 +246,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -257,11 +257,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitCube() -> vec3f { @@ -291,10 +291,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -302,11 +302,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randUniformExclusive() -> f32 { @@ -353,10 +353,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -364,11 +364,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { @@ -407,10 +407,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -418,11 +418,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randBernoulli(p: f32) -> f32 { @@ -451,10 +451,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -462,11 +462,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { @@ -494,10 +494,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -505,11 +505,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randUniformExclusive() -> f32 { @@ -542,10 +542,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -553,11 +553,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randUniformExclusive() -> f32 { @@ -591,10 +591,10 @@ describe('probability distribution plot example', () => { @group(0) @binding(0) var seedBuffer: array; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -602,11 +602,11 @@ describe('probability distribution plot example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randUniformExclusive() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts index 4cd0057425..dbe13f72fd 100644 --- a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts +++ b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts @@ -23,10 +23,10 @@ describe('ripple-cube example', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed_1: vec2f; + var seed: vec2f; fn seed3(value: vec3f) { - seed_1 = (value.xy + vec2f(value.z)); + seed = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -34,11 +34,11 @@ describe('ripple-cube example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { @@ -75,10 +75,10 @@ describe('ripple-cube example', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed_1: vec2f; + var seed: vec2f; fn seed3(value: vec3f) { - seed_1 = (value.xy + vec2f(value.z)); + seed = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -86,11 +86,11 @@ describe('ripple-cube example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { @@ -190,10 +190,10 @@ describe('ripple-cube example', () => { return color; } - var seed_1: vec2f; + var seed: vec2f; fn seed3(value: vec3f) { - seed_1 = (value.xy + vec2f(value.z)); + seed = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -201,11 +201,11 @@ describe('ripple-cube example', () => { } fn sample_1() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { @@ -305,10 +305,10 @@ describe('ripple-cube example', () => { @group(0) @binding(1) var timeUniform: f32; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index a4b16a680a..0f1b46e97e 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -19,10 +19,10 @@ describe('slime mold 3d example', () => { expect(shaderCodes).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -30,11 +30,11 @@ describe('slime mold 3d example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randUniformExclusive() -> f32 { @@ -126,10 +126,10 @@ describe('slime mold 3d example', () => { textureStore(newState, _arg_0.gid.xyz, vec4f(newValue, 0f, 0f, 1f)); } - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -199,11 +199,11 @@ describe('slime mold 3d example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { @@ -318,10 +318,10 @@ describe('slime mold 3d example', () => { return fullScreenTriangle_Output(vec4f(pos[in.vertexIndex], 0, 1), uv[in.vertexIndex]); } - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -355,11 +355,11 @@ describe('slime mold 3d example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index 2aadb83f25..9cdf638876 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -19,10 +19,10 @@ describe('slime mold example', () => { expect(shaderCodes).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -30,11 +30,11 @@ describe('slime mold example', () => { } fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randInUnitCircle() -> vec2f { @@ -109,10 +109,10 @@ describe('slime mold example', () => { textureStore(newState, _arg_0.gid.xy, vec4f(newColor, 1f)); } - var seed_2: vec2f; + var seed: vec2f; fn seed_1(value: f32) { - seed_2 = vec2f(value, 0f); + seed = vec2f(value, 0f); } fn randSeed(seed: f32) { @@ -129,11 +129,11 @@ describe('slime mold example', () => { @group(0) @binding(0) var agentsData: array; fn sample() -> f32 { - let a = dot(seed_2, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_2, vec2f(54.47856521606445, 345.8415222167969)); - seed_2.x = fract((cos(a) * 136.8168f)); - seed_2.y = fract((cos(b) * 534.7645f)); - return seed_2.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts b/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts index ed7b70f221..c5251d3c79 100644 --- a/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts +++ b/packages/typegpu/tests/examples/individual/smoky-triangle.test.ts @@ -21,10 +21,10 @@ describe('smoky triangle', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed_1: vec2f; + var seed: vec2f; fn seed3(value: vec3f) { - seed_1 = (value.xy + vec2f(value.z)); + seed = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -32,11 +32,11 @@ describe('smoky triangle', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index 5d72e81ea4..40a3b2632d 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -208,7 +208,7 @@ describe('tgsl parsing test example', () => { return s; } - @group(0) @binding(0) var result_1: i32; + @group(0) @binding(0) var result: i32; @compute @workgroup_size(1) fn computeRunTests() { var s = true; @@ -218,10 +218,10 @@ describe('tgsl parsing test example', () => { s = (s && arrayAndStructConstructorsTest()); s = (s && pointersTest()); if (s) { - result_1 = 1i; + result = 1i; } else { - result_1 = 0i; + result = 0i; } }" `); diff --git a/packages/typegpu/tests/examples/individual/uniformity.test.ts b/packages/typegpu/tests/examples/individual/uniformity.test.ts index a135dfd7ff..33b84bd3a4 100644 --- a/packages/typegpu/tests/examples/individual/uniformity.test.ts +++ b/packages/typegpu/tests/examples/individual/uniformity.test.ts @@ -40,10 +40,10 @@ describe('uniformity test example', () => { @group(0) @binding(1) var gridSizeUniform: f32; - var seed_1: vec2f; + var seed: vec2f; fn seed2(value: vec2f) { - seed_1 = value; + seed = value; } fn randSeed2(seed: vec2f) { @@ -51,11 +51,11 @@ describe('uniformity test example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randFloat01() -> f32 { @@ -73,14 +73,14 @@ describe('uniformity test example', () => { return vec4f(vec3f(randFloat01()), 1f); } - var seed_2: u32; + var seed_1: u32; fn seed2_1(value: vec2f) { - seed_2 = u32(((value.x * 32768f) + (value.y * 1024f))); + seed_1 = u32(((value.x * 32768f) + (value.y * 1024f))); } - fn randSeed2_1(seed: vec2f) { - seed2_1(seed); + fn randSeed2_1(seed_1: vec2f) { + seed2_1(seed_1); } fn u32To01Float(value: u32) -> f32 { @@ -91,8 +91,8 @@ describe('uniformity test example', () => { } fn sample_1() -> f32 { - seed_2 = ((seed_2 * 1664525u) + 1013904223u); - return u32To01Float(seed_2); + seed_1 = ((seed_1 * 1664525u) + 1013904223u); + return u32To01Float(seed_1); } fn randFloat01_1() -> f32 { diff --git a/packages/typegpu/tests/examples/individual/vaporrave.test.ts b/packages/typegpu/tests/examples/individual/vaporrave.test.ts index 1ddc9d276b..c39d076d81 100644 --- a/packages/typegpu/tests/examples/individual/vaporrave.test.ts +++ b/packages/typegpu/tests/examples/individual/vaporrave.test.ts @@ -21,10 +21,10 @@ describe('vaporrave example', () => { @group(0) @binding(1) var memoryBuffer: array; - var seed_1: vec2f; + var seed: vec2f; fn seed3(value: vec3f) { - seed_1 = (value.xy + vec2f(value.z)); + seed = (value.xy + vec2f(value.z)); } fn randSeed3(seed: vec3f) { @@ -32,11 +32,11 @@ describe('vaporrave example', () => { } fn sample() -> f32 { - let a = dot(seed_1, vec2f(23.140779495239258, 232.6168975830078)); - let b = dot(seed_1, vec2f(54.47856521606445, 345.8415222167969)); - seed_1.x = fract((cos(a) * 136.8168f)); - seed_1.y = fract((cos(b) * 534.7645f)); - return seed_1.y; + let a = dot(seed, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed, vec2f(54.47856521606445, 345.8415222167969)); + seed.x = fract((cos(a) * 136.8168f)); + seed.y = fract((cos(b) * 534.7645f)); + return seed.y; } fn randOnUnitSphere() -> vec3f { From da9c104b5c5cdae1b6ab0c3791bac874ff865fed Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 19 Feb 2026 13:03:47 +0100 Subject: [PATCH 38/67] lost game of life --- .../examples/individual/game-of-life.test.ts | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/packages/typegpu/tests/examples/individual/game-of-life.test.ts b/packages/typegpu/tests/examples/individual/game-of-life.test.ts index 2703291472..fd3be0ded4 100644 --- a/packages/typegpu/tests/examples/individual/game-of-life.test.ts +++ b/packages/typegpu/tests/examples/individual/game-of-life.test.ts @@ -16,6 +16,78 @@ describe('game of life example', () => { expectedCalls: 2, }, device); - expect(shaderCodes).toMatchInlineSnapshot(`""`); + expect(shaderCodes).toMatchInlineSnapshot(` + "@group(0) @binding(0) var sizeUniform: vec3u; + + fn getIndex(x: u32, y: u32) -> u32 { + return (((y % 64u) * 64u) + (x % 64u)); + } + + @group(1) @binding(0) var current: array; + + fn getCell(x: u32, y: u32) -> u32 { + return current[getIndex(x, y)]; + } + + fn countNeighbors(x: u32, y: u32) -> u32 { + return (((((((getCell((x - 1u), (y - 1u)) + getCell(x, (y - 1u))) + getCell((x + 1u), (y - 1u))) + getCell((x - 1u), y)) + getCell((x + 1u), y)) + getCell((x - 1u), (y + 1u))) + getCell(x, (y + 1u))) + getCell((x + 1u), (y + 1u))); + } + + @group(1) @binding(1) var next: array; + + fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { + let n = countNeighbors(x, y); + next[getIndex(x, y)] = u32(select((n == 3u), ((n == 2u) || (n == 3u)), (getCell(x, y) == 1u))); + } + + struct mainCompute_Input { + @builtin(global_invocation_id) id: vec3u, + } + + @compute @workgroup_size(16, 16, 1) fn mainCompute(in: mainCompute_Input) { + if (any(in.id >= sizeUniform)) { + return; + } + wrappedCallback(in.id.x, in.id.y, in.id.z); + } + + struct vertexFn_Output { + @builtin(position) pos: vec4f, + @location(0) @interpolate(flat) cell: u32, + @location(1) uv: vec2f, + } + + struct vertexFn_Input { + @builtin(instance_index) iid: u32, + @location(0) cell: u32, + @location(1) pos: vec2u, + } + + @vertex fn vertexFn(_arg_0: vertexFn_Input) -> vertexFn_Output { + const w = 64u; + const h = 64u; + let col = (_arg_0.iid % w); + let row = u32((f32(_arg_0.iid) / f32(w))); + let gx = (col + _arg_0.pos.x); + let gy = (row + _arg_0.pos.y); + let maxWH = f32(max(w, h)); + let x = (((f32(gx) * 2f) - f32(w)) / maxWH); + let y = (((f32(gy) * 2f) - f32(h)) / maxWH); + return vertexFn_Output(vec4f(x, y, 0f, 1f), _arg_0.cell, vec2f(((x + 1f) * 0.5f), ((y + 1f) * 0.5f))); + } + + struct fragmentFn_Input { + @location(0) @interpolate(flat) cell: u32, + @location(1) uv: vec2f, + } + + @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { + if ((_arg_0.cell == 0u)) { + discard;; + } + var u = (_arg_0.uv / 1.5f); + return vec4f(u.x, u.y, (1f - u.x), 0.8f); + }" + `); }); }); From c90cdeb9a025f6c892c1c08662d07064a05d4ef8 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 19 Feb 2026 17:19:38 +0100 Subject: [PATCH 39/67] block externals with tests + more elegant for of --- packages/typegpu/src/resolutionCtx.ts | 36 +++++++++- .../typegpu/src/tgsl/generationHelpers.ts | 2 + packages/typegpu/src/tgsl/shaderGenerator.ts | 3 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 28 +++++--- packages/typegpu/src/types.ts | 3 + .../typegpu/tests/tgsl/wgslGenerator.test.ts | 66 +++++++++++++++++++ 6 files changed, 126 insertions(+), 12 deletions(-) diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 60a6372e85..9b531128ec 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -163,6 +163,7 @@ class ItemStateStackImpl implements ItemStateStack { this._stack.push({ type: 'blockScope', declarations: new Map(), + externals: new Map(), }); } @@ -232,7 +233,8 @@ class ItemStateStackImpl implements ItemStateStack { } if (layer?.type === 'blockScope') { - const snippet = layer.declarations.get(id); + // the order matters + const snippet = layer.declarations.get(id) ?? layer.externals.get(id); if (snippet !== undefined) { return snippet; } @@ -260,6 +262,30 @@ class ItemStateStackImpl implements ItemStateStack { throw new Error('No block scope found to define a variable in.'); } + + pushBlockExternals(externals: Record) { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + if (layer?.type === 'blockScope') { + Object.entries(externals).forEach(([id, snippet]) => { + layer.externals.set(id, snippet); + }); + return; + } + } + throw new Error('No block scope found to push externals in.'); + } + + popBlockExternals() { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + if (layer?.type === 'blockScope') { + layer.externals.clear(); + return; + } + } + throw new Error('No block scope found to pop overrides from.'); + } } const INDENT = [ @@ -427,6 +453,14 @@ export class ResolutionCtxImpl implements ResolutionCtx { this._itemStateStack.pop('blockScope'); } + pushBlockExternals(externals: Record) { + this._itemStateStack.pushBlockExternals(externals); + } + + popBlockExternals() { + this._itemStateStack.popBlockExternals(); + } + generateLog(op: string, args: Snippet[]): Snippet { return this.#logGenerator.generateLog(this, op, args); } diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index d655b02f35..135d0a7c9d 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -92,6 +92,8 @@ export type GenerationCtx = ResolutionCtx & { generateLog(op: string, args: Snippet[]): Snippet; getById(id: string): Snippet | null; defineVariable(id: string, snippet: Snippet): void; + pushBlockExternals(externals: Record): void; + popBlockExternals(): void; /** * Types that are used in `return` statements are diff --git a/packages/typegpu/src/tgsl/shaderGenerator.ts b/packages/typegpu/src/tgsl/shaderGenerator.ts index 5c467459d2..d80f890606 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator.ts @@ -2,10 +2,11 @@ import type { Block, Expression, Statement } from 'tinyest'; import type { Snippet } from '../data/snippet.ts'; import type { GenerationCtx } from './generationHelpers.ts'; import type { BaseData } from '../data/wgslTypes.ts'; +import type { ExternalMap } from '../../src/core/resolve/externals.ts'; export interface ShaderGenerator { initGenerator(ctx: GenerationCtx): void; - block(body: Block): string; + block(body: Block, externalMap?: ExternalMap): string; identifier(id: string): Snippet; typedExpression(expression: Expression, expectedType: BaseData): Snippet; expression(expression: Expression): Snippet; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 409ba2d7c3..f4c7f72a9d 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -36,6 +36,7 @@ import { } from './conversion.ts'; import { ArrayExpression, + coerceToSnippet, concretize, type GenerationCtx, numericLiteralToSnippet, @@ -51,6 +52,7 @@ import type { AnyFn } from '../core/function/fnTypes.ts'; import { arrayLength } from '../std/array.ts'; import { AutoStruct } from '../data/autoStruct.ts'; import { mathToStd } from './math.ts'; +import { ExternalMap } from '../../src/core/resolve/externals.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -200,8 +202,19 @@ class WgslGenerator implements ShaderGenerator { public block( [_, statements]: tinyest.Block, + externalMap?: ExternalMap, ): string { this.ctx.pushBlockScope(); + + if (externalMap) { + const externals = Object.fromEntries( + Object.entries(externalMap).map(( + [id, value], + ) => [id, coerceToSnippet(value)]), + ); + this.ctx.pushBlockExternals(externals); + } + try { this.ctx.indent(); const body = statements.map((statement) => this.statement(statement)) @@ -1186,6 +1199,7 @@ ${this.ctx.pre}else ${alternate}`; iterableSnippet, snip(index, u32, 'runtime'), ); + if (!elementSnippet) { throw new WgslTypeError( '`for ... of ...` loops only support array or vector iterables', @@ -1231,7 +1245,6 @@ ${this.ctx.pre}else ${alternate}`; // If it's ephemeral, it's a value that cannot change. If it's a reference, we take // an implicit pointer to it let loopVarKind = 'let'; - const loopVarName = this.ctx.makeNameValid(loopVar[1]); if (!isEphemeralSnippet(elementSnippet)) { if (elementSnippet.origin === 'constant-tgpu-const-ref') { @@ -1256,19 +1269,12 @@ ${this.ctx.pre}else ${alternate}`; } } - const loopVarSnippet = snip( - loopVarName, - elementType, - elementSnippet.origin, - ); - this.ctx.defineVariable(loopVarName, loopVarSnippet); - const forStr = stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ tryConvertSnippet(this.ctx, elementCountSnippet, u32, false) }; ${index}++) {`; - this.ctx.indent(); + const loopVarName = this.ctx.makeNameValid(loopVar[1]); const loopVarDeclStr = stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ tryConvertSnippet( @@ -1280,7 +1286,9 @@ ${this.ctx.pre}else ${alternate}`; };`; const bodyStr = `${this.ctx.pre}${ - this.block(blockifySingleStatement(body)) + this.block(blockifySingleStatement(body), { + [loopVar[1]]: snip(loopVarName, elementType, elementSnippet.origin), + }) }`; this.ctx.dedent(); diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 6174abde71..35d4b36e94 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -124,6 +124,7 @@ export type SlotBindingLayer = { export type BlockScopeLayer = { type: 'blockScope'; declarations: Map; + externals: Map; }; export type StackLayer = @@ -151,6 +152,8 @@ export interface ItemStateStack { externalMap: Record, ): FunctionScopeLayer; pushBlockScope(): void; + pushBlockExternals(externals: Record): void; + popBlockExternals(): void; pop(type: T): Extract; pop(): StackLayer | undefined; diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index bbcba1dfe5..f778fcb810 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1602,4 +1602,70 @@ describe('wgslGenerator', () => { }" `); }); + + it('block externals do not override identifiers', () => { + const f = () => { + 'use gpu'; + const y = 100; + const x = y; + return x; + }; + + const parsed = getMetaData(f)?.ast?.body as tinyest.Block; + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.u32, + {}, + ); + + const res = wgslGenerator.block( + parsed, + { x: 42 }, + ); + + expect(res).toMatchInlineSnapshot(` + "{ + const y = 100; + const x = y; + return u32(x); + }" + `); + }); + }); + + it('block externals are injected correctly', () => { + const f = () => { + 'use gpu'; + for (const x of []) { + const y = x; + } + }; + + const parsed = getMetaData(f)?.ast?.body as tinyest.Block; + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.Void, + {}, + ); + + const res = wgslGenerator.block( + (parsed[1][0] as tinyest.ForOf)[3] as tinyest.Block, + { x: 67 }, + ); + + expect(res).toMatchInlineSnapshot(` + "{ + const y = 67; + }" + `); + }); + }); }); From 70f30709badef22ecc8ffdc903691d1df74b266a Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 00:15:46 +0100 Subject: [PATCH 40/67] for ... of ... fixes + tests --- packages/typegpu/src/tgsl/wgslGenerator.ts | 90 ++++++------ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 132 +++++++++++++++--- 2 files changed, 152 insertions(+), 70 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 32acbeebaf..91dc9de10c 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1172,64 +1172,56 @@ ${this.ctx.pre}else ${alternate}`; '`for ... of ...` loops only support iterables stored in variables', ); } + try { + this.ctx.pushBlockScope(); - // Our index name will be some element from infinite sequence (i, ii, iii, ...). - // If user defines `i` and `ii` before `for ... of ...` loop, then our index name will be `iii`. - // If user defines `i` inside `for ... of ...` then it will be scoped to a new block, - // so we can safely use `i`. - let index = 'i'; // it will be valid name, no need to call this.ctx.makeNameValid - while (this.ctx.getById(index) !== null) { - index += 'i'; - } + const index = this.ctx.makeNameValid('i'); - const elementSnippet = accessIndex( - iterableSnippet, - snip(index, u32, 'runtime'), - ); - if (!elementSnippet) { - throw new WgslTypeError( - '`for ... of ...` loops only support array or vector iterables', + const elementSnippet = accessIndex( + iterableSnippet, + snip(index, u32, 'runtime'), ); - } + if (!elementSnippet) { + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + } - const iterableDataType = iterableSnippet.dataType; - let elementCountSnippet: Snippet; - let elementType = elementSnippet.dataType; + const iterableDataType = iterableSnippet.dataType; + let elementCountSnippet: Snippet; + let elementType = elementSnippet.dataType; - if (elementType === UnknownData) { - throw new WgslTypeError( - stitch`The elements in iterable ${iterableSnippet} are of unknown type`, - ); - } + if (elementType === UnknownData) { + throw new WgslTypeError( + stitch`The elements in iterable ${iterableSnippet} are of unknown type`, + ); + } - if (wgsl.isWgslArray(iterableDataType)) { - elementCountSnippet = iterableDataType.elementCount > 0 - ? snip( - `${iterableDataType.elementCount}`, + if (wgsl.isWgslArray(iterableDataType)) { + elementCountSnippet = iterableDataType.elementCount > 0 + ? snip( + `${iterableDataType.elementCount}`, + u32, + 'constant', + ) + : arrayLength[$gpuCallable].call(this.ctx, [iterableSnippet]); + } else if (wgsl.isVec(iterableDataType)) { + elementCountSnippet = snip( + `${Number(iterableDataType.type.match(/\d/))}`, u32, 'constant', - ) - : arrayLength[$gpuCallable].call(this.ctx, [iterableSnippet]); - } else if (wgsl.isVec(iterableDataType)) { - elementCountSnippet = snip( - `${Number(iterableDataType.type.match(/\d/))}`, - u32, - 'constant', - ); - } else { - throw new WgslTypeError( - '`for ... of ...` loops only support array or vector iterables', - ); - } - - if (loopVar[0] !== NODE.const) { - throw new WgslTypeError( - 'Only `for (const ... of ... )` loops are supported', - ); - } + ); + } else { + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + } - try { - this.ctx.pushBlockScope(); + if (loopVar[0] !== NODE.const) { + throw new WgslTypeError( + 'Only `for (const ... of ... )` loops are supported', + ); + } // If it's ephemeral, it's a value that cannot change. If it's a reference, we take // an implicit pointer to it diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 0b65bafd66..d7f2b6f709 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -450,7 +450,6 @@ describe('wgslGenerator', () => { 'use gpu'; const arr = [1, 2, 3]; for (const foo of arr) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } }; @@ -458,9 +457,7 @@ describe('wgslGenerator', () => { const main2 = () => { 'use gpu'; const arr = [1, 2, 3]; - // biome-ignore lint/style/useConst: it's a part of the test for (let foo of arr) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } }; @@ -483,7 +480,6 @@ describe('wgslGenerator', () => { let res = d.f32(); for (const foo of arr) { res += foo; - const i = res; // this is intentional, scoping `i` to a block separate from `arr` index } }; @@ -495,7 +491,6 @@ describe('wgslGenerator', () => { let foo = arr[i]; { res += foo; - let i = res; } } }" @@ -521,8 +516,8 @@ describe('wgslGenerator', () => { for (var i = 0u; i < 3; i++) { let foo = arr[i]; { - for (var i = 0u; i < 3; i++) { - let boo = arr[i]; + for (var i_1 = 0u; i_1 < 3; i_1++) { + let boo = arr[i_1]; { res += (foo * boo); } @@ -552,8 +547,8 @@ describe('wgslGenerator', () => { for (var i = 0u; i < 3; i++) { let foo = arr[i]; { - for (var i = 0u; i < 3; i++) { - let foo2 = arr[i]; + for (var i_1 = 0u; i_1 < 3; i_1++) { + let foo2 = arr[i_1]; { res += (foo2 * foo2); } @@ -588,7 +583,7 @@ describe('wgslGenerator', () => { `); }); - it('creates correct code for "for ... of ..." statement using runtime size array', ({ root }) => { + it('creates correct code for "for ... of ..." statement using runtime size array', () => { const layout = tgpu.bindGroupLayout({ arr: { storage: d.arrayOf(d.f32) }, }); @@ -623,13 +618,11 @@ describe('wgslGenerator', () => { 'use gpu'; const v1 = lazyV4u.$; for (const foo of v1) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } const v2 = comptimeVec(); for (const foo of v2) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } }; @@ -717,7 +710,6 @@ describe('wgslGenerator', () => { 'use gpu'; const testStruct = TestStruct({ arr: [1, 8, 8, 2] }); for (const foo of testStruct.arr) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } }; @@ -743,7 +735,6 @@ describe('wgslGenerator', () => { const main = () => { 'use gpu'; for (const foo of [1, 2, 3]) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } }; @@ -765,9 +756,8 @@ describe('wgslGenerator', () => { const main = () => { 'use gpu'; const testStruct = TestStruct({ x: 1, y: 2 }); - //@ts-expect-error: let's assume it has an iterator + // @ts-expect-error: let's assume it has an iterator for (const foo of testStruct) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } }; @@ -784,8 +774,8 @@ describe('wgslGenerator', () => { const main = () => { 'use gpu'; const arr = [1, 2, 3]; - // biome-ignore lint/style/useConst: it's a part of the test for (let foo of arr) { + continue; } }; @@ -829,7 +819,7 @@ describe('wgslGenerator', () => { for (var i = 0u; i < 3; i++) { let foo = arr[i]; { - let i = foo; + let i_1 = foo; } } }" @@ -840,7 +830,6 @@ describe('wgslGenerator', () => { const i = 7; const arr = [1, 2, 3]; for (const foo of arr) { - // biome-ignore lint/complexity/noUselessContinue: it's a part of the test continue; } }; @@ -849,8 +838,109 @@ describe('wgslGenerator', () => { "fn f2() { const i = 7; var arr = array(1, 2, 3); - for (var ii = 0u; ii < 3; ii++) { - let foo = arr[ii]; + for (var i_1 = 0u; i_1 < 3; i_1++) { + let foo = arr[i_1]; + { + continue; + } + } + }" + `); + }); + + it('handles "for ... of ..." internal index variable when "i" is buffer used earlier', ({ root }) => { + const i = root.createUniform(d.u32, 7); + + const f = () => { + 'use gpu'; + const arr = [1, 2, 3, i.$]; + for (const foo of arr) { + continue; + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var i: u32; + + fn f() { + var arr = array(1u, 2u, 3u, i); + for (var i_1 = 0u; i_1 < 4; i_1++) { + let foo = arr[i_1]; + { + continue; + } + } + }" + `); + }); + + it('handles "for ... of ..." internal index variable when "i" is buffer used later', ({ root }) => { + const i = root.createUniform(d.u32, 7); + const f = () => { + 'use gpu'; + const arr = [1, 2, 3]; + for (const foo of arr) { + const x = foo + i.$; + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var i_1: u32; + + fn f() { + var arr = array(1, 2, 3); + for (var i = 0u; i < 3; i++) { + let foo = arr[i]; + { + let x = (foo + i32(i_1)); + } + } + }" + `); + }); + + it('handles "for ... of ..." internal index variable when "i" is buffer returned from accessor', ({ root }) => { + const i = root.createUniform(d.u32, 7); + + const acc = tgpu.accessor(d.u32, () => i.$); + + const f = () => { + 'use gpu'; + const arr = [1, 2, 3]; + for (const foo of arr) { + const x = foo + acc.$; + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var i_1: u32; + + fn f() { + var arr = array(1, 2, 3); + for (var i = 0u; i < 3; i++) { + let foo = arr[i]; + { + let x = (foo + i32(i_1)); + } + } + }" + `); + }); + + it('handles "for ... of ..." internal index variable when "i" is loop variable', ({ root }) => { + const f = () => { + 'use gpu'; + const arr = [1, 2, 3]; + for (const i of arr) { + continue; + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var arr = array(1, 2, 3); + for (var i = 0u; i < 3; i++) { + let i_1 = arr[i]; { continue; } From b445d7ee66db3b3a9de4cdab6a320bf664cf741d Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 00:22:29 +0100 Subject: [PATCH 41/67] cleanup --- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index d7f2b6f709..27ca534e2c 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -874,7 +874,7 @@ describe('wgslGenerator', () => { `); }); - it('handles "for ... of ..." internal index variable when "i" is buffer used later', ({ root }) => { + it('handles "for ... of ..." internal index variable when "i" is the buffer used later', ({ root }) => { const i = root.createUniform(d.u32, 7); const f = () => { 'use gpu'; @@ -899,7 +899,7 @@ describe('wgslGenerator', () => { `); }); - it('handles "for ... of ..." internal index variable when "i" is buffer returned from accessor', ({ root }) => { + it('handles "for ... of ..." internal index variable when "i" is the buffer returned from accessor', ({ root }) => { const i = root.createUniform(d.u32, 7); const acc = tgpu.accessor(d.u32, () => i.$); @@ -927,22 +927,24 @@ describe('wgslGenerator', () => { `); }); - it('handles "for ... of ..." internal index variable when "i" is loop variable', ({ root }) => { + it('handles "for ... of ..." internal index variable when "i" is the loop variable', () => { const f = () => { 'use gpu'; const arr = [1, 2, 3]; + let res = 0; for (const i of arr) { - continue; + res += i; } }; expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { var arr = array(1, 2, 3); + var res = 0; for (var i = 0u; i < 3; i++) { let i_1 = arr[i]; { - continue; + res += i_1; } } }" From 3067577efc5327e4563c1a6d398a6979a252620f Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 01:07:30 +0100 Subject: [PATCH 42/67] rolldown tests --- .../typegpu/tests/tgsl/nameClashes.test.ts | 47 +++++++++++++++++++ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 29 ++++++++++++ 2 files changed, 76 insertions(+) diff --git a/packages/typegpu/tests/tgsl/nameClashes.test.ts b/packages/typegpu/tests/tgsl/nameClashes.test.ts index c913427436..f424e61724 100644 --- a/packages/typegpu/tests/tgsl/nameClashes.test.ts +++ b/packages/typegpu/tests/tgsl/nameClashes.test.ts @@ -186,3 +186,50 @@ test('should give new names to functions that collide with builtins', () => { }" `); }); + +// TODO: enable when we transition to `rolldown` +// test('should allow duplicate name after block end', () => { +// const main = () => { +// 'use gpu'; +// for (let i = 0; i < 3; i++) { +// const foo = i + 1; +// } +// const foo = d.u32(7); +// return foo; +// }; + +// expect(tgpu.resolve([main])).toMatchInlineSnapshot(` +// "fn main() -> u32 { +// for (var i = 0; (i < 3i); i++) { +// let foo = (i + 1i); +// } +// const foo = 7u; +// return foo; +// }" +// `); +// }); +// +// test('should give declarations new names when they are shadowed', () => { +// const main = () => { +// 'use gpu'; +// const i = 0; +// { +// const i = 1; +// { +// const i = 2; +// } +// } +// }; + +// expect(tgpu.resolve([main])).toMatchInlineSnapshot(` +// "fn main() { +// const i = 0; +// { +// const i_1 = 1; +// { +// const i_2 = 2; +// } +// } +// }" +// `); +// }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 27ca534e2c..19b6aaa6e6 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -951,6 +951,35 @@ describe('wgslGenerator', () => { `); }); + // TODO: enable when we transition to `rolldown` + // it('handles "for ... of ..." loop variable name when there is shadowning', ({ root }) => { + // const i = root.createUniform(d.u32, 7); + + // const f = () => { + // 'use gpu'; + // const arr = [1, 2, 3, i.$]; + // let res = 0; + // for (const i of arr) { + // res += i; + // } + // }; + + // expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + // "@group(0) @binding(0) var i: u32; + + // fn f() { + // var arr = array(1u, 2u, 3u, i); + // var res = 0; + // for (var i_1 = 0u; i_1 < 4; i_1++) { + // let i_2 = arr[i_1]; + // { + // res += i32(i_2); + // } + // } + // }" + // `); + // }); + it('creates correct resources for lazy values and slots', () => { const testFn = tgpu.fn([], d.vec4u)(() => lazyV4u.$); From 49b57d6cd06d6aeff62a3cde6dbe4372ff392168 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 10:46:12 +0100 Subject: [PATCH 43/67] more cleanup --- packages/typegpu/src/nameRegistry.ts | 5 +++-- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameRegistry.ts index db5f121752..15e3d71e61 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameRegistry.ts @@ -459,8 +459,9 @@ abstract class NameRegistryImpl implements NameRegistry { } get #usedBlockScopeNames(): Set | undefined { - return this.#scopeStack.findLast((scope) => scope.type === 'blockScope') - ?.usedBlockScopeNames; + return (this.#scopeStack[this.#scopeStack.length - 1] as + | BlockScopeLayer + | undefined)?.usedBlockScopeNames; } makeUnique(primer: string | undefined, global: boolean): string { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 19b6aaa6e6..4f70ebf29d 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -848,7 +848,7 @@ describe('wgslGenerator', () => { `); }); - it('handles "for ... of ..." internal index variable when "i" is buffer used earlier', ({ root }) => { + it('handles "for ... of ..." internal index variable when "i" is the buffer used earlier', ({ root }) => { const i = root.createUniform(d.u32, 7); const f = () => { From 8edf9b29b817fed2fe167d2d9955dbec7690ba83 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 12:07:45 +0100 Subject: [PATCH 44/67] rename --- packages/typegpu/src/resolutionCtx.ts | 16 ++++++++-------- packages/typegpu/src/tgsl/generationHelpers.ts | 4 ++-- packages/typegpu/src/tgsl/wgslGenerator.ts | 4 ++-- packages/typegpu/src/types.ts | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 9b531128ec..6261ddef35 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -263,7 +263,7 @@ class ItemStateStackImpl implements ItemStateStack { throw new Error('No block scope found to define a variable in.'); } - pushBlockExternals(externals: Record) { + setBlockExternals(externals: Record) { for (let i = this._stack.length - 1; i >= 0; --i) { const layer = this._stack[i]; if (layer?.type === 'blockScope') { @@ -273,10 +273,10 @@ class ItemStateStackImpl implements ItemStateStack { return; } } - throw new Error('No block scope found to push externals in.'); + throw new Error('No block scope found to set externals in.'); } - popBlockExternals() { + clearBlockExternals() { for (let i = this._stack.length - 1; i >= 0; --i) { const layer = this._stack[i]; if (layer?.type === 'blockScope') { @@ -284,7 +284,7 @@ class ItemStateStackImpl implements ItemStateStack { return; } } - throw new Error('No block scope found to pop overrides from.'); + throw new Error('No block scope found to clear externals in.'); } } @@ -453,12 +453,12 @@ export class ResolutionCtxImpl implements ResolutionCtx { this._itemStateStack.pop('blockScope'); } - pushBlockExternals(externals: Record) { - this._itemStateStack.pushBlockExternals(externals); + setBlockExternals(externals: Record) { + this._itemStateStack.setBlockExternals(externals); } - popBlockExternals() { - this._itemStateStack.popBlockExternals(); + clearBlockExternals() { + this._itemStateStack.clearBlockExternals(); } generateLog(op: string, args: Snippet[]): Snippet { diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 135d0a7c9d..94b7a33489 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -92,8 +92,8 @@ export type GenerationCtx = ResolutionCtx & { generateLog(op: string, args: Snippet[]): Snippet; getById(id: string): Snippet | null; defineVariable(id: string, snippet: Snippet): void; - pushBlockExternals(externals: Record): void; - popBlockExternals(): void; + setBlockExternals(externals: Record): void; + clearBlockExternals(): void; /** * Types that are used in `return` statements are diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index f4c7f72a9d..6c3f034a70 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -52,7 +52,7 @@ import type { AnyFn } from '../core/function/fnTypes.ts'; import { arrayLength } from '../std/array.ts'; import { AutoStruct } from '../data/autoStruct.ts'; import { mathToStd } from './math.ts'; -import { ExternalMap } from '../../src/core/resolve/externals.ts'; +import type { ExternalMap } from '../../src/core/resolve/externals.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -212,7 +212,7 @@ class WgslGenerator implements ShaderGenerator { [id, value], ) => [id, coerceToSnippet(value)]), ); - this.ctx.pushBlockExternals(externals); + this.ctx.setBlockExternals(externals); } try { diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 35d4b36e94..082e408a3f 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -152,8 +152,8 @@ export interface ItemStateStack { externalMap: Record, ): FunctionScopeLayer; pushBlockScope(): void; - pushBlockExternals(externals: Record): void; - popBlockExternals(): void; + setBlockExternals(externals: Record): void; + clearBlockExternals(): void; pop(type: T): Extract; pop(): StackLayer | undefined; From 1657f9d9065333d75e4a534ffa4cdd93b7082c42 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 14:57:41 +0100 Subject: [PATCH 45/67] circular deps --- packages/typegpu/src/tgsl/forOfUtils.ts | 95 +++++++++++++++++++ .../typegpu/src/tgsl/generationHelpers.ts | 88 +---------------- packages/typegpu/src/tgsl/wgslGenerator.ts | 47 +++++---- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 5 +- 4 files changed, 129 insertions(+), 106 deletions(-) create mode 100644 packages/typegpu/src/tgsl/forOfUtils.ts diff --git a/packages/typegpu/src/tgsl/forOfUtils.ts b/packages/typegpu/src/tgsl/forOfUtils.ts new file mode 100644 index 0000000000..b119ed02d9 --- /dev/null +++ b/packages/typegpu/src/tgsl/forOfUtils.ts @@ -0,0 +1,95 @@ +import { UnknownData } from '../data/dataTypes.ts'; +import { isEphemeralSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { stitch } from '../core/resolve/stitch.ts'; +import * as wgsl from '../data/wgslTypes.ts'; +import { u32 } from '../data/numeric.ts'; +import { invariant, WgslTypeError } from '../errors.ts'; +import { arrayLength } from '../std/array.ts'; +import { accessIndex } from './accessIndex.ts'; +import { createPtrFromOrigin, implicitFrom } from '../data/ptr.ts'; +import { $gpuCallable } from '../shared/symbols.ts'; +import { concretize, type GenerationCtx } from './generationHelpers.ts'; + +export function getLoopVarKind(elementSnippet: Snippet) { + // If it's ephemeral, it's a value that cannot change. If it's a reference, we take + // an implicit pointer to it + return elementSnippet.origin === 'constant-tgpu-const-ref' ? 'const' : 'let'; +} + +export function getElementSnippet(iterableSnippet: Snippet, index: string) { + const elementSnippet = accessIndex( + iterableSnippet, + snip(index, u32, 'runtime'), + ); + + if (!elementSnippet) { + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); + } + + return elementSnippet; +} + +export function getElementType( + elementSnippet: Snippet, + iterableSnippet: Snippet, +) { + let elementType = elementSnippet.dataType; + if (elementType === UnknownData) { + throw new WgslTypeError( + stitch`The elements in iterable ${iterableSnippet} are of unknown type`, + ); + } + + if ( + isEphemeralSnippet(elementSnippet) || + elementSnippet.origin === 'constant-tgpu-const-ref' || + elementSnippet.origin === 'runtime-tgpu-const-ref' + ) { + return elementType; + } + + if (!wgsl.isPtr(elementType)) { + const ptrType = createPtrFromOrigin( + elementSnippet.origin, + concretize(elementType as wgsl.AnyWgslData) as wgsl.StorableData, + ); + invariant( + ptrType !== undefined, + `Creating pointer type from origin ${elementSnippet.origin}`, + ); + elementType = ptrType; + } + + return implicitFrom(elementType as wgsl.Ptr); +} + +export function getElementCountSnippet( + ctx: GenerationCtx, + iterableSnippet: Snippet, +) { + const iterableDataType = iterableSnippet.dataType; + + if (wgsl.isWgslArray(iterableDataType)) { + return iterableDataType.elementCount > 0 + ? snip( + `${iterableDataType.elementCount}`, + u32, + 'constant', + ) + : arrayLength[$gpuCallable].call(ctx, [iterableSnippet]); + } + + if (wgsl.isVec(iterableDataType)) { + return snip( + `${iterableDataType.componentCount}`, + u32, + 'constant', + ); + } + + throw new WgslTypeError( + '`for ... of ...` loops only support array or vector iterables', + ); +} diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 302136d3c1..94b7a33489 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -27,13 +27,8 @@ import { } from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; import { stitch } from '../../src/core/resolve/stitch.ts'; -import * as wgsl from '../data/wgslTypes.ts'; -import { u32 } from '../data/numeric.ts'; -import { invariant, WgslTypeError } from '../../src/errors.ts'; -import { arrayLength } from '../std/array.ts'; -import { accessIndex } from './accessIndex.ts'; -import { createPtrFromOrigin, implicitFrom } from '../../src/data/ptr.ts'; -import { $gpuCallable, $internal, $resolve } from '../../src/shared/symbols.ts'; +import { WgslTypeError } from '../../src/errors.ts'; +import { $internal, $resolve } from '../../src/shared/symbols.ts'; export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { @@ -196,82 +191,3 @@ export class ArrayExpression implements SelfResolvable { ); } } - -export const forOfHelpers = { - getLoopVarKind(elementSnippet: Snippet) { - // If it's ephemeral, it's a value that cannot change. If it's a reference, we take - // an implicit pointer to it - return elementSnippet.origin === 'constant-tgpu-const-ref' - ? 'const' - : 'let'; - }, - getElementSnippet(iterableSnippet: Snippet, index: string) { - const elementSnippet = accessIndex( - iterableSnippet, - snip(index, u32, 'runtime'), - ); - - if (!elementSnippet) { - throw new WgslTypeError( - '`for ... of ...` loops only support array or vector iterables', - ); - } - - return elementSnippet; - }, - getElementType(elementSnippet: Snippet, iterableSnippet: Snippet) { - let elementType = elementSnippet.dataType; - if (elementType === UnknownData) { - throw new WgslTypeError( - stitch`The elements in iterable ${iterableSnippet} are of unknown type`, - ); - } - - if ( - isEphemeralSnippet(elementSnippet) || - elementSnippet.origin === 'constant-tgpu-const-ref' || - elementSnippet.origin === 'runtime-tgpu-const-ref' - ) { - return elementType; - } - - if (!wgsl.isPtr(elementType)) { - const ptrType = createPtrFromOrigin( - elementSnippet.origin, - concretize(elementType as wgsl.AnyWgslData) as wgsl.StorableData, - ); - invariant( - ptrType !== undefined, - `Creating pointer type from origin ${elementSnippet.origin}`, - ); - elementType = ptrType; - } - - return implicitFrom(elementType as wgsl.Ptr); - }, - getElementCountSnippet(ctx: GenerationCtx, iterableSnippet: Snippet) { - const iterableDataType = iterableSnippet.dataType; - - if (wgsl.isWgslArray(iterableDataType)) { - return iterableDataType.elementCount > 0 - ? snip( - `${iterableDataType.elementCount}`, - u32, - 'constant', - ) - : arrayLength[$gpuCallable].call(ctx, [iterableSnippet]); - } - - if (wgsl.isVec(iterableDataType)) { - return snip( - `${iterableDataType.componentCount}`, - u32, - 'constant', - ); - } - - throw new WgslTypeError( - '`for ... of ...` loops only support array or vector iterables', - ); - }, -} as const; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 38e9260d68..10d64fccc2 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -38,7 +38,6 @@ import { ArrayExpression, coerceToSnippet, concretize, - forOfHelpers, type GenerationCtx, numericLiteralToSnippet, } from './generationHelpers.ts'; @@ -54,6 +53,7 @@ import type { AnyFn } from '../core/function/fnTypes.ts'; import { AutoStruct } from '../data/autoStruct.ts'; import { mathToStd } from './math.ts'; import type { ExternalMap } from '../../src/core/resolve/externals.ts'; +import * as forOfUtils from './forOfUtils.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -1186,6 +1186,8 @@ ${this.ctx.pre}else ${alternate}`; ); } + let ctxIndent = false; + try { this.ctx.pushBlockScope(); @@ -1232,27 +1234,29 @@ ${this.ctx.pre}else ${alternate}`; if (!shouldUnroll && !ephemeralIterable) { const index = this.ctx.makeNameValid('i'); - const elementSnippet = forOfHelpers.getElementSnippet( + const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, index, ); - const elementCountSnippet = forOfHelpers.getElementCountSnippet( + const elementCountSnippet = forOfUtils.getElementCountSnippet( this.ctx, iterableSnippet, ); - const loopVarName = this.ctx.makeNameValid(loopVar[1]); - const loopVarKind = forOfHelpers.getLoopVarKind(elementSnippet); - const elementType = forOfHelpers.getElementType( - elementSnippet, - iterableSnippet, - ); - const forStr = + const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ tryConvertSnippet(this.ctx, elementCountSnippet, u32, false) }; ${index}++) {`; this.ctx.indent(); + ctxIndent = true; + + const loopVarName = this.ctx.makeNameValid(loopVar[1]); + const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet); + const elementType = forOfUtils.getElementType( + elementSnippet, + iterableSnippet, + ); const loopVarDeclStr = stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ @@ -1264,27 +1268,32 @@ ${this.ctx.pre}else ${alternate}`; ) };`; - const loopVarSnippet = snip( - loopVarName, - elementType, - elementSnippet.origin, - ); - const bodyStr = `${this.ctx.pre}${ this.block(blockifySingleStatement(body), { - [loopVar[1]]: loopVarSnippet, + [loopVar[1]]: snip( + loopVarName, + elementType, + elementSnippet.origin, + ), }) }`; this.ctx.dedent(); + ctxIndent = false; - return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; + return stitch`${forHeaderStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; } throw new Error( - '`for ... of ...` loops only support iterables stored in variables', + `\`for ... of ...\` loops only support iterables stored in variables. +----- +You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. +-----`, ); } finally { + if (ctxIndent) { + this.ctx.dedent(); + } this.ctx.popBlockScope(); } } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index ab6d5217c1..9664a82948 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -743,7 +743,10 @@ describe('wgslGenerator', () => { [Error: Resolution of the following tree failed: - - fn*:main - - fn*:main(): \`for ... of ...\` loops only support iterables stored in variables] + - fn*:main(): \`for ... of ...\` loops only support iterables stored in variables. + ----- + You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. + -----] `); }); From 368062feae8126e10726b9f64c6e3bccf43e5989 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 21:20:10 +0100 Subject: [PATCH 46/67] unrolling names of struct fields --- packages/typegpu/src/tgsl/accessIndex.ts | 9 + packages/typegpu/src/tgsl/forOfUtils.ts | 29 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 96 ++++-- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 30 ++ packages/typegpu/tests/unroll.test.ts | 326 ++++++++++++++---- 5 files changed, 384 insertions(+), 106 deletions(-) diff --git a/packages/typegpu/src/tgsl/accessIndex.ts b/packages/typegpu/src/tgsl/accessIndex.ts index 7e509e2438..2cbf0793e0 100644 --- a/packages/typegpu/src/tgsl/accessIndex.ts +++ b/packages/typegpu/src/tgsl/accessIndex.ts @@ -18,8 +18,10 @@ import { isPtr, isVec, isWgslArray, + isWgslStruct, } from '../data/wgslTypes.ts'; import { isKnownAtComptime } from '../types.ts'; +import { accessProp } from './accessProp.ts'; import { coerceToSnippet } from './generationHelpers.ts'; const indexableTypeToResult = { @@ -129,5 +131,12 @@ export function accessIndex( ); } + if ( + isWgslStruct(target.dataType) && isKnownAtComptime(index) && + typeof index.value === 'string' + ) { + return accessProp(target, index.value); + } + return undefined; } diff --git a/packages/typegpu/src/tgsl/forOfUtils.ts b/packages/typegpu/src/tgsl/forOfUtils.ts index b119ed02d9..b57ea2f6b2 100644 --- a/packages/typegpu/src/tgsl/forOfUtils.ts +++ b/packages/typegpu/src/tgsl/forOfUtils.ts @@ -8,7 +8,11 @@ import { arrayLength } from '../std/array.ts'; import { accessIndex } from './accessIndex.ts'; import { createPtrFromOrigin, implicitFrom } from '../data/ptr.ts'; import { $gpuCallable } from '../shared/symbols.ts'; -import { concretize, type GenerationCtx } from './generationHelpers.ts'; +import { + ArrayExpression, + concretize, + type GenerationCtx, +} from './generationHelpers.ts'; export function getLoopVarKind(elementSnippet: Snippet) { // If it's ephemeral, it's a value that cannot change. If it's a reference, we take @@ -68,27 +72,38 @@ export function getElementType( export function getElementCountSnippet( ctx: GenerationCtx, iterableSnippet: Snippet, + unroll: boolean = false, ) { - const iterableDataType = iterableSnippet.dataType; + const { value, dataType } = iterableSnippet; - if (wgsl.isWgslArray(iterableDataType)) { - return iterableDataType.elementCount > 0 + if (wgsl.isWgslArray(dataType)) { + return dataType.elementCount > 0 ? snip( - `${iterableDataType.elementCount}`, + `${dataType.elementCount}`, u32, 'constant', ) : arrayLength[$gpuCallable].call(ctx, [iterableSnippet]); } - if (wgsl.isVec(iterableDataType)) { + if (wgsl.isVec(dataType)) { return snip( - `${iterableDataType.componentCount}`, + `${dataType.componentCount}`, u32, 'constant', ); } + if (unroll) { + if (Array.isArray(value)) { + return snip(`${value.length}`, u32, 'constant'); + } + + if (value instanceof ArrayExpression) { + return snip(`${value.elements.length}`, u32, 'constant'); + } + } + throw new WgslTypeError( '`for ... of ...` loops only support array or vector iterables', ); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 10d64fccc2..79adeccb9a 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -47,7 +47,7 @@ import type { ShaderGenerator } from './shaderGenerator.ts'; import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; -import { UnrolledIterable } from '../../src/core/unroll/tgpuUnroll.ts'; +import { UnrolledIterable as UnrollableIterable } from '../../src/core/unroll/tgpuUnroll.ts'; import { isGenericFn } from '../core/function/tgpuFn.ts'; import type { AnyFn } from '../core/function/fnTypes.ts'; import { AutoStruct } from '../data/autoStruct.ts'; @@ -1192,43 +1192,83 @@ ${this.ctx.pre}else ${alternate}`; this.ctx.pushBlockScope(); const iterableExpr = this.expression(iterable); - const shouldUnroll = iterableExpr.value instanceof UnrolledIterable; + let shouldUnroll = iterableExpr.value instanceof UnrollableIterable; const iterableSnippet = shouldUnroll - ? iterableExpr.value.snippet + ? (iterableExpr.value as UnrollableIterable).snippet : iterableExpr; const ephemeralIterable = isEphemeralSnippet(iterableSnippet); + const elementCountSnippet = forOfUtils.getElementCountSnippet( + this.ctx, + iterableSnippet, + shouldUnroll, + ); - if (shouldUnroll && ephemeralIterable) { - if (iterableSnippet.value instanceof ArrayExpression) { - const loopVarName = this.ctx.makeNameValid(loopVar[1]); + if ( + shouldUnroll && + !isKnownAtComptime(iterableSnippet) + ) { + console.warn( + 'Cannot unroll loop. The iterable is unknown at compile-time. Fallback to regular loop.', + ); + shouldUnroll = false; + } - const blockified = blockifySingleStatement(body); - const iterations = iterableSnippet.value.elements.map((e) => - `${this.ctx.pre}${ - this.block(blockified, { [`${loopVarName}`]: e }) - }` + if (shouldUnroll) { + // we know it is a number, because it is known at compile-time + if ((elementCountSnippet.value as number) > 8) { + console.warn( + `Unrolling ${elementCountSnippet.value} iterations exceeds recommended limit of 8. Consider using a smaller array or runtime loop.`, ); - - return iterations.join('\n'); } - if (Array.isArray(iterableSnippet.value)) { - const loopVarName = this.ctx.makeNameValid(loopVar[1]); + if (ephemeralIterable) { + const { value, dataType } = iterableSnippet; + if (value instanceof ArrayExpression) { + const blockified = blockifySingleStatement(body); + const iterations = value.elements.map((e) => + `${this.ctx.pre}${ + this.block(blockified, { [`${loopVar[1]}`]: e }) // `e` is already a snippet + }` + ); - const blockified = blockifySingleStatement(body); - const iterations = iterableSnippet.value.map((e) => - `${this.ctx.pre}${ - this.block(blockified, { [`${loopVarName}`]: e }) - }` - ); + return iterations.join('\n'); + } - return iterations.join('\n'); - } + if (wgsl.isVec(dataType)) { + const blockified = blockifySingleStatement(body); + const iterations = (value as wgsl.AnyVecInstance) // it's ephemeral, it's not a string + .map((e) => + `${this.ctx.pre}${ + this.block(blockified, { + [loopVar[1]]: snip( // we can give `e` a type + e, + dataType.primitive, + iterableSnippet.origin, + ), + }) + }` + ); - throw new WgslTypeError('Cannot unroll. Unsupported iterable.'); - } + return iterations.join('\n'); + } + + if (Array.isArray(value)) { + const blockified = blockifySingleStatement(body); + + const iterations = value.map((e) => + `${this.ctx.pre}${ + this.block(blockified, { + [loopVar[1]]: e, // cannot infer `e` type, we will do it later + }) + }` + ); + + return iterations.join('\n'); + } + + // if not any of the above types, then error has been already thrown by `getElementCountSnippet` + } - if (shouldUnroll && !ephemeralIterable) { throw new Error('Not implemented'); } @@ -1238,10 +1278,6 @@ ${this.ctx.pre}else ${alternate}`; iterableSnippet, index, ); - const elementCountSnippet = forOfUtils.getElementCountSnippet( - this.ctx, - iterableSnippet, - ); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 9664a82948..4c958f21b0 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -647,6 +647,36 @@ describe('wgslGenerator', () => { `); }); + it('creates correct code for "for ... of ..." statements using buffer iterable', ({ root }) => { + const b = root.createUniform(d.arrayOf(d.u32, 7)); + const acc = tgpu.accessor(d.arrayOf(d.u32, 7), b); + + const f = () => { + 'use gpu'; + let result = d.u32(0); + for (const foo of acc.$) { + result += foo; + } + + return result; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var b: array; + + fn f() -> u32 { + var result = 0u; + for (var i = 0u; i < 7; i++) { + let foo = b[i]; + { + result += foo; + } + } + return result; + }" + `); + }); + it('creates correct code for "for ... of ..." statements using vector iterables', () => { const main = () => { 'use gpu'; diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 9904ea571a..0cf2618146 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -1,16 +1,17 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, vi } from 'vitest'; +import { it } from './utils/extendedIt.ts'; import * as d from '../src/data/index.ts'; import tgpu from '../src/index.ts'; describe('tgpu.unroll', () => { - it('called outside shader and outside forOf returns passed iterable', () => { + it('called outside the gpu function returns passed iterable', () => { const arr = [1, 2, 3]; const x = tgpu['~unstable'].unroll(arr); expect(x).toBe(arr); }); - it('called inside shader but outside forOf returns passed iterable', () => { + it('called inside the gpu function but outside of forOf returns passed iterable', () => { const layout = tgpu.bindGroupLayout({ arr: { storage: d.arrayOf(d.f32) }, }); @@ -36,7 +37,7 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls forOf of array expression of primitives', () => { + it('unrolls array expression of primitives', () => { const f = () => { 'use gpu'; let res = 0; @@ -63,11 +64,11 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls forOf with variable override', () => { + it('unrolls correctly when loop variable is overriding', () => { const f = () => { 'use gpu'; const foo = d.vec3f(6); - for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { // this foo is in different block layer, it will be found first + for (const foo of tgpu['~unstable'].unroll([1, 2])) { const boo = foo; } }; @@ -76,45 +77,23 @@ describe('tgpu.unroll', () => { "fn f() { var foo = vec3f(6); { - var boo = vec3f(7); + const boo = 1; } { - var boo = vec3f(3); + const boo = 2; } }" `); }); - it('unrolls forOf with variable from different scope override', () => { - const foo = 2; - - const f = () => { - 'use gpu'; - for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { // this foo is in different block layer, it will be found first - const boo = foo; - } - }; - - expect(tgpu.resolve([f])).toMatchInlineSnapshot(` - "fn f() { - { - var boo = vec3f(7); - } - { - var boo = vec3f(3); - } - }" - `); - }); - - it('unrolls forOf with variable being overriden', () => { + it('unrolls correctly when loop variable is overriden', () => { const f = () => { 'use gpu'; let fooResult = d.f32(0); - for (const foo of tgpu['~unstable'].unroll([d.vec3f(7), d.vec3f(3)])) { + for (const foo of tgpu['~unstable'].unroll([1, 2])) { const boo = foo; { - const foo = boo.x; // this foo is in different block layer, it will see the previous foo + const foo = boo; fooResult += foo; } const bar = foo; @@ -127,27 +106,27 @@ describe('tgpu.unroll', () => { "fn f() -> f32 { var fooResult = 0f; { - var boo = vec3f(7); + const boo = 1; { - let foo2 = boo.x; - fooResult += foo2; + const foo2 = boo; + fooResult += f32(foo2); } - var bar = vec3f(7); + const bar = 1; } { - var boo = vec3f(3); + const boo = 2; { - let foo2 = boo.x; - fooResult += foo2; + const foo2 = boo; + fooResult += f32(foo2); } - var bar = vec3f(3); + const bar = 2; } return fooResult; }" `); }); - it('unrolls forOf of array expression of complex types', () => { + it('unrolls array expression of complex types', () => { const Boid = d.struct({ pos: d.vec2i, vel: d.vec2f, @@ -155,10 +134,12 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; + const b1 = Boid({ pos: d.vec2i(1), vel: d.vec2f(1) }); + const b2 = Boid({ pos: d.vec2i(2), vel: d.vec2f(2) }); let res = d.vec2f(); - for (const foo of tgpu['~unstable'].unroll([d.vec2f(7), d.vec2f(3)])) { + for (const foo of tgpu['~unstable'].unroll([b1, b2])) { for (const boo of tgpu['~unstable'].unroll([Boid(), Boid()])) { - res = res.add(foo).add(boo.vel); + res = res.add(foo.vel).add(boo.vel); } } @@ -172,21 +153,23 @@ describe('tgpu.unroll', () => { } fn f() -> vec2f { + var b1 = Boid(vec2i(1), vec2f(1)); + var b2 = Boid(vec2i(2), vec2f(2)); var res = vec2f(); { { - res = ((res + vec2f(7)) + Boid().vel); + res = ((res + b1.vel) + Boid().vel); } { - res = ((res + vec2f(7)) + Boid().vel); + res = ((res + b1.vel) + Boid().vel); } } { { - res = ((res + vec2f(3)) + Boid().vel); + res = ((res + b2.vel) + Boid().vel); } { - res = ((res + vec2f(3)) + Boid().vel); + res = ((res + b2.vel) + Boid().vel); } } return res; @@ -194,7 +177,7 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls forOf of array expression of copied values', () => { + it('unrolls array expression of copies', () => { const f = () => { 'use gpu'; let res = d.vec2f(); @@ -229,7 +212,161 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls forOf of external comptime iterable', () => { + it('unrolls array expression of struct field names - (simple)', () => { + const values = { a: 1, b: 2, c: 3 }; + const list = Object.keys(values) as (keyof typeof values)[]; + + const f = () => { + 'use gpu'; + let result = d.u32(0); + for (const prop of tgpu['~unstable'].unroll(list)) { + result += values[prop]; + } + return result; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> u32 { + var result = 0u; + { + result += 1u; + } + { + result += 2u; + } + { + result += 3u; + } + return result; + }" + `); + }); + + it('unrolls array expression of struct field names - (complex)', () => { + const variants = { + foo: (x: number) => { + 'use gpu'; + return 6 * x; + }, + boo: (x: number) => { + 'use gpu'; + return 7 * x; + }, + }; + + const Weights = d.struct(Object.fromEntries( + Object.keys(variants).map((name) => [`${name}`, d.f32]), + )); + + const variantsKey = Object.keys(variants) as (keyof typeof variants)[]; + + const computeWeight = tgpu.fn([Weights], d.f32)( + (weights: d.Infer) => { + 'use gpu'; + + let p = d.f32(0); + for (const key of tgpu['~unstable'].unroll(variantsKey)) { + // @ts-expect-error: trust me + p += weights[key] * variants[key](p); + } + return p; + }, + ); + + expect(tgpu.resolve([computeWeight])).toMatchInlineSnapshot(` + "fn foo(x: f32) -> f32 { + return (6f * x); + } + + fn boo(x: f32) -> f32 { + return (7f * x); + } + + struct Weights { + foo: f32, + boo: f32, + } + + fn computeWeight(weights: Weights) -> f32 { + var p = 0f; + { + p += (weights.foo * foo(p)); + } + { + p += (weights.boo * boo(p)); + } + return p; + }" + `); + }); + + it('unrolls array expression of pointers', () => { + const f = () => { + 'use gpu'; + let res = d.vec2f(); + const v1 = d.vec2f(7); + const v2 = d.vec2f(3); + for (const foo of tgpu['~unstable'].unroll([v1, v2])) { + res = res.add(foo); + const boo = foo; + boo.x = 6; + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> vec2f { + var res = vec2f(); + var v1 = vec2f(7); + var v2 = vec2f(3); + { + res = (res + v1); + let boo = (&v1); + (*boo).x = 6f; + } + { + res = (res + v2); + let boo = (&v2); + (*boo).x = 6f; + } + return res; + }" + `); + }); + + it('unrolls ephemeral vector', () => { + const f = () => { + 'use gpu'; + let res = d.u32(0); + for (const foo of tgpu['~unstable'].unroll(d.vec4u(1, 2, 3, 4))) { + res += foo; + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> u32 { + var res = 0u; + { + res += 1u; + } + { + res += 2u; + } + { + res += 3u; + } + { + res += 4u; + } + return res; + }" + `); + }); + + it('unrolls external compile-time iterable', () => { const arr = [1, 2, 3]; const f = () => { @@ -259,31 +396,87 @@ describe('tgpu.unroll', () => { `); }); - it.skip('throws error when number of iterations is unknown at comptime', () => { - const layout = tgpu.bindGroupLayout({ - arr: { storage: d.arrayOf(d.f32) }, - }); + it('warns when iterable is unknown at compile-time and fallbacks to regular loop', ({ root }) => { + const b = root.createUniform(d.arrayOf(d.u32, 7)); + const acc = tgpu.accessor(d.arrayOf(d.u32, 7), b); const f = () => { 'use gpu'; - let res = d.f32(0); - for (const foo of tgpu['~unstable'].unroll(layout.$.arr)) { - res += foo; + let result = d.u32(0); + for (const foo of tgpu['~unstable'].unroll(acc.$)) { + result += foo; + } + + return result; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var b: array; + + fn f() -> u32 { + var result = 0u; + for (var i = 0u; i < 7; i++) { + let foo = b[i]; + { + result += foo; + } + } + return result; + }" + `); + }); + + it('warns when number of iteration to unroll is greater than 8', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation( + () => {}, + ); + + const f = () => { + 'use gpu'; + for (const foo of tgpu['~unstable'].unroll([1, 2, 3, 4, 5, 6, 7, 8, 9])) { + continue; } }; - expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` - [Error: Resolution of the following tree failed: - - - - fn*:f - - fn*:f(): Cannot unroll loop. Number of iterations unknown at compile time] + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + { + continue; + } + { + continue; + } + { + continue; + } + { + continue; + } + { + continue; + } + { + continue; + } + { + continue; + } + { + continue; + } + { + continue; + } + }" `); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + 'Unrolling 9 iterations exceeds recommended limit of 8. Consider using a smaller array or runtime loop.', + ); }); // TODO // - // add basic external, derived, maybe slot and accessor - // // const arr = [1, 2, 3]; // for (const foo of tgpu['~unstable'].unroll(arr)) { // should operate on indices // result -= foo; @@ -298,9 +491,4 @@ describe('tgpu.unroll', () => { // const foo = 1; // result += d.f32(foo); // } - - // const foo = 1; - // for (const foo of tgpu['~unstable'].unroll([1, 2, 3])) { // should operate on values - // result += d.f32(foo); - // } }); From 235b54676fac2a093cd279c8583f63919c582717 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Fri, 20 Feb 2026 21:22:59 +0100 Subject: [PATCH 47/67] unroll is stable :) --- packages/typegpu/src/tgpu.ts | 1 + packages/typegpu/src/tgpuUnstable.ts | 1 - packages/typegpu/tests/unroll.test.ts | 43 ++++++++++++--------------- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/typegpu/src/tgpu.ts b/packages/typegpu/src/tgpu.ts index f9587e63ed..1b1030603a 100644 --- a/packages/typegpu/src/tgpu.ts +++ b/packages/typegpu/src/tgpu.ts @@ -11,5 +11,6 @@ export { accessor, mutableAccessor } from './core/slot/accessor.ts'; export { privateVar, workgroupVar } from './core/variable/tgpuVariable.ts'; export { vertexLayout } from './core/vertexLayout/vertexLayout.ts'; export { bindGroupLayout } from './tgpuBindGroupLayout.ts'; +export { unroll } from './core/unroll/tgpuUnroll.ts'; export * as '~unstable' from './tgpuUnstable.ts'; diff --git a/packages/typegpu/src/tgpuUnstable.ts b/packages/typegpu/src/tgpuUnstable.ts index d80c155593..07d27928e8 100644 --- a/packages/typegpu/src/tgpuUnstable.ts +++ b/packages/typegpu/src/tgpuUnstable.ts @@ -43,4 +43,3 @@ export { /** @deprecated This feature is now stable, use tgpu.vertexLayout. */ vertexLayout, } from './core/vertexLayout/vertexLayout.ts'; -export { unroll } from './core/unroll/tgpuUnroll.ts'; diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 0cf2618146..a36385b392 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -6,7 +6,7 @@ import tgpu from '../src/index.ts'; describe('tgpu.unroll', () => { it('called outside the gpu function returns passed iterable', () => { const arr = [1, 2, 3]; - const x = tgpu['~unstable'].unroll(arr); + const x = tgpu.unroll(arr); expect(x).toBe(arr); }); @@ -18,11 +18,11 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; - const a = tgpu['~unstable'].unroll([1, 2, 3]); + const a = tgpu.unroll([1, 2, 3]); const v1 = d.vec2f(7); - const v2 = tgpu['~unstable'].unroll(v1); // this should return a pointer - const arr = tgpu['~unstable'].unroll(layout.$.arr); // this should return a pointer + const v2 = tgpu.unroll(v1); // this should return a pointer + const arr = tgpu.unroll(layout.$.arr); // this should return a pointer }; expect(tgpu.resolve([f])).toMatchInlineSnapshot(` @@ -41,7 +41,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; let res = 0; - for (const foo of tgpu['~unstable'].unroll([1, 2, 3])) { + for (const foo of tgpu.unroll([1, 2, 3])) { res += foo; } return res; @@ -68,7 +68,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; const foo = d.vec3f(6); - for (const foo of tgpu['~unstable'].unroll([1, 2])) { + for (const foo of tgpu.unroll([1, 2])) { const boo = foo; } }; @@ -90,7 +90,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; let fooResult = d.f32(0); - for (const foo of tgpu['~unstable'].unroll([1, 2])) { + for (const foo of tgpu.unroll([1, 2])) { const boo = foo; { const foo = boo; @@ -137,8 +137,8 @@ describe('tgpu.unroll', () => { const b1 = Boid({ pos: d.vec2i(1), vel: d.vec2f(1) }); const b2 = Boid({ pos: d.vec2i(2), vel: d.vec2f(2) }); let res = d.vec2f(); - for (const foo of tgpu['~unstable'].unroll([b1, b2])) { - for (const boo of tgpu['~unstable'].unroll([Boid(), Boid()])) { + for (const foo of tgpu.unroll([b1, b2])) { + for (const boo of tgpu.unroll([Boid(), Boid()])) { res = res.add(foo.vel).add(boo.vel); } } @@ -183,7 +183,7 @@ describe('tgpu.unroll', () => { let res = d.vec2f(); const v1 = d.vec2f(7); const v2 = d.vec2f(3); - for (const foo of tgpu['~unstable'].unroll([d.vec2f(v1), d.vec2f(v2)])) { + for (const foo of tgpu.unroll([d.vec2f(v1), d.vec2f(v2)])) { res = res.add(foo); const boo = foo; boo.x = 0; @@ -219,7 +219,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; let result = d.u32(0); - for (const prop of tgpu['~unstable'].unroll(list)) { + for (const prop of tgpu.unroll(list)) { result += values[prop]; } return result; @@ -265,7 +265,7 @@ describe('tgpu.unroll', () => { 'use gpu'; let p = d.f32(0); - for (const key of tgpu['~unstable'].unroll(variantsKey)) { + for (const key of tgpu.unroll(variantsKey)) { // @ts-expect-error: trust me p += weights[key] * variants[key](p); } @@ -306,7 +306,7 @@ describe('tgpu.unroll', () => { let res = d.vec2f(); const v1 = d.vec2f(7); const v2 = d.vec2f(3); - for (const foo of tgpu['~unstable'].unroll([v1, v2])) { + for (const foo of tgpu.unroll([v1, v2])) { res = res.add(foo); const boo = foo; boo.x = 6; @@ -339,7 +339,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; let res = d.u32(0); - for (const foo of tgpu['~unstable'].unroll(d.vec4u(1, 2, 3, 4))) { + for (const foo of tgpu.unroll(d.vec4u(1, 2, 3, 4))) { res += foo; } @@ -372,7 +372,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; let result = 0; - for (const foo of tgpu['~unstable'].unroll(arr)) { + for (const foo of tgpu.unroll(arr)) { result += foo; } @@ -403,7 +403,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; let result = d.u32(0); - for (const foo of tgpu['~unstable'].unroll(acc.$)) { + for (const foo of tgpu.unroll(acc.$)) { result += foo; } @@ -433,7 +433,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; - for (const foo of tgpu['~unstable'].unroll([1, 2, 3, 4, 5, 6, 7, 8, 9])) { + for (const foo of tgpu.unroll([1, 2, 3, 4, 5, 6, 7, 8, 9])) { continue; } }; @@ -478,17 +478,12 @@ describe('tgpu.unroll', () => { // TODO // // const arr = [1, 2, 3]; - // for (const foo of tgpu['~unstable'].unroll(arr)) { // should operate on indices + // for (const foo of tgpu.unroll(arr)) { // should operate on indices // result -= foo; // } // const v = d.vec2f(); - // for (const foo of tgpu['~unstable'].unroll(v)) { // should operate on indices + // for (const foo of tgpu.unroll(v)) { // should operate on indices // result *= foo; // } - - // for (const foo of tgpu['~unstable'].unroll([1, 2, 3])) { // should operate on values - // const foo = 1; - // result += d.f32(foo); - // } }); From c765aba5410419547533578a70ac57c0d710228a Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Sat, 21 Feb 2026 00:15:17 +0100 Subject: [PATCH 48/67] working unroll (I hope) --- packages/typegpu/src/tgsl/wgslGenerator.ts | 96 ++++---- packages/typegpu/tests/unroll.test.ts | 255 ++++++++++++++++++--- 2 files changed, 270 insertions(+), 81 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 79adeccb9a..89e9fb430b 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1188,9 +1188,8 @@ ${this.ctx.pre}else ${alternate}`; let ctxIndent = false; + this.ctx.pushBlockScope(); try { - this.ctx.pushBlockScope(); - const iterableExpr = this.expression(iterable); let shouldUnroll = iterableExpr.value instanceof UnrollableIterable; const iterableSnippet = shouldUnroll @@ -1202,10 +1201,12 @@ ${this.ctx.pre}else ${alternate}`; iterableSnippet, shouldUnroll, ); + const originalLoopVarName = loopVar[1]; + const blockified = blockifySingleStatement(body); if ( shouldUnroll && - !isKnownAtComptime(iterableSnippet) + (stitch`${elementCountSnippet}`).includes('arrayLength') ) { console.warn( 'Cannot unroll loop. The iterable is unknown at compile-time. Fallback to regular loop.', @@ -1214,8 +1215,9 @@ ${this.ctx.pre}else ${alternate}`; } if (shouldUnroll) { + const count = elementCountSnippet.value as number; // we know it is a number, because it is known at compile-time - if ((elementCountSnippet.value as number) > 8) { + if (count > 8) { console.warn( `Unrolling ${elementCountSnippet.value} iterations exceeds recommended limit of 8. Consider using a smaller array or runtime loop.`, ); @@ -1223,61 +1225,52 @@ ${this.ctx.pre}else ${alternate}`; if (ephemeralIterable) { const { value, dataType } = iterableSnippet; - if (value instanceof ArrayExpression) { - const blockified = blockifySingleStatement(body); - const iterations = value.elements.map((e) => - `${this.ctx.pre}${ - this.block(blockified, { [`${loopVar[1]}`]: e }) // `e` is already a snippet - }` - ); - - return iterations.join('\n'); - } - - if (wgsl.isVec(dataType)) { - const blockified = blockifySingleStatement(body); - const iterations = (value as wgsl.AnyVecInstance) // it's ephemeral, it's not a string - .map((e) => - `${this.ctx.pre}${ - this.block(blockified, { - [loopVar[1]]: snip( // we can give `e` a type - e, - dataType.primitive, - iterableSnippet.origin, - ), - }) - }` - ); - - return iterations.join('\n'); - } - if (Array.isArray(value)) { - const blockified = blockifySingleStatement(body); + const elements = value instanceof ArrayExpression + ? value.elements // already snippets + : wgsl.isVec(dataType) + ? (value as wgsl.AnyVecInstance).map((e) => + snip(e, dataType.primitive, iterableSnippet.origin) // we can give `e` the exact type inferred from vector + ) + : Array.isArray(value) + ? value // type inferred later + : []; // error already thrown by `getElementCountSnippet` - const iterations = value.map((e) => + return elements + .map((e) => `${this.ctx.pre}${ - this.block(blockified, { - [loopVar[1]]: e, // cannot infer `e` type, we will do it later - }) + this.block(blockified, { [originalLoopVarName]: e }) }` - ); - - return iterations.join('\n'); - } - - // if not any of the above types, then error has been already thrown by `getElementCountSnippet` + ) + .join('\n'); } - throw new Error('Not implemented'); + // iterable stored in a variable, we still can unroll it + return Array.from({ length: count }, (_, i) => { + const elementSnippet = forOfUtils.getElementSnippet( + iterableSnippet, + `${i}u`, + ); + return `${this.ctx.pre}${ + this.block(blockified, { + [originalLoopVarName]: elementSnippet, + }) + }`; + }).join('\n'); } - if (!shouldUnroll && !ephemeralIterable) { + if (!ephemeralIterable) { const index = this.ctx.makeNameValid('i'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, index, ); + const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet); + const elementType = forOfUtils.getElementType( + elementSnippet, + iterableSnippet, + ); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ @@ -1287,13 +1280,6 @@ ${this.ctx.pre}else ${alternate}`; this.ctx.indent(); ctxIndent = true; - const loopVarName = this.ctx.makeNameValid(loopVar[1]); - const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet); - const elementType = forOfUtils.getElementType( - elementSnippet, - iterableSnippet, - ); - const loopVarDeclStr = stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ tryConvertSnippet( @@ -1305,8 +1291,8 @@ ${this.ctx.pre}else ${alternate}`; };`; const bodyStr = `${this.ctx.pre}${ - this.block(blockifySingleStatement(body), { - [loopVar[1]]: snip( + this.block(blockified, { + [originalLoopVarName]: snip( loopVarName, elementType, elementSnippet.origin, diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index a36385b392..7c1d421cf7 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -300,6 +300,7 @@ describe('tgpu.unroll', () => { `); }); + // TODO: is this dangerous?? it('unrolls array expression of pointers', () => { const f = () => { 'use gpu'; @@ -396,34 +397,40 @@ describe('tgpu.unroll', () => { `); }); - it('warns when iterable is unknown at compile-time and fallbacks to regular loop', ({ root }) => { - const b = root.createUniform(d.arrayOf(d.u32, 7)); - const acc = tgpu.accessor(d.arrayOf(d.u32, 7), b); + it('warns when iterable element count is unknown at compile-time and fallbacks to regular loop', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation( + () => {}, + ); + + const layout = tgpu.bindGroupLayout({ + arr: { storage: d.arrayOf(d.f32) }, + }); const f = () => { 'use gpu'; - let result = d.u32(0); - for (const foo of tgpu.unroll(acc.$)) { - result += foo; + let res = d.f32(0); + for (const foo of tgpu.unroll(layout.$.arr)) { + res += foo; } - - return result; }; expect(tgpu.resolve([f])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var b: array; + "@group(0) @binding(0) var arr: array; - fn f() -> u32 { - var result = 0u; - for (var i = 0u; i < 7; i++) { - let foo = b[i]; + fn f() { + var res = 0f; + for (var i = 0u; i < arrayLength((&arr)); i++) { + let foo = arr[i]; { - result += foo; + res += foo; } } - return result; }" `); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + 'Cannot unroll loop. The iterable is unknown at compile-time. Fallback to regular loop.', + ); }); it('warns when number of iteration to unroll is greater than 8', () => { @@ -475,15 +482,211 @@ describe('tgpu.unroll', () => { ); }); - // TODO - // - // const arr = [1, 2, 3]; - // for (const foo of tgpu.unroll(arr)) { // should operate on indices - // result -= foo; - // } - - // const v = d.vec2f(); - // for (const foo of tgpu.unroll(v)) { // should operate on indices - // result *= foo; - // } + it('unrolls named iterable of primitives', () => { + const f = () => { + 'use gpu'; + const arr = [1, 2, 3]; + let res = d.f32(0); + for (const foo of tgpu.unroll(arr)) { + res += foo; + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> f32 { + var arr = array(1, 2, 3); + var res = 0f; + { + res += f32(arr[0u]); + } + { + res += f32(arr[1u]); + } + { + res += f32(arr[2u]); + } + return res; + }" + `); + }); + + it('unrolls named iterable of vectors', () => { + const f = () => { + 'use gpu'; + + const v1 = d.vec2f(1); + const v2 = d.vec2f(8); + const v3 = d.vec2f(2); + const arr = d.arrayOf(d.vec2f, 4)([v1, v2, v2, v3]); + let res = d.vec2f(); + + for (const foo of tgpu.unroll(arr)) { + res = res.add(foo); + foo.x = 7; + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> vec2f { + var v1 = vec2f(1); + var v2 = vec2f(8); + var v3 = vec2f(2); + var arr = array(v1, v2, v2, v3); + var res = vec2f(); + { + res = (res + arr[0u]); + arr[0u].x = 7f; + } + { + res = (res + arr[1u]); + arr[1u].x = 7f; + } + { + res = (res + arr[2u]); + arr[2u].x = 7f; + } + { + res = (res + arr[3u]); + arr[3u].x = 7f; + } + return res; + }" + `); + }); + + it('unrolls named iterable of complex types', () => { + const Boid = d.struct({ + pos: d.vec2i, + vel: d.vec2f, + }); + + const f = () => { + 'use gpu'; + const b1 = Boid({ pos: d.vec2i(1), vel: d.vec2f(1) }); + const b2 = Boid({ pos: d.vec2i(2), vel: d.vec2f(2) }); + const arr = d.arrayOf(Boid, 2)([b1, b2]); + let res = d.vec2f(); + + for (const foo of tgpu.unroll(arr)) { + res = res.add(foo.vel); + foo.pos.x = 7; + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "struct Boid { + pos: vec2i, + vel: vec2f, + } + + fn f() -> vec2f { + var b1 = Boid(vec2i(1), vec2f(1)); + var b2 = Boid(vec2i(2), vec2f(2)); + var arr = array(b1, b2); + var res = vec2f(); + { + res = (res + arr[0u].vel); + arr[0u].pos.x = 7i; + } + { + res = (res + arr[1u].vel); + arr[1u].pos.x = 7i; + } + return res; + }" + `); + }); + + it('unrolls buffer iterable', ({ root }) => { + const b = root.createUniform(d.arrayOf(d.u32, 7)); + const acc = tgpu.accessor(d.arrayOf(d.u32, 7), b); + + const f = () => { + 'use gpu'; + let result = d.u32(0); + for (const foo of tgpu.unroll(acc.$)) { + result += foo; + } + + return result; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var b: array; + + fn f() -> u32 { + var result = 0u; + { + result += b[0u]; + } + { + result += b[1u]; + } + { + result += b[2u]; + } + { + result += b[3u]; + } + { + result += b[4u]; + } + { + result += b[5u]; + } + { + result += b[6u]; + } + return result; + }" + `); + }); + + it('can be conditinally applied', () => { + const unroll = tgpu.accessor(d.bool, true); + + const f = () => { + 'use gpu'; + const arr = [1, 2, 3]; + let r = d.f32(0); + for (const foo of (unroll.$ ? tgpu.unroll(arr) : arr)) { + r += foo; + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var arr = array(1, 2, 3); + var r = 0f; + { + r += f32(arr[0u]); + } + { + r += f32(arr[1u]); + } + { + r += f32(arr[2u]); + } + }" + `); + expect(tgpu.resolve([tgpu.fn(f).with(unroll, false)])) + .toMatchInlineSnapshot(` + "fn f() { + var arr = array(1, 2, 3); + var r = 0f; + for (var i = 0u; i < 3; i++) { + let foo = arr[i]; + { + r += f32(foo); + } + } + }" + `); + }); }); From 7a2901c4c3d7ab94c95bd3d88739e6b125ec7d7f Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 11:30:48 +0100 Subject: [PATCH 49/67] regular for header in new block scope --- packages/typegpu/src/tgsl/wgslGenerator.ts | 29 +++++++++++-------- .../tests/examples/individual/3d-fish.test.ts | 8 ++--- .../tests/examples/individual/blur.test.ts | 6 ++-- .../examples/individual/jelly-slider.test.ts | 2 +- .../examples/individual/jelly-switch.test.ts | 2 +- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 91dc9de10c..381511ffd8 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1140,18 +1140,23 @@ ${this.ctx.pre}else ${alternate}`; if (statement[0] === NODE.for) { const [_, init, condition, update, body] = statement; - const [initStatement, conditionExpr, updateStatement] = this.ctx - .withResetIndentLevel(() => [ - init ? this.statement(init) : undefined, - condition ? this.typedExpression(condition, bool) : undefined, - update ? this.statement(update) : undefined, - ]); - - const initStr = initStatement ? initStatement.slice(0, -1) : ''; - const updateStr = updateStatement ? updateStatement.slice(0, -1) : ''; - - const bodyStr = this.block(blockifySingleStatement(body)); - return stitch`${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr}`; + try { + this.ctx.pushBlockScope(); + const [initStatement, conditionExpr, updateStatement] = this.ctx + .withResetIndentLevel(() => [ + init ? this.statement(init) : undefined, + condition ? this.typedExpression(condition, bool) : undefined, + update ? this.statement(update) : undefined, + ]); + + const initStr = initStatement ? initStatement.slice(0, -1) : ''; + const updateStr = updateStatement ? updateStatement.slice(0, -1) : ''; + + const bodyStr = this.block(blockifySingleStatement(body)); + return stitch`${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr}`; + } finally { + this.ctx.popBlockScope(); + } } if (statement[0] === NODE.while) { diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 0f5f2cb656..4c75b546b6 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -160,11 +160,11 @@ describe('3d fish example', () => { if ((cohesionCount > 0i)) { cohesion = (((1f / f32(cohesionCount)) * cohesion) - (*fishData).position); } - for (var i_1 = 0; (i_1 < 3i); i_1 += 1i) { + for (var i = 0; (i < 3i); i += 1i) { var repulsion = vec3f(); - repulsion[i_1] = 1f; - let axisAquariumSize = (vec3f(10, 4, 10)[i_1] / 2f); - let axisPosition = (*fishData).position[i_1]; + repulsion[i] = 1f; + let axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); + let axisPosition = (*fishData).position[i]; const distance_1 = 0.1; if ((axisPosition > (axisAquariumSize - distance_1))) { let str2 = (axisPosition - (axisAquariumSize - distance_1)); diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index 8732ae8e21..137c90c309 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -62,9 +62,9 @@ describe('blur example', () => { } } workgroupBarrier(); - for (var r_1 = 0; (r_1 < 4i); r_1++) { + for (var r = 0; (r < 4i); r++) { for (var c = 0; (c < 4i); c++) { - var writeIndex = (baseIndex + vec2i(c, r_1)); + var writeIndex = (baseIndex + vec2i(c, r)); if ((flip != 0u)) { writeIndex = writeIndex.yx; } @@ -73,7 +73,7 @@ describe('blur example', () => { var acc = vec3f(); for (var f = 0; (f < (*settings2).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[r_1][i] * (1f / f32((*settings2).filterDim)))); + acc = (acc + (tileData[r][i] * (1f / f32((*settings2).filterDim)))); } textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index 4d8f6242eb..544d5b5ae7 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -533,7 +533,7 @@ describe('jelly-slider example', () => { return background; } var distanceFromOrigin = max(0f, intersection.tMin); - for (var i_1 = 0; (i_1 < 64i); i_1++) { + for (var i = 0; (i < 64i); i++) { if ((totalSteps >= 64u)) { break; } diff --git a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts index 061c3e8411..8b4760c223 100644 --- a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts @@ -337,7 +337,7 @@ describe('jelly switch example', () => { return background; } var distanceFromOrigin = max(0f, intersection.tMin); - for (var i_1 = 0; (i_1 < 64i); i_1++) { + for (var i = 0; (i < 64i); i++) { if ((totalSteps >= 64u)) { break; } From 4a0843308f63e563bfb2292c3442b1909df71784 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 15:31:43 +0100 Subject: [PATCH 50/67] forbid pointers in AE --- packages/typegpu/src/tgsl/wgslGenerator.ts | 60 ++++++--- packages/typegpu/tests/unroll.test.ts | 138 +++++++-------------- 2 files changed, 91 insertions(+), 107 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 6086ee3c3e..19d2ad5e8d 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1210,28 +1210,27 @@ ${this.ctx.pre}else ${alternate}`; const originalLoopVarName = loopVar[1]; const blockified = blockifySingleStatement(body); - if ( - shouldUnroll && - (stitch`${elementCountSnippet}`).includes('arrayLength') - ) { - console.warn( - 'Cannot unroll loop. The iterable is unknown at compile-time. Fallback to regular loop.', - ); - shouldUnroll = false; - } - if (shouldUnroll) { - const count = elementCountSnippet.value as number; - // we know it is a number, because it is known at compile-time - if (count > 8) { - console.warn( - `Unrolling ${elementCountSnippet.value} iterations exceeds recommended limit of 8. Consider using a smaller array or runtime loop.`, + if ((stitch`${elementCountSnippet}`).includes('arrayLength')) { + throw new Error( + 'Cannot unroll loop. Length of iterable is unknown at compile-time.', ); } + // we know it is a number, because it is known at compile-time + const length = elementCountSnippet.value as number; + if (length === 0) { + return ''; + } + if (ephemeralIterable) { const { value, dataType } = iterableSnippet; + // when `iterableSnippet.value` is ArrayExpression, we need to resolve it to ensure the array being created is valid + if (value instanceof ArrayExpression) { + this.ctx.resolve(value); + } + const elements = value instanceof ArrayExpression ? value.elements // already snippets : wgsl.isVec(dataType) @@ -1242,6 +1241,19 @@ ${this.ctx.pre}else ${alternate}`; ? value // type inferred later : []; // error already thrown by `getElementCountSnippet` + // TODO: replace it with tinyest traversal + const bodyTemplate = this.block(blockified, { + [originalLoopVarName]: elements[0], + }); + if ( + bodyTemplate.includes('break') || + bodyTemplate.includes('continue') + ) { + throw new WgslTypeError( + 'Cannot unroll loop containing `break` or `continue`', + ); + } + return elements .map((e) => `${this.ctx.pre}${ @@ -1251,8 +1263,24 @@ ${this.ctx.pre}else ${alternate}`; .join('\n'); } + // TODO: replace it with tinyest traversal + const bodyTemplate = this.block(blockified, { + [originalLoopVarName]: forOfUtils.getElementSnippet( + iterableSnippet, + '0u', + ), + }); + if ( + bodyTemplate.includes('break') || + bodyTemplate.includes('continue') + ) { + throw new WgslTypeError( + 'Cannot unroll loop containing `break` or `continue`', + ); + } + // iterable stored in a variable, we still can unroll it - return Array.from({ length: count }, (_, i) => { + return Array.from({ length }, (_, i) => { const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, `${i}u`, diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 7c1d421cf7..97d51b99dc 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -137,7 +137,7 @@ describe('tgpu.unroll', () => { const b1 = Boid({ pos: d.vec2i(1), vel: d.vec2f(1) }); const b2 = Boid({ pos: d.vec2i(2), vel: d.vec2f(2) }); let res = d.vec2f(); - for (const foo of tgpu.unroll([b1, b2])) { + for (const foo of tgpu.unroll([Boid(b1), Boid(b2)])) { for (const boo of tgpu.unroll([Boid(), Boid()])) { res = res.add(foo.vel).add(boo.vel); } @@ -300,8 +300,7 @@ describe('tgpu.unroll', () => { `); }); - // TODO: is this dangerous?? - it('unrolls array expression of pointers', () => { + it('throws when iterable is array expression of pointers', () => { const f = () => { 'use gpu'; let res = d.vec2f(); @@ -309,30 +308,21 @@ describe('tgpu.unroll', () => { const v2 = d.vec2f(3); for (const foo of tgpu.unroll([v1, v2])) { res = res.add(foo); - const boo = foo; - boo.x = 6; + foo.x = 6; } return res; }; - expect(tgpu.resolve([f])).toMatchInlineSnapshot(` - "fn f() -> vec2f { - var res = vec2f(); - var v1 = vec2f(7); - var v2 = vec2f(3); - { - res = (res + v1); - let boo = (&v1); - (*boo).x = 6f; - } - { - res = (res + v2); - let boo = (&v2); - (*boo).x = 6f; - } - return res; - }" + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f() + - ArrayExpression: 'v1' reference cannot be used in an array constructor. + ----- + Try 'vec2f(v1)' or 'arrayOf(vec2f, count)([...])' to copy the value instead. + -----] `); }); @@ -398,10 +388,6 @@ describe('tgpu.unroll', () => { }); it('warns when iterable element count is unknown at compile-time and fallbacks to regular loop', ({ root }) => { - using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation( - () => {}, - ); - const layout = tgpu.bindGroupLayout({ arr: { storage: d.arrayOf(d.f32) }, }); @@ -414,72 +400,12 @@ describe('tgpu.unroll', () => { } }; - expect(tgpu.resolve([f])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var arr: array; - - fn f() { - var res = 0f; - for (var i = 0u; i < arrayLength((&arr)); i++) { - let foo = arr[i]; - { - res += foo; - } - } - }" - `); - - expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Cannot unroll loop. The iterable is unknown at compile-time. Fallback to regular loop.', - ); - }); - - it('warns when number of iteration to unroll is greater than 8', () => { - using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation( - () => {}, - ); - - const f = () => { - 'use gpu'; - for (const foo of tgpu.unroll([1, 2, 3, 4, 5, 6, 7, 8, 9])) { - continue; - } - }; - - expect(tgpu.resolve([f])).toMatchInlineSnapshot(` - "fn f() { - { - continue; - } - { - continue; - } - { - continue; - } - { - continue; - } - { - continue; - } - { - continue; - } - { - continue; - } - { - continue; - } - { - continue; - } - }" + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f(): Cannot unroll loop. Length of iterable is unknown at compile-time.] `); - - expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Unrolling 9 iterations exceeds recommended limit of 8. Consider using a smaller array or runtime loop.', - ); }); it('unrolls named iterable of primitives', () => { @@ -689,4 +615,34 @@ describe('tgpu.unroll', () => { }" `); }); + + it('throws when `continue` or `break` is used inside the loop body', () => { + const f1 = () => { + 'use gpu'; + for (const foo of tgpu.unroll([1, 2])) { + continue; + } + }; + + expect(() => tgpu.resolve([f1])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f1 + - fn*:f1(): Cannot unroll loop containing \`break\` or \`continue\`] + `); + + const f2 = () => { + 'use gpu'; + for (const foo of tgpu.unroll([1, 2])) { + break; + } + }; + + expect(() => tgpu.resolve([f2])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f2 + - fn*:f2(): Cannot unroll loop containing \`break\` or \`continue\`] + `); + }); }); From 42da80690214dc4497f29579d9e2174f8484cb01 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 15:40:20 +0100 Subject: [PATCH 51/67] cleanup --- packages/typegpu/src/tgsl/wgslGenerator.ts | 54 +++++++++------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 19d2ad5e8d..689890d055 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1194,8 +1194,8 @@ ${this.ctx.pre}else ${alternate}`; let ctxIndent = false; - this.ctx.pushBlockScope(); try { + this.ctx.pushBlockScope(); const iterableExpr = this.expression(iterable); let shouldUnroll = iterableExpr.value instanceof UnrollableIterable; const iterableSnippet = shouldUnroll @@ -1241,46 +1241,27 @@ ${this.ctx.pre}else ${alternate}`; ? value // type inferred later : []; // error already thrown by `getElementCountSnippet` + const blocks = elements + .map((e) => + `${this.ctx.pre}${ + this.block(blockified, { [originalLoopVarName]: e }) + }` + ); + // TODO: replace it with tinyest traversal - const bodyTemplate = this.block(blockified, { - [originalLoopVarName]: elements[0], - }); if ( - bodyTemplate.includes('break') || - bodyTemplate.includes('continue') + blocks[0]?.includes('break') || blocks[0]?.includes('continue') ) { throw new WgslTypeError( 'Cannot unroll loop containing `break` or `continue`', ); } - return elements - .map((e) => - `${this.ctx.pre}${ - this.block(blockified, { [originalLoopVarName]: e }) - }` - ) - .join('\n'); - } - - // TODO: replace it with tinyest traversal - const bodyTemplate = this.block(blockified, { - [originalLoopVarName]: forOfUtils.getElementSnippet( - iterableSnippet, - '0u', - ), - }); - if ( - bodyTemplate.includes('break') || - bodyTemplate.includes('continue') - ) { - throw new WgslTypeError( - 'Cannot unroll loop containing `break` or `continue`', - ); + return blocks.join('\n'); } // iterable stored in a variable, we still can unroll it - return Array.from({ length }, (_, i) => { + const blocks = Array.from({ length }, (_, i) => { const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, `${i}u`, @@ -1290,7 +1271,18 @@ ${this.ctx.pre}else ${alternate}`; [originalLoopVarName]: elementSnippet, }) }`; - }).join('\n'); + }); + + // TODO: replace it with tinyest traversal + if ( + blocks[0]?.includes('break') || blocks[0]?.includes('continue') + ) { + throw new WgslTypeError( + 'Cannot unroll loop containing `break` or `continue`', + ); + } + + return blocks.join('\n'); } if (!ephemeralIterable) { From 060d5170bf5a26caee60d939061524d0ed96666d Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 16:55:27 +0100 Subject: [PATCH 52/67] unroll in examples --- .../algorithms/jump-flood-distance/index.ts | 4 +- .../algorithms/jump-flood-voronoi/index.ts | 21 +- .../background-segmentation/shaders.ts | 8 +- .../examples/image-processing/blur/index.ts | 8 +- .../src/examples/rendering/3d-fish/compute.ts | 4 +- .../src/examples/rendering/clouds/utils.ts | 3 +- .../rendering/cubemap-reflection/icosphere.ts | 2 +- .../examples/rendering/jelly-slider/taa.ts | 4 +- .../examples/rendering/jelly-switch/taa.ts | 4 +- .../src/examples/simple/liquid-glass/index.ts | 2 +- .../src/examples/simple/ripple-cube/pbr.ts | 4 +- .../simple/ripple-cube/post-processing.ts | 4 +- .../fluid-double-buffering/index.ts | 22 +- .../simulation/slime-mold-3d/index.ts | 5 +- .../examples/simulation/slime-mold/index.ts | 4 +- .../simulation/stable-fluid/simulation.ts | 2 +- .../tests/examples/individual/3d-fish.test.ts | 38 +- .../tests/examples/individual/blur.test.ts | 360 +++++++++++++++++- .../tests/examples/individual/clouds.test.ts | 12 +- .../individual/cubemap-reflection.test.ts | 142 ++++++- .../individual/fluid-double-buffering.test.ts | 130 ++++++- .../examples/individual/jelly-slider.test.ts | 66 +++- .../examples/individual/jelly-switch.test.ts | 66 +++- .../individual/jump-flood-distance.test.ts | 146 ++++++- .../individual/jump-flood-voronoi.test.ts | 101 ++++- .../examples/individual/liquid-glass.test.ts | 14 +- .../examples/individual/ripple-cube.test.ts | 73 +++- .../examples/individual/slime-mold-3d.test.ts | 75 +++- .../examples/individual/slime-mold.test.ts | 82 +++- .../examples/individual/stable-fluid.test.ts | 52 ++- 30 files changed, 1332 insertions(+), 126 deletions(-) diff --git a/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts b/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts index 68ed8df8ab..e18d9298f0 100644 --- a/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts @@ -186,8 +186,8 @@ const jumpFlood = root.createGuardedComputePipeline((x, y) => { let bestInsideDist = 1e20; let bestOutsideDist = 1e20; - for (let dy = -1; dy <= 1; dy++) { - for (let dx = -1; dx <= 1; dx++) { + for (const dx of tgpu.unroll([-1, 0, 1])) { + for (const dy of tgpu.unroll([-1, 0, 1])) { const sample = sampleWithOffset( pingPongLayout.$.readView, d.vec2i(x, y), diff --git a/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts b/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts index bedb3d0542..42ffb3d41f 100644 --- a/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts @@ -165,22 +165,23 @@ const jumpFlood = root.createGuardedComputePipeline((x, y) => { let minDist = 1e20; let bestSample = SampleResult({ color: d.vec4f(), coord: d.vec2f(-1) }); - for (let dy = -1; dy <= 1; dy++) { - for (let dx = -1; dx <= 1; dx++) { + for (const dy of tgpu.unroll([-1, 0, 1])) { + for (const dx of tgpu.unroll([-1, 0, 1])) { const sample = sampleWithOffset( pingPongLayout.$.readView, d.vec2i(x, y), d.vec2i(dx * offset, dy * offset), ); - if (sample.coord.x < 0) { - continue; - } - - const dist = std.distance(d.vec2f(x, y), sample.coord.mul(d.vec2f(size))); - if (dist < minDist) { - minDist = dist; - bestSample = SampleResult(sample); + if (sample.coord.x >= 0) { + const dist = std.distance( + d.vec2f(x, y), + sample.coord.mul(d.vec2f(size)), + ); + if (dist < minDist) { + minDist = dist; + bestSample = SampleResult(sample); + } } } } diff --git a/apps/typegpu-docs/src/examples/image-processing/background-segmentation/shaders.ts b/apps/typegpu-docs/src/examples/image-processing/background-segmentation/shaders.ts index 3569da6019..19c2244828 100644 --- a/apps/typegpu-docs/src/examples/image-processing/background-segmentation/shaders.ts +++ b/apps/typegpu-docs/src/examples/image-processing/background-segmentation/shaders.ts @@ -56,8 +56,8 @@ export const computeFn = tgpu.computeFn({ ).sub(d.vec2i(filterOffset, 0)); // Load a tile of pixels into shared memory - for (let r = 0; r < 4; r++) { - for (let c = 0; c < 4; c++) { + for (const r of tgpu.unroll([0, 1, 2, 3])) { + for (const c of tgpu.unroll([0, 1, 2, 3])) { let loadIndex = baseIndex.add(d.vec2i(c, r)); if (flipAccess.$) { loadIndex = loadIndex.yx; @@ -75,8 +75,8 @@ export const computeFn = tgpu.computeFn({ std.workgroupBarrier(); // Apply the horizontal blur filter and write to the output texture - for (let r = 0; r < 4; r++) { - for (let c = 0; c < 4; c++) { + for (const r of tgpu.unroll([0, 1, 2, 3])) { + for (const c of tgpu.unroll([0, 1, 2, 3])) { let writeIndex = baseIndex.add(d.vec2i(c, r)); if (flipAccess.$) { writeIndex = writeIndex.yx; diff --git a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts index 86b959f33d..43eaa3c804 100644 --- a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts @@ -75,8 +75,8 @@ const computeFn = tgpu.computeFn({ ).sub(d.vec2i(filterOffset, 0)); // Load a tile of pixels into shared memory - for (let r = 0; r < 4; r++) { - for (let c = 0; c < 4; c++) { + for (const r of tgpu.unroll([0, 1, 2, 3])) { + for (const c of tgpu.unroll([0, 1, 2, 3])) { let loadIndex = baseIndex.add(d.vec2i(c, r)); if (ioLayout.$.flip !== 0) { loadIndex = loadIndex.yx; @@ -94,8 +94,8 @@ const computeFn = tgpu.computeFn({ std.workgroupBarrier(); // Apply the horizontal blur filter and write to the output texture - for (let r = 0; r < 4; r++) { - for (let c = 0; c < 4; c++) { + for (const r of tgpu.unroll([0, 1, 2, 3])) { + for (const c of tgpu.unroll([0, 1, 2, 3])) { let writeIndex = baseIndex.add(d.vec2i(c, r)); if (ioLayout.$.flip !== 0) { writeIndex = writeIndex.yx; diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts index d69c6547e0..4bbf60ee7a 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts @@ -1,4 +1,4 @@ -import { d, std } from 'typegpu'; +import tgpu, { d, std } from 'typegpu'; import * as p from './params.ts'; import { computeBindGroupLayout as layout } from './schemas.ts'; import { projectPointOnLine } from './tgsl-helpers.ts'; @@ -42,7 +42,7 @@ export const simulate = (fishIndex: number) => { fishData.position, ); } - for (let i = 0; i < 3; i += 1) { + for (const i of tgpu.unroll([0, 1, 2])) { const repulsion = d.vec3f(); repulsion[i] = 1.0; diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts index 71a1bec9b5..c5cff34695 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts +++ b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts @@ -72,12 +72,13 @@ export const raymarch = tgpu.fn([d.vec3f, d.vec3f, d.vec3f], d.vec4f)( }, ); +const iterations = Array.from({ length: FBM_OCTAVES }); const fbm = tgpu.fn([d.vec3f], d.f32)((pos) => { let sum = d.f32(); let amp = d.f32(CLOUD_AMPLITUDE); let freq = d.f32(CLOUD_FREQUENCY); - for (let i = 0; i < FBM_OCTAVES; i++) { + for (const i of tgpu.unroll(iterations)) { sum += noise3d(std.mul(pos, freq)) * amp; amp *= FBM_PERSISTENCE; freq *= FBM_LACUNARITY; diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts index f81d3fbf6e..07c4a97e89 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts @@ -171,7 +171,7 @@ export class IcosphereGenerator { ]); const baseIndexNext = triangleIndex * 12; - for (let i = d.u32(0); i < 12; i++) { + for (const i of tgpu.unroll([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])) { const reprojectedVertex = newVertices[i]; const triBase = i - (i % 3); diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-slider/taa.ts b/apps/typegpu-docs/src/examples/rendering/jelly-slider/taa.ts index 6956e2f700..2ce69e7096 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-slider/taa.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-slider/taa.ts @@ -25,8 +25,8 @@ export const taaResolveFn = tgpu.computeFn({ const dimensions = std.textureDimensions(taaResolveLayout.$.currentTexture); - for (let x = -1; x <= 1; x++) { - for (let y = -1; y <= 1; y++) { + for (const x of tgpu.unroll([-1, 0, 1])) { + for (const y of tgpu.unroll([-1, 0, 1])) { const sampleCoord = d.vec2i(gid.xy).add(d.vec2i(x, y)); const clampedCoord = std.clamp( sampleCoord, diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts b/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts index 3748b5cd1e..2474ec5265 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts @@ -25,8 +25,8 @@ export const taaResolveFn = tgpu.computeFn({ const dimensions = std.textureDimensions(taaResolveLayout.$.currentTexture); - for (let x = -1; x <= 1; x++) { - for (let y = -1; y <= 1; y++) { + for (const x of tgpu.unroll([-1, 0, 1])) { + for (const y of tgpu.unroll([-1, 0, 1])) { const sampleCoord = d.vec2i(gid.xy).add(d.vec2i(x, y)); const clampedCoord = std.clamp( sampleCoord, diff --git a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts index 1fc10579c3..e07e821ebe 100644 --- a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts +++ b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts @@ -96,7 +96,7 @@ const sampleWithChromaticAberration = ( ) => { 'use gpu'; const samples = d.arrayOf(d.vec3f, 3)(); - for (let i = 0; i < 3; i++) { + for (const i of tgpu.unroll([0, 1, 2])) { const channelOffset = dir.mul((d.f32(i) - 1.0) * offset); samples[i] = std.textureSampleBias(tex, sampler, uv.sub(channelOffset), blur).rgb; diff --git a/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts b/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts index 31867f24b4..feabd482ca 100644 --- a/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts +++ b/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts @@ -86,13 +86,15 @@ export const evaluateLight = ( .mul(ndotl); }; +const lightCountIterations = Array.from({ length: LIGHT_COUNT }) + .map((_, i) => i); export const shade = (p: d.v3f, n: d.v3f, v: d.v3f): d.v3f => { 'use gpu'; const material = materialAccess.$; const f0 = std.mix(d.vec3f(0.04), material.albedo, material.metallic); let lo = d.vec3f(0); - for (let i = 0; i < LIGHT_COUNT; i++) { + for (const i of tgpu.unroll(lightCountIterations)) { lo = lo.add(evaluateLight(p, n, v, lightsAccess.$[i], material, f0)); } diff --git a/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts b/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts index c3f52ae727..09440ad921 100644 --- a/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts +++ b/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts @@ -82,8 +82,8 @@ export function createPostProcessingPipelines( let minColor = d.vec3f(9999); let maxColor = d.vec3f(-9999); - for (let ox = -1; ox <= 1; ox++) { - for (let oy = -1; oy <= 1; oy++) { + for (const ox of tgpu.unroll([-1, 0, 1])) { + for (const oy of tgpu.unroll([-1, 0, 1])) { const sampleCoord = coord.add(d.vec2i(ox, oy)); const clampedCoord = std.clamp( sampleCoord, diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index 6a93d3578f..f01bf31b96 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -160,21 +160,19 @@ const computeVelocity = (x: number, y: number): d.v2f => { ]; let dirChoiceCount = 1; - for (const offset of neighborOffsets) { + for (const offset of tgpu.unroll(neighborOffsets)) { const neighborDensity = getCell(x + offset.x, y + offset.y); const cost = neighborDensity.z + d.f32(offset.y) * gravityCost; - if (!isValidFlowOut(x + offset.x, y + offset.y)) { - continue; - } - - if (cost === leastCost) { - dirChoices[dirChoiceCount] = d.vec2f(d.f32(offset.x), d.f32(offset.y)); - dirChoiceCount++; - } else if (cost < leastCost) { - leastCost = cost; - dirChoices[0] = d.vec2f(d.f32(offset.x), d.f32(offset.y)); - dirChoiceCount = 1; + if (isValidFlowOut(x + offset.x, y + offset.y)) { + if (cost === leastCost) { + dirChoices[dirChoiceCount] = d.vec2f(d.f32(offset.x), d.f32(offset.y)); + dirChoiceCount++; + } else if (cost < leastCost) { + leastCost = cost; + dirChoices[0] = d.vec2f(d.f32(offset.x), d.f32(offset.y)); + dirChoiceCount = 1; + } } } diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts index 30a277d288..f6989878ff 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts @@ -177,6 +177,8 @@ const getPerpendicular = (dir: d.v3f) => { return std.normalize(std.cross(dir, axis)); }; +const numSamples = 8; +const samplesIterations = Array.from({ length: numSamples }).map((_, i) => i); const sense3D = (pos: d.v3f, direction: d.v3f) => { 'use gpu'; const dims = std.textureDimensions(computeLayout.$.oldState); @@ -188,8 +190,7 @@ const sense3D = (pos: d.v3f, direction: d.v3f) => { const perp1 = getPerpendicular(direction); const perp2 = std.cross(direction, perp1); - const numSamples = 8; - for (let i = 0; i < numSamples; i++) { + for (const i of tgpu.unroll(samplesIterations)) { const theta = (i / numSamples) * 2 * Math.PI; const coneOffset = perp1.mul(std.cos(theta)).add(perp2.mul(std.sin(theta))); diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts index 2110fa5f26..edf4a9e6a1 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts @@ -168,8 +168,8 @@ const blur = tgpu.computeFn({ let count = d.f32(); // 3x3 blur kernel - for (let offsetY = -1; offsetY <= 1; offsetY++) { - for (let offsetX = -1; offsetX <= 1; offsetX++) { + for (const offsetY of tgpu.unroll([-1, 0, 1])) { + for (const offsetX of tgpu.unroll([-1, 0, 1])) { const samplePos = d.vec2i(gid.xy).add(d.vec2i(offsetX, offsetY)); const dimsi = d.vec2i(dims); diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts index 21bb70bdbf..d424c1adb1 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts @@ -9,7 +9,7 @@ const getNeighbors = tgpu.fn([d.vec2i, d.vec2i], d.arrayOf(d.vec2i, 4))( d.vec2i(1, 0), d.vec2i(0, 1), ]; - for (let i = 0; i < 4; i++) { + for (const i of tgpu.unroll([0, 1, 2, 3])) { adjacentOffsets[i] = std.clamp( std.add(coords, adjacentOffsets[i]), d.vec2i(), diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 4c75b546b6..e69f56efaf 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -160,11 +160,41 @@ describe('3d fish example', () => { if ((cohesionCount > 0i)) { cohesion = (((1f / f32(cohesionCount)) * cohesion) - (*fishData).position); } - for (var i = 0; (i < 3i); i += 1i) { + { var repulsion = vec3f(); - repulsion[i] = 1f; - let axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); - let axisPosition = (*fishData).position[i]; + repulsion[0i] = 1f; + const axisAquariumSize = 5f; + let axisPosition = (*fishData).position[0i]; + const distance_1 = 0.1; + if ((axisPosition > (axisAquariumSize - distance_1))) { + let str2 = (axisPosition - (axisAquariumSize - distance_1)); + wallRepulsion = (wallRepulsion - (repulsion * str2)); + } + if ((axisPosition < (-(axisAquariumSize) + distance_1))) { + let str2 = ((-(axisAquariumSize) + distance_1) - axisPosition); + wallRepulsion = (wallRepulsion + (repulsion * str2)); + } + } + { + var repulsion = vec3f(); + repulsion[1i] = 1f; + const axisAquariumSize = 2f; + let axisPosition = (*fishData).position[1i]; + const distance_1 = 0.1; + if ((axisPosition > (axisAquariumSize - distance_1))) { + let str2 = (axisPosition - (axisAquariumSize - distance_1)); + wallRepulsion = (wallRepulsion - (repulsion * str2)); + } + if ((axisPosition < (-(axisAquariumSize) + distance_1))) { + let str2 = ((-(axisAquariumSize) + distance_1) - axisPosition); + wallRepulsion = (wallRepulsion + (repulsion * str2)); + } + } + { + var repulsion = vec3f(); + repulsion[2i] = 1f; + const axisAquariumSize = 5f; + let axisPosition = (*fishData).position[2i]; const distance_1 = 0.1; if ((axisPosition > (axisAquariumSize - distance_1))) { let str2 = (axisPosition - (axisAquariumSize - distance_1)); diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index 137c90c309..c425c02f7e 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -52,28 +52,370 @@ describe('blur example', () => { let filterOffset = i32((f32(((*settings2).filterDim - 1i)) / 2f)); var dims = vec2i(textureDimensions(inTexture)); var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u((*settings2).blockDim, 4u)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0i)); - for (var r = 0; (r < 4i); r++) { - for (var c = 0; (c < 4i); c++) { - var loadIndex = (baseIndex + vec2i(c, r)); + { + { + var loadIndex = (baseIndex + vec2i()); if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[r][((_arg_0.lid.x * 4u) + u32(c))] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + tileData[0i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(1, 0)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[0i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(2, 0)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[0i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(3, 0)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[0i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + } + { + { + var loadIndex = (baseIndex + vec2i(0, 1)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[1i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(1)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[1i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(2, 1)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[1i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(3, 1)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[1i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + } + { + { + var loadIndex = (baseIndex + vec2i(0, 2)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[2i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(1, 2)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[2i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(2)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[2i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(3, 2)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[2i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + } + { + { + var loadIndex = (baseIndex + vec2i(0, 3)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[3i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(1, 3)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[3i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(2, 3)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[3i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + } + { + var loadIndex = (baseIndex + vec2i(3)); + if ((flip != 0u)) { + loadIndex = loadIndex.yx; + } + tileData[3i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } } workgroupBarrier(); - for (var r = 0; (r < 4i); r++) { - for (var c = 0; (c < 4i); c++) { - var writeIndex = (baseIndex + vec2i(c, r)); + { + { + var writeIndex = (baseIndex + vec2i()); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 0i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[0i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(1, 0)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 1i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[0i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(2, 0)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 2i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[0i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(3, 0)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 3i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[0i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + } + { + { + var writeIndex = (baseIndex + vec2i(0, 1)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 0i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[1i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(1)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 1i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[1i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(2, 1)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 2i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[1i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(3, 1)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 3i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[1i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + } + { + { + var writeIndex = (baseIndex + vec2i(0, 2)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 0i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[2i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(1, 2)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 1i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[2i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(2)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 2i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[2i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(3, 2)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 3i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[2i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + } + { + { + var writeIndex = (baseIndex + vec2i(0, 3)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 0i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[3i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(1, 3)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 1i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[3i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(2, 3)); + if ((flip != 0u)) { + writeIndex = writeIndex.yx; + } + let center = (i32((4u * _arg_0.lid.x)) + 2i); + if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { + var acc = vec3f(); + for (var f = 0; (f < (*settings2).filterDim); f++) { + let i = ((center + f) - filterOffset); + acc = (acc + (tileData[3i][i] * (1f / f32((*settings2).filterDim)))); + } + textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + } + } + { + var writeIndex = (baseIndex + vec2i(3)); if ((flip != 0u)) { writeIndex = writeIndex.yx; } - let center = (i32((4u * _arg_0.lid.x)) + c); + let center = (i32((4u * _arg_0.lid.x)) + 3i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { var acc = vec3f(); for (var f = 0; (f < (*settings2).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[r][i] * (1f / f32((*settings2).filterDim)))); + acc = (acc + (tileData[3i][i] * (1f / f32((*settings2).filterDim)))); } textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } diff --git a/packages/typegpu/tests/examples/individual/clouds.test.ts b/packages/typegpu/tests/examples/individual/clouds.test.ts index df70cf0435..2c513c3cdc 100644 --- a/packages/typegpu/tests/examples/individual/clouds.test.ts +++ b/packages/typegpu/tests/examples/individual/clouds.test.ts @@ -86,7 +86,17 @@ describe('clouds example', () => { var sum = 0f; var amp = 1f; var freq = 1.399999976158142f; - for (var i = 0; (i < 3i); i++) { + { + sum += (noise3d((pos * freq)) * amp); + amp *= 0.5f; + freq *= 2f; + } + { + sum += (noise3d((pos * freq)) * amp); + amp *= 0.5f; + freq *= 2f; + } + { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; freq *= 2f; diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index 83dbbb3084..5201942025 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -82,14 +82,146 @@ describe('cubemap reflection example', () => { var v31 = vec4f(normalize(calculateMidpoint(v3, v1).xyz), 1f); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); let baseIndexNext = (triangleIndex * 12u); - for (var i = 0u; (i < 12u); i++) { - let reprojectedVertex = (&newVertices[i]); - let triBase = (i - (i % 3u)); + { + let reprojectedVertex = (&newVertices[0i]); + let triBase = (0 - (0 % 3)); var normal = (*reprojectedVertex); if ((smoothFlag == 0u)) { - normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1u)], newVertices[(triBase + 2u)]); + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); } - let outIndex = (baseIndexNext + i); + let outIndex = (baseIndexNext + 0u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[1i]); + let triBase = (1 - (1 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 1u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[2i]); + let triBase = (2 - (2 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 2u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[3i]); + let triBase = (3 - (3 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 3u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[4i]); + let triBase = (4 - (4 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 4u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[5i]); + let triBase = (5 - (5 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 5u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[6i]); + let triBase = (6 - (6 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 6u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[7i]); + let triBase = (7 - (7 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 7u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[8i]); + let triBase = (8 - (8 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 8u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[9i]); + let triBase = (9 - (9 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 9u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[10i]); + let triBase = (10 - (10 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 10u); + let nextVertex = (&(*nextVertices)[outIndex]); + (*nextVertex).position = packVec2u((*reprojectedVertex)); + (*nextVertex).normal = packVec2u(normal); + } + { + let reprojectedVertex = (&newVertices[11i]); + let triBase = (11 - (11 % 3)); + var normal = (*reprojectedVertex); + if ((smoothFlag == 0u)) { + normal = getAverageNormal(newVertices[triBase], newVertices[(triBase + 1i)], newVertices[(triBase + 2i)]); + } + let outIndex = (baseIndexNext + 11u); let nextVertex = (&(*nextVertices)[outIndex]); (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 1bc06bccb2..a04fc178d8 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -178,22 +178,69 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; - for (var i = 0u; i < 4; i++) { - let offset = (&neighborOffsets[i]); - { - var neighborDensity = getCell((x + (*offset).x), (y + (*offset).y)); - let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); - if (!isValidFlowOut((x + (*offset).x), (y + (*offset).y))) { - continue; + { + var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[0u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y))) { + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[0u].x), f32(neighborOffsets[0u].y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(neighborOffsets[0u].x), f32(neighborOffsets[0u].y)); + dirChoiceCount = 1i; + } } + } + } + { + var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[1u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y))) { + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[1u].x), f32(neighborOffsets[1u].y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(neighborOffsets[1u].x), f32(neighborOffsets[1u].y)); + dirChoiceCount = 1i; + } + } + } + } + { + var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[2u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y))) { + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[2u].x), f32(neighborOffsets[2u].y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(neighborOffsets[2u].x), f32(neighborOffsets[2u].y)); + dirChoiceCount = 1i; + } + } + } + } + { + var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[3u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y))) { if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[3u].x), f32(neighborOffsets[3u].y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoices[0i] = vec2f(f32(neighborOffsets[3u].x), f32(neighborOffsets[3u].y)); dirChoiceCount = 1i; } } @@ -358,22 +405,69 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; - for (var i = 0u; i < 4; i++) { - let offset = (&neighborOffsets[i]); - { - var neighborDensity = getCell((x + (*offset).x), (y + (*offset).y)); - let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); - if (!isValidFlowOut((x + (*offset).x), (y + (*offset).y))) { - continue; + { + var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[0u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y))) { + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[0u].x), f32(neighborOffsets[0u].y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(neighborOffsets[0u].x), f32(neighborOffsets[0u].y)); + dirChoiceCount = 1i; + } } + } + } + { + var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[1u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y))) { + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[1u].x), f32(neighborOffsets[1u].y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(neighborOffsets[1u].x), f32(neighborOffsets[1u].y)); + dirChoiceCount = 1i; + } + } + } + } + { + var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[2u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y))) { + if ((cost == leastCost)) { + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[2u].x), f32(neighborOffsets[2u].y)); + dirChoiceCount++; + } + else { + if ((cost < leastCost)) { + leastCost = cost; + dirChoices[0i] = vec2f(f32(neighborOffsets[2u].x), f32(neighborOffsets[2u].y)); + dirChoiceCount = 1i; + } + } + } + } + { + var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); + let cost = (neighborDensity.z + (f32(neighborOffsets[3u].y) * gravityCost)); + if (isValidFlowOut((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y))) { if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoices[dirChoiceCount] = vec2f(f32(neighborOffsets[3u].x), f32(neighborOffsets[3u].y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0i] = vec2f(f32((*offset).x), f32((*offset).y)); + dirChoices[0i] = vec2f(f32(neighborOffsets[3u].x), f32(neighborOffsets[3u].y)); dirChoiceCount = 1i; } } diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index 544d5b5ae7..c09e22f2ca 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -608,9 +608,69 @@ describe('jelly-slider example', () => { var minColor = vec3f(9999); var maxColor = vec3f(-9999); var dimensions = textureDimensions(currentTexture); - for (var x = -1; (x <= 1i); x++) { - for (var y = -1; (y <= 1i); y++) { - var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(x, y)); + { + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + } + { + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i()); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + } + { + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); var neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); diff --git a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts index 8b4760c223..091110a679 100644 --- a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts @@ -414,9 +414,69 @@ describe('jelly switch example', () => { var minColor = vec3f(9999); var maxColor = vec3f(-9999); var dimensions = textureDimensions(currentTexture); - for (var x = -1; (x <= 1i); x++) { - for (var y = -1; (y <= 1i); y++) { - var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(x, y)); + { + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + } + { + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i()); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + } + { + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); + var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + minColor = min(minColor, neighborColor.rgb); + maxColor = max(maxColor, neighborColor.rgb); + } + { + var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); var neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); diff --git a/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts index 41b9960c3a..86a20c9dc7 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts @@ -80,9 +80,149 @@ describe('jump flood (distance) example', () => { var bestOutsideCoord = vec2f(-1); var bestInsideDist = 1e+20; var bestOutsideDist = 1e+20; - for (var dy = -1; (dy <= 1i); dy++) { - for (var dx = -1; (dx <= 1i); dx++) { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((dx * offset), (dy * offset))); + { + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + } + { + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + } + { + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); + if ((sample.inside.x >= 0f)) { + let dInside = distance(pos, (sample.inside * vec2f(size))); + if ((dInside < bestInsideDist)) { + bestInsideDist = dInside; + bestInsideCoord = sample.inside; + } + } + if ((sample.outside.x >= 0f)) { + let dOutside = distance(pos, (sample.outside * vec2f(size))); + if ((dOutside < bestOutsideDist)) { + bestOutsideDist = dOutside; + bestOutsideCoord = sample.outside; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); if ((sample.inside.x >= 0f)) { let dInside = distance(pos, (sample.inside * vec2f(size))); if ((dInside < bestInsideDist)) { diff --git a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts index fbb318b98c..5a97c87695 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts @@ -131,16 +131,99 @@ describe('jump flood (voronoi) example', () => { var size = textureDimensions(readView); var minDist = 1e+20; var bestSample = SampleResult(vec4f(), vec2f(-1)); - for (var dy = -1; (dy <= 1i); dy++) { - for (var dx = -1; (dx <= 1i); dx++) { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((dx * offset), (dy * offset))); - if ((sample.coord.x < 0f)) { - continue; + { + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } } - let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); - if ((dist < minDist)) { - minDist = dist; - bestSample = sample; + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } + } + } + } + { + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } + } + } + } + { + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } + } + } + { + var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); + if ((sample.coord.x >= 0f)) { + let dist = distance(vec2f(f32(x), f32(y)), (sample.coord * vec2f(size))); + if ((dist < minDist)) { + minDist = dist; + bestSample = sample; + } } } } diff --git a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts index 39c3e0995e..f34a5d28bf 100644 --- a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts +++ b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts @@ -107,9 +107,17 @@ describe('liquid-glass example', () => { fn sampleWithChromaticAberration(tex: texture_2d, sampler2: sampler, uv: vec2f, offset: f32, dir: vec2f, blur: f32) -> vec3f { var samples = array(); - for (var i = 0; (i < 3i); i++) { - var channelOffset = (dir * ((f32(i) - 1f) * offset)); - samples[i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; + { + var channelOffset = (dir * (-1f * offset)); + samples[0i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; + } + { + var channelOffset = (dir * (0f * offset)); + samples[1i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; + } + { + var channelOffset = (dir * (1f * offset)); + samples[2i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; } return vec3f(samples[0i].x, samples[1i].y, samples[2i].z); } diff --git a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts index dbe13f72fd..677f3e2271 100644 --- a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts +++ b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts @@ -478,8 +478,11 @@ describe('ripple-cube example', () => { let material = (&materialUniform); var f0 = mix(vec3f(0.03999999910593033), (*material).albedo, (*material).metallic); var lo = vec3f(); - for (var i = 0; (i < 2i); i++) { - lo = (lo + evaluateLight(p, n, v, lightsUniform[i], (*material), f0)); + { + lo = (lo + evaluateLight(p, n, v, lightsUniform[0i], (*material), f0)); + } + { + lo = (lo + evaluateLight(p, n, v, lightsUniform[1i], (*material), f0)); } var reflectDir = reflect(v, n); var pScaled = (p * 50f); @@ -559,9 +562,69 @@ describe('ripple-cube example', () => { var historyColor = textureLoad(historyTexture, coord, 0); var minColor = vec3f(9999); var maxColor = vec3f(-9999); - for (var ox = -1; (ox <= 1i); ox++) { - for (var oy = -1; (oy <= 1i); oy++) { - var sampleCoord = (coord + vec2i(ox, oy)); + { + { + var sampleCoord = (coord + vec2i(-1)); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + { + var sampleCoord = (coord + vec2i(-1, 0)); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + { + var sampleCoord = (coord + vec2i(-1, 1)); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + } + { + { + var sampleCoord = (coord + vec2i(0, -1)); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + { + var sampleCoord = (coord + vec2i()); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + { + var sampleCoord = (coord + vec2i(0, 1)); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + } + { + { + var sampleCoord = (coord + vec2i(1, -1)); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + { + var sampleCoord = (coord + vec2i(1, 0)); + var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + minColor = min(minColor, neighbor); + maxColor = max(maxColor, neighbor); + } + { + var sampleCoord = (coord + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index 0f1b46e97e..306d89d593 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -184,9 +184,78 @@ describe('slime mold 3d example', () => { var totalWeight = 0f; var perp1 = getPerpendicular(direction); var perp2 = cross(direction, perp1); - const numSamples = 8; - for (var i = 0; (i < numSamples); i++) { - let theta = (((f32(i) / f32(numSamples)) * 2f) * 3.141592653589793f); + { + const theta = 0.; + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params.sensorDistance)); + var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); + let weight = textureLoad(oldState, sensorPosInt).x; + weightedDir = (weightedDir + (sensorDir * weight)); + totalWeight = (totalWeight + weight); + } + { + const theta = 0.7853981633974483; + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params.sensorDistance)); + var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); + let weight = textureLoad(oldState, sensorPosInt).x; + weightedDir = (weightedDir + (sensorDir * weight)); + totalWeight = (totalWeight + weight); + } + { + const theta = 1.5707963267948966; + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params.sensorDistance)); + var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); + let weight = textureLoad(oldState, sensorPosInt).x; + weightedDir = (weightedDir + (sensorDir * weight)); + totalWeight = (totalWeight + weight); + } + { + const theta = 2.356194490192345; + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params.sensorDistance)); + var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); + let weight = textureLoad(oldState, sensorPosInt).x; + weightedDir = (weightedDir + (sensorDir * weight)); + totalWeight = (totalWeight + weight); + } + { + const theta = 3.141592653589793; + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params.sensorDistance)); + var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); + let weight = textureLoad(oldState, sensorPosInt).x; + weightedDir = (weightedDir + (sensorDir * weight)); + totalWeight = (totalWeight + weight); + } + { + const theta = 3.9269908169872414; + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params.sensorDistance)); + var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); + let weight = textureLoad(oldState, sensorPosInt).x; + weightedDir = (weightedDir + (sensorDir * weight)); + totalWeight = (totalWeight + weight); + } + { + const theta = 4.71238898038469; + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params.sensorDistance)); + var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); + let weight = textureLoad(oldState, sensorPosInt).x; + weightedDir = (weightedDir + (sensorDir * weight)); + totalWeight = (totalWeight + weight); + } + { + const theta = 5.497787143782138; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); var sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); var sensorPos = (pos + (sensorDir * params.sensorDistance)); diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index 9cdf638876..61bf5491be 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -93,9 +93,85 @@ describe('slime mold example', () => { } var sum = vec3f(); var count = 0f; - for (var offsetY = -1; (offsetY <= 1i); offsetY++) { - for (var offsetX = -1; (offsetX <= 1i); offsetX++) { - var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(offsetX, offsetY)); + { + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1)); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + } + { + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i()); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + } + { + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); + var dimsi = vec2i(dims); + if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { + var color = textureLoad(oldState, vec2u(samplePos)).rgb; + sum = (sum + color); + count = (count + 1f); + } + } + { + var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1)); var dimsi = vec2i(dims); if (((((samplePos.x >= 0i) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0i)) && (samplePos.y < dimsi.y))) { var color = textureLoad(oldState, vec2u(samplePos)).rgb; diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index 040a569566..b0f491987e 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -62,8 +62,17 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - for (var i = 0; (i < 4i); i++) { - adjacentOffsets[i] = clamp((coords + adjacentOffsets[i]), vec2i(), (bounds - vec2i(1))); + { + adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } return adjacentOffsets; } @@ -102,8 +111,17 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - for (var i = 0; (i < 4i); i++) { - adjacentOffsets[i] = clamp((coords + adjacentOffsets[i]), vec2i(), (bounds - vec2i(1))); + { + adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } return adjacentOffsets; } @@ -130,8 +148,17 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - for (var i = 0; (i < 4i); i++) { - adjacentOffsets[i] = clamp((coords + adjacentOffsets[i]), vec2i(), (bounds - vec2i(1))); + { + adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } return adjacentOffsets; } @@ -161,8 +188,17 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - for (var i = 0; (i < 4i); i++) { - adjacentOffsets[i] = clamp((coords + adjacentOffsets[i]), vec2i(), (bounds - vec2i(1))); + { + adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); + } + { + adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } return adjacentOffsets; } From 5fbd3d8b8db126c22db1d4f82c42154035104a02 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 17:20:35 +0100 Subject: [PATCH 53/67] docs --- .../src/content/docs/fundamentals/utils.mdx | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx index 82716f3d60..0099270296 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx @@ -267,3 +267,59 @@ Otherwise, for example when using `tgpu.resolve` on a WGSL template, logs are ig - `console.log` only works in fragment and compute shaders. This is due to a [WebGPU limitation](https://www.w3.org/TR/WGSL/#address-space) that does not allow modifying buffers during the vertex shader stage. - `console.log` currently does not support template literals (but you can use [string substitutions](https://developer.mozilla.org/en-US/docs/Web/API/console#using_string_substitutions), or just pass multiple arguments instead). + +## *for...of...* loops + +TypeGPU supports `for...of...` loops in shader functions. The only constraints are that the loop variable must be declared with `const` and the iterable must be stored in a variable. + +```ts twoslash +import tgpu, { d } from 'typegpu'; + +const processNeighbor = (cell: d.v2i) => {}; + +// ---cut--- +const processNeighbors = (cell: d.v2i) => { + 'use gpu'; + + const offsets = [ + d.vec2i(0, 1), + d.vec2i(0, -1), + d.vec2i(1, 0), + d.vec2i(-1, 0), + ]; + + for (const offset of offsets) { + processNeighbor(cell.add(offset)); + } +}; +``` + +## *tgpu.unroll* + +For code with small, fixed iteration counts, you can use `tgpu.unroll` to unroll loops at compile time. This eliminates branch prediction overhead and can significantly improve performance. + +### Usage + +Wrap your iterable with `tgpu.unroll()`: + +```ts twoslash +import tgpu, { d } from 'typegpu'; + +const processNeighbor = (cell: d.v2i) => {}; + +// ---cut--- +const processNeighbors = (cell: d.v2i) => { + 'use gpu'; + + for (const dy of tgpu.unroll([-1, 0, 1])) { + for (const dx of tgpu.unroll([-1, 0, 1])) { + processNeighbor(cell.add(d.vec2i(dx, dy))); + } + } +}; +``` + +:::note +- There are no constraints on how large a loop can be for unrolling. We will always try to unroll it, and if we can't, you'll receive an error. +- You cannot use `continue` or `break` inside loop that you intend to unroll later. +::: From 994c57cdd8f871f57269a765f0533c7aa4641c72 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 21:11:56 +0100 Subject: [PATCH 54/67] review changes --- packages/typegpu/src/tgsl/shaderGenerator.ts | 2 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 2 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 39 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/src/tgsl/shaderGenerator.ts b/packages/typegpu/src/tgsl/shaderGenerator.ts index d80f890606..90531d294a 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator.ts @@ -2,7 +2,7 @@ import type { Block, Expression, Statement } from 'tinyest'; import type { Snippet } from '../data/snippet.ts'; import type { GenerationCtx } from './generationHelpers.ts'; import type { BaseData } from '../data/wgslTypes.ts'; -import type { ExternalMap } from '../../src/core/resolve/externals.ts'; +import type { ExternalMap } from '../core/resolve/externals.ts'; export interface ShaderGenerator { initGenerator(ctx: GenerationCtx): void; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 25f30d1e58..66cbb7f0f1 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -52,7 +52,7 @@ import type { AnyFn } from '../core/function/fnTypes.ts'; import { arrayLength } from '../std/array.ts'; import { AutoStruct } from '../data/autoStruct.ts'; import { mathToStd } from './math.ts'; -import type { ExternalMap } from '../../src/core/resolve/externals.ts'; +import type { ExternalMap } from '../core/resolve/externals.ts'; const { NodeTypeCatalog: NODE } = tinyest; diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index bc2593ad4c..52f669ff04 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1668,4 +1668,43 @@ describe('wgslGenerator', () => { `); }); }); + + it('block externals are respected in nested blocks', () => { + const f = () => { + 'use gpu'; + let result = d.i32(0); + const list = d.arrayOf(d.i32, 3)([1, 2, 3]); + for (const elem of list) { + { + // We use the `elem` in a nested block + result += elem; + } + } + }; + + const parsed = getMetaData(f)?.ast?.body as tinyest.Block; + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.Void, + {}, + ); + + const res = wgslGenerator.block( + (parsed[1][2] as tinyest.ForOf)[3] as tinyest.Block, + { result: snip('result', d.i32, 'function'), elem: 7 }, + ); + + expect(res).toMatchInlineSnapshot(` + "{ + { + result += 7i; + } + }" + `); + }); + }); }); From 5980198f43683e4f2a6ee9f2f38e0dc08e5b54d1 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 21:27:48 +0100 Subject: [PATCH 55/67] review changes --- packages/typegpu/src/tgsl/wgslGenerator.ts | 5 --- packages/typegpu/tests/unroll.test.ts | 52 ++++++++++++++-------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 1f19e1961d..9d4f404614 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1226,11 +1226,6 @@ ${this.ctx.pre}else ${alternate}`; if (ephemeralIterable) { const { value, dataType } = iterableSnippet; - // when `iterableSnippet.value` is ArrayExpression, we need to resolve it to ensure the array being created is valid - if (value instanceof ArrayExpression) { - this.ctx.resolve(value); - } - const elements = value instanceof ArrayExpression ? value.elements // already snippets : wgsl.isVec(dataType) diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 97d51b99dc..e532833425 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -137,9 +137,10 @@ describe('tgpu.unroll', () => { const b1 = Boid({ pos: d.vec2i(1), vel: d.vec2f(1) }); const b2 = Boid({ pos: d.vec2i(2), vel: d.vec2f(2) }); let res = d.vec2f(); - for (const foo of tgpu.unroll([Boid(b1), Boid(b2)])) { + for (const foo of tgpu.unroll([b1, b2])) { for (const boo of tgpu.unroll([Boid(), Boid()])) { - res = res.add(foo.vel).add(boo.vel); + const baz = foo; + res = res.add(baz.vel).add(boo.vel); } } @@ -158,18 +159,22 @@ describe('tgpu.unroll', () => { var res = vec2f(); { { - res = ((res + b1.vel) + Boid().vel); + let baz = (&b1); + res = ((res + (*baz).vel) + Boid().vel); } { - res = ((res + b1.vel) + Boid().vel); + let baz = (&b1); + res = ((res + (*baz).vel) + Boid().vel); } } { { - res = ((res + b2.vel) + Boid().vel); + let baz = (&b2); + res = ((res + (*baz).vel) + Boid().vel); } { - res = ((res + b2.vel) + Boid().vel); + let baz = (&b2); + res = ((res + (*baz).vel) + Boid().vel); } } return res; @@ -242,7 +247,7 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls array expression of struct field names - (complex)', () => { + it('unrolls array expression of struct field names - (complex)', () => { const variants = { foo: (x: number) => { 'use gpu'; @@ -300,29 +305,38 @@ describe('tgpu.unroll', () => { `); }); - it('throws when iterable is array expression of pointers', () => { + it('unrolls array expression of pointers', () => { const f = () => { 'use gpu'; let res = d.vec2f(); const v1 = d.vec2f(7); const v2 = d.vec2f(3); for (const foo of tgpu.unroll([v1, v2])) { + const boo = foo; res = res.add(foo); - foo.x = 6; + boo.x = 6; } return res; }; - expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` - [Error: Resolution of the following tree failed: - - - - fn*:f - - fn*:f() - - ArrayExpression: 'v1' reference cannot be used in an array constructor. - ----- - Try 'vec2f(v1)' or 'arrayOf(vec2f, count)([...])' to copy the value instead. - -----] + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> vec2f { + var res = vec2f(); + var v1 = vec2f(7); + var v2 = vec2f(3); + { + let boo = (&v1); + res = (res + v1); + (*boo).x = 6f; + } + { + let boo = (&v2); + res = (res + v2); + (*boo).x = 6f; + } + return res; + }" `); }); @@ -387,7 +401,7 @@ describe('tgpu.unroll', () => { `); }); - it('warns when iterable element count is unknown at compile-time and fallbacks to regular loop', ({ root }) => { + it('throws when iterable element count is unknown at compile-time', () => { const layout = tgpu.bindGroupLayout({ arr: { storage: d.arrayOf(d.f32) }, }); From 8b38ee6f7f6c7e379dbb1fb05e08d39e5edcbfd2 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Mon, 23 Feb 2026 21:52:05 +0100 Subject: [PATCH 56/67] partial review changes --- packages/typegpu/tests/unroll.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index e532833425..7e6989d0b6 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -86,7 +86,7 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls correctly when loop variable is overriden', () => { + it('unrolls correctly when loop variable is overridden', () => { const f = () => { 'use gpu'; let fooResult = d.f32(0); @@ -588,7 +588,7 @@ describe('tgpu.unroll', () => { `); }); - it('can be conditinally applied', () => { + it('can be conditionally applied', () => { const unroll = tgpu.accessor(d.bool, true); const f = () => { From 89d9c83347439277ad0fc63f596f73f2ebf5c93a Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 13:23:31 +0100 Subject: [PATCH 57/67] cleanup --- .../src/examples/rendering/clouds/utils.ts | 2 +- packages/typegpu/src/tgsl/forOfUtils.ts | 13 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 155 +++++++----------- .../tests/examples/individual/3d-fish.test.ts | 3 + .../tests/examples/individual/blur.test.ts | 40 +++++ .../tests/examples/individual/clouds.test.ts | 3 + .../individual/cubemap-reflection.test.ts | 12 ++ .../individual/fluid-double-buffering.test.ts | 16 +- .../examples/individual/jelly-slider.test.ts | 12 ++ .../examples/individual/jelly-switch.test.ts | 12 ++ .../individual/jump-flood-distance.test.ts | 12 ++ .../individual/jump-flood-voronoi.test.ts | 12 ++ .../examples/individual/liquid-glass.test.ts | 3 + .../examples/individual/ripple-cube.test.ts | 14 ++ .../examples/individual/slime-mold-3d.test.ts | 8 + .../examples/individual/slime-mold.test.ts | 12 ++ .../examples/individual/stable-fluid.test.ts | 16 ++ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 44 ++--- packages/typegpu/tests/unroll.test.ts | 123 +++++++++++++- 19 files changed, 385 insertions(+), 127 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts index c5cff34695..ddf3a86247 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts +++ b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts @@ -72,7 +72,7 @@ export const raymarch = tgpu.fn([d.vec3f, d.vec3f, d.vec3f], d.vec4f)( }, ); -const iterations = Array.from({ length: FBM_OCTAVES }); +const iterations = Array.from({ length: FBM_OCTAVES }, (_, i) => i); const fbm = tgpu.fn([d.vec3f], d.f32)((pos) => { let sum = d.f32(); let amp = d.f32(CLOUD_AMPLITUDE); diff --git a/packages/typegpu/src/tgsl/forOfUtils.ts b/packages/typegpu/src/tgsl/forOfUtils.ts index b57ea2f6b2..b6968217fd 100644 --- a/packages/typegpu/src/tgsl/forOfUtils.ts +++ b/packages/typegpu/src/tgsl/forOfUtils.ts @@ -20,7 +20,10 @@ export function getLoopVarKind(elementSnippet: Snippet) { return elementSnippet.origin === 'constant-tgpu-const-ref' ? 'const' : 'let'; } -export function getElementSnippet(iterableSnippet: Snippet, index: string) { +export function getElementSnippet( + iterableSnippet: Snippet, + index: string | number, +) { const elementSnippet = accessIndex( iterableSnippet, snip(index, u32, 'runtime'), @@ -79,7 +82,7 @@ export function getElementCountSnippet( if (wgsl.isWgslArray(dataType)) { return dataType.elementCount > 0 ? snip( - `${dataType.elementCount}`, + dataType.elementCount, u32, 'constant', ) @@ -88,7 +91,7 @@ export function getElementCountSnippet( if (wgsl.isVec(dataType)) { return snip( - `${dataType.componentCount}`, + dataType.componentCount, u32, 'constant', ); @@ -96,11 +99,11 @@ export function getElementCountSnippet( if (unroll) { if (Array.isArray(value)) { - return snip(`${value.length}`, u32, 'constant'); + return snip(value.length, u32, 'constant'); } if (value instanceof ArrayExpression) { - return snip(`${value.elements.length}`, u32, 'constant'); + return snip(value.elements.length, u32, 'constant'); } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 18d0cd85da..802357a475 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1198,11 +1198,10 @@ ${this.ctx.pre}else ${alternate}`; try { this.ctx.pushBlockScope(); const iterableExpr = this.expression(iterable); - let shouldUnroll = iterableExpr.value instanceof UnrollableIterable; + const shouldUnroll = iterableExpr.value instanceof UnrollableIterable; const iterableSnippet = shouldUnroll ? (iterableExpr.value as UnrollableIterable).snippet : iterableExpr; - const ephemeralIterable = isEphemeralSnippet(iterableSnippet); const elementCountSnippet = forOfUtils.getElementCountSnippet( this.ctx, iterableSnippet, @@ -1218,56 +1217,26 @@ ${this.ctx.pre}else ${alternate}`; ); } - // we know it is a number, because it is known at compile-time const length = elementCountSnippet.value as number; if (length === 0) { return ''; } - if (ephemeralIterable) { - const { value, dataType } = iterableSnippet; - - const elements = value instanceof ArrayExpression - ? value.elements // already snippets - : wgsl.isVec(dataType) - ? (value as wgsl.AnyVecInstance).map((e) => - snip(e, dataType.primitive, iterableSnippet.origin) // we can give `e` the exact type inferred from vector - ) - : Array.isArray(value) - ? value // type inferred later - : []; // error already thrown by `getElementCountSnippet` - - const blocks = elements - .map((e) => - `${this.ctx.pre}${ - this.block(blockified, { [originalLoopVarName]: e }) - }` - ); - - // TODO: replace it with tinyest traversal - if ( - blocks[0]?.includes('break') || blocks[0]?.includes('continue') - ) { - throw new WgslTypeError( - 'Cannot unroll loop containing `break` or `continue`', - ); - } + const { value } = iterableSnippet; - return blocks.join('\n'); - } + const elements = value instanceof ArrayExpression + ? value.elements + : Array.from( + { length }, + (_, i) => forOfUtils.getElementSnippet(iterableSnippet, i), + ); - // iterable stored in a variable, we still can unroll it - const blocks = Array.from({ length }, (_, i) => { - const elementSnippet = forOfUtils.getElementSnippet( - iterableSnippet, - `${i}u`, + const blocks = elements + .map((e, i) => + `${this.ctx.pre}// unrolled iteration #${i}, '${originalLoopVarName}' is '${stitch`${e}`}'\n${this.ctx.pre}${ + this.block(blockified, { [originalLoopVarName]: e }) + }` ); - return `${this.ctx.pre}${ - this.block(blockified, { - [originalLoopVarName]: elementSnippet, - }) - }`; - }); // TODO: replace it with tinyest traversal if ( @@ -1281,59 +1250,59 @@ ${this.ctx.pre}else ${alternate}`; return blocks.join('\n'); } - if (!ephemeralIterable) { - const index = this.ctx.makeNameValid('i'); - const elementSnippet = forOfUtils.getElementSnippet( - iterableSnippet, - index, - ); - const loopVarName = this.ctx.makeNameValid(originalLoopVarName); - const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet); - const elementType = forOfUtils.getElementType( - elementSnippet, - iterableSnippet, + if (isEphemeralSnippet(iterableSnippet)) { + throw new Error( + `\`for ... of ...\` loops only support iterables stored in variables. + ----- + You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. + -----`, ); - - const forHeaderStr = - stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ - tryConvertSnippet(this.ctx, elementCountSnippet, u32, false) - }; ${index}++) {`; - - this.ctx.indent(); - ctxIndent = true; - - const loopVarDeclStr = - stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ - tryConvertSnippet( - this.ctx, - elementSnippet, - elementType, - false, - ) - };`; - - const bodyStr = `${this.ctx.pre}${ - this.block(blockified, { - [originalLoopVarName]: snip( - loopVarName, - elementType, - elementSnippet.origin, - ), - }) - }`; - - this.ctx.dedent(); - ctxIndent = false; - - return stitch`${forHeaderStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; } - throw new Error( - `\`for ... of ...\` loops only support iterables stored in variables. ------ -You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. ------`, + const index = this.ctx.makeNameValid('i'); + const elementSnippet = forOfUtils.getElementSnippet( + iterableSnippet, + index, ); + const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet); + const elementType = forOfUtils.getElementType( + elementSnippet, + iterableSnippet, + ); + + const forHeaderStr = + stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ + tryConvertSnippet(this.ctx, elementCountSnippet, u32, false) + }; ${index}++) {`; + + this.ctx.indent(); + ctxIndent = true; + + const loopVarDeclStr = + stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${ + tryConvertSnippet( + this.ctx, + elementSnippet, + elementType, + false, + ) + };`; + + const bodyStr = `${this.ctx.pre}${ + this.block(blockified, { + [originalLoopVarName]: snip( + loopVarName, + elementType, + elementSnippet.origin, + ), + }) + }`; + + this.ctx.dedent(); + ctxIndent = false; + + return stitch`${forHeaderStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`; } finally { if (ctxIndent) { this.ctx.dedent(); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 6b15c24d10..03a1679987 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -160,6 +160,7 @@ describe('3d fish example', () => { if ((cohesionCount > 0i)) { cohesion = ((cohesion / f32(cohesionCount)) - (*fishData).position); } + // unrolled iteration #0, 'i' is '0' { var repulsion = vec3f(); repulsion[0i] = 1f; @@ -175,6 +176,7 @@ describe('3d fish example', () => { wallRepulsion = (wallRepulsion + (repulsion * str2)); } } + // unrolled iteration #1, 'i' is '1' { var repulsion = vec3f(); repulsion[1i] = 1f; @@ -190,6 +192,7 @@ describe('3d fish example', () => { wallRepulsion = (wallRepulsion + (repulsion * str2)); } } + // unrolled iteration #2, 'i' is '2' { var repulsion = vec3f(); repulsion[2i] = 1f; diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index c425c02f7e..9fb82e92dd 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -52,7 +52,9 @@ describe('blur example', () => { let filterOffset = i32((f32(((*settings2).filterDim - 1i)) / 2f)); var dims = vec2i(textureDimensions(inTexture)); var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u((*settings2).blockDim, 4u)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0i)); + // unrolled iteration #0, 'r' is '0' { + // unrolled iteration #0, 'c' is '0' { var loadIndex = (baseIndex + vec2i()); if ((flip != 0u)) { @@ -60,6 +62,7 @@ describe('blur example', () => { } tileData[0i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #1, 'c' is '1' { var loadIndex = (baseIndex + vec2i(1, 0)); if ((flip != 0u)) { @@ -67,6 +70,7 @@ describe('blur example', () => { } tileData[0i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #2, 'c' is '2' { var loadIndex = (baseIndex + vec2i(2, 0)); if ((flip != 0u)) { @@ -74,6 +78,7 @@ describe('blur example', () => { } tileData[0i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #3, 'c' is '3' { var loadIndex = (baseIndex + vec2i(3, 0)); if ((flip != 0u)) { @@ -82,7 +87,9 @@ describe('blur example', () => { tileData[0i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } } + // unrolled iteration #1, 'r' is '1' { + // unrolled iteration #0, 'c' is '0' { var loadIndex = (baseIndex + vec2i(0, 1)); if ((flip != 0u)) { @@ -90,6 +97,7 @@ describe('blur example', () => { } tileData[1i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #1, 'c' is '1' { var loadIndex = (baseIndex + vec2i(1)); if ((flip != 0u)) { @@ -97,6 +105,7 @@ describe('blur example', () => { } tileData[1i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #2, 'c' is '2' { var loadIndex = (baseIndex + vec2i(2, 1)); if ((flip != 0u)) { @@ -104,6 +113,7 @@ describe('blur example', () => { } tileData[1i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #3, 'c' is '3' { var loadIndex = (baseIndex + vec2i(3, 1)); if ((flip != 0u)) { @@ -112,7 +122,9 @@ describe('blur example', () => { tileData[1i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } } + // unrolled iteration #2, 'r' is '2' { + // unrolled iteration #0, 'c' is '0' { var loadIndex = (baseIndex + vec2i(0, 2)); if ((flip != 0u)) { @@ -120,6 +132,7 @@ describe('blur example', () => { } tileData[2i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #1, 'c' is '1' { var loadIndex = (baseIndex + vec2i(1, 2)); if ((flip != 0u)) { @@ -127,6 +140,7 @@ describe('blur example', () => { } tileData[2i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #2, 'c' is '2' { var loadIndex = (baseIndex + vec2i(2)); if ((flip != 0u)) { @@ -134,6 +148,7 @@ describe('blur example', () => { } tileData[2i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #3, 'c' is '3' { var loadIndex = (baseIndex + vec2i(3, 2)); if ((flip != 0u)) { @@ -142,7 +157,9 @@ describe('blur example', () => { tileData[2i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } } + // unrolled iteration #3, 'r' is '3' { + // unrolled iteration #0, 'c' is '0' { var loadIndex = (baseIndex + vec2i(0, 3)); if ((flip != 0u)) { @@ -150,6 +167,7 @@ describe('blur example', () => { } tileData[3i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #1, 'c' is '1' { var loadIndex = (baseIndex + vec2i(1, 3)); if ((flip != 0u)) { @@ -157,6 +175,7 @@ describe('blur example', () => { } tileData[3i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #2, 'c' is '2' { var loadIndex = (baseIndex + vec2i(2, 3)); if ((flip != 0u)) { @@ -164,6 +183,7 @@ describe('blur example', () => { } tileData[3i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } + // unrolled iteration #3, 'c' is '3' { var loadIndex = (baseIndex + vec2i(3)); if ((flip != 0u)) { @@ -173,7 +193,9 @@ describe('blur example', () => { } } workgroupBarrier(); + // unrolled iteration #0, 'r' is '0' { + // unrolled iteration #0, 'c' is '0' { var writeIndex = (baseIndex + vec2i()); if ((flip != 0u)) { @@ -189,6 +211,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #1, 'c' is '1' { var writeIndex = (baseIndex + vec2i(1, 0)); if ((flip != 0u)) { @@ -204,6 +227,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #2, 'c' is '2' { var writeIndex = (baseIndex + vec2i(2, 0)); if ((flip != 0u)) { @@ -219,6 +243,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #3, 'c' is '3' { var writeIndex = (baseIndex + vec2i(3, 0)); if ((flip != 0u)) { @@ -235,7 +260,9 @@ describe('blur example', () => { } } } + // unrolled iteration #1, 'r' is '1' { + // unrolled iteration #0, 'c' is '0' { var writeIndex = (baseIndex + vec2i(0, 1)); if ((flip != 0u)) { @@ -251,6 +278,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #1, 'c' is '1' { var writeIndex = (baseIndex + vec2i(1)); if ((flip != 0u)) { @@ -266,6 +294,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #2, 'c' is '2' { var writeIndex = (baseIndex + vec2i(2, 1)); if ((flip != 0u)) { @@ -281,6 +310,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #3, 'c' is '3' { var writeIndex = (baseIndex + vec2i(3, 1)); if ((flip != 0u)) { @@ -297,7 +327,9 @@ describe('blur example', () => { } } } + // unrolled iteration #2, 'r' is '2' { + // unrolled iteration #0, 'c' is '0' { var writeIndex = (baseIndex + vec2i(0, 2)); if ((flip != 0u)) { @@ -313,6 +345,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #1, 'c' is '1' { var writeIndex = (baseIndex + vec2i(1, 2)); if ((flip != 0u)) { @@ -328,6 +361,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #2, 'c' is '2' { var writeIndex = (baseIndex + vec2i(2)); if ((flip != 0u)) { @@ -343,6 +377,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #3, 'c' is '3' { var writeIndex = (baseIndex + vec2i(3, 2)); if ((flip != 0u)) { @@ -359,7 +394,9 @@ describe('blur example', () => { } } } + // unrolled iteration #3, 'r' is '3' { + // unrolled iteration #0, 'c' is '0' { var writeIndex = (baseIndex + vec2i(0, 3)); if ((flip != 0u)) { @@ -375,6 +412,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #1, 'c' is '1' { var writeIndex = (baseIndex + vec2i(1, 3)); if ((flip != 0u)) { @@ -390,6 +428,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #2, 'c' is '2' { var writeIndex = (baseIndex + vec2i(2, 3)); if ((flip != 0u)) { @@ -405,6 +444,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } + // unrolled iteration #3, 'c' is '3' { var writeIndex = (baseIndex + vec2i(3)); if ((flip != 0u)) { diff --git a/packages/typegpu/tests/examples/individual/clouds.test.ts b/packages/typegpu/tests/examples/individual/clouds.test.ts index 2c513c3cdc..0acd21062d 100644 --- a/packages/typegpu/tests/examples/individual/clouds.test.ts +++ b/packages/typegpu/tests/examples/individual/clouds.test.ts @@ -86,16 +86,19 @@ describe('clouds example', () => { var sum = 0f; var amp = 1f; var freq = 1.399999976158142f; + // unrolled iteration #0, 'i' is '0' { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; freq *= 2f; } + // unrolled iteration #1, 'i' is '1' { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; freq *= 2f; } + // unrolled iteration #2, 'i' is '2' { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index 5201942025..d2eff3a5f8 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -82,6 +82,7 @@ describe('cubemap reflection example', () => { var v31 = vec4f(normalize(calculateMidpoint(v3, v1).xyz), 1f); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); let baseIndexNext = (triangleIndex * 12u); + // unrolled iteration #0, 'i' is '0' { let reprojectedVertex = (&newVertices[0i]); let triBase = (0 - (0 % 3)); @@ -94,6 +95,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #1, 'i' is '1' { let reprojectedVertex = (&newVertices[1i]); let triBase = (1 - (1 % 3)); @@ -106,6 +108,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #2, 'i' is '2' { let reprojectedVertex = (&newVertices[2i]); let triBase = (2 - (2 % 3)); @@ -118,6 +121,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #3, 'i' is '3' { let reprojectedVertex = (&newVertices[3i]); let triBase = (3 - (3 % 3)); @@ -130,6 +134,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #4, 'i' is '4' { let reprojectedVertex = (&newVertices[4i]); let triBase = (4 - (4 % 3)); @@ -142,6 +147,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #5, 'i' is '5' { let reprojectedVertex = (&newVertices[5i]); let triBase = (5 - (5 % 3)); @@ -154,6 +160,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #6, 'i' is '6' { let reprojectedVertex = (&newVertices[6i]); let triBase = (6 - (6 % 3)); @@ -166,6 +173,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #7, 'i' is '7' { let reprojectedVertex = (&newVertices[7i]); let triBase = (7 - (7 % 3)); @@ -178,6 +186,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #8, 'i' is '8' { let reprojectedVertex = (&newVertices[8i]); let triBase = (8 - (8 % 3)); @@ -190,6 +199,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #9, 'i' is '9' { let reprojectedVertex = (&newVertices[9i]); let triBase = (9 - (9 % 3)); @@ -202,6 +212,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #10, 'i' is '10' { let reprojectedVertex = (&newVertices[10i]); let triBase = (10 - (10 % 3)); @@ -214,6 +225,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } + // unrolled iteration #11, 'i' is '11' { let reprojectedVertex = (&newVertices[11i]); let triBase = (11 - (11 % 3)); diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index a04fc178d8..6ccbc8ee93 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -36,7 +36,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var i = 0u; i < 4; i++) { + for (var i = 0u; i < 4u; i++) { let obs = (&obstacles[i]); { if (((*obs).enabled == 0u)) { @@ -131,7 +131,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var i = 0u; i < 4; i++) { + for (var i = 0u; i < 4u; i++) { let obs = (&obstacles[i]); { if (((*obs).enabled == 0u)) { @@ -178,6 +178,7 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; + // unrolled iteration #0, 'offset' is 'neighborOffsets[0u]' { var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[0u].y) * gravityCost)); @@ -195,6 +196,7 @@ describe('fluid double buffering example', () => { } } } + // unrolled iteration #1, 'offset' is 'neighborOffsets[1u]' { var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[1u].y) * gravityCost)); @@ -212,6 +214,7 @@ describe('fluid double buffering example', () => { } } } + // unrolled iteration #2, 'offset' is 'neighborOffsets[2u]' { var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[2u].y) * gravityCost)); @@ -229,6 +232,7 @@ describe('fluid double buffering example', () => { } } } + // unrolled iteration #3, 'offset' is 'neighborOffsets[3u]' { var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[3u].y) * gravityCost)); @@ -358,7 +362,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var i = 0u; i < 4; i++) { + for (var i = 0u; i < 4u; i++) { let obs = (&obstacles[i]); { if (((*obs).enabled == 0u)) { @@ -405,6 +409,7 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; + // unrolled iteration #0, 'offset' is 'neighborOffsets[0u]' { var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[0u].y) * gravityCost)); @@ -422,6 +427,7 @@ describe('fluid double buffering example', () => { } } } + // unrolled iteration #1, 'offset' is 'neighborOffsets[1u]' { var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[1u].y) * gravityCost)); @@ -439,6 +445,7 @@ describe('fluid double buffering example', () => { } } } + // unrolled iteration #2, 'offset' is 'neighborOffsets[2u]' { var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[2u].y) * gravityCost)); @@ -456,6 +463,7 @@ describe('fluid double buffering example', () => { } } } + // unrolled iteration #3, 'offset' is 'neighborOffsets[3u]' { var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[3u].y) * gravityCost)); @@ -578,7 +586,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(1) var obstacles: array; fn isInsideObstacle(x: i32, y: i32) -> bool { - for (var i = 0u; i < 4; i++) { + for (var i = 0u; i < 4u; i++) { let obs = (&obstacles[i]); { if (((*obs).enabled == 0u)) { diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index c09e22f2ca..ed2623a999 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -608,7 +608,9 @@ describe('jelly-slider example', () => { var minColor = vec3f(9999); var maxColor = vec3f(-9999); var dimensions = textureDimensions(currentTexture); + // unrolled iteration #0, 'x' is '-1' { + // unrolled iteration #0, 'y' is '-1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -616,6 +618,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #1, 'y' is '0' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -623,6 +626,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #2, 'y' is '1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -631,7 +635,9 @@ describe('jelly-slider example', () => { maxColor = max(maxColor, neighborColor.rgb); } } + // unrolled iteration #1, 'x' is '0' { + // unrolled iteration #0, 'y' is '-1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -639,6 +645,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #1, 'y' is '0' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i()); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -646,6 +653,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #2, 'y' is '1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -654,7 +662,9 @@ describe('jelly-slider example', () => { maxColor = max(maxColor, neighborColor.rgb); } } + // unrolled iteration #2, 'x' is '1' { + // unrolled iteration #0, 'y' is '-1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -662,6 +672,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #1, 'y' is '0' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -669,6 +680,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #2, 'y' is '1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); diff --git a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts index 091110a679..c7b5624941 100644 --- a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts @@ -414,7 +414,9 @@ describe('jelly switch example', () => { var minColor = vec3f(9999); var maxColor = vec3f(-9999); var dimensions = textureDimensions(currentTexture); + // unrolled iteration #0, 'x' is '-1' { + // unrolled iteration #0, 'y' is '-1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -422,6 +424,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #1, 'y' is '0' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -429,6 +432,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #2, 'y' is '1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -437,7 +441,9 @@ describe('jelly switch example', () => { maxColor = max(maxColor, neighborColor.rgb); } } + // unrolled iteration #1, 'x' is '0' { + // unrolled iteration #0, 'y' is '-1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -445,6 +451,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #1, 'y' is '0' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i()); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -452,6 +459,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #2, 'y' is '1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -460,7 +468,9 @@ describe('jelly switch example', () => { maxColor = max(maxColor, neighborColor.rgb); } } + // unrolled iteration #2, 'x' is '1' { + // unrolled iteration #0, 'y' is '-1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -468,6 +478,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #1, 'y' is '0' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -475,6 +486,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } + // unrolled iteration #2, 'y' is '1' { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); diff --git a/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts index 86a20c9dc7..3c8d898cf6 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts @@ -80,7 +80,9 @@ describe('jump flood (distance) example', () => { var bestOutsideCoord = vec2f(-1); var bestInsideDist = 1e+20; var bestOutsideDist = 1e+20; + // unrolled iteration #0, 'dx' is '-1' { + // unrolled iteration #0, 'dy' is '-1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); if ((sample.inside.x >= 0f)) { @@ -98,6 +100,7 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #1, 'dy' is '0' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); if ((sample.inside.x >= 0f)) { @@ -115,6 +118,7 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #2, 'dy' is '1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); if ((sample.inside.x >= 0f)) { @@ -133,7 +137,9 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #1, 'dx' is '0' { + // unrolled iteration #0, 'dy' is '-1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); if ((sample.inside.x >= 0f)) { @@ -151,6 +157,7 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #1, 'dy' is '0' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); if ((sample.inside.x >= 0f)) { @@ -168,6 +175,7 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #2, 'dy' is '1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); if ((sample.inside.x >= 0f)) { @@ -186,7 +194,9 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #2, 'dx' is '1' { + // unrolled iteration #0, 'dy' is '-1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); if ((sample.inside.x >= 0f)) { @@ -204,6 +214,7 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #1, 'dy' is '0' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); if ((sample.inside.x >= 0f)) { @@ -221,6 +232,7 @@ describe('jump flood (distance) example', () => { } } } + // unrolled iteration #2, 'dy' is '1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); if ((sample.inside.x >= 0f)) { diff --git a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts index 5a97c87695..687e7b1630 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts @@ -131,7 +131,9 @@ describe('jump flood (voronoi) example', () => { var size = textureDimensions(readView); var minDist = 1e+20; var bestSample = SampleResult(vec4f(), vec2f(-1)); + // unrolled iteration #0, 'dy' is '-1' { + // unrolled iteration #0, 'dx' is '-1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); if ((sample.coord.x >= 0f)) { @@ -142,6 +144,7 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #1, 'dx' is '0' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); if ((sample.coord.x >= 0f)) { @@ -152,6 +155,7 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #2, 'dx' is '1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); if ((sample.coord.x >= 0f)) { @@ -163,7 +167,9 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #1, 'dy' is '0' { + // unrolled iteration #0, 'dx' is '-1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); if ((sample.coord.x >= 0f)) { @@ -174,6 +180,7 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #1, 'dx' is '0' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); if ((sample.coord.x >= 0f)) { @@ -184,6 +191,7 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #2, 'dx' is '1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); if ((sample.coord.x >= 0f)) { @@ -195,7 +203,9 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #2, 'dy' is '1' { + // unrolled iteration #0, 'dx' is '-1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); if ((sample.coord.x >= 0f)) { @@ -206,6 +216,7 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #1, 'dx' is '0' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); if ((sample.coord.x >= 0f)) { @@ -216,6 +227,7 @@ describe('jump flood (voronoi) example', () => { } } } + // unrolled iteration #2, 'dx' is '1' { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); if ((sample.coord.x >= 0f)) { diff --git a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts index f34a5d28bf..0919149434 100644 --- a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts +++ b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts @@ -107,14 +107,17 @@ describe('liquid-glass example', () => { fn sampleWithChromaticAberration(tex: texture_2d, sampler2: sampler, uv: vec2f, offset: f32, dir: vec2f, blur: f32) -> vec3f { var samples = array(); + // unrolled iteration #0, 'i' is '0' { var channelOffset = (dir * (-1f * offset)); samples[0i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; } + // unrolled iteration #1, 'i' is '1' { var channelOffset = (dir * (0f * offset)); samples[1i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; } + // unrolled iteration #2, 'i' is '2' { var channelOffset = (dir * (1f * offset)); samples[2i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; diff --git a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts index 677f3e2271..264579f5d7 100644 --- a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts +++ b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts @@ -478,9 +478,11 @@ describe('ripple-cube example', () => { let material = (&materialUniform); var f0 = mix(vec3f(0.03999999910593033), (*material).albedo, (*material).metallic); var lo = vec3f(); + // unrolled iteration #0, 'i' is '0' { lo = (lo + evaluateLight(p, n, v, lightsUniform[0i], (*material), f0)); } + // unrolled iteration #1, 'i' is '1' { lo = (lo + evaluateLight(p, n, v, lightsUniform[1i], (*material), f0)); } @@ -562,7 +564,9 @@ describe('ripple-cube example', () => { var historyColor = textureLoad(historyTexture, coord, 0); var minColor = vec3f(9999); var maxColor = vec3f(-9999); + // unrolled iteration #0, 'ox' is '-1' { + // unrolled iteration #0, 'oy' is '-1' { var sampleCoord = (coord + vec2i(-1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -570,6 +574,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } + // unrolled iteration #1, 'oy' is '0' { var sampleCoord = (coord + vec2i(-1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -577,6 +582,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } + // unrolled iteration #2, 'oy' is '1' { var sampleCoord = (coord + vec2i(-1, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -585,7 +591,9 @@ describe('ripple-cube example', () => { maxColor = max(maxColor, neighbor); } } + // unrolled iteration #1, 'ox' is '0' { + // unrolled iteration #0, 'oy' is '-1' { var sampleCoord = (coord + vec2i(0, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -593,6 +601,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } + // unrolled iteration #1, 'oy' is '0' { var sampleCoord = (coord + vec2i()); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -600,6 +609,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } + // unrolled iteration #2, 'oy' is '1' { var sampleCoord = (coord + vec2i(0, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -608,7 +618,9 @@ describe('ripple-cube example', () => { maxColor = max(maxColor, neighbor); } } + // unrolled iteration #2, 'ox' is '1' { + // unrolled iteration #0, 'oy' is '-1' { var sampleCoord = (coord + vec2i(1, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -616,6 +628,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } + // unrolled iteration #1, 'oy' is '0' { var sampleCoord = (coord + vec2i(1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -623,6 +636,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } + // unrolled iteration #2, 'oy' is '1' { var sampleCoord = (coord + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index 306d89d593..8c7ab9fceb 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -184,6 +184,7 @@ describe('slime mold 3d example', () => { var totalWeight = 0f; var perp1 = getPerpendicular(direction); var perp2 = cross(direction, perp1); + // unrolled iteration #0, 'i' is '0' { const theta = 0.; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -194,6 +195,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } + // unrolled iteration #1, 'i' is '1' { const theta = 0.7853981633974483; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -204,6 +206,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } + // unrolled iteration #2, 'i' is '2' { const theta = 1.5707963267948966; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -214,6 +217,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } + // unrolled iteration #3, 'i' is '3' { const theta = 2.356194490192345; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -224,6 +228,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } + // unrolled iteration #4, 'i' is '4' { const theta = 3.141592653589793; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -234,6 +239,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } + // unrolled iteration #5, 'i' is '5' { const theta = 3.9269908169872414; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -244,6 +250,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } + // unrolled iteration #6, 'i' is '6' { const theta = 4.71238898038469; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -254,6 +261,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } + // unrolled iteration #7, 'i' is '7' { const theta = 5.497787143782138; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index 61bf5491be..b3d81d8bef 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -93,7 +93,9 @@ describe('slime mold example', () => { } var sum = vec3f(); var count = 0f; + // unrolled iteration #0, 'offsetY' is '-1' { + // unrolled iteration #0, 'offsetX' is '-1' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1)); var dimsi = vec2i(dims); @@ -103,6 +105,7 @@ describe('slime mold example', () => { count = (count + 1f); } } + // unrolled iteration #1, 'offsetX' is '0' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); var dimsi = vec2i(dims); @@ -112,6 +115,7 @@ describe('slime mold example', () => { count = (count + 1f); } } + // unrolled iteration #2, 'offsetX' is '1' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); var dimsi = vec2i(dims); @@ -122,7 +126,9 @@ describe('slime mold example', () => { } } } + // unrolled iteration #1, 'offsetY' is '0' { + // unrolled iteration #0, 'offsetX' is '-1' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); var dimsi = vec2i(dims); @@ -132,6 +138,7 @@ describe('slime mold example', () => { count = (count + 1f); } } + // unrolled iteration #1, 'offsetX' is '0' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i()); var dimsi = vec2i(dims); @@ -141,6 +148,7 @@ describe('slime mold example', () => { count = (count + 1f); } } + // unrolled iteration #2, 'offsetX' is '1' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); var dimsi = vec2i(dims); @@ -151,7 +159,9 @@ describe('slime mold example', () => { } } } + // unrolled iteration #2, 'offsetY' is '1' { + // unrolled iteration #0, 'offsetX' is '-1' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); var dimsi = vec2i(dims); @@ -161,6 +171,7 @@ describe('slime mold example', () => { count = (count + 1f); } } + // unrolled iteration #1, 'offsetX' is '0' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); var dimsi = vec2i(dims); @@ -170,6 +181,7 @@ describe('slime mold example', () => { count = (count + 1f); } } + // unrolled iteration #2, 'offsetX' is '1' { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1)); var dimsi = vec2i(dims); diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index b0f491987e..2a2c3eba08 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -62,15 +62,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); + // unrolled iteration #0, 'i' is '0' { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #1, 'i' is '1' { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #2, 'i' is '2' { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #3, 'i' is '3' { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } @@ -111,15 +115,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); + // unrolled iteration #0, 'i' is '0' { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #1, 'i' is '1' { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #2, 'i' is '2' { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #3, 'i' is '3' { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } @@ -148,15 +156,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); + // unrolled iteration #0, 'i' is '0' { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #1, 'i' is '1' { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #2, 'i' is '2' { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #3, 'i' is '3' { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } @@ -188,15 +200,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); + // unrolled iteration #0, 'i' is '0' { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #1, 'i' is '1' { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #2, 'i' is '2' { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } + // unrolled iteration #3, 'i' is '3' { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index a02ac716a7..2a76099a05 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -487,7 +487,7 @@ describe('wgslGenerator', () => { "fn main() { var arr = array(1f, 2f, 3f); var res = 0f; - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { res += foo; @@ -513,10 +513,10 @@ describe('wgslGenerator', () => { "fn main() { var arr = array(1f, 2f, 3f); var res = 0f; - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { - for (var i_1 = 0u; i_1 < 3; i_1++) { + for (var i_1 = 0u; i_1 < 3u; i_1++) { let boo = arr[i_1]; { res += (foo * boo); @@ -544,10 +544,10 @@ describe('wgslGenerator', () => { "fn main() { var arr = array(1f, 2f, 3f); var res = 0f; - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { - for (var i_1 = 0u; i_1 < 3; i_1++) { + for (var i_1 = 0u; i_1 < 3u; i_1++) { let foo2 = arr[i_1]; { res += (foo2 * foo2); @@ -573,7 +573,7 @@ describe('wgslGenerator', () => { "fn main() { var arr = array(vec2f(1), vec2f(2), vec2f(3)); var res = 0; - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = (&arr[i]); { res += i32((*foo).x); @@ -630,14 +630,14 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { var v1 = vec4u(44, 88, 132, 176); - for (var i = 0u; i < 4; i++) { + for (var i = 0u; i < 4u; i++) { let foo = v1[i]; { continue; } } var v2 = vec2f(1, 2); - for (var i = 0u; i < 2; i++) { + for (var i = 0u; i < 2u; i++) { let foo = v2[i]; { continue; @@ -666,7 +666,7 @@ describe('wgslGenerator', () => { fn f() -> u32 { var result = 0u; - for (var i = 0u; i < 7; i++) { + for (var i = 0u; i < 7u; i++) { let foo = b[i]; { result += foo; @@ -709,19 +709,19 @@ describe('wgslGenerator', () => { var res1 = 0f; var res2 = 0u; var res3 = false; - for (var i = 0u; i < 4; i++) { + for (var i = 0u; i < 4u; i++) { let foo = v1[i]; { res1 += foo; } } - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = v2[i]; { res2 *= foo; } } - for (var i = 0u; i < 2; i++) { + for (var i = 0u; i < 2u; i++) { let foo = v3[i]; { res3 = (foo != res3); @@ -751,7 +751,7 @@ describe('wgslGenerator', () => { fn main() { var testStruct = TestStruct(array(1f, 8f, 8f, 2f)); - for (var i = 0u; i < 4; i++) { + for (var i = 0u; i < 4u; i++) { let foo = testStruct.arr[i]; { continue; @@ -774,9 +774,9 @@ describe('wgslGenerator', () => { - - fn*:main - fn*:main(): \`for ... of ...\` loops only support iterables stored in variables. - ----- - You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. - -----] + ----- + You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. + -----] `); }); @@ -849,7 +849,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f1])).toMatchInlineSnapshot(` "fn f1() { var arr = array(1, 2, 3); - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { let i_1 = foo; @@ -871,7 +871,7 @@ describe('wgslGenerator', () => { "fn f2() { const i = 7; var arr = array(1, 2, 3); - for (var i_1 = 0u; i_1 < 3; i_1++) { + for (var i_1 = 0u; i_1 < 3u; i_1++) { let foo = arr[i_1]; { continue; @@ -897,7 +897,7 @@ describe('wgslGenerator', () => { fn f() { var arr = array(1u, 2u, 3u, i); - for (var i_1 = 0u; i_1 < 4; i_1++) { + for (var i_1 = 0u; i_1 < 4u; i_1++) { let foo = arr[i_1]; { continue; @@ -922,7 +922,7 @@ describe('wgslGenerator', () => { fn f() { var arr = array(1, 2, 3); - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { let x = (foo + i32(i_1)); @@ -950,7 +950,7 @@ describe('wgslGenerator', () => { fn f() { var arr = array(1, 2, 3); - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { let x = (foo + i32(i_1)); @@ -974,7 +974,7 @@ describe('wgslGenerator', () => { "fn f() { var arr = array(1, 2, 3); var res = 0; - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let i_1 = arr[i]; { res += i_1; diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 7e6989d0b6..a9091c19b3 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -50,12 +50,15 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { var res = 0; + // unrolled iteration #0, 'foo' is '1' { res += 1i; } + // unrolled iteration #1, 'foo' is '2' { res += 2i; } + // unrolled iteration #2, 'foo' is '3' { res += 3i; } @@ -76,9 +79,11 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { var foo = vec3f(6); + // unrolled iteration #0, 'foo2' is '1' { const boo = 1; } + // unrolled iteration #1, 'foo2' is '2' { const boo = 2; } @@ -105,6 +110,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> f32 { var fooResult = 0f; + // unrolled iteration #0, 'foo' is '1' { const boo = 1; { @@ -113,6 +119,7 @@ describe('tgpu.unroll', () => { } const bar = 1; } + // unrolled iteration #1, 'foo' is '2' { const boo = 2; { @@ -157,21 +164,27 @@ describe('tgpu.unroll', () => { var b1 = Boid(vec2i(1), vec2f(1)); var b2 = Boid(vec2i(2), vec2f(2)); var res = vec2f(); + // unrolled iteration #0, 'foo' is 'b1' { + // unrolled iteration #0, 'boo' is 'Boid()' { let baz = (&b1); res = ((res + (*baz).vel) + Boid().vel); } + // unrolled iteration #1, 'boo' is 'Boid()' { let baz = (&b1); res = ((res + (*baz).vel) + Boid().vel); } } + // unrolled iteration #1, 'foo' is 'b2' { + // unrolled iteration #0, 'boo' is 'Boid()' { let baz = (&b2); res = ((res + (*baz).vel) + Boid().vel); } + // unrolled iteration #1, 'boo' is 'Boid()' { let baz = (&b2); res = ((res + (*baz).vel) + Boid().vel); @@ -202,11 +215,13 @@ describe('tgpu.unroll', () => { var res = vec2f(); var v1 = vec2f(7); var v2 = vec2f(3); + // unrolled iteration #0, 'foo' is 'v1' { res = (res + v1); var boo = v1; boo.x = 0f; } + // unrolled iteration #1, 'foo' is 'v2' { res = (res + v2); var boo = v2; @@ -233,12 +248,15 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> u32 { var result = 0u; + // unrolled iteration #0, 'prop' is 'a' { result += 1u; } + // unrolled iteration #1, 'prop' is 'b' { result += 2u; } + // unrolled iteration #2, 'prop' is 'c' { result += 3u; } @@ -294,9 +312,11 @@ describe('tgpu.unroll', () => { fn computeWeight(weights: Weights) -> f32 { var p = 0f; + // unrolled iteration #0, 'key' is 'foo' { p += (weights.foo * foo(p)); } + // unrolled iteration #1, 'key' is 'boo' { p += (weights.boo * boo(p)); } @@ -325,11 +345,13 @@ describe('tgpu.unroll', () => { var res = vec2f(); var v1 = vec2f(7); var v2 = vec2f(3); + // unrolled iteration #0, 'foo' is 'v1' { let boo = (&v1); res = (res + v1); (*boo).x = 6f; } + // unrolled iteration #1, 'foo' is 'v2' { let boo = (&v2); res = (res + v2); @@ -340,7 +362,7 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls ephemeral vector', () => { + it('unrolls ephemeral vector - (instance)', () => { const f = () => { 'use gpu'; let res = d.u32(0); @@ -354,15 +376,19 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> u32 { var res = 0u; + // unrolled iteration #0, 'foo' is '1u' { res += 1u; } + // unrolled iteration #1, 'foo' is '2u' { res += 2u; } + // unrolled iteration #2, 'foo' is '3u' { res += 3u; } + // unrolled iteration #3, 'foo' is '4u' { res += 4u; } @@ -371,6 +397,41 @@ describe('tgpu.unroll', () => { `); }); + it('unrolls ephemeral vector - (string)', () => { + const f = () => { + 'use gpu'; + + const v = d.vec3f(7); + + let res = 0; + for (const pos of tgpu.unroll(d.vec3f(v))) { + res = res + pos; + } + + return res; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var v = vec3f(7); + var res = 0; + // unrolled iteration #0, 'pos' is 'v[0u]' + { + res = i32((f32(res) + v[0u])); + } + // unrolled iteration #1, 'pos' is 'v[1u]' + { + res = i32((f32(res) + v[1u])); + } + // unrolled iteration #2, 'pos' is 'v[2u]' + { + res = i32((f32(res) + v[2u])); + } + return res; + }" + `); + }); + it('unrolls external compile-time iterable', () => { const arr = [1, 2, 3]; @@ -387,12 +448,15 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { var result = 0; + // unrolled iteration #0, 'foo' is '1' { result += 1i; } + // unrolled iteration #1, 'foo' is '2' { result += 2i; } + // unrolled iteration #2, 'foo' is '3' { result += 3i; } @@ -438,12 +502,15 @@ describe('tgpu.unroll', () => { "fn f() -> f32 { var arr = array(1, 2, 3); var res = 0f; + // unrolled iteration #0, 'foo' is 'arr[0u]' { res += f32(arr[0u]); } + // unrolled iteration #1, 'foo' is 'arr[1u]' { res += f32(arr[1u]); } + // unrolled iteration #2, 'foo' is 'arr[2u]' { res += f32(arr[2u]); } @@ -477,18 +544,22 @@ describe('tgpu.unroll', () => { var v3 = vec2f(2); var arr = array(v1, v2, v2, v3); var res = vec2f(); + // unrolled iteration #0, 'foo' is 'arr[0u]' { res = (res + arr[0u]); arr[0u].x = 7f; } + // unrolled iteration #1, 'foo' is 'arr[1u]' { res = (res + arr[1u]); arr[1u].x = 7f; } + // unrolled iteration #2, 'foo' is 'arr[2u]' { res = (res + arr[2u]); arr[2u].x = 7f; } + // unrolled iteration #3, 'foo' is 'arr[3u]' { res = (res + arr[3u]); arr[3u].x = 7f; @@ -530,10 +601,12 @@ describe('tgpu.unroll', () => { var b2 = Boid(vec2i(2), vec2f(2)); var arr = array(b1, b2); var res = vec2f(); + // unrolled iteration #0, 'foo' is 'arr[0u]' { res = (res + arr[0u].vel); arr[0u].pos.x = 7i; } + // unrolled iteration #1, 'foo' is 'arr[1u]' { res = (res + arr[1u].vel); arr[1u].pos.x = 7i; @@ -562,24 +635,31 @@ describe('tgpu.unroll', () => { fn f() -> u32 { var result = 0u; + // unrolled iteration #0, 'foo' is 'b[0u]' { result += b[0u]; } + // unrolled iteration #1, 'foo' is 'b[1u]' { result += b[1u]; } + // unrolled iteration #2, 'foo' is 'b[2u]' { result += b[2u]; } + // unrolled iteration #3, 'foo' is 'b[3u]' { result += b[3u]; } + // unrolled iteration #4, 'foo' is 'b[4u]' { result += b[4u]; } + // unrolled iteration #5, 'foo' is 'b[5u]' { result += b[5u]; } + // unrolled iteration #6, 'foo' is 'b[6u]' { result += b[6u]; } @@ -604,12 +684,15 @@ describe('tgpu.unroll', () => { "fn f() { var arr = array(1, 2, 3); var r = 0f; + // unrolled iteration #0, 'foo' is 'arr[0u]' { r += f32(arr[0u]); } + // unrolled iteration #1, 'foo' is 'arr[1u]' { r += f32(arr[1u]); } + // unrolled iteration #2, 'foo' is 'arr[2u]' { r += f32(arr[2u]); } @@ -620,7 +703,7 @@ describe('tgpu.unroll', () => { "fn f() { var arr = array(1, 2, 3); var r = 0f; - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { r += f32(foo); @@ -659,4 +742,40 @@ describe('tgpu.unroll', () => { - fn*:f2(): Cannot unroll loop containing \`break\` or \`continue\`] `); }); + + it('throws when `continue` is used in nested blocks', () => { + const f = () => { + 'use gpu'; + for (const foo of tgpu.unroll([1, 2])) { + const boo = foo; + { + if (boo === foo) { + continue; + } + } + } + }; + + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f(): Cannot unroll loop containing \`break\` or \`continue\`] + `); + }); + + it.skip('unrolls when `continue` is used in nested loop', () => { + const f = () => { + 'use gpu'; + for (const foo of tgpu.unroll([1, 2])) { + for (let i = 0; i < 2; i++) { + if (i === foo) { + continue; + } + } + } + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(); + }); }); From 73ec870360b4ff5a29c6713df87c369f05ceda3b Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 13:42:48 +0100 Subject: [PATCH 58/67] more cleanup --- apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts | 3 +-- .../src/examples/simulation/slime-mold-3d/index.ts | 2 +- packages/typegpu/src/tgsl/forOfUtils.ts | 4 ++-- packages/typegpu/src/tgsl/wgslGenerator.ts | 8 ++++++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts b/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts index feabd482ca..b4408fc9af 100644 --- a/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts +++ b/apps/typegpu-docs/src/examples/simple/ripple-cube/pbr.ts @@ -86,8 +86,7 @@ export const evaluateLight = ( .mul(ndotl); }; -const lightCountIterations = Array.from({ length: LIGHT_COUNT }) - .map((_, i) => i); +const lightCountIterations = Array.from({ length: LIGHT_COUNT }, (_, i) => i); export const shade = (p: d.v3f, n: d.v3f, v: d.v3f): d.v3f => { 'use gpu'; const material = materialAccess.$; diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts index e558b9651c..201c05c662 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts @@ -177,7 +177,7 @@ const getPerpendicular = (dir: d.v3f) => { }; const numSamples = 8; -const samplesIterations = Array.from({ length: numSamples }).map((_, i) => i); +const samplesIterations = Array.from({ length: numSamples }, (_, i) => i); const sense3D = (pos: d.v3f, direction: d.v3f) => { 'use gpu'; const dims = std.textureDimensions(computeLayout.$.oldState); diff --git a/packages/typegpu/src/tgsl/forOfUtils.ts b/packages/typegpu/src/tgsl/forOfUtils.ts index b6968217fd..36578c19ca 100644 --- a/packages/typegpu/src/tgsl/forOfUtils.ts +++ b/packages/typegpu/src/tgsl/forOfUtils.ts @@ -22,11 +22,11 @@ export function getLoopVarKind(elementSnippet: Snippet) { export function getElementSnippet( iterableSnippet: Snippet, - index: string | number, + index: Snippet, ) { const elementSnippet = accessIndex( iterableSnippet, - snip(index, u32, 'runtime'), + index, ); if (!elementSnippet) { diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 802357a475..7bb8c69469 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1228,7 +1228,11 @@ ${this.ctx.pre}else ${alternate}`; ? value.elements : Array.from( { length }, - (_, i) => forOfUtils.getElementSnippet(iterableSnippet, i), + (_, i) => + forOfUtils.getElementSnippet( + iterableSnippet, + snip(i, u32, 'constant'), + ), ); const blocks = elements @@ -1262,7 +1266,7 @@ ${this.ctx.pre}else ${alternate}`; const index = this.ctx.makeNameValid('i'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, - index, + snip(index, u32, 'runtime'), ); const loopVarName = this.ctx.makeNameValid(originalLoopVarName); const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet); From e88c10a7d7021988ce47244d55a43aacc7cde61d Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 14:14:47 +0100 Subject: [PATCH 59/67] continue and break detection --- packages/typegpu/src/tgsl/wgslGenerator.ts | 46 ++++++++---- packages/typegpu/tests/unroll.test.ts | 84 ++++++++++++++++++++-- 2 files changed, 111 insertions(+), 19 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 7bb8c69469..4de440cb2d 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -187,6 +187,8 @@ const binaryOpCodeToCodegen = { class WgslGenerator implements ShaderGenerator { #ctx: GenerationCtx | undefined = undefined; + // used to detect `continue` and `break` nodes in loop body + #unrolling = false; public initGenerator(ctx: GenerationCtx) { this.#ctx = ctx; @@ -1155,6 +1157,8 @@ ${this.ctx.pre}else ${alternate}`; if (statement[0] === NODE.for) { const [_, init, condition, update, body] = statement; + const prevUnrollingFlag = this.#unrolling; + this.#unrolling = false; try { this.ctx.pushBlockScope(); @@ -1171,17 +1175,24 @@ ${this.ctx.pre}else ${alternate}`; const bodyStr = this.block(blockifySingleStatement(body)); return stitch`${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr}`; } finally { + this.#unrolling = prevUnrollingFlag; this.ctx.popBlockScope(); } } if (statement[0] === NODE.while) { - const [_, condition, body] = statement; - const condSnippet = this.typedExpression(condition, bool); - const conditionStr = this.ctx.resolve(condSnippet.value).value; + const prevUnrollingFlag = this.#unrolling; + this.#unrolling = false; + try { + const [_, condition, body] = statement; + const condSnippet = this.typedExpression(condition, bool); + const conditionStr = this.ctx.resolve(condSnippet.value).value; - const bodyStr = this.block(blockifySingleStatement(body)); - return `${this.ctx.pre}while (${conditionStr}) ${bodyStr}`; + const bodyStr = this.block(blockifySingleStatement(body)); + return `${this.ctx.pre}while (${conditionStr}) ${bodyStr}`; + } finally { + this.#unrolling = prevUnrollingFlag; + } } if (statement[0] === NODE.forOf) { @@ -1194,6 +1205,7 @@ ${this.ctx.pre}else ${alternate}`; } let ctxIndent = false; + const prevUnrollingFlag = this.#unrolling; try { this.ctx.pushBlockScope(); @@ -1217,6 +1229,8 @@ ${this.ctx.pre}else ${alternate}`; ); } + this.#unrolling = true; + const length = elementCountSnippet.value as number; if (length === 0) { return ''; @@ -1242,15 +1256,6 @@ ${this.ctx.pre}else ${alternate}`; }` ); - // TODO: replace it with tinyest traversal - if ( - blocks[0]?.includes('break') || blocks[0]?.includes('continue') - ) { - throw new WgslTypeError( - 'Cannot unroll loop containing `break` or `continue`', - ); - } - return blocks.join('\n'); } @@ -1263,6 +1268,8 @@ ${this.ctx.pre}else ${alternate}`; ); } + this.#unrolling = false; + const index = this.ctx.makeNameValid('i'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, @@ -1311,15 +1318,26 @@ ${this.ctx.pre}else ${alternate}`; if (ctxIndent) { this.ctx.dedent(); } + this.#unrolling = prevUnrollingFlag; this.ctx.popBlockScope(); } } if (statement[0] === NODE.continue) { + if (this.#unrolling) { + throw new WgslTypeError( + 'Cannot unroll loop containing `continue`', + ); + } return `${this.ctx.pre}continue;`; } if (statement[0] === NODE.break) { + if (this.#unrolling) { + throw new WgslTypeError( + 'Cannot unroll loop containing `break`', + ); + } return `${this.ctx.pre}break;`; } diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index a9091c19b3..15bd085f86 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -725,7 +725,7 @@ describe('tgpu.unroll', () => { [Error: Resolution of the following tree failed: - - fn*:f1 - - fn*:f1(): Cannot unroll loop containing \`break\` or \`continue\`] + - fn*:f1(): Cannot unroll loop containing \`continue\`] `); const f2 = () => { @@ -739,7 +739,7 @@ describe('tgpu.unroll', () => { [Error: Resolution of the following tree failed: - - fn*:f2 - - fn*:f2(): Cannot unroll loop containing \`break\` or \`continue\`] + - fn*:f2(): Cannot unroll loop containing \`break\`] `); }); @@ -760,22 +760,96 @@ describe('tgpu.unroll', () => { [Error: Resolution of the following tree failed: - - fn*:f - - fn*:f(): Cannot unroll loop containing \`break\` or \`continue\`] + - fn*:f(): Cannot unroll loop containing \`continue\`] `); }); - it.skip('unrolls when `continue` is used in nested loop', () => { + it('unrolls when `continue` or `break` is used in nested loop', () => { const f = () => { 'use gpu'; + const arr = [1, 2, 3]; + for (const foo of tgpu.unroll([1, 2])) { for (let i = 0; i < 2; i++) { if (i === foo) { continue; } } + let i = 2; + while (i > 2) { + i--; + break; + } + + for (const boo of arr) { + continue; + } } }; - expect(tgpu.resolve([f])).toMatchInlineSnapshot(); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var arr = array(1, 2, 3); + // unrolled iteration #0, 'foo' is '1' + { + for (var i2 = 0; (i2 < 2i); i2++) { + if ((i2 == 1i)) { + continue; + } + } + var i = 2; + while ((i > 2i)) { + i--; + break; + } + for (var i_1 = 0u; i_1 < 3u; i_1++) { + let boo = arr[i_1]; + { + continue; + } + } + } + // unrolled iteration #1, 'foo' is '2' + { + for (var i2 = 0; (i2 < 2i); i2++) { + if ((i2 == 2i)) { + continue; + } + } + var i = 2; + while ((i > 2i)) { + i--; + break; + } + for (var i_1 = 0u; i_1 < 3u; i_1++) { + let boo = arr[i_1]; + { + continue; + } + } + } + }" + `); + }); + + it('unrolling flag is set correclty', () => { + const f = () => { + 'use gpu'; + const arr = [1, 2, 3]; + + for (const foo of tgpu.unroll([1, 2])) { + for (const boo of arr) { + continue; + } + break; + } + }; + + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f(): Cannot unroll loop containing \`break\`] + `); }); }); From 65b04d9090a28f1b8b02673f4210c053b51477c0 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 14:26:25 +0100 Subject: [PATCH 60/67] oxlint --- apps/typegpu-docs/src/examples/rendering/clouds/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts index ddf3a86247..d5d09e63e3 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts +++ b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts @@ -78,7 +78,7 @@ const fbm = tgpu.fn([d.vec3f], d.f32)((pos) => { let amp = d.f32(CLOUD_AMPLITUDE); let freq = d.f32(CLOUD_FREQUENCY); - for (const i of tgpu.unroll(iterations)) { + for (const _i of tgpu.unroll(iterations)) { sum += noise3d(std.mul(pos, freq)) * amp; amp *= FBM_PERSISTENCE; freq *= FBM_LACUNARITY; From 552f505baac56dacf838dbee5534c5372b699627 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 14:26:25 +0100 Subject: [PATCH 61/67] oxlint --- apps/typegpu-docs/src/examples/rendering/clouds/utils.ts | 2 +- packages/typegpu/tests/examples/individual/clouds.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts index ddf3a86247..d5d09e63e3 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts +++ b/apps/typegpu-docs/src/examples/rendering/clouds/utils.ts @@ -78,7 +78,7 @@ const fbm = tgpu.fn([d.vec3f], d.f32)((pos) => { let amp = d.f32(CLOUD_AMPLITUDE); let freq = d.f32(CLOUD_FREQUENCY); - for (const i of tgpu.unroll(iterations)) { + for (const _i of tgpu.unroll(iterations)) { sum += noise3d(std.mul(pos, freq)) * amp; amp *= FBM_PERSISTENCE; freq *= FBM_LACUNARITY; diff --git a/packages/typegpu/tests/examples/individual/clouds.test.ts b/packages/typegpu/tests/examples/individual/clouds.test.ts index 0acd21062d..1b79afc6dd 100644 --- a/packages/typegpu/tests/examples/individual/clouds.test.ts +++ b/packages/typegpu/tests/examples/individual/clouds.test.ts @@ -86,19 +86,19 @@ describe('clouds example', () => { var sum = 0f; var amp = 1f; var freq = 1.399999976158142f; - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0, '_i' is '0' { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; freq *= 2f; } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1, '_i' is '1' { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; freq *= 2f; } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2, '_i' is '2' { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; From 819f3e8b85b206042b2bc8431e650d5040c5ba93 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 14:36:41 +0100 Subject: [PATCH 62/67] oxlint --- packages/typegpu/src/tgsl/wgslGenerator.ts | 2 +- packages/typegpu/tests/unroll.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 4de440cb2d..237390a326 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1212,7 +1212,7 @@ ${this.ctx.pre}else ${alternate}`; const iterableExpr = this.expression(iterable); const shouldUnroll = iterableExpr.value instanceof UnrollableIterable; const iterableSnippet = shouldUnroll - ? (iterableExpr.value as UnrollableIterable).snippet + ? iterableExpr.value.snippet : iterableExpr; const elementCountSnippet = forOfUtils.getElementCountSnippet( this.ctx, diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 15bd085f86..03f82ead30 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -278,7 +278,7 @@ describe('tgpu.unroll', () => { }; const Weights = d.struct(Object.fromEntries( - Object.keys(variants).map((name) => [`${name}`, d.f32]), + Object.keys(variants).map((name) => [name, d.f32]), )); const variantsKey = Object.keys(variants) as (keyof typeof variants)[]; From ab119e675274461b8ddc692c7db9c928fe07ea73 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 15:11:24 +0100 Subject: [PATCH 63/67] cleanup --- packages/typegpu/src/tgsl/wgslGenerator.ts | 4 +--- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 237390a326..5ec6928665 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1283,9 +1283,7 @@ ${this.ctx.pre}else ${alternate}`; ); const forHeaderStr = - stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${ - tryConvertSnippet(this.ctx, elementCountSnippet, u32, false) - }; ${index}++) {`; + stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${elementCountSnippet}; ${index}++) {`; this.ctx.indent(); ctxIndent = true; diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 082ba0f7e4..2a76099a05 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -516,7 +516,7 @@ describe('wgslGenerator', () => { for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { - for (var i_1 = 0u; i_1 < 3; i_1++) { + for (var i_1 = 0u; i_1 < 3u; i_1++) { let boo = arr[i_1]; { res += (foo * boo); @@ -547,7 +547,7 @@ describe('wgslGenerator', () => { for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { - for (var i_1 = 0u; i_1 < 3; i_1++) { + for (var i_1 = 0u; i_1 < 3u; i_1++) { let foo2 = arr[i_1]; { res += (foo2 * foo2); @@ -871,7 +871,7 @@ describe('wgslGenerator', () => { "fn f2() { const i = 7; var arr = array(1, 2, 3); - for (var i_1 = 0u; i_1 < 3; i_1++) { + for (var i_1 = 0u; i_1 < 3u; i_1++) { let foo = arr[i_1]; { continue; @@ -897,7 +897,7 @@ describe('wgslGenerator', () => { fn f() { var arr = array(1u, 2u, 3u, i); - for (var i_1 = 0u; i_1 < 4; i_1++) { + for (var i_1 = 0u; i_1 < 4u; i_1++) { let foo = arr[i_1]; { continue; @@ -922,7 +922,7 @@ describe('wgslGenerator', () => { fn f() { var arr = array(1, 2, 3); - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { let x = (foo + i32(i_1)); @@ -950,7 +950,7 @@ describe('wgslGenerator', () => { fn f() { var arr = array(1, 2, 3); - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let foo = arr[i]; { let x = (foo + i32(i_1)); @@ -974,7 +974,7 @@ describe('wgslGenerator', () => { "fn f() { var arr = array(1, 2, 3); var res = 0; - for (var i = 0u; i < 3; i++) { + for (var i = 0u; i < 3u; i++) { let i_1 = arr[i]; { res += i_1; From 7dca2e72c12a36cb8cd5929336348806a4af329f Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 15:15:58 +0100 Subject: [PATCH 64/67] deleted test --- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 2a76099a05..c63fac6f51 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1105,6 +1105,56 @@ describe('wgslGenerator', () => { .toStrictEqual(4); }); + it('generates correct code for array expressions', () => { + const testFn = tgpu.fn([], d.u32)(() => { + const arr = [d.u32(1), 2, 3]; + return arr[1] as number; + }); + + expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` + "fn testFn() -> u32 { + var arr = array(1u, 2u, 3u); + return arr[1i]; + }" + `); + + const astInfo = getMetaData( + testFn[$internal].implementation as (...args: unknown[]) => unknown, + ); + + if (!astInfo) { + throw new Error('Expected prebuilt AST to be present'); + } + + expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( + `"[0,[[13,"arr",[100,[[6,[7,"d","u32"],[[5,"1"]]],[5,"2"],[5,"3"]]]],[10,[8,"arr",[5,"1"]]]]]"`, + ); + + provideCtx(ctx, () => { + ctx[$internal].itemStateStack.pushFunctionScope( + 'normal', + [], + {}, + d.u32, + (astInfo.externals as () => Record)() ?? {}, + ); + + // Check for: const arr = [1, 2, 3] + // ^ this should be an array + wgslGenerator.initGenerator(ctx); + const res = wgslGenerator.expression( + // deno-fmt-ignore: it's better that way + ( + astInfo.ast?.body[1][0] as tinyest.Const + )[2] as unknown as tinyest.Expression, + ); + + expect(d.isWgslArray(res.dataType)).toBe(true); + expect((res.dataType as unknown as WgslArray).elementCount).toBe(3); + expect((res.dataType as unknown as WgslArray).elementType).toBe(d.u32); + }); + }); + it('generates correct code for complex array expressions', () => { const testFn = tgpu.fn([], d.u32)(() => { const arr = [ From 7040af8a38ab2ec7d874ed62a7bea4909346c2a1 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 15:53:17 +0100 Subject: [PATCH 65/67] review changes --- packages/typegpu/src/tgsl/wgslGenerator.ts | 9 +++ packages/typegpu/tests/unroll.test.ts | 75 +++++++--------------- 2 files changed, 31 insertions(+), 53 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 5ec6928665..638beb869d 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1249,6 +1249,15 @@ ${this.ctx.pre}else ${alternate}`; ), ); + if ( + isEphemeralSnippet(elements[0] as Snippet) && + !wgsl.isNaturallyEphemeral(elements[0]?.dataType) + ) { + throw new WgslTypeError( + 'Cannot unroll loop. The elements of iterable are emphemeral but not naturally ephemeral.', + ); + } + const blocks = elements .map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}, '${originalLoopVarName}' is '${stitch`${e}`}'\n${this.ctx.pre}${ diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 03f82ead30..3d81e297c0 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -145,10 +145,9 @@ describe('tgpu.unroll', () => { const b2 = Boid({ pos: d.vec2i(2), vel: d.vec2f(2) }); let res = d.vec2f(); for (const foo of tgpu.unroll([b1, b2])) { - for (const boo of tgpu.unroll([Boid(), Boid()])) { - const baz = foo; - res = res.add(baz.vel).add(boo.vel); - } + const boo = foo; + res = res.add(foo.vel); + boo.pos = d.vec2i(); } return res; @@ -166,69 +165,39 @@ describe('tgpu.unroll', () => { var res = vec2f(); // unrolled iteration #0, 'foo' is 'b1' { - // unrolled iteration #0, 'boo' is 'Boid()' - { - let baz = (&b1); - res = ((res + (*baz).vel) + Boid().vel); - } - // unrolled iteration #1, 'boo' is 'Boid()' - { - let baz = (&b1); - res = ((res + (*baz).vel) + Boid().vel); - } + let boo = (&b1); + res = (res + b1.vel); + (*boo).pos = vec2i(); } // unrolled iteration #1, 'foo' is 'b2' { - // unrolled iteration #0, 'boo' is 'Boid()' - { - let baz = (&b2); - res = ((res + (*baz).vel) + Boid().vel); - } - // unrolled iteration #1, 'boo' is 'Boid()' - { - let baz = (&b2); - res = ((res + (*baz).vel) + Boid().vel); - } + let boo = (&b2); + res = (res + b2.vel); + (*boo).pos = vec2i(); } return res; }" `); }); - it('unrolls array expression of copies', () => { + it('throws when iterable elements are ephemeral but not naturally emphemeral', () => { + const Boid = d.struct({ + pos: d.vec2i, + vel: d.vec2f, + }); + const f = () => { 'use gpu'; - let res = d.vec2f(); - const v1 = d.vec2f(7); - const v2 = d.vec2f(3); - for (const foo of tgpu.unroll([d.vec2f(v1), d.vec2f(v2)])) { - res = res.add(foo); - const boo = foo; - boo.x = 0; + for (const foo of tgpu.unroll([Boid()])) { + continue; } - - return res; }; - expect(tgpu.resolve([f])).toMatchInlineSnapshot(` - "fn f() -> vec2f { - var res = vec2f(); - var v1 = vec2f(7); - var v2 = vec2f(3); - // unrolled iteration #0, 'foo' is 'v1' - { - res = (res + v1); - var boo = v1; - boo.x = 0f; - } - // unrolled iteration #1, 'foo' is 'v2' - { - res = (res + v2); - var boo = v2; - boo.x = 0f; - } - return res; - }" + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:f + - fn*:f(): Cannot unroll loop. The elements of iterable are emphemeral but not naturally ephemeral.] `); }); From 0a94c841820bfcde0d2099aa86932c789f32dcc8 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 17:59:41 +0100 Subject: [PATCH 66/67] review changes --- packages/typegpu/src/core/unroll/tgpuUnroll.ts | 7 +++---- packages/typegpu/src/tgsl/wgslGenerator.ts | 8 ++++---- packages/typegpu/src/types.ts | 2 -- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 2 +- packages/typegpu/tests/unroll.test.ts | 8 ++++---- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/typegpu/src/core/unroll/tgpuUnroll.ts b/packages/typegpu/src/core/unroll/tgpuUnroll.ts index 853b560865..cfb31fd563 100644 --- a/packages/typegpu/src/core/unroll/tgpuUnroll.ts +++ b/packages/typegpu/src/core/unroll/tgpuUnroll.ts @@ -6,7 +6,6 @@ import { } from '../../../src/shared/symbols.ts'; import { setName } from '../../../src/shared/meta.ts'; import type { DualFn } from '../../../src/types.ts'; - import type { AnyData } from '../../../src/data/dataTypes.ts'; import { type ResolvedSnippet, @@ -18,9 +17,9 @@ import type { ResolutionCtx, SelfResolvable } from '../../../src/types.ts'; /** * The result of calling `tgpu.unroll(...)`. The code responsible for * generating shader code can check if the value of a snippet is - * an instance of `UnrolledIterable`, and act accordingly. + * an instance of `UnrollableIterable`, and act accordingly. */ -export class UnrolledIterable implements SelfResolvable { +export class UnrollableIterable implements SelfResolvable { readonly [$internal] = true; constructor(public readonly snippet: Snippet) {} @@ -47,7 +46,7 @@ export const unroll = (() => { impl[$internal] = true; impl[$gpuCallable] = { call(_ctx, [value]) { - return snip(new UnrolledIterable(value), value.dataType, value.origin); + return snip(new UnrollableIterable(value), value.dataType, value.origin); }, }; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 638beb869d..afc3e85d33 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -47,7 +47,7 @@ import type { ShaderGenerator } from './shaderGenerator.ts'; import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; -import { UnrolledIterable as UnrollableIterable } from '../../src/core/unroll/tgpuUnroll.ts'; +import { UnrollableIterable } from '../core/unroll/tgpuUnroll.ts'; import { isGenericFn } from '../core/function/tgpuFn.ts'; import type { AnyFn } from '../core/function/fnTypes.ts'; import { AutoStruct } from '../data/autoStruct.ts'; @@ -1223,9 +1223,9 @@ ${this.ctx.pre}else ${alternate}`; const blockified = blockifySingleStatement(body); if (shouldUnroll) { - if ((stitch`${elementCountSnippet}`).includes('arrayLength')) { + if (!isKnownAtComptime(elementCountSnippet)) { throw new Error( - 'Cannot unroll loop. Length of iterable is unknown at compile-time.', + 'Cannot unroll loop. Length of iterable is unknown at comptime.', ); } @@ -1272,7 +1272,7 @@ ${this.ctx.pre}else ${alternate}`; throw new Error( `\`for ... of ...\` loops only support iterables stored in variables. ----- - You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. + You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at comptime, the loop will be unrolled. -----`, ); } diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 28502eb633..1125a50c03 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -161,8 +161,6 @@ export interface ItemStateStack { readSlot(slot: TgpuSlot): T | undefined; getSnippetById(id: string): Snippet | undefined; defineBlockVariable(id: string, snippet: Snippet): void; - setBlockExternals(externals: Record): void; - clearBlockExternals(): void; } /** diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index c63fac6f51..46a8def741 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -775,7 +775,7 @@ describe('wgslGenerator', () => { - fn*:main - fn*:main(): \`for ... of ...\` loops only support iterables stored in variables. ----- - You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at compile-time, the loop will be unrolled. + You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at comptime, the loop will be unrolled. -----] `); }); diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index 3d81e297c0..cc65dd6134 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -189,7 +189,7 @@ describe('tgpu.unroll', () => { const f = () => { 'use gpu'; for (const foo of tgpu.unroll([Boid()])) { - continue; + const boo = foo; } }; @@ -401,7 +401,7 @@ describe('tgpu.unroll', () => { `); }); - it('unrolls external compile-time iterable', () => { + it('unrolls external comptime iterable', () => { const arr = [1, 2, 3]; const f = () => { @@ -434,7 +434,7 @@ describe('tgpu.unroll', () => { `); }); - it('throws when iterable element count is unknown at compile-time', () => { + it('throws when iterable element count is unknown at comptime', () => { const layout = tgpu.bindGroupLayout({ arr: { storage: d.arrayOf(d.f32) }, }); @@ -451,7 +451,7 @@ describe('tgpu.unroll', () => { [Error: Resolution of the following tree failed: - - fn*:f - - fn*:f(): Cannot unroll loop. Length of iterable is unknown at compile-time.] + - fn*:f(): Cannot unroll loop. Length of iterable is unknown at comptime.] `); }); From f2fca4e0eee6c6beaa3d58ef2be92a4796e98922 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 24 Feb 2026 18:12:53 +0100 Subject: [PATCH 67/67] more review changes --- packages/typegpu/src/tgsl/wgslGenerator.ts | 2 +- .../tests/examples/individual/3d-fish.test.ts | 6 +- .../tests/examples/individual/blur.test.ts | 80 ++++++------ .../tests/examples/individual/clouds.test.ts | 6 +- .../individual/cubemap-reflection.test.ts | 24 ++-- .../individual/fluid-double-buffering.test.ts | 16 +-- .../examples/individual/jelly-slider.test.ts | 24 ++-- .../examples/individual/jelly-switch.test.ts | 24 ++-- .../individual/jump-flood-distance.test.ts | 24 ++-- .../individual/jump-flood-voronoi.test.ts | 24 ++-- .../examples/individual/liquid-glass.test.ts | 6 +- .../examples/individual/ripple-cube.test.ts | 28 ++--- .../examples/individual/slime-mold-3d.test.ts | 16 +-- .../examples/individual/slime-mold.test.ts | 24 ++-- .../examples/individual/stable-fluid.test.ts | 32 ++--- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +- packages/typegpu/tests/unroll.test.ts | 117 ++++++++++-------- 17 files changed, 238 insertions(+), 219 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index afc3e85d33..0c7d8a02ea 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1260,7 +1260,7 @@ ${this.ctx.pre}else ${alternate}`; const blocks = elements .map((e, i) => - `${this.ctx.pre}// unrolled iteration #${i}, '${originalLoopVarName}' is '${stitch`${e}`}'\n${this.ctx.pre}${ + `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${ this.block(blockified, { [originalLoopVarName]: e }) }` ); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 03a1679987..03d8de4ed8 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -160,7 +160,7 @@ describe('3d fish example', () => { if ((cohesionCount > 0i)) { cohesion = ((cohesion / f32(cohesionCount)) - (*fishData).position); } - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { var repulsion = vec3f(); repulsion[0i] = 1f; @@ -176,7 +176,7 @@ describe('3d fish example', () => { wallRepulsion = (wallRepulsion + (repulsion * str2)); } } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { var repulsion = vec3f(); repulsion[1i] = 1f; @@ -192,7 +192,7 @@ describe('3d fish example', () => { wallRepulsion = (wallRepulsion + (repulsion * str2)); } } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { var repulsion = vec3f(); repulsion[2i] = 1f; diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index 9fb82e92dd..f34a83cf36 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -52,9 +52,9 @@ describe('blur example', () => { let filterOffset = i32((f32(((*settings2).filterDim - 1i)) / 2f)); var dims = vec2i(textureDimensions(inTexture)); var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u((*settings2).blockDim, 4u)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0i)); - // unrolled iteration #0, 'r' is '0' + // unrolled iteration #0 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var loadIndex = (baseIndex + vec2i()); if ((flip != 0u)) { @@ -62,7 +62,7 @@ describe('blur example', () => { } tileData[0i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var loadIndex = (baseIndex + vec2i(1, 0)); if ((flip != 0u)) { @@ -70,7 +70,7 @@ describe('blur example', () => { } tileData[0i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var loadIndex = (baseIndex + vec2i(2, 0)); if ((flip != 0u)) { @@ -78,7 +78,7 @@ describe('blur example', () => { } tileData[0i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var loadIndex = (baseIndex + vec2i(3, 0)); if ((flip != 0u)) { @@ -87,9 +87,9 @@ describe('blur example', () => { tileData[0i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } } - // unrolled iteration #1, 'r' is '1' + // unrolled iteration #1 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var loadIndex = (baseIndex + vec2i(0, 1)); if ((flip != 0u)) { @@ -97,7 +97,7 @@ describe('blur example', () => { } tileData[1i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var loadIndex = (baseIndex + vec2i(1)); if ((flip != 0u)) { @@ -105,7 +105,7 @@ describe('blur example', () => { } tileData[1i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var loadIndex = (baseIndex + vec2i(2, 1)); if ((flip != 0u)) { @@ -113,7 +113,7 @@ describe('blur example', () => { } tileData[1i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var loadIndex = (baseIndex + vec2i(3, 1)); if ((flip != 0u)) { @@ -122,9 +122,9 @@ describe('blur example', () => { tileData[1i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } } - // unrolled iteration #2, 'r' is '2' + // unrolled iteration #2 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var loadIndex = (baseIndex + vec2i(0, 2)); if ((flip != 0u)) { @@ -132,7 +132,7 @@ describe('blur example', () => { } tileData[2i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var loadIndex = (baseIndex + vec2i(1, 2)); if ((flip != 0u)) { @@ -140,7 +140,7 @@ describe('blur example', () => { } tileData[2i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var loadIndex = (baseIndex + vec2i(2)); if ((flip != 0u)) { @@ -148,7 +148,7 @@ describe('blur example', () => { } tileData[2i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var loadIndex = (baseIndex + vec2i(3, 2)); if ((flip != 0u)) { @@ -157,9 +157,9 @@ describe('blur example', () => { tileData[2i][((_arg_0.lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } } - // unrolled iteration #3, 'r' is '3' + // unrolled iteration #3 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var loadIndex = (baseIndex + vec2i(0, 3)); if ((flip != 0u)) { @@ -167,7 +167,7 @@ describe('blur example', () => { } tileData[3i][((_arg_0.lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var loadIndex = (baseIndex + vec2i(1, 3)); if ((flip != 0u)) { @@ -175,7 +175,7 @@ describe('blur example', () => { } tileData[3i][((_arg_0.lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var loadIndex = (baseIndex + vec2i(2, 3)); if ((flip != 0u)) { @@ -183,7 +183,7 @@ describe('blur example', () => { } tileData[3i][((_arg_0.lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var loadIndex = (baseIndex + vec2i(3)); if ((flip != 0u)) { @@ -193,9 +193,9 @@ describe('blur example', () => { } } workgroupBarrier(); - // unrolled iteration #0, 'r' is '0' + // unrolled iteration #0 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var writeIndex = (baseIndex + vec2i()); if ((flip != 0u)) { @@ -211,7 +211,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var writeIndex = (baseIndex + vec2i(1, 0)); if ((flip != 0u)) { @@ -227,7 +227,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var writeIndex = (baseIndex + vec2i(2, 0)); if ((flip != 0u)) { @@ -243,7 +243,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var writeIndex = (baseIndex + vec2i(3, 0)); if ((flip != 0u)) { @@ -260,9 +260,9 @@ describe('blur example', () => { } } } - // unrolled iteration #1, 'r' is '1' + // unrolled iteration #1 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var writeIndex = (baseIndex + vec2i(0, 1)); if ((flip != 0u)) { @@ -278,7 +278,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var writeIndex = (baseIndex + vec2i(1)); if ((flip != 0u)) { @@ -294,7 +294,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var writeIndex = (baseIndex + vec2i(2, 1)); if ((flip != 0u)) { @@ -310,7 +310,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var writeIndex = (baseIndex + vec2i(3, 1)); if ((flip != 0u)) { @@ -327,9 +327,9 @@ describe('blur example', () => { } } } - // unrolled iteration #2, 'r' is '2' + // unrolled iteration #2 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var writeIndex = (baseIndex + vec2i(0, 2)); if ((flip != 0u)) { @@ -345,7 +345,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var writeIndex = (baseIndex + vec2i(1, 2)); if ((flip != 0u)) { @@ -361,7 +361,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var writeIndex = (baseIndex + vec2i(2)); if ((flip != 0u)) { @@ -377,7 +377,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var writeIndex = (baseIndex + vec2i(3, 2)); if ((flip != 0u)) { @@ -394,9 +394,9 @@ describe('blur example', () => { } } } - // unrolled iteration #3, 'r' is '3' + // unrolled iteration #3 { - // unrolled iteration #0, 'c' is '0' + // unrolled iteration #0 { var writeIndex = (baseIndex + vec2i(0, 3)); if ((flip != 0u)) { @@ -412,7 +412,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #1, 'c' is '1' + // unrolled iteration #1 { var writeIndex = (baseIndex + vec2i(1, 3)); if ((flip != 0u)) { @@ -428,7 +428,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #2, 'c' is '2' + // unrolled iteration #2 { var writeIndex = (baseIndex + vec2i(2, 3)); if ((flip != 0u)) { @@ -444,7 +444,7 @@ describe('blur example', () => { textureStore(outTexture, writeIndex, vec4f(acc, 1f)); } } - // unrolled iteration #3, 'c' is '3' + // unrolled iteration #3 { var writeIndex = (baseIndex + vec2i(3)); if ((flip != 0u)) { diff --git a/packages/typegpu/tests/examples/individual/clouds.test.ts b/packages/typegpu/tests/examples/individual/clouds.test.ts index 1b79afc6dd..c358a845c9 100644 --- a/packages/typegpu/tests/examples/individual/clouds.test.ts +++ b/packages/typegpu/tests/examples/individual/clouds.test.ts @@ -86,19 +86,19 @@ describe('clouds example', () => { var sum = 0f; var amp = 1f; var freq = 1.399999976158142f; - // unrolled iteration #0, '_i' is '0' + // unrolled iteration #0 { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; freq *= 2f; } - // unrolled iteration #1, '_i' is '1' + // unrolled iteration #1 { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; freq *= 2f; } - // unrolled iteration #2, '_i' is '2' + // unrolled iteration #2 { sum += (noise3d((pos * freq)) * amp); amp *= 0.5f; diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index d2eff3a5f8..641e6b7cca 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -82,7 +82,7 @@ describe('cubemap reflection example', () => { var v31 = vec4f(normalize(calculateMidpoint(v3, v1).xyz), 1f); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); let baseIndexNext = (triangleIndex * 12u); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { let reprojectedVertex = (&newVertices[0i]); let triBase = (0 - (0 % 3)); @@ -95,7 +95,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { let reprojectedVertex = (&newVertices[1i]); let triBase = (1 - (1 % 3)); @@ -108,7 +108,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { let reprojectedVertex = (&newVertices[2i]); let triBase = (2 - (2 % 3)); @@ -121,7 +121,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #3, 'i' is '3' + // unrolled iteration #3 { let reprojectedVertex = (&newVertices[3i]); let triBase = (3 - (3 % 3)); @@ -134,7 +134,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #4, 'i' is '4' + // unrolled iteration #4 { let reprojectedVertex = (&newVertices[4i]); let triBase = (4 - (4 % 3)); @@ -147,7 +147,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #5, 'i' is '5' + // unrolled iteration #5 { let reprojectedVertex = (&newVertices[5i]); let triBase = (5 - (5 % 3)); @@ -160,7 +160,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #6, 'i' is '6' + // unrolled iteration #6 { let reprojectedVertex = (&newVertices[6i]); let triBase = (6 - (6 % 3)); @@ -173,7 +173,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #7, 'i' is '7' + // unrolled iteration #7 { let reprojectedVertex = (&newVertices[7i]); let triBase = (7 - (7 % 3)); @@ -186,7 +186,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #8, 'i' is '8' + // unrolled iteration #8 { let reprojectedVertex = (&newVertices[8i]); let triBase = (8 - (8 % 3)); @@ -199,7 +199,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #9, 'i' is '9' + // unrolled iteration #9 { let reprojectedVertex = (&newVertices[9i]); let triBase = (9 - (9 % 3)); @@ -212,7 +212,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #10, 'i' is '10' + // unrolled iteration #10 { let reprojectedVertex = (&newVertices[10i]); let triBase = (10 - (10 % 3)); @@ -225,7 +225,7 @@ describe('cubemap reflection example', () => { (*nextVertex).position = packVec2u((*reprojectedVertex)); (*nextVertex).normal = packVec2u(normal); } - // unrolled iteration #11, 'i' is '11' + // unrolled iteration #11 { let reprojectedVertex = (&newVertices[11i]); let triBase = (11 - (11 % 3)); diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 6ccbc8ee93..e4f7c5d35e 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -178,7 +178,7 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; - // unrolled iteration #0, 'offset' is 'neighborOffsets[0u]' + // unrolled iteration #0 { var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[0u].y) * gravityCost)); @@ -196,7 +196,7 @@ describe('fluid double buffering example', () => { } } } - // unrolled iteration #1, 'offset' is 'neighborOffsets[1u]' + // unrolled iteration #1 { var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[1u].y) * gravityCost)); @@ -214,7 +214,7 @@ describe('fluid double buffering example', () => { } } } - // unrolled iteration #2, 'offset' is 'neighborOffsets[2u]' + // unrolled iteration #2 { var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[2u].y) * gravityCost)); @@ -232,7 +232,7 @@ describe('fluid double buffering example', () => { } } } - // unrolled iteration #3, 'offset' is 'neighborOffsets[3u]' + // unrolled iteration #3 { var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[3u].y) * gravityCost)); @@ -409,7 +409,7 @@ describe('fluid double buffering example', () => { var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; - // unrolled iteration #0, 'offset' is 'neighborOffsets[0u]' + // unrolled iteration #0 { var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[0u].y) * gravityCost)); @@ -427,7 +427,7 @@ describe('fluid double buffering example', () => { } } } - // unrolled iteration #1, 'offset' is 'neighborOffsets[1u]' + // unrolled iteration #1 { var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[1u].y) * gravityCost)); @@ -445,7 +445,7 @@ describe('fluid double buffering example', () => { } } } - // unrolled iteration #2, 'offset' is 'neighborOffsets[2u]' + // unrolled iteration #2 { var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[2u].y) * gravityCost)); @@ -463,7 +463,7 @@ describe('fluid double buffering example', () => { } } } - // unrolled iteration #3, 'offset' is 'neighborOffsets[3u]' + // unrolled iteration #3 { var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); let cost = (neighborDensity.z + (f32(neighborOffsets[3u].y) * gravityCost)); diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index ed2623a999..5f0e5daca5 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -608,9 +608,9 @@ describe('jelly-slider example', () => { var minColor = vec3f(9999); var maxColor = vec3f(-9999); var dimensions = textureDimensions(currentTexture); - // unrolled iteration #0, 'x' is '-1' + // unrolled iteration #0 { - // unrolled iteration #0, 'y' is '-1' + // unrolled iteration #0 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -618,7 +618,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #1, 'y' is '0' + // unrolled iteration #1 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -626,7 +626,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #2, 'y' is '1' + // unrolled iteration #2 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -635,9 +635,9 @@ describe('jelly-slider example', () => { maxColor = max(maxColor, neighborColor.rgb); } } - // unrolled iteration #1, 'x' is '0' + // unrolled iteration #1 { - // unrolled iteration #0, 'y' is '-1' + // unrolled iteration #0 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -645,7 +645,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #1, 'y' is '0' + // unrolled iteration #1 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i()); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -653,7 +653,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #2, 'y' is '1' + // unrolled iteration #2 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -662,9 +662,9 @@ describe('jelly-slider example', () => { maxColor = max(maxColor, neighborColor.rgb); } } - // unrolled iteration #2, 'x' is '1' + // unrolled iteration #2 { - // unrolled iteration #0, 'y' is '-1' + // unrolled iteration #0 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -672,7 +672,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #1, 'y' is '0' + // unrolled iteration #1 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -680,7 +680,7 @@ describe('jelly-slider example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #2, 'y' is '1' + // unrolled iteration #2 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); diff --git a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts index c7b5624941..1bc1427683 100644 --- a/packages/typegpu/tests/examples/individual/jelly-switch.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-switch.test.ts @@ -414,9 +414,9 @@ describe('jelly switch example', () => { var minColor = vec3f(9999); var maxColor = vec3f(-9999); var dimensions = textureDimensions(currentTexture); - // unrolled iteration #0, 'x' is '-1' + // unrolled iteration #0 { - // unrolled iteration #0, 'y' is '-1' + // unrolled iteration #0 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -424,7 +424,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #1, 'y' is '0' + // unrolled iteration #1 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -432,7 +432,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #2, 'y' is '1' + // unrolled iteration #2 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -441,9 +441,9 @@ describe('jelly switch example', () => { maxColor = max(maxColor, neighborColor.rgb); } } - // unrolled iteration #1, 'x' is '0' + // unrolled iteration #1 { - // unrolled iteration #0, 'y' is '-1' + // unrolled iteration #0 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -451,7 +451,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #1, 'y' is '0' + // unrolled iteration #1 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i()); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -459,7 +459,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #2, 'y' is '1' + // unrolled iteration #2 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -468,9 +468,9 @@ describe('jelly switch example', () => { maxColor = max(maxColor, neighborColor.rgb); } } - // unrolled iteration #2, 'x' is '1' + // unrolled iteration #2 { - // unrolled iteration #0, 'y' is '-1' + // unrolled iteration #0 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -478,7 +478,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #1, 'y' is '0' + // unrolled iteration #1 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); @@ -486,7 +486,7 @@ describe('jelly switch example', () => { minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } - // unrolled iteration #2, 'y' is '1' + // unrolled iteration #2 { var sampleCoord = (vec2i(_arg_0.gid.xy) + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); diff --git a/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts index 3c8d898cf6..a72e565679 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-distance.test.ts @@ -80,9 +80,9 @@ describe('jump flood (distance) example', () => { var bestOutsideCoord = vec2f(-1); var bestInsideDist = 1e+20; var bestOutsideDist = 1e+20; - // unrolled iteration #0, 'dx' is '-1' + // unrolled iteration #0 { - // unrolled iteration #0, 'dy' is '-1' + // unrolled iteration #0 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); if ((sample.inside.x >= 0f)) { @@ -100,7 +100,7 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #1, 'dy' is '0' + // unrolled iteration #1 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); if ((sample.inside.x >= 0f)) { @@ -118,7 +118,7 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #2, 'dy' is '1' + // unrolled iteration #2 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); if ((sample.inside.x >= 0f)) { @@ -137,9 +137,9 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #1, 'dx' is '0' + // unrolled iteration #1 { - // unrolled iteration #0, 'dy' is '-1' + // unrolled iteration #0 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); if ((sample.inside.x >= 0f)) { @@ -157,7 +157,7 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #1, 'dy' is '0' + // unrolled iteration #1 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); if ((sample.inside.x >= 0f)) { @@ -175,7 +175,7 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #2, 'dy' is '1' + // unrolled iteration #2 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); if ((sample.inside.x >= 0f)) { @@ -194,9 +194,9 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #2, 'dx' is '1' + // unrolled iteration #2 { - // unrolled iteration #0, 'dy' is '-1' + // unrolled iteration #0 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); if ((sample.inside.x >= 0f)) { @@ -214,7 +214,7 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #1, 'dy' is '0' + // unrolled iteration #1 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); if ((sample.inside.x >= 0f)) { @@ -232,7 +232,7 @@ describe('jump flood (distance) example', () => { } } } - // unrolled iteration #2, 'dy' is '1' + // unrolled iteration #2 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); if ((sample.inside.x >= 0f)) { diff --git a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts index 687e7b1630..0c82f1a43d 100644 --- a/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts +++ b/packages/typegpu/tests/examples/individual/jump-flood-voronoi.test.ts @@ -131,9 +131,9 @@ describe('jump flood (voronoi) example', () => { var size = textureDimensions(readView); var minDist = 1e+20; var bestSample = SampleResult(vec4f(), vec2f(-1)); - // unrolled iteration #0, 'dy' is '-1' + // unrolled iteration #0 { - // unrolled iteration #0, 'dx' is '-1' + // unrolled iteration #0 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); if ((sample.coord.x >= 0f)) { @@ -144,7 +144,7 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #1, 'dx' is '0' + // unrolled iteration #1 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); if ((sample.coord.x >= 0f)) { @@ -155,7 +155,7 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #2, 'dx' is '1' + // unrolled iteration #2 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); if ((sample.coord.x >= 0f)) { @@ -167,9 +167,9 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #1, 'dy' is '0' + // unrolled iteration #1 { - // unrolled iteration #0, 'dx' is '-1' + // unrolled iteration #0 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); if ((sample.coord.x >= 0f)) { @@ -180,7 +180,7 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #1, 'dx' is '0' + // unrolled iteration #1 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); if ((sample.coord.x >= 0f)) { @@ -191,7 +191,7 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #2, 'dx' is '1' + // unrolled iteration #2 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); if ((sample.coord.x >= 0f)) { @@ -203,9 +203,9 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #2, 'dy' is '1' + // unrolled iteration #2 { - // unrolled iteration #0, 'dx' is '-1' + // unrolled iteration #0 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); if ((sample.coord.x >= 0f)) { @@ -216,7 +216,7 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #1, 'dx' is '0' + // unrolled iteration #1 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); if ((sample.coord.x >= 0f)) { @@ -227,7 +227,7 @@ describe('jump flood (voronoi) example', () => { } } } - // unrolled iteration #2, 'dx' is '1' + // unrolled iteration #2 { var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); if ((sample.coord.x >= 0f)) { diff --git a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts index 0919149434..5a7e2af782 100644 --- a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts +++ b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts @@ -107,17 +107,17 @@ describe('liquid-glass example', () => { fn sampleWithChromaticAberration(tex: texture_2d, sampler2: sampler, uv: vec2f, offset: f32, dir: vec2f, blur: f32) -> vec3f { var samples = array(); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { var channelOffset = (dir * (-1f * offset)); samples[0i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { var channelOffset = (dir * (0f * offset)); samples[1i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { var channelOffset = (dir * (1f * offset)); samples[2i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).rgb; diff --git a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts index 264579f5d7..199a1408ed 100644 --- a/packages/typegpu/tests/examples/individual/ripple-cube.test.ts +++ b/packages/typegpu/tests/examples/individual/ripple-cube.test.ts @@ -478,11 +478,11 @@ describe('ripple-cube example', () => { let material = (&materialUniform); var f0 = mix(vec3f(0.03999999910593033), (*material).albedo, (*material).metallic); var lo = vec3f(); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { lo = (lo + evaluateLight(p, n, v, lightsUniform[0i], (*material), f0)); } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { lo = (lo + evaluateLight(p, n, v, lightsUniform[1i], (*material), f0)); } @@ -564,9 +564,9 @@ describe('ripple-cube example', () => { var historyColor = textureLoad(historyTexture, coord, 0); var minColor = vec3f(9999); var maxColor = vec3f(-9999); - // unrolled iteration #0, 'ox' is '-1' + // unrolled iteration #0 { - // unrolled iteration #0, 'oy' is '-1' + // unrolled iteration #0 { var sampleCoord = (coord + vec2i(-1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -574,7 +574,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } - // unrolled iteration #1, 'oy' is '0' + // unrolled iteration #1 { var sampleCoord = (coord + vec2i(-1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -582,7 +582,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } - // unrolled iteration #2, 'oy' is '1' + // unrolled iteration #2 { var sampleCoord = (coord + vec2i(-1, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -591,9 +591,9 @@ describe('ripple-cube example', () => { maxColor = max(maxColor, neighbor); } } - // unrolled iteration #1, 'ox' is '0' + // unrolled iteration #1 { - // unrolled iteration #0, 'oy' is '-1' + // unrolled iteration #0 { var sampleCoord = (coord + vec2i(0, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -601,7 +601,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } - // unrolled iteration #1, 'oy' is '0' + // unrolled iteration #1 { var sampleCoord = (coord + vec2i()); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -609,7 +609,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } - // unrolled iteration #2, 'oy' is '1' + // unrolled iteration #2 { var sampleCoord = (coord + vec2i(0, 1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -618,9 +618,9 @@ describe('ripple-cube example', () => { maxColor = max(maxColor, neighbor); } } - // unrolled iteration #2, 'ox' is '1' + // unrolled iteration #2 { - // unrolled iteration #0, 'oy' is '-1' + // unrolled iteration #0 { var sampleCoord = (coord + vec2i(1, -1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -628,7 +628,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } - // unrolled iteration #1, 'oy' is '0' + // unrolled iteration #1 { var sampleCoord = (coord + vec2i(1, 0)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); @@ -636,7 +636,7 @@ describe('ripple-cube example', () => { minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } - // unrolled iteration #2, 'oy' is '1' + // unrolled iteration #2 { var sampleCoord = (coord + vec2i(1)); var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index 8c7ab9fceb..c2211a2e8f 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -184,7 +184,7 @@ describe('slime mold 3d example', () => { var totalWeight = 0f; var perp1 = getPerpendicular(direction); var perp2 = cross(direction, perp1); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { const theta = 0.; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -195,7 +195,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { const theta = 0.7853981633974483; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -206,7 +206,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { const theta = 1.5707963267948966; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -217,7 +217,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - // unrolled iteration #3, 'i' is '3' + // unrolled iteration #3 { const theta = 2.356194490192345; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -228,7 +228,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - // unrolled iteration #4, 'i' is '4' + // unrolled iteration #4 { const theta = 3.141592653589793; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -239,7 +239,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - // unrolled iteration #5, 'i' is '5' + // unrolled iteration #5 { const theta = 3.9269908169872414; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -250,7 +250,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - // unrolled iteration #6, 'i' is '6' + // unrolled iteration #6 { const theta = 4.71238898038469; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); @@ -261,7 +261,7 @@ describe('slime mold 3d example', () => { weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } - // unrolled iteration #7, 'i' is '7' + // unrolled iteration #7 { const theta = 5.497787143782138; var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index b3d81d8bef..42f5f6beab 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -93,9 +93,9 @@ describe('slime mold example', () => { } var sum = vec3f(); var count = 0f; - // unrolled iteration #0, 'offsetY' is '-1' + // unrolled iteration #0 { - // unrolled iteration #0, 'offsetX' is '-1' + // unrolled iteration #0 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1)); var dimsi = vec2i(dims); @@ -105,7 +105,7 @@ describe('slime mold example', () => { count = (count + 1f); } } - // unrolled iteration #1, 'offsetX' is '0' + // unrolled iteration #1 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(0, -1)); var dimsi = vec2i(dims); @@ -115,7 +115,7 @@ describe('slime mold example', () => { count = (count + 1f); } } - // unrolled iteration #2, 'offsetX' is '1' + // unrolled iteration #2 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1, -1)); var dimsi = vec2i(dims); @@ -126,9 +126,9 @@ describe('slime mold example', () => { } } } - // unrolled iteration #1, 'offsetY' is '0' + // unrolled iteration #1 { - // unrolled iteration #0, 'offsetX' is '-1' + // unrolled iteration #0 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1, 0)); var dimsi = vec2i(dims); @@ -138,7 +138,7 @@ describe('slime mold example', () => { count = (count + 1f); } } - // unrolled iteration #1, 'offsetX' is '0' + // unrolled iteration #1 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i()); var dimsi = vec2i(dims); @@ -148,7 +148,7 @@ describe('slime mold example', () => { count = (count + 1f); } } - // unrolled iteration #2, 'offsetX' is '1' + // unrolled iteration #2 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1, 0)); var dimsi = vec2i(dims); @@ -159,9 +159,9 @@ describe('slime mold example', () => { } } } - // unrolled iteration #2, 'offsetY' is '1' + // unrolled iteration #2 { - // unrolled iteration #0, 'offsetX' is '-1' + // unrolled iteration #0 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(-1, 1)); var dimsi = vec2i(dims); @@ -171,7 +171,7 @@ describe('slime mold example', () => { count = (count + 1f); } } - // unrolled iteration #1, 'offsetX' is '0' + // unrolled iteration #1 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(0, 1)); var dimsi = vec2i(dims); @@ -181,7 +181,7 @@ describe('slime mold example', () => { count = (count + 1f); } } - // unrolled iteration #2, 'offsetX' is '1' + // unrolled iteration #2 { var samplePos = (vec2i(_arg_0.gid.xy) + vec2i(1)); var dimsi = vec2i(dims); diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index 2a2c3eba08..a82228bd28 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -62,19 +62,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #3, 'i' is '3' + // unrolled iteration #3 { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } @@ -115,19 +115,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #3, 'i' is '3' + // unrolled iteration #3 { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } @@ -156,19 +156,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #3, 'i' is '3' + // unrolled iteration #3 { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } @@ -200,19 +200,19 @@ describe('stable-fluid example', () => { fn getNeighbors(coords: vec2i, bounds: vec2i) -> array { var adjacentOffsets = array(vec2i(-1, 0), vec2i(0, -1), vec2i(1, 0), vec2i(0, 1)); - // unrolled iteration #0, 'i' is '0' + // unrolled iteration #0 { adjacentOffsets[0i] = clamp((coords + adjacentOffsets[0i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #1, 'i' is '1' + // unrolled iteration #1 { adjacentOffsets[1i] = clamp((coords + adjacentOffsets[1i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #2, 'i' is '2' + // unrolled iteration #2 { adjacentOffsets[2i] = clamp((coords + adjacentOffsets[2i]), vec2i(), (bounds - vec2i(1))); } - // unrolled iteration #3, 'i' is '3' + // unrolled iteration #3 { adjacentOffsets[3i] = clamp((coords + adjacentOffsets[3i]), vec2i(), (bounds - vec2i(1))); } diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 46a8def741..e45f5ab06c 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1145,8 +1145,8 @@ describe('wgslGenerator', () => { const res = wgslGenerator.expression( // deno-fmt-ignore: it's better that way ( - astInfo.ast?.body[1][0] as tinyest.Const - )[2] as unknown as tinyest.Expression, + astInfo.ast?.body[1][0] as tinyest.Const + )[2] as unknown as tinyest.Expression, ); expect(d.isWgslArray(res.dataType)).toBe(true); diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index cc65dd6134..d507b61da0 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -50,15 +50,15 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { var res = 0; - // unrolled iteration #0, 'foo' is '1' + // unrolled iteration #0 { res += 1i; } - // unrolled iteration #1, 'foo' is '2' + // unrolled iteration #1 { res += 2i; } - // unrolled iteration #2, 'foo' is '3' + // unrolled iteration #2 { res += 3i; } @@ -79,11 +79,11 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { var foo = vec3f(6); - // unrolled iteration #0, 'foo2' is '1' + // unrolled iteration #0 { const boo = 1; } - // unrolled iteration #1, 'foo2' is '2' + // unrolled iteration #1 { const boo = 2; } @@ -110,7 +110,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> f32 { var fooResult = 0f; - // unrolled iteration #0, 'foo' is '1' + // unrolled iteration #0 { const boo = 1; { @@ -119,7 +119,7 @@ describe('tgpu.unroll', () => { } const bar = 1; } - // unrolled iteration #1, 'foo' is '2' + // unrolled iteration #1 { const boo = 2; { @@ -163,13 +163,13 @@ describe('tgpu.unroll', () => { var b1 = Boid(vec2i(1), vec2f(1)); var b2 = Boid(vec2i(2), vec2f(2)); var res = vec2f(); - // unrolled iteration #0, 'foo' is 'b1' + // unrolled iteration #0 { let boo = (&b1); res = (res + b1.vel); (*boo).pos = vec2i(); } - // unrolled iteration #1, 'foo' is 'b2' + // unrolled iteration #1 { let boo = (&b2); res = (res + b2.vel); @@ -217,15 +217,15 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> u32 { var result = 0u; - // unrolled iteration #0, 'prop' is 'a' + // unrolled iteration #0 { result += 1u; } - // unrolled iteration #1, 'prop' is 'b' + // unrolled iteration #1 { result += 2u; } - // unrolled iteration #2, 'prop' is 'c' + // unrolled iteration #2 { result += 3u; } @@ -281,11 +281,11 @@ describe('tgpu.unroll', () => { fn computeWeight(weights: Weights) -> f32 { var p = 0f; - // unrolled iteration #0, 'key' is 'foo' + // unrolled iteration #0 { p += (weights.foo * foo(p)); } - // unrolled iteration #1, 'key' is 'boo' + // unrolled iteration #1 { p += (weights.boo * boo(p)); } @@ -314,13 +314,13 @@ describe('tgpu.unroll', () => { var res = vec2f(); var v1 = vec2f(7); var v2 = vec2f(3); - // unrolled iteration #0, 'foo' is 'v1' + // unrolled iteration #0 { let boo = (&v1); res = (res + v1); (*boo).x = 6f; } - // unrolled iteration #1, 'foo' is 'v2' + // unrolled iteration #1 { let boo = (&v2); res = (res + v2); @@ -345,19 +345,19 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> u32 { var res = 0u; - // unrolled iteration #0, 'foo' is '1u' + // unrolled iteration #0 { res += 1u; } - // unrolled iteration #1, 'foo' is '2u' + // unrolled iteration #1 { res += 2u; } - // unrolled iteration #2, 'foo' is '3u' + // unrolled iteration #2 { res += 3u; } - // unrolled iteration #3, 'foo' is '4u' + // unrolled iteration #3 { res += 4u; } @@ -384,15 +384,15 @@ describe('tgpu.unroll', () => { "fn f() -> i32 { var v = vec3f(7); var res = 0; - // unrolled iteration #0, 'pos' is 'v[0u]' + // unrolled iteration #0 { res = i32((f32(res) + v[0u])); } - // unrolled iteration #1, 'pos' is 'v[1u]' + // unrolled iteration #1 { res = i32((f32(res) + v[1u])); } - // unrolled iteration #2, 'pos' is 'v[2u]' + // unrolled iteration #2 { res = i32((f32(res) + v[2u])); } @@ -417,15 +417,15 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { var result = 0; - // unrolled iteration #0, 'foo' is '1' + // unrolled iteration #0 { result += 1i; } - // unrolled iteration #1, 'foo' is '2' + // unrolled iteration #1 { result += 2i; } - // unrolled iteration #2, 'foo' is '3' + // unrolled iteration #2 { result += 3i; } @@ -434,7 +434,7 @@ describe('tgpu.unroll', () => { `); }); - it('throws when iterable element count is unknown at comptime', () => { + it('throws when iterable is unknown at comptime', () => { const layout = tgpu.bindGroupLayout({ arr: { storage: d.arrayOf(d.f32) }, }); @@ -471,15 +471,15 @@ describe('tgpu.unroll', () => { "fn f() -> f32 { var arr = array(1, 2, 3); var res = 0f; - // unrolled iteration #0, 'foo' is 'arr[0u]' + // unrolled iteration #0 { res += f32(arr[0u]); } - // unrolled iteration #1, 'foo' is 'arr[1u]' + // unrolled iteration #1 { res += f32(arr[1u]); } - // unrolled iteration #2, 'foo' is 'arr[2u]' + // unrolled iteration #2 { res += f32(arr[2u]); } @@ -513,22 +513,22 @@ describe('tgpu.unroll', () => { var v3 = vec2f(2); var arr = array(v1, v2, v2, v3); var res = vec2f(); - // unrolled iteration #0, 'foo' is 'arr[0u]' + // unrolled iteration #0 { res = (res + arr[0u]); arr[0u].x = 7f; } - // unrolled iteration #1, 'foo' is 'arr[1u]' + // unrolled iteration #1 { res = (res + arr[1u]); arr[1u].x = 7f; } - // unrolled iteration #2, 'foo' is 'arr[2u]' + // unrolled iteration #2 { res = (res + arr[2u]); arr[2u].x = 7f; } - // unrolled iteration #3, 'foo' is 'arr[3u]' + // unrolled iteration #3 { res = (res + arr[3u]); arr[3u].x = 7f; @@ -570,12 +570,12 @@ describe('tgpu.unroll', () => { var b2 = Boid(vec2i(2), vec2f(2)); var arr = array(b1, b2); var res = vec2f(); - // unrolled iteration #0, 'foo' is 'arr[0u]' + // unrolled iteration #0 { res = (res + arr[0u].vel); arr[0u].pos.x = 7i; } - // unrolled iteration #1, 'foo' is 'arr[1u]' + // unrolled iteration #1 { res = (res + arr[1u].vel); arr[1u].pos.x = 7i; @@ -604,31 +604,31 @@ describe('tgpu.unroll', () => { fn f() -> u32 { var result = 0u; - // unrolled iteration #0, 'foo' is 'b[0u]' + // unrolled iteration #0 { result += b[0u]; } - // unrolled iteration #1, 'foo' is 'b[1u]' + // unrolled iteration #1 { result += b[1u]; } - // unrolled iteration #2, 'foo' is 'b[2u]' + // unrolled iteration #2 { result += b[2u]; } - // unrolled iteration #3, 'foo' is 'b[3u]' + // unrolled iteration #3 { result += b[3u]; } - // unrolled iteration #4, 'foo' is 'b[4u]' + // unrolled iteration #4 { result += b[4u]; } - // unrolled iteration #5, 'foo' is 'b[5u]' + // unrolled iteration #5 { result += b[5u]; } - // unrolled iteration #6, 'foo' is 'b[6u]' + // unrolled iteration #6 { result += b[6u]; } @@ -653,15 +653,15 @@ describe('tgpu.unroll', () => { "fn f() { var arr = array(1, 2, 3); var r = 0f; - // unrolled iteration #0, 'foo' is 'arr[0u]' + // unrolled iteration #0 { r += f32(arr[0u]); } - // unrolled iteration #1, 'foo' is 'arr[1u]' + // unrolled iteration #1 { r += f32(arr[1u]); } - // unrolled iteration #2, 'foo' is 'arr[2u]' + // unrolled iteration #2 { r += f32(arr[2u]); } @@ -759,7 +759,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { var arr = array(1, 2, 3); - // unrolled iteration #0, 'foo' is '1' + // unrolled iteration #0 { for (var i2 = 0; (i2 < 2i); i2++) { if ((i2 == 1i)) { @@ -778,7 +778,7 @@ describe('tgpu.unroll', () => { } } } - // unrolled iteration #1, 'foo' is '2' + // unrolled iteration #1 { for (var i2 = 0; (i2 < 2i); i2++) { if ((i2 == 2i)) { @@ -801,7 +801,7 @@ describe('tgpu.unroll', () => { `); }); - it('unrolling flag is set correclty', () => { + it('unrolling flag is set correctly', () => { const f = () => { 'use gpu'; const arr = [1, 2, 3]; @@ -821,4 +821,23 @@ describe('tgpu.unroll', () => { - fn*:f(): Cannot unroll loop containing \`break\`] `); }); + + it('unrolls correctly an empty loop', () => { + const arr: number[] = []; + const f = () => { + 'use gpu'; + let a = 0; + for (const foo of tgpu.unroll(arr)) { + a += 1; + } + return a; + }; + + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var a = 0; + return a; + }" + `); + }); });