diff --git a/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts b/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts index 8cd55aa412..86ba2283ac 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts @@ -39,6 +39,8 @@ describe('console log example', () => { expect(shaderCodes).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -48,8 +50,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -90,6 +90,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -99,8 +101,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -150,6 +150,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -159,8 +161,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -203,6 +203,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -212,8 +214,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() { @@ -834,6 +834,8 @@ describe('console log example', () => { num: u32, } + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -843,8 +845,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -995,6 +995,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1004,8 +1006,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() { @@ -1052,6 +1052,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1061,8 +1063,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1103,6 +1103,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1112,8 +1114,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1158,6 +1158,8 @@ describe('console log example', () => { @group(0) @binding(1) var logCountUniform: u32; + var dataBlockIndex: u32; + @group(0) @binding(2) var indexBuffer: atomic; struct SerializedLogData { @@ -1167,8 +1169,6 @@ describe('console log example', () => { @group(0) @binding(3) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1212,6 +1212,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1221,8 +1223,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1299,6 +1299,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1308,8 +1310,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() { @@ -1453,6 +1453,8 @@ describe('console log example', () => { return mainVertex_Output(vec4f(positions[vertexIndex], 0f, 1f)); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -1462,8 +1464,6 @@ describe('console log example', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1506,6 +1506,8 @@ describe('console log example', () => { return mainVertex_Output(vec4f(positions[vertexIndex], 0f, 1f)); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -1515,8 +1517,6 @@ describe('console log example', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1552,6 +1552,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1561,8 +1563,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index 0dca0ae376..9213fc8551 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -66,18 +66,15 @@ export function addReturnTypeToExternals( } } -function identifierRegex(name: string) { - return new RegExp( - `(? { - const externalRegex = identifierRegex(externalName); - if (wgsl && externalName !== 'Out' && externalName !== 'in' && !externalRegex.test(wgsl)) { - console.warn(`The external '${externalName}' wasn't used in the resolved template.`); - // continue anyway, we still might need to resolve the external + const keys = Object.keys(externalMap); + if (keys.length === 0) { + return wgsl; + } + + // Avoid resolving the same item multiple times during one call. + const cache: Map = new Map(); + + return wgsl.replaceAll(boundedPropChain, (match) => { + const chain = match.split('.'); + + if (!Object.hasOwn(externalMap, chain.at(0) as string)) { + // this prop access does not start with an external + return match; } - if (isResolvable(external)) { - if (isNamable(external) && getName(external) === undefined) { - setName(external, externalName.split('.').at(-1) as string); + let currentItem: unknown = externalMap; + let name: string | undefined = undefined; + let suffix = ''; + + for (const [i, elem] of chain.entries()) { + currentItem = (currentItem as ExternalMap)[elem]; + name = elem; + if (isResolvable(currentItem)) { + suffix = chain + .slice(i + 1) + .map((s) => `.${s}`) + .join(''); + break; + } + + if (typeof currentItem !== 'object' || currentItem === null || i === chain.length - 1) { + console.warn( + `During resolution, the external '${chain.slice(0, i + 1).join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, + ); + return match; } - return acc.replaceAll(externalRegex, ctx.resolve(external).value); } - if (external !== null && typeof external === 'object') { - const foundProperties = [ - ...wgsl.matchAll( - new RegExp( - `${externalName - .replaceAll('.', '\\.') - .replaceAll('$', '\\$')}\\.(?.*?)(?![\\w\\$_])`, - 'g', - ), - ), - ].map((found) => found[1]); - const uniqueProperties = [...new Set(foundProperties)]; - - return uniqueProperties.reduce( - (innerAcc: string, prop) => - prop && prop in external - ? replaceExternalsInWgsl( - ctx, - { - [`${externalName}.${prop}`]: external[prop as keyof typeof external], - }, - innerAcc, - ) - : innerAcc, - acc, - ); + if (isNamable(currentItem) && getName(currentItem) === undefined && name !== undefined) { + setName(currentItem, name); } - console.warn( - `During resolution, the external '${externalName}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, - ); + let resolved = cache.get(currentItem); + if (resolved === undefined) { + resolved = ctx.resolve(currentItem).value; + cache.set(currentItem, resolved); + } - return acc; - }, wgsl); + return resolved + suffix; + }); } diff --git a/packages/typegpu/tests/declare.test.ts b/packages/typegpu/tests/declare.test.ts index 2b018c3192..bcaf64d09f 100644 --- a/packages/typegpu/tests/declare.test.ts +++ b/packages/typegpu/tests/declare.test.ts @@ -5,7 +5,7 @@ describe('tgpu.declare', () => { it('should inject provided declaration when resolving a function', () => { const declaration = tgpu['~unstable'].declare('@group(0) @binding(0) var val: f32;'); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ declaration }); + const empty = tgpu.fn([])`declaration () { /* do nothing */ }`.$uses({ declaration }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "@group(0) @binding(0) var val: f32; @@ -33,7 +33,7 @@ struct Output { x: u32, }`); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ decl1, decl2 }); + const empty = tgpu.fn([])`decl1 decl2 () { /* do nothing */ }`.$uses({ decl1, decl2 }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "@group(0) @binding(0) var val: f32; @@ -48,17 +48,17 @@ struct Output { it('should replace nested declarations', () => { const declaration = tgpu['~unstable'] - .declare('@group(0) @binding(0) var val: f32;') + .declare('@group(0) @binding(0) var val: f32; nested') .$uses({ nested: tgpu['~unstable'].declare('struct Output { x: u32 }'), }); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ declaration }); + const empty = tgpu.fn([])`declaration () { /* do nothing */ }`.$uses({ declaration }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "struct Output { x: u32 } - @group(0) @binding(0) var val: f32; + @group(0) @binding(0) var val: f32; fn empty() { /* do nothing */ }" `); @@ -73,7 +73,7 @@ struct Output { .declare('@group(0) @binding(0) var val: Output;') .$uses({ Output }); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ declaration }); + const empty = tgpu.fn([])`declaration () { /* do nothing */ }`.$uses({ declaration }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "struct Output { diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 83e8e1e787..4e215f25a4 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -310,12 +310,12 @@ fn main() { }); expect(resolved).toMatchInlineSnapshot(` - "@group(0) @binding(0) var intensity: u32; - - fn get_color() -> vec3f { + "fn get_color() -> vec3f { let color = vec3f(); return color; } + + @group(0) @binding(0) var intensity: u32; fn main () { let c = get_color() * intensity; }" @@ -495,35 +495,34 @@ describe('tgpu resolveWithContext', () => { expect(configSpy.mock.lastCall?.[0].bindings).toEqual([[colorSlot, v]]); }); - it('should warn when external WGSL is not used', () => { + it('should warn when external is neither wgsl nor an object', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - tgpu.resolveWithContext({ - template: 'fn testFn() { return; }', - externals: { - ArraySchema: d.arrayOf(d.u32, 4), - JavaScriptObject: { field: d.vec2f() }, - }, + tgpu.resolve({ + template: 'fn testFn() { var a = identity(1); return; }', + externals: { identity: (a: number) => a }, }); expect(consoleWarnSpy).toHaveBeenCalledWith( - "The external 'ArraySchema' wasn't used in the resolved template.", - ); - expect(consoleWarnSpy).toHaveBeenCalledWith( - "The external 'JavaScriptObject' wasn't used in the resolved template.", + "During resolution, the external 'identity' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", ); }); - it('should warn when external is neither wgsl nor an object', () => { + it('should warn when the end of external chain was reached without a resolvable', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - tgpu.resolve({ - template: 'fn testFn() { var a = identity(1); return; }', - externals: { identity: (a: number) => a }, - }); + const getColor = tgpu.fn([])`() { + let color = EXT.p.q; + }`.$uses({ EXT: { p: { q: { r: d.vec3f() } } } }); + + expect(tgpu.resolve([getColor])).toMatchInlineSnapshot(` + "fn getColor() { + let color = EXT.p.q; + }" + `); expect(consoleWarnSpy).toHaveBeenCalledWith( - "During resolution, the external 'identity' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", + "During resolution, the external 'EXT.p.q' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", ); }); diff --git a/packages/typegpu/tests/tgpuGenericFn.test.ts b/packages/typegpu/tests/tgpuGenericFn.test.ts index fd136ced01..df737ded64 100644 --- a/packages/typegpu/tests/tgpuGenericFn.test.ts +++ b/packages/typegpu/tests/tgpuGenericFn.test.ts @@ -318,6 +318,8 @@ describe('TgpuGenericFn - shellless callback wrapper', () => { expect(tgpu.resolve([pipeline.pipeline])).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -327,8 +329,6 @@ describe('TgpuGenericFn - shellless callback wrapper', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { diff --git a/packages/typegpu/tests/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index 997b15d46f..c1a8e19607 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -84,6 +84,8 @@ describe('wgslGenerator with console.log', () => { return vs_Output(vec4f()); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -93,8 +95,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -166,6 +166,8 @@ describe('wgslGenerator with console.log', () => { return vs_Output(vec4f()); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -175,8 +177,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -257,6 +257,8 @@ describe('wgslGenerator with console.log', () => { return VertexOut(vec4f()); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -266,8 +268,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -317,7 +317,9 @@ describe('wgslGenerator with console.log', () => { const pipeline = root.createComputePipeline({ compute: fn }); expect(tgpu.resolve([pipeline])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var indexBuffer: atomic; + "var dataBlockIndex: u32; + + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { id: u32, @@ -326,8 +328,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -375,7 +375,9 @@ describe('wgslGenerator with console.log', () => { }); expect(tgpu.resolve([pipeline])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var indexBuffer: atomic; + "var dataBlockIndex: u32; + + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { id: u32, @@ -384,8 +386,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -453,7 +453,9 @@ describe('wgslGenerator with console.log', () => { }); expect(tgpu.resolve([pipeline])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var indexBuffer: atomic; + "var dataBlockIndex: u32; + + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { id: u32, @@ -462,8 +464,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -539,6 +539,8 @@ describe('wgslGenerator with console.log', () => { data: array, } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -548,8 +550,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -690,6 +690,8 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var myUniform: vec2f; + var dataBlockIndex: u32; + @group(0) @binding(2) var indexBuffer: atomic; struct SerializedLogData { @@ -699,8 +701,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(3) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -753,6 +753,8 @@ describe('wgslGenerator with console.log', () => { expect(tgpu.resolve([myPipeline.pipeline])).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -762,8 +764,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() {