From 1036589cc5b11b4bfd3b819ad669a9ce90ad9472 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 10 Apr 2026 12:30:13 +0200 Subject: [PATCH 01/34] fix: Improvement to argument usage tracking --- .../individual-example-tests/3d-fish.test.ts | 18 +- .../bitonic-sort.test.ts | 4 +- .../individual-example-tests/blur.test.ts | 8 +- .../camera-thresholding.test.ts | 8 +- .../individual-example-tests/caustics.test.ts | 8 +- .../chroma-keying.test.ts | 8 +- .../individual-example-tests/confetti.test.ts | 6 +- .../cubemap-reflection.test.ts | 18 +- .../individual-example-tests/disco.test.ts | 16 +- .../fluid-double-buffering.test.ts | 8 +- .../game-of-life.test.ts | 8 +- .../gradient-tiles.test.ts | 4 +- .../individual-example-tests/gravity.test.ts | 20 +- .../image-tuning.test.ts | 8 +- .../jelly-slider.test.ts | 16 +- .../jelly-switch.test.ts | 16 +- .../jump-flood-distance.test.ts | 8 +- .../liquid-glass.test.ts | 8 +- .../individual-example-tests/oklab.test.ts | 8 +- .../phong-reflection.test.ts | 10 +- .../point-light-shadow.test.ts | 16 +- .../ray-marching.test.ts | 8 +- .../ripple-cube.test.ts | 8 +- .../simple-shadow.test.ts | 10 +- .../slime-mold-3d.test.ts | 8 +- .../slime-mold.test.ts | 8 +- .../stable-fluid.test.ts | 8 +- .../uniformity.test.ts | 16 +- .../vaporrave.test.ts | 8 +- .../xor-dev-centrifuge-2.test.ts | 8 +- .../xor-dev-runner.test.ts | 8 +- packages/typegpu/src/core/function/autoIO.ts | 4 +- .../src/core/function/entryInputRouter.ts | 39 +- packages/typegpu/src/core/function/fnCore.ts | 51 +- .../src/core/function/shelllessImpl.ts | 2 +- .../src/core/function/tgpuComputeFn.ts | 5 +- packages/typegpu/src/core/function/tgpuFn.ts | 2 +- .../src/core/function/tgpuFragmentFn.ts | 2 +- .../typegpu/src/core/function/tgpuVertexFn.ts | 2 +- packages/typegpu/src/data/snippet.ts | 7 +- packages/typegpu/src/resolutionCtx.ts | 257 +++---- packages/typegpu/src/tgsl/accessProp.ts | 11 +- packages/typegpu/src/tgsl/conversion.ts | 2 +- packages/typegpu/src/tgsl/shaderGenerator.ts | 4 +- .../src/tgsl/shaderGenerator_members.ts | 14 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 25 +- packages/typegpu/src/types.ts | 18 +- packages/typegpu/tests/renderPipeline.test.ts | 33 +- .../tests/tgsl/extensionEnabled.test.ts | 14 +- .../typegpu/tests/tgsl/nameClashes.test.ts | 16 +- .../tests/tgsl/ternaryOperator.test.ts | 68 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 673 +++--------------- packages/typegpu/tests/tgslFn.test.ts | 24 +- packages/typegpu/tests/utils/parseResolved.ts | 1 - 54 files changed, 578 insertions(+), 1010 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts index dca6c22f4e..6430284a2e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts @@ -305,15 +305,6 @@ describe('3d fish example', () => { return vertexShader_Output(worldPosition.xyz, worldNormal, canvasPosition, (*currentModelData).variant, _arg_textureUV, (*currentModelData).applySeaFog, (*currentModelData).applySeaDesaturation); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - @location(2) variant: f32, - @location(3) textureUV: vec2f, - @location(4) @interpolate(flat) applySeaFog: u32, - @location(5) @interpolate(flat) applySeaDesaturation: u32, - } - @group(0) @binding(1) var modelTexture: texture_2d; @group(0) @binding(3) var sampler_1: sampler; @@ -418,6 +409,15 @@ describe('3d fish example', () => { return vec3f(r, g, b); } + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + @location(2) variant: f32, + @location(3) textureUV: vec2f, + @location(4) @interpolate(flat) applySeaFog: u32, + @location(5) @interpolate(flat) applySeaDesaturation: u32, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var textureColorWithAlpha = textureSample(modelTexture, sampler_1, _arg_0.textureUV); var textureColor = textureColorWithAlpha.rgb; diff --git a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts index 06b1c586cb..6368fd715f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts @@ -118,12 +118,12 @@ describe('bitonic sort example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var data_1: array; + struct fragmentFn_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var data_1: array; - @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { let data = (&data_1); let arrayLength_1 = arrayLength(&(*data)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts index 00df4923ed..7f81d96515 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts @@ -470,14 +470,14 @@ describe('blur example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct renderFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var renderView: texture_2d; @group(0) @binding(1) var sampler_1: sampler; + struct renderFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn renderFragment(_arg_0: renderFragment_Input) -> @location(0) vec4f { return textureSample(renderView, sampler_1, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts index 43e10c97b2..783bb59558 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts @@ -32,10 +32,6 @@ describe('camera thresholding example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFrag_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransformUniform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('camera thresholding example', () => { @group(0) @binding(3) var thresholdBuffer: f32; + struct mainFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { var uv2 = ((uvTransformUniform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts index a1938d5c34..9d011f7a74 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts @@ -31,10 +31,6 @@ describe('caustics example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var tileDensity: f32; fn tilePattern(uv: vec2f) -> f32 { @@ -120,6 +116,10 @@ describe('caustics example', () => { return mat2x2f(vec2f(cos(angle), sin(angle)), vec2f(-(sin(angle)), cos(angle))); } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f((-1.9866933079506122f + (_arg_0.uv.x * 3f)), 4.900332889206208f)); var skewedUv = (skewMat * _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts index 9b25129771..60175ac939 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts @@ -32,10 +32,6 @@ describe('chroma keying example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('chroma keying example', () => { @group(0) @binding(3) var threshold: f32; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var uv2 = ((uvTransform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts index 3e39636f7a..921721055b 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts @@ -59,10 +59,10 @@ describe('confetti example', () => { struct VertexIn { @location(0) tilt: f32, - @location(1) angle: f32, - @location(2) color: vec4f, - @location(3) center: vec2f, @builtin(vertex_index) vertexIndex: u32, + @location(1) angle: f32, + @location(2) center: vec2f, + @location(3) color: vec4f, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { diff --git a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts index c77faffba2..fe28f8827d 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts @@ -257,14 +257,14 @@ describe('cubemap reflection example', () => { return cubeVertexFn_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct cubeFragmentFn_Input { - @location(0) texCoord: vec3f, - } - @group(1) @binding(0) var cubemap: texture_cube; @group(1) @binding(1) var texSampler: sampler; + struct cubeFragmentFn_Input { + @location(0) texCoord: vec3f, + } + @fragment fn cubeFragmentFn(_arg_0: cubeFragmentFn_Input) -> @location(0) vec4f { return textureSample(cubemap, texSampler, normalize(_arg_0.texCoord)); } @@ -287,11 +287,6 @@ describe('cubemap reflection example', () => { return vertexFn_Output((camera.projection * (camera.view * _arg_position)), _arg_normal, _arg_position); } - struct fragmentFn_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec4f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -314,6 +309,11 @@ describe('cubemap reflection example', () => { @group(1) @binding(1) var texSampler: sampler; + struct fragmentFn_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec4f, + } + @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { var normalizedNormal = normalize(_arg_0.normal.xyz); var normalizedLightDir = normalize(light.direction); diff --git a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts index 8ea4e8d7f7..ba6d090535 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts @@ -32,10 +32,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment2_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -65,6 +61,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment2_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment2(_arg_0: mainFragment2_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; @@ -243,10 +243,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment1_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -276,6 +272,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment1_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment1(_arg_0: mainFragment1_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; diff --git a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts index ef73e74189..df9620d6ed 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts @@ -558,10 +558,6 @@ describe('fluid double buffering example', () => { return vertexMain_Output(vec4f(pos[_arg_idx].x, pos[_arg_idx].y, 0f, 1f), uv[_arg_idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - fn coordsToIndex(x: i32, y: i32) -> i32 { return (x + (y * 256i)); } @@ -595,6 +591,10 @@ describe('fluid double buffering example', () => { return false; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let x = i32((_arg_0.uv.x * 256f)); let y = i32((_arg_0.uv.y * 256f)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts index 3028c19bf5..516692dc41 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts @@ -214,10 +214,6 @@ describe('game of life example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct displayFragment_Input { - @location(0) uv: vec2f, - } - struct ZoomParams { enabled: u32, level: f32, @@ -242,6 +238,10 @@ describe('game of life example', () => { @group(0) @binding(2) var viewModeUniform: u32; + struct displayFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn displayFragment(_arg_0: displayFragment_Input) -> @location(0) vec4f { let zoom = (&zoomUniform); let gs = f32(gameSizeUniform); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts index f0d3016198..d3c209eb18 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts @@ -32,12 +32,12 @@ describe('gradient tiles example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var spanUniform: vec2f; + struct fragment_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var spanUniform: vec2f; - @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { let red = (floor((_arg_0.uv.x * spanUniform.x)) / spanUniform.x); let green = (floor((_arg_0.uv.y * spanUniform.y)) / spanUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts index 55ed0d2fb8..899b6cb76e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts @@ -192,14 +192,14 @@ describe('gravity example', () => { return skyBoxVertex_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct skyBoxFragment_Input { - @location(0) texCoord: vec3f, - } - @group(0) @binding(1) var skyBox: texture_cube; @group(0) @binding(2) var sampler_1: sampler; + struct skyBoxFragment_Input { + @location(0) texCoord: vec3f, + } + @fragment fn skyBoxFragment(_arg_0: skyBoxFragment_Input) -> @location(0) vec4f { return textureSample(skyBox, sampler_1, normalize(_arg_0.texCoord)); } @@ -250,6 +250,12 @@ describe('gravity example', () => { return mainVertex_Output(positionOnCanvas, _arg_uv, _arg_normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); } + @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; + + @group(0) @binding(1) var sampler_1: sampler; + + @group(0) @binding(2) var lightSource: vec3f; + struct mainFragment_Input { @location(0) uv: vec2f, @location(1) normals: vec3f, @@ -259,12 +265,6 @@ describe('gravity example', () => { @location(5) ambientLightFactor: f32, } - @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; - - @group(0) @binding(1) var sampler_1: sampler; - - @group(0) @binding(2) var lightSource: vec3f; - @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { if ((_arg_0.destroyed == 1u)) { discard;; diff --git a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts index a9fa1865c8..059828005e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts @@ -37,10 +37,6 @@ describe('image tuning example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var imageView: texture_2d; @group(0) @binding(1) var imageSampler: sampler; @@ -68,6 +64,10 @@ describe('image tuning example', () => { @group(0) @binding(4) var adjustments: Adjustments; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var color = textureSample(imageView, imageSampler, _arg_0.uv).rgb; let inputLuminance = dot(color, vec3f(0.29899999499320984, 0.5870000123977661, 0.11400000005960464)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts index 941ee300b4..976bdbaaf2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts @@ -66,10 +66,6 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -583,6 +579,10 @@ describe('jelly-slider example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -713,14 +713,14 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts index 7a192b5f54..6e507a2602 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts @@ -37,10 +37,6 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -385,6 +381,10 @@ describe('jelly switch example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -505,14 +505,14 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts index 3fb0e7d685..dcf73b0927 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts @@ -303,10 +303,6 @@ describe('jump flood (distance) example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct distanceFrag_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var distTexture: texture_2d; @group(1) @binding(1) var sampler_1: sampler; @@ -322,6 +318,10 @@ describe('jump flood (distance) example', () => { const insideGradient: array = array(vec3f(0.05000000074505806, 0.05000000074505806, 0.15000000596046448), vec3f(0.10000000149011612, 0.20000000298023224, 0.30000001192092896), vec3f(0.20000000298023224, 0.44999998807907104, 0.550000011920929), vec3f(0.4000000059604645, 0.75, 0.699999988079071), vec3f(0.8999999761581421, 1, 0.949999988079071)); + struct distanceFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn distanceFrag(_arg_0: distanceFrag_Input) -> @location(0) vec4f { var size = textureDimensions(distTexture); var dist = textureSample(distTexture, sampler_1, _arg_0.uv).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts index e247d26a22..56a5b4ecf5 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts @@ -64,10 +64,6 @@ describe('liquid-glass example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var mousePosUniform: vec2f; struct Params { @@ -137,6 +133,10 @@ describe('liquid-glass example', () => { return mix(vec4f(color, 1f), vec4f(tint.color, 1f), tint.strength); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var posInBoxSpace = (_arg_0.uv - mousePosUniform); let sdfDist = sdRoundedBox2d(posInBoxSpace, paramsUniform.rectDims, paramsUniform.radius); diff --git a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts index b8e64d3654..23abf35258 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts @@ -32,10 +32,6 @@ describe('oklab example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - struct item { hue: f32, alpha: f32, @@ -226,6 +222,10 @@ describe('oklab example', () => { return 1f; } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv - 0.5f) * vec2f(2, -2)); let hue = uniforms.hue; diff --git a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts index 1cfbf878ea..0240d9ae44 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts @@ -54,11 +54,6 @@ describe('phong reflection example', () => { return vertexShader_Output(_arg_modelPosition, _arg_modelNormal, canvasPosition); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - } - struct ExampleControls { lightColor: vec3f, lightDirection: vec3f, @@ -69,6 +64,11 @@ describe('phong reflection example', () => { @group(0) @binding(1) var exampleControlsUniform: ExampleControls; + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var lightColor = normalize(exampleControlsUniform.lightColor); var lightDirection = normalize(exampleControlsUniform.lightDirection); diff --git a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts index 283d8d652b..2ce441c6b2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts @@ -41,12 +41,12 @@ describe('point light shadow example', () => { return vertexDepth_Output(pos, worldPos); } + @group(0) @binding(1) var lightPosition: vec3f; + struct fragmentDepth_Input { @location(0) worldPos: vec3f, } - @group(0) @binding(1) var lightPosition: vec3f; - @fragment fn fragmentDepth(_arg_0: fragmentDepth_Input) -> @builtin(frag_depth) f32 { let dist = length((_arg_0.worldPos - lightPosition)); return (dist / 100f); @@ -74,12 +74,6 @@ describe('point light shadow example', () => { return vertexMain_Output(pos, worldPos, uv, worldNormal); } - struct fragmentMain_Input { - @location(0) worldPos: vec3f, - @location(1) uv: vec2f, - @location(2) normal: vec3f, - } - @group(1) @binding(3) var lightPosition: vec3f; struct item { @@ -97,6 +91,12 @@ describe('point light shadow example', () => { @group(1) @binding(2) var shadowSampler: sampler_comparison; + struct fragmentMain_Input { + @location(0) worldPos: vec3f, + @location(1) uv: vec2f, + @location(2) normal: vec3f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let lightPos = (&lightPosition); var toLight = ((*lightPos) - _arg_0.worldPos); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts index 1d56a2d307..bdfa96686f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts @@ -31,10 +31,6 @@ describe('ray-marching example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolution: vec2f; struct Shape { @@ -148,6 +144,10 @@ describe('ray-marching example', () => { return res; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolution.x / resolution.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts index e73568d898..24dcc7ee1c 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts @@ -765,10 +765,6 @@ describe('ripple-cube example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var colorTexture: texture_2d; @group(1) @binding(2) var sampler_1: sampler; @@ -782,6 +778,10 @@ describe('ripple-cube example', () => { @group(0) @binding(0) var bloomUniform: BloomParams; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var color = textureSample(colorTexture, sampler_1, _arg_0.uv); var bloomColor = textureSample(bloomTexture, sampler_1, _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts index f1f6775cc0..e6d2de8222 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts @@ -89,11 +89,6 @@ describe('simple shadow example', () => { return mainVert_Output(clipPos, transformedNormal, worldPos.xyz); } - struct mainFrag_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec3f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -118,6 +113,11 @@ describe('simple shadow example', () => { @group(0) @binding(3) var paramsUniform: VisParams; + struct mainFrag_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec3f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { let instanceInfo_1 = (&instanceInfo); var N = normalize(_arg_0.normal.xyz); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts index 16ec9b7e12..daab5433e6 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts @@ -382,10 +382,6 @@ describe('slime mold 3d example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - var seed: vec2f; fn seed2(value: vec2f) { @@ -438,6 +434,10 @@ describe('slime mold 3d example', () => { @group(0) @binding(1) var sampler_1: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { randSeed2(_arg_0.uv); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), (1f - (_arg_0.uv.y * 2f))); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts index 44d050241e..4b77dc41f4 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts @@ -308,14 +308,14 @@ describe('slime mold example', () => { return fullScreenTriangle_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), uv[_arg_vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var state: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { return textureSample(state, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts index 0e97b0d9e9..b94b01c01f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts @@ -259,16 +259,16 @@ describe('stable-fluid example', () => { return renderFn_Output(vec4f(vertices[_arg_idx], 0f, 1f), texCoords[_arg_idx]); } - struct fragmentImageFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var result: texture_2d; @group(0) @binding(2) var linSampler: sampler; @group(0) @binding(1) var background: texture_2d; + struct fragmentImageFn_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentImageFn(_arg_0: fragmentImageFn_Input) -> @location(0) vec4f { const pixelStep = 0.001953125f; let leftSample = textureSample(result, linSampler, vec2f((_arg_0.uv.x - pixelStep), _arg_0.uv.y)).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts index 21ccd861f6..11a61fe114 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts @@ -35,10 +35,6 @@ describe('uniformity test example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var canvasRatioUniform: f32; @group(0) @binding(1) var gridSizeUniform: f32; @@ -65,6 +61,10 @@ describe('uniformity test example', () => { return sample(); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); @@ -72,10 +72,6 @@ describe('uniformity test example', () => { return vec4f(vec3f(randFloat01()), 1f); } - struct fragmentShader_Input_1 { - @location(0) uv: vec2f, - } - var seed_1: u32; fn seed2_1(value: vec2f) { @@ -102,6 +98,10 @@ describe('uniformity test example', () => { return sample_1(); } + struct fragmentShader_Input_1 { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader_1(_arg_0: fragmentShader_Input_1) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts index d7834afa66..dafd303f91 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts @@ -79,10 +79,6 @@ describe('vaporrave example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; struct Ray { @@ -219,6 +215,10 @@ describe('vaporrave example', () => { @group(0) @binding(5) var glowIntensityUniform: f32; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolutionUniform.x / resolutionUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts index 9abffaf6b8..894d6e4d67 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts @@ -30,10 +30,6 @@ describe('xor dev centrifuge example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - struct Params { time: f32, aspectRatio: f32, @@ -51,6 +47,10 @@ describe('xor dev centrifuge example', () => { return select(tanh(v), sign(v), (abs(v) > vec3f(10))); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let params = (¶msUniform); var ratio = vec2f((*params).aspectRatio, 1f); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts index 419c95807c..ec9b1c27a0 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts @@ -34,10 +34,6 @@ describe('xor dev runner example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var colorUniform: vec3f; struct Camera { @@ -82,6 +78,10 @@ describe('xor dev runner example', () => { return select(tanh(v), sign(v), (abs(v) > 10f)); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var icolor = (colorUniform * 4f); var ray = getRayForUV(_arg_0.uv); diff --git a/packages/typegpu/src/core/function/autoIO.ts b/packages/typegpu/src/core/function/autoIO.ts index 6feece51d5..b35a092b1a 100644 --- a/packages/typegpu/src/core/function/autoIO.ts +++ b/packages/typegpu/src/core/function/autoIO.ts @@ -85,7 +85,7 @@ export class AutoFragmentFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'fragmentFn'); } - this.#core = createFnCore(impl, '@fragment '); + this.#core = createFnCore(impl, 'fragment'); this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations); setName(this.autoIn, 'FragmentIn'); this.autoOut = new AutoStruct(builtinFragmentOut, vec4f); @@ -131,7 +131,7 @@ export class AutoVertexFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'vertexFn'); } - this.#core = createFnCore(impl, '@vertex '); + this.#core = createFnCore(impl, 'vertex'); this.autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations); setName(this.autoIn, 'VertexIn'); this.autoOut = new AutoStruct(builtinVertexOut, undefined); diff --git a/packages/typegpu/src/core/function/entryInputRouter.ts b/packages/typegpu/src/core/function/entryInputRouter.ts index 8ba6669372..b756366c4b 100644 --- a/packages/typegpu/src/core/function/entryInputRouter.ts +++ b/packages/typegpu/src/core/function/entryInputRouter.ts @@ -1,12 +1,7 @@ -import { undecorate } from '../../data/dataTypes.ts'; -import { snip, type Snippet } from '../../data/snippet.ts'; +import { type Snippet } from '../../data/snippet.ts'; import { $internal, $repr } from '../../shared/symbols.ts'; import { type BaseData, isWgslStruct } from '../../data/wgslTypes.ts'; - -interface PositionalArgEntry { - argName: string; - type: BaseData; -} +import type { FunctionArgumentAccess } from '../../types.ts'; /** * Routes `(input) => { input.x }` style property access to the correct WGSL @@ -18,38 +13,32 @@ export class EntryInputRouter implements BaseData { readonly type = 'entry-input-router' as const; // Type-token only, not present at runtime: declare readonly [$repr]: never; - readonly structArgName: string; - readonly dataSchema: BaseData | undefined; + + readonly structArg: FunctionArgumentAccess | undefined; /** Maps schemaKey → { WGSL arg name, type } */ - readonly positionalArgsMap: Map; + readonly positionalArgsMap: Map; constructor( - structArgName: string, - dataSchema: BaseData | undefined, - positionalArgs: { schemaKey: string; argName: string; type: BaseData }[], + structArg: FunctionArgumentAccess | undefined, + positionalArgs: { schemaKey: string; arg: FunctionArgumentAccess }[], ) { - this.structArgName = structArgName; - this.dataSchema = dataSchema; - this.positionalArgsMap = new Map( - positionalArgs.map((a) => [a.schemaKey, { argName: a.argName, type: a.type }]), - ); + this.structArg = structArg; + this.positionalArgsMap = new Map(positionalArgs.map((a) => [a.schemaKey, a.arg])); } toString(): string { return 'entry-input-router'; } - accessProp(propName: string): Snippet | undefined { + accessProp(propName: string): Snippet | { target: Snippet; prop: string } | undefined { const positionalEntry = this.positionalArgsMap.get(propName); if (positionalEntry) { - return snip(positionalEntry.argName, positionalEntry.type, 'argument'); + return positionalEntry(); } - if (this.dataSchema && isWgslStruct(this.dataSchema)) { - const propType = this.dataSchema.propTypes[propName]; - if (propType) { - return snip(`${this.structArgName}.${propName}`, undecorate(propType), 'argument'); - } + const structSnippet = this.structArg?.(); + if (structSnippet && isWgslStruct(structSnippet.dataType)) { + return { target: structSnippet, prop: propName }; } return undefined; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 66429067e5..cf4cda90f7 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -5,7 +5,7 @@ import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTy import { MissingLinksError } from '../../errors.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; -import type { ResolutionCtx } from '../../types.ts'; +import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; import { applyExternals, type ExternalMap, replaceExternalsInWgsl } from '../resolve/externals.ts'; import { extractArgs } from './extractArgs.ts'; import type { Implementation, SeparatedEntryArgs } from './fnTypes.ts'; @@ -32,7 +32,11 @@ export interface FnCore { ): ResolvedSnippet; } -export function createFnCore(implementation: Implementation, fnAttribute = ''): FnCore { +export function createFnCore( + implementation: Implementation, + functionType: 'normal' | TgpuShaderStage, + workgroupSize?: number[], +): FnCore { /** * External application has to be deferred until resolution because * some externals can reference the owner function which has not been @@ -83,7 +87,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): let header = ''; let body = ''; - if (fnAttribute !== '' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput && validArgNames) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { @@ -140,7 +144,17 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): body = replacedImpl.slice(providedArgs.range.end); } - ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); + let attributes = ''; + if (functionType === 'compute') { + attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `; + } else if (functionType === 'vertex') { + attributes = `@vertex `; + } else if (functionType === 'fragment') { + attributes = `@fragment `; + } + + ctx.addDeclaration(`${attributes}fn ${id}${header}${body}`); + return snip(id, returnType, /* origin */ 'runtime'); } @@ -178,7 +192,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // If an entrypoint implementation has a second argument, it represents the output schema. // We look at the identifier chosen by the user and add it to externals. const maybeSecondArg = ast.params[1]; - if (maybeSecondArg && maybeSecondArg.type === 'i' && fnAttribute !== '') { + if (maybeSecondArg && maybeSecondArg.type === 'i' && functionType !== 'normal') { applyExternals(externalMap, { // oxlint-disable-next-line typescript/no-non-null-assertion -- entry functions cannot be shellless [maybeSecondArg.name]: undecorate(returnType!), @@ -187,18 +201,8 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // generate wgsl string - const { - head, - body, - returnType: actualReturnType, - } = ctx.fnToWgsl({ - functionType: fnAttribute.includes('@compute') - ? 'compute' - : fnAttribute.includes('@vertex') - ? 'vertex' - : fnAttribute.includes('@fragment') - ? 'fragment' - : 'normal', + const { code, returnType: actualReturnType } = ctx.fnToWgsl({ + functionType, argTypes, entryInput, params: ast.params, @@ -207,9 +211,16 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): externalMap, }); - ctx.addDeclaration( - `${fnAttribute}fn ${id}${ctx.resolve(head).value}${ctx.resolve(body).value}`, - ); + let attributes = ''; + if (functionType === 'compute') { + attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `; + } else if (functionType === 'vertex') { + attributes = `@vertex `; + } else if (functionType === 'fragment') { + attributes = `@fragment `; + } + + ctx.addDeclaration(`${attributes}fn ${id}${code}`); return snip(id, actualReturnType, /* origin */ 'runtime'); }, diff --git a/packages/typegpu/src/core/function/shelllessImpl.ts b/packages/typegpu/src/core/function/shelllessImpl.ts index 23b60bdde1..9263303a31 100644 --- a/packages/typegpu/src/core/function/shelllessImpl.ts +++ b/packages/typegpu/src/core/function/shelllessImpl.ts @@ -31,7 +31,7 @@ export function createShelllessImpl( argTypes: BaseData[], implementation: (...args: never[]) => unknown, ): ShelllessImpl { - const core = createFnCore(implementation, ''); + const core = createFnCore(implementation, 'normal'); return { [$internal]: true, diff --git a/packages/typegpu/src/core/function/tgpuComputeFn.ts b/packages/typegpu/src/core/function/tgpuComputeFn.ts index 6f48cbe643..9cef2d49cc 100644 --- a/packages/typegpu/src/core/function/tgpuComputeFn.ts +++ b/packages/typegpu/src/core/function/tgpuComputeFn.ts @@ -114,10 +114,7 @@ function createComputeFn>( [$getNameForward]: FnCore; }; - const core = createFnCore( - implementation, - `@compute @workgroup_size(${workgroupSize.join(', ')}) `, - ); + const core = createFnCore(implementation, 'compute', workgroupSize); const result: This = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index 65ea52de65..40f6548922 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -173,7 +173,7 @@ function createFn( implementation = _implementation; } - const core = createFnCore(implementation as Implementation, ''); + const core = createFnCore(implementation as Implementation, 'normal'); const fnBase = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFragmentFn.ts b/packages/typegpu/src/core/function/tgpuFragmentFn.ts index 631617bcdc..cb3b4c990f 100644 --- a/packages/typegpu/src/core/function/tgpuFragmentFn.ts +++ b/packages/typegpu/src/core/function/tgpuFragmentFn.ts @@ -183,7 +183,7 @@ function createFragmentFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@fragment '); + const core = createFnCore(implementation, 'fragment'); const outputType = shell.returnType; if (typeof implementation === 'string') { addReturnTypeToExternals(implementation, outputType, (externals) => diff --git a/packages/typegpu/src/core/function/tgpuVertexFn.ts b/packages/typegpu/src/core/function/tgpuVertexFn.ts index a261634b3d..8c5b230891 100644 --- a/packages/typegpu/src/core/function/tgpuVertexFn.ts +++ b/packages/typegpu/src/core/function/tgpuVertexFn.ts @@ -158,7 +158,7 @@ function createVertexFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@vertex '); + const core = createFnCore(implementation, 'vertex'); const entryInput: SeparatedEntryArgs = separateAllAsPositional(shell.in ?? {}); const result: This = { diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index fe658ef99d..9c2e954f0d 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -74,14 +74,9 @@ export interface Snippet { readonly origin: Origin; } -export interface ResolvedSnippet { +export interface ResolvedSnippet extends Snippet { readonly value: string; - /** - * The type that `value` is assignable to (not necessary exactly inferred as). - * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float - */ readonly dataType: BaseData; - readonly origin: Origin; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5b3daa0067..5eedec298d 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -12,10 +12,9 @@ import { type TgpuLazy, type TgpuSlot, } from './core/slot/slotTypes.ts'; -import { getAttributesString } from './data/attributes.ts'; -import { isData, undecorate, UnknownData } from './data/dataTypes.ts'; +import { isData, UnknownData } from './data/dataTypes.ts'; import { bool } from './data/numeric.ts'; -import { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; +import { type Origin, type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; import { type BaseData, isPtr, isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts'; import { invariant, MissingSlotValueError, ResolutionError, WgslTypeError } from './errors.ts'; import { provideCtx, topLevelState } from './execMode.ts'; @@ -41,6 +40,7 @@ import type { ExecMode, ExecState, FnToWgslOptions, + FunctionArgumentAccess, FunctionScopeLayer, ItemLayer, ItemStateStack, @@ -58,6 +58,7 @@ import { createIoSchema } from './core/function/ioSchema.ts'; import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; +import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; /** * Inserted into bind group entry definitions that belong @@ -115,16 +116,14 @@ class ItemStateStackImpl implements ItemStateStack { pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, returnType: BaseData | undefined, externalMap: Record, ): FunctionScopeLayer { const scope: FunctionScopeLayer = { type: 'functionScope', functionType, - args, - argAliases, + argAccess, returnType, externalMap, reportedReturnTypes: new Set(), @@ -184,13 +183,9 @@ class ItemStateStackImpl implements ItemStateStack { const layer = this._stack[i]; if (layer?.type === 'functionScope') { - const arg = layer.args.find((a) => a.value === id); - if (arg !== undefined) { - return arg; - } - - if (layer.argAliases[id]) { - return layer.argAliases[id]; + const access = layer.argAccess[id]; + if (access) { + return access(); } const external = layer.externalMap[id]; @@ -311,6 +306,39 @@ interface FixedBindingConfig { resource: object; } +function createArgument( + name: string, + type: BaseData, + origin: Origin = 'argument', +): FunctionArgument { + let used = false; + + return { + name, + access: () => { + used = true; + return snip(name, type, origin); + }, + decoratedType: type, + get used() { + return used; + }, + }; +} + +function createArgumentPropAccess( + argAccess: FunctionArgumentAccess, + prop: string, +): FunctionArgumentAccess { + return () => { + const argSnippet = argAccess(); + if (!argSnippet) { + return undefined; + } + return accessProp(argSnippet, prop); + }; +} + export class ResolutionCtxImpl implements ResolutionCtx { readonly #namespaceInternal: NamespaceInternal; @@ -438,28 +466,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { return this.#logGenerator.logResources; } - fnToWgsl(options: FnToWgslOptions): { head: Wgsl; body: Wgsl; returnType: BaseData } { + fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { let fnScopePushed = false; try { this.#namespaceInternal.nameRegistry.pushFunctionScope(); - const args: Snippet[] = []; - const argAliases: [string, Snippet][] = []; - // For entry functions: collect pending header entries to be filtered after body generation. - const pendingHeaderEntries: { argName: string; header: string }[] = []; + const args: FunctionArgument[] = []; + const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; - const structArgName = this.makeNameValid('_arg_0'); - const structArg = dataSchema ? snip(structArgName, dataSchema, 'argument') : undefined; + const structArg = dataSchema + ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + : undefined; + if (structArg) { args.push(structArg); - pendingHeaderEntries.push({ - argName: structArgName, - header: `${structArgName}: ${this.resolve(dataSchema).value}`, - }); } if (firstParam?.type === FuncParameterType.destructuredObject) { @@ -467,45 +491,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const argName = this.makeNameValid(alias); - const argSnippet = snip(argName, argInfo.type, 'argument'); - args.push(argSnippet); - argAliases.push([alias, argSnippet]); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(argInfo.type)}${argName}: ${this.resolve(undecorate(argInfo.type)).value}`, - }); + const arg = createArgument(this.makeNameValid(alias), argInfo.type); + args.push(arg); + argAccess[alias] = arg.access; } else if (structArg) { - const propSnippet = accessProp(structArg, name); - if (propSnippet) { - argAliases.push([alias, propSnippet]); - } + argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. - const proxyEntries: Array<{ schemaKey: string; argName: string; type: BaseData }> = []; + const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - const s = snip(argName, a.type, 'argument'); - args.push(s); - proxyEntries.push({ schemaKey: a.schemaKey, argName, type: a.type }); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } - const router = new EntryInputRouter(structArgName, dataSchema, proxyEntries); - argAliases.push([firstParam.name, snip(firstParam.name, router, 'argument')]); + const router = new EntryInputRouter(structArg?.access, proxyEntries); + argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - args.push(snip(argName, a.type, 'argument')); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + argAccess[argName] = arg.access; } } } else { @@ -528,22 +538,17 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const rawName = astParam.name; - const snippet = snip(this.makeNameValid(rawName), argType, origin); - args.push(snippet); - if (snippet.value !== rawName) { - argAliases.push([rawName, snippet]); - } + const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + args.push(arg); + argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objSnippet = snip(`_arg_${i}`, argType, origin); - args.push(objSnippet); - argAliases.push( - ...astParam.props.map( - ({ name, alias }) => [alias, accessProp(objSnippet, name)] as [string, Snippet], - ), - ); + const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + args.push(objArg); + for (const { name, alias } of astParam.props) { + argAccess[alias] = createArgumentPropAccess(objArg.access, name); + } break; } case undefined: { @@ -551,7 +556,16 @@ export class ResolutionCtxImpl implements ResolutionCtx { // If we're not using an auto-struct, it's not going to // have any properties anyway. if (!(argType instanceof AutoStruct)) { - args.push(snip(`_arg_${i}`, argType, origin)); + args.push({ + name: this.makeNameValid(`_arg_${i}`), + access: () => { + throw new Error( + `Unreachable: Accessing an argument that wasn't named in the function signature`, + ); + }, + decoratedType: argType, + used: false, + }); } } } @@ -560,68 +574,71 @@ export class ResolutionCtxImpl implements ResolutionCtx { const scope = this._itemStateStack.pushFunctionScope( options.functionType, - args, - Object.fromEntries(argAliases), + argAccess, options.returnType, options.externalMap, ); fnScopePushed = true; - const body = this.gen.functionDefinition(options.body); + let returnType: BaseData | undefined; - let returnType = options.returnType; - if (returnType instanceof AutoStruct) { - // We're expecting an "auto" return type, so if there were structs returned, - // we accept the struct, otherwise we let the rest of the code unify on a - // primitive type. - if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { - returnType = returnType.completeStruct; - } else { - returnType = undefined; - } - } + const code = this.gen.functionDefinition({ + functionType: options.functionType, + args, + body: options.body, + determineReturnType: () => { + if (returnType) { + // Already determined + return returnType; + } - if (!returnType) { - const returnTypes = [...scope.reportedReturnTypes]; - if (returnTypes.length === 0) { - returnType = Void; - } else { - const conversion = getBestConversion(returnTypes); - if (conversion && !conversion.hasImplicitConversions) { - returnType = conversion.targetType; + returnType = options.returnType; + if (returnType instanceof AutoStruct) { + // We're expecting an "auto" return type, so if there were structs returned, + // we accept the struct, otherwise we let the rest of the code unify on a + // primitive type. + if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { + returnType = returnType.completeStruct; + } else { + returnType = undefined; + } } - } - if (!returnType) { - throw new Error( - `Expected function to have a single return type, got [${returnTypes.join( - ', ', - )}]. Cast explicitly to the desired type.`, - ); - } + if (!returnType) { + const returnTypes = [...scope.reportedReturnTypes]; + if (returnTypes.length === 0) { + returnType = Void; + } else { + const conversion = getBestConversion(returnTypes); + if (conversion && !conversion.hasImplicitConversions) { + returnType = conversion.targetType; + } + } - returnType = concretize(returnType); + if (!returnType) { + throw new Error( + `Expected function to have a single return type, got [${returnTypes.join( + ', ', + )}]. Cast explicitly to the desired type.`, + ); + } - if (options.functionType === 'vertex' || options.functionType === 'fragment') { - returnType = createIoSchema(returnType as IOData); - } - } + returnType = concretize(returnType); - if (options.entryInput) { - const headerParts = pendingHeaderEntries - .filter(({ argName }) => isArgUsedInBody(argName, body)) - .map(({ header }) => header); - const argList = headerParts.join(', '); - const returnStr = - returnType.type !== 'void' - ? `-> ${getAttributesString(returnType)}${this.resolve(returnType).value} ` - : ''; - return { head: `(${argList}) ${returnStr}`, body, returnType }; + if (options.functionType === 'vertex' || options.functionType === 'fragment') { + returnType = createIoSchema(returnType as IOData); + } + } + return returnType; + }, + }); + + if (!returnType) { + throw new Error(`Failed to determine return type`); } return { - head: resolveFunctionHeader(this, args, returnType), - body, + code, returnType, }; } finally { @@ -1079,17 +1096,3 @@ export function resolve(item: Wgsl, options: ResolutionCtxImplOptions): Resoluti logResources: ctx.logResources, }; } - -function isArgUsedInBody(argName: string, body: string): boolean { - return new RegExp(`\\b${argName}\\b`).test(body); -} - -function resolveFunctionHeader(ctx: ResolutionCtx, args: Snippet[], returnType: BaseData) { - const argList = args - .map((arg) => `${arg.value}: ${ctx.resolve(arg.dataType as BaseData).value}`) - .join(', '); - - return returnType.type !== 'void' - ? `(${argList}) -> ${getAttributesString(returnType)}${ctx.resolve(returnType).value} ` - : `(${argList}) `; -} diff --git a/packages/typegpu/src/tgsl/accessProp.ts b/packages/typegpu/src/tgsl/accessProp.ts index b264a154ba..3410d3e383 100644 --- a/packages/typegpu/src/tgsl/accessProp.ts +++ b/packages/typegpu/src/tgsl/accessProp.ts @@ -10,7 +10,7 @@ import { } from '../data/dataTypes.ts'; import { abstractInt, bool, f16, f32, i32, u32 } from '../data/numeric.ts'; import { derefSnippet } from '../data/ref.ts'; -import { isEphemeralSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { isEphemeralSnippet, isSnippet, snip, type Snippet } from '../data/snippet.ts'; import { vec2b, vec2f, @@ -160,7 +160,14 @@ export function accessProp(target: Snippet, propName: string): Snippet | undefin } if (target.dataType instanceof EntryInputRouter) { - return target.dataType.accessProp(propName); + const result = target.dataType.accessProp(propName); + if (isSnippet(result)) { + return result; + } + if (result) { + return accessProp(result.target, result.prop); + } + return undefined; } if (isPtr(target.dataType)) { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 0d8ae68163..a11954784a 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -294,7 +294,7 @@ export function convertToCommonType( if ((TEST || DEV) && verbose && conversion.hasImplicitConversions) { console.warn( `Implicit conversions from [\n${values - .map((v) => ` ${v.value}: ${safeStringify(v.dataType)}`) + .map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`) .join(',\n')}\n] to ${conversion.targetType.type} are supported, but not recommended. Consider using explicit conversions instead.`, ); diff --git a/packages/typegpu/src/tgsl/shaderGenerator.ts b/packages/typegpu/src/tgsl/shaderGenerator.ts index e482b03cc8..adde48bf00 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator.ts @@ -1,7 +1,7 @@ -import type { Block } from 'tinyest'; import type { BaseData } from '../data/wgslTypes.ts'; import type { GenerationCtx } from './generationHelpers.ts'; import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; /** * **NOTE: This is an unstable API and may change in the future.** @@ -12,7 +12,7 @@ import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; export interface ShaderGenerator { initGenerator(ctx: GenerationCtx): void; - functionDefinition(body: Block): string; + functionDefinition(options: FunctionDefinitionOptions): string; typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet; typeAnnotation(schema: BaseData): string; } diff --git a/packages/typegpu/src/tgsl/shaderGenerator_members.ts b/packages/typegpu/src/tgsl/shaderGenerator_members.ts index a33ac92b24..8dfc3eae57 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator_members.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator_members.ts @@ -1,4 +1,16 @@ +import type { Block } from 'tinyest'; +import type { BaseData } from '../data/wgslTypes.ts'; +import type { FunctionArgument, TgpuShaderStage } from '../types.ts'; + export { UnknownData } from '../data/dataTypes.ts'; // types -export type { ResolutionCtx } from '../types.ts'; +export type { ResolutionCtx, FunctionArgument } from '../types.ts'; + +export interface FunctionDefinitionOptions { + readonly functionType: 'normal' | TgpuShaderStage; + readonly args: readonly FunctionArgument[]; + readonly body: Block; + + determineReturnType(): BaseData; +} diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index b0b9f1e740..16ecd07522 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -51,6 +51,8 @@ import { mathToStd } from './math.ts'; import type { ExternalMap } from '../core/resolve/externals.ts'; import * as forOfUtils from './forOfUtils.ts'; import { isTgpuRange } from '../std/range.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; +import { getAttributesString } from '../data/attributes.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -833,8 +835,27 @@ ${this.ctx.pre}}`; assertExhaustive(expression); } - public functionDefinition(body: tinyest.Block): string { - return this._block(body); + public functionDefinition(options: FunctionDefinitionOptions): string { + // Function body + const body = this._block(options.body); + + // Function header + const returnType = options.determineReturnType(); + + const argList = options.args + // Stripping out unused arguments in entry functions + .filter((arg) => arg.used || options.functionType === 'normal') + .map((arg) => { + return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`; + }) + .join(', '); + + const head = + returnType.type !== 'void' + ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` + : `(${argList}) `; + + return `${head}${body}`; } /** diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 1c92f8a43f..75f9e71697 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -99,11 +99,19 @@ export type ItemLayer = { usedSlots: Set>; }; +export type FunctionArgumentAccess = () => Snippet | undefined; + +export interface FunctionArgument { + name: string; + access: FunctionArgumentAccess; + decoratedType: BaseData; + used: boolean; +} + export type FunctionScopeLayer = { type: 'functionScope'; functionType: 'normal' | 'compute' | 'vertex' | 'fragment'; - args: Snippet[]; - argAliases: Record; + argAccess: Record; externalMap: Record; /** * The return type of the function. If undefined, the type should be inferred @@ -138,8 +146,7 @@ export interface ItemStateStack { pushSlotBindings(pairs: SlotValuePair[]): void; pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, /** * The return type of the function. If undefined, the type should be inferred * from the implementation (relevant for shellless functions). @@ -305,8 +312,7 @@ export interface ResolutionCtx { resolveSnippet(snippet: Snippet): ResolvedSnippet; fnToWgsl(options: FnToWgslOptions): { - head: Wgsl; - body: Wgsl; + code: string; returnType: BaseData; }; diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 786f30f108..46763ca6af 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -277,13 +277,6 @@ describe('root.withVertex(...).withFragment(...)', () => { return vertexMain_Output(vec3f(), vec3f(), vec3f(), 0f, 0u, vec4f()); } - struct fragmentMain_Input { - @location(3) baz3: u32, - @location(1) bar: vec3f, - @location(2) foo: vec3f, - @location(5) baz2: f32, - } - @fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(); }" @@ -1568,8 +1561,8 @@ describe('root.createRenderPipeline', () => { } struct VertexIn { - @builtin(vertex_index) vertexIndex: u32, @location(0) localPos: vec3f, + @builtin(vertex_index) vertexIndex: u32, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { @@ -1712,18 +1705,13 @@ describe('root.createRenderPipeline', () => { { "arrayStride": 16, "attributes": [ - { - "format": "float32", - "offset": 12, - "shaderLocation": 0, - }, { "format": "float32x3", "offset": 0, - "shaderLocation": 1, + "shaderLocation": 0, }, ], - "stepMode": "instance", + "stepMode": "vertex", }, { "arrayStride": 16, @@ -1731,10 +1719,15 @@ describe('root.createRenderPipeline', () => { { "format": "float32x3", "offset": 0, + "shaderLocation": 1, + }, + { + "format": "float32", + "offset": 12, "shaderLocation": 2, }, ], - "stepMode": "vertex", + "stepMode": "instance", }, ], "module": "mockShaderModule", @@ -1751,10 +1744,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "instanceBuffer", + "label": "vertexBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 16, + "size": 48, "unmap": [MockFunction], "usage": 44, }, @@ -1766,10 +1759,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "vertexBuffer", + "label": "instanceBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 48, + "size": 16, "unmap": [MockFunction], "usage": 44, }, diff --git a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts index 727e9fed70..58150fbee5 100644 --- a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts +++ b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts @@ -17,14 +17,14 @@ describe('extension based pruning', () => { }); expect(tgpu.resolve([someFn], { enableExtensions: ['f16'] })).toMatchInlineSnapshot(` - "enable f16; + "enable f16; - fn someFn() -> f32 { - { - return 6.599609375f; - } - }" - `); + fn someFn() -> f32 { + { + return 6.599609375f; + } + }" + `); expect(tgpu.resolve([someFn])).toMatchInlineSnapshot(` "fn someFn() -> f32 { diff --git a/packages/typegpu/tests/tgsl/nameClashes.test.ts b/packages/typegpu/tests/tgsl/nameClashes.test.ts index 86167245ed..586e8e3bf1 100644 --- a/packages/typegpu/tests/tgsl/nameClashes.test.ts +++ b/packages/typegpu/tests/tgsl/nameClashes.test.ts @@ -223,14 +223,14 @@ test('should allow duplicate name after block end', () => { }; expect(tgpu.resolve([main])).toMatchInlineSnapshot(` - "fn main() -> u32 { - for (var i = 0; (i < 3i); i++) { - let foo = (i + 1i); - } - const foo = 7u; - return foo; - }" - `); + "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', () => { diff --git a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts index fd37903d12..c41cc6b27c 100644 --- a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts +++ b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts @@ -18,14 +18,14 @@ describe('ternary operator', () => { myFn.with(mySlot, false).$name('falseFn'), ]), ).toMatchInlineSnapshot(` - "fn trueFn() -> u32 { - return 10u; - } - - fn falseFn() -> u32 { - return 20u; - }" - `); + "fn trueFn() -> u32 { + return 10u; + } + + fn falseFn() -> u32 { + return 20u; + }" + `); }); it('should work for different comptime known expressions', () => { @@ -72,22 +72,22 @@ describe('ternary operator', () => { myFn.with(mySlot, 3).$name('threeFn'), ]), ).toMatchInlineSnapshot(` - "fn myFn() -> u32 { - return -1u; - } - - fn oneFn() -> u32 { - return 10u; - } - - fn twoFn() -> u32 { - return 20u; - } - - fn threeFn() -> u32 { - return 30u; - }" - `); + "fn myFn() -> u32 { + return -1u; + } + + fn oneFn() -> u32 { + return 10u; + } + + fn twoFn() -> u32 { + return 20u; + } + + fn threeFn() -> u32 { + return 30u; + }" + `); }); it('should not include unused dependencies', ({ root }) => { @@ -103,20 +103,20 @@ describe('ternary operator', () => { }); expect(tgpu.resolve([myFn.with(mySlot, true).$name('trueFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myUniform: u32; + "@group(0) @binding(0) var myUniform: u32; - fn trueFn() -> u32 { - return myUniform; - }" - `); + fn trueFn() -> u32 { + return myUniform; + }" + `); expect(tgpu.resolve([myFn.with(mySlot, false).$name('falseFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myReadonly: u32; + "@group(0) @binding(0) var myReadonly: u32; - fn falseFn() -> u32 { - return myReadonly; - }" - `); + fn falseFn() -> u32 { + return myReadonly; + }" + `); }); it('should handle undefined', ({ root }) => { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index b973015c9e..7d1ffc1120 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -3,8 +3,7 @@ import { beforeEach, describe, expect } from 'vitest'; import { namespace } from '../../src/core/resolve/namespace.ts'; import * as d from '../../src/data/index.ts'; import { abstractFloat, abstractInt } from '../../src/data/numeric.ts'; -import { snip } from '../../src/data/snippet.ts'; -import { Void, type WgslArray } from '../../src/data/wgslTypes.ts'; +import { type WgslArray } from '../../src/data/wgslTypes.ts'; import { provideCtx } from '../../src/execMode.ts'; import tgpu from '../../src/index.js'; import { ResolutionCtxImpl } from '../../src/resolutionCtx.ts'; @@ -15,7 +14,7 @@ import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from 'typegpu-testing-utility'; import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; -import { extractSnippetFromFn } from '../utils/parseResolved.ts'; +import { expectDataTypeOf, extractSnippetFromFn } from '../utils/parseResolved.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -40,19 +39,11 @@ describe('wgslGenerator', () => { return true; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot(`"[0,[[10,true]]]"`); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.bool, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - return true; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> bool { + return true; + }" + `); }); it('creates a function body', () => { @@ -63,23 +54,13 @@ describe('wgslGenerator', () => { return a; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot( - `"[0,[[12,"a",[5,"12"]],[2,"a","+=",[5,"21"]],[10,"a"]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.i32, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - var a = 12; - a += 21i; - return a; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> i32 { + var a = 12; + a += 21i; + return a; + }" + `); }); it('creates correct resources for numeric literals', () => { @@ -134,57 +115,22 @@ describe('wgslGenerator', () => { }); const testBuffer = root.createBuffer(TestStruct).$usage('storage'); - const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$.a + testUsage.$.b.x; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - if (!astInfo) { - throw new Error('Expected prebuilt AST to be present'); - } + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.a; + }).toStrictEqual(d.u32); - expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( - `"[0,[[10,[1,[7,[7,"testUsage","$"],"a"],"+",[7,[7,[7,"testUsage","$"],"b"],"x"]]]]]"`, - ); - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.b.x; + }).toStrictEqual(d.u32); - provideCtx(ctx, () => { - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res1 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[1], - ); - - expect(res1.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res2 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[3], - ); - expect(res2.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const sum = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - expect(sum.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.a + testUsage.$.b.x; + }).toStrictEqual(d.u32); }); it('generates correct resources for external resource array index access', ({ root }) => { @@ -192,42 +138,10 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('uniform'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$[3] as number; - }); - - 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,[[10,[8,[7,"testUsage","$"],[5,"3"]]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return testUsage.$[3]; - // ^ this should be a u32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$[3]; + }).toStrictEqual(d.u32); }); it('generates correct resources for nested struct with atomics in a complex expression', ({ @@ -253,156 +167,29 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [d.u32], - d.vec4f, - )((idx) => { - const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - const vec = std.mix(d.vec4f(), testUsage.$.a, value); - std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - return vec; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - - if (!astInfo?.ast) { - throw new Error('Expected prebuilt AST to be present'); - } - - expect(JSON.stringify(astInfo.ast.body)).toMatchInlineSnapshot( - `"[0,[[13,"value",[6,[7,"std","atomicLoad"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"y"]]]],[13,"vec",[6,[7,"std","mix"],[[6,[7,"d","vec4f"],[]],[7,[7,"testUsage","$"],"a"],"value"]]],[6,[7,"std","atomicStore"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"x"],[7,"vec","y"]]],[10,"vec"]]]"`, - ); - - if (astInfo.ast.params.filter((arg) => arg.type !== 'i').length > 0) { - throw new Error('Expected arguments as identifier names in ast'); - } - - const args = astInfo.ast.params.map((arg) => - snip((arg as { type: 'i'; name: string }).name, d.u32, /* origin */ 'runtime'), - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - args, - {}, - d.vec4f, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - // ^ this part should be a i32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.i32); - - // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); - // ^ this part should be a vec4f - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'value', d.i32, 'runtime'); - const res2 = wgslGenerator._expression( - (astInfo.ast?.body[1][1] as tinyest.Const)[2] as tinyest.Expression, - ); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res2.dataType).toStrictEqual(d.vec4f); - - // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - // ^ this part should be an atomic u32 - // ^ this part should be void - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'function'); - const res3 = wgslGenerator._expression( - (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, - ); - const res4 = wgslGenerator._expression(astInfo.ast?.body[1][2] as tinyest.Expression); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res3.dataType).toStrictEqual(d.atomic(d.u32)); - expect(res4.dataType).toStrictEqual(Void); - }); - }); - - it('creates correct code for for statements', () => { - const main = () => { + // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); + // ^ this part should be a i32 + expectDataTypeOf(() => { 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[14,[12,"i",[5,"0"]],[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); + const idx = d.u32(0); + std.atomicLoad(testUsage.$.b.aa[idx]!.y); + }).toStrictEqual(d.i32); - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); - - it('creates correct code for for statements with outside init', () => { - const main = () => { + // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); + // ^ this part should be a vec4f + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - for (; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[14,null,[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - for (; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); + const value = std.atomicLoad(testUsage.$.b.aa[0]!.y); + std.mix(d.vec4f(), testUsage.$.a, value); + }).toStrictEqual(d.vec4f); - it('creates correct code for while statements', () => { - const main = () => { + // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); + // ^ this part should be an atomic u32 + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - while (i < 10) { - i += 1; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[15,[1,"i","<",[5,"10"]],[0,[[2,"i","+=",[5,"1"]]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - while ((i < 10i)) { - i += 1i; - } - }" - `); + const idx = d.u32(0); + testUsage.$.b.aa[idx]!.x; + }).toStrictEqual(d.atomic(d.u32)); }); it('parses correctly "for ... of ..." statements', () => { @@ -979,6 +766,11 @@ describe('wgslGenerator', () => { }); it('creates correct resources for lazy values and slots', () => { + expectDataTypeOf(() => { + 'use gpu'; + lazyV4u.$; + }).toStrictEqual(d.vec4u); + const testFn = tgpu.fn([], d.vec4u)(() => lazyV4u.$); expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` @@ -986,76 +778,14 @@ describe('wgslGenerator', () => { return vec4u(44, 88, 132, 176); }" `); - - 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,[[10,[7,"lazyV4u","$"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.vec4u, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return lazyV4u.$; - // ^ this should be a vec4u - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.vec4u); - }); }); it('creates correct resources for indexing into a lazy value', () => { - const testFn = tgpu.fn( - [d.u32], - d.f32, - )((idx) => { - return lazyV2f.$[idx] as number; - }); - - 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,[[10,[8,[7,"lazyV2f","$"],"idx"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [snip('idx', d.u32, /* origin */ 'runtime')], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return lazyV2f.$[idx]; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + const idx = d.u32(0); + lazyV2f.$[idx]; + }).toStrictEqual(d.f32); }); it('creates intermediate representation for array expression', () => { @@ -1081,44 +811,11 @@ describe('wgslGenerator', () => { }); 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( - (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); - }); + "fn testFn() -> u32 { + var arr = array(1u, 2u, 3u); + return arr[1i]; + }" + `); }); it('generates correct code for complex array expressions', () => { @@ -1140,39 +837,6 @@ 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( - (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); - }); }); it('does not autocast lhs of an assignment', () => { @@ -1223,38 +887,15 @@ describe('wgslGenerator', () => { }" `); - 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,"TestStruct",[[104,{"x":[5,"1"],"y":[5,"2"]}]]],[6,"TestStruct",[[104,{"x":[5,"3"],"y":[5,"4"]}]]]]]],[10,[7,[8,"arr",[5,"1"]],"y"]]]]"`, - ); - - const res = provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; - // ^ this should be an array - wgslGenerator.initGenerator(ctx); - return wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); + const arraySnippet = extractSnippetFromFn(() => { + 'use gpu'; + const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; + arr; }); - 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); + expect(d.isWgslArray(arraySnippet.dataType)).toBe(true); + expect((arraySnippet.dataType as unknown as WgslArray).elementCount).toBe(2); + expect((arraySnippet.dataType as unknown as WgslArray).elementType).toBe(TestStruct); }); it('generates correct code for array expressions with lazy elements', () => { @@ -1272,37 +913,6 @@ describe('wgslGenerator', () => { return arr[1i].y; }" `); - - 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,[[7,"lazyV2f","$"],[6,[7,"std","mul"],[[7,"lazyV2f","$"],[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); }); it('allows for member access on values returned from function calls', () => { @@ -1340,34 +950,10 @@ describe('wgslGenerator', () => { }" `); - const astInfo = getMetaData(fnTwo[$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,[[10,[7,[7,[6,"fnOne",[]],"y"],"x"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return fnOne().y.x; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + fnOne().y.x; + }).toStrictEqual(d.f32); }); it('generates correct code for conditional with single statement', () => { @@ -1440,27 +1026,6 @@ describe('wgslGenerator', () => { `); }); - it('generates correct code for for loops with single statements', () => { - const main = () => { - 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const gen = provideCtx(ctx, () => - wgslGenerator.functionDefinition(getMetaData(main)?.ast?.body as tinyest.Block), - ); - - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); - it('generates correct code for while loops with single statements', () => { const main = tgpu.fn([])(() => { let i = 0; @@ -1781,59 +1346,51 @@ describe('wgslGenerator', () => { it('block externals do not override identifiers', () => { const f = () => { 'use gpu'; - const y = 100; - const x = y; - return x; + const list = [1]; + for (const x of tgpu.unroll(list)) { + 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); - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var list = array(1); + // unrolled iteration #0 + { + const y = 100; + const x = y; + return x; + } + }" + `); }); it('block externals are injected correctly', () => { const f = () => { 'use gpu'; - for (const x of []) { + for (const x of tgpu.unroll([1])) { 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; - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + // unrolled iteration #0 + { + const y = 1; + } + }" + `); }); 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) { + const list = [1]; + for (const elem of tgpu.unroll(list)) { { // We use the `elem` in a nested block result += elem; @@ -1841,24 +1398,18 @@ describe('wgslGenerator', () => { } }; - 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(` - "{ + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var result = 0i; + var list = array(1); + // unrolled iteration #0 + { { - result += 7i; + result += list[0u]; } - }" - `); - }); + } + }" + `); }); it('prunes comptime if/else', () => { diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 529e9f0cfa..9278a82a00 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -363,11 +363,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -410,11 +406,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -444,11 +436,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - @fragment fn fragmentFn() -> @location(0) vec4f { + "@fragment fn fragmentFn() -> @location(0) vec4f { var hmm = vec4f(1.25); return hmm; }" @@ -481,11 +469,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index b798ff9f8c..9c94a073c0 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -29,7 +29,6 @@ export function extractSnippetFromFn(cb: () => unknown): Snippet { ctx[$internal].itemStateStack.pushItem(); ctx[$internal].itemStateStack.pushFunctionScope( 'normal', - [], {}, undefined, (meta.externals as () => Record)() ?? {}, From d7926fa3e51520f998e13c66eb7303ce373d2414 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 11 Apr 2026 13:31:41 +0200 Subject: [PATCH 02/34] refactor: Naming cleanup --- .../typegpu/src/core/buffer/bufferUsage.ts | 4 +- .../typegpu/src/core/constant/tgpuConstant.ts | 2 +- packages/typegpu/src/core/function/fnCore.ts | 27 +-- .../typegpu/src/core/resolve/namespace.ts | 55 +----- .../typegpu/src/core/resolve/resolveData.ts | 5 +- packages/typegpu/src/core/sampler/sampler.ts | 4 +- .../src/core/texture/externalTexture.ts | 2 +- packages/typegpu/src/core/texture/texture.ts | 4 +- .../typegpu/src/core/variable/tgpuVariable.ts | 2 +- packages/typegpu/src/data/autoStruct.ts | 2 +- packages/typegpu/src/data/struct.ts | 2 +- .../src/{nameRegistry.ts => nameUtils.ts} | 166 +----------------- packages/typegpu/src/resolutionCtx.ts | 135 ++++++++++---- packages/typegpu/src/tgsl/wgslGenerator.ts | 31 ++-- packages/typegpu/src/types.ts | 25 ++- packages/typegpu/tests/namespace.test.ts | 30 +--- packages/typegpu/tests/resolve.test.ts | 4 +- 17 files changed, 187 insertions(+), 313 deletions(-) rename packages/typegpu/src/{nameRegistry.ts => nameUtils.ts} (58%) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 24f28dcbca..d8a1028e78 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -118,7 +118,7 @@ class TgpuFixedBufferImpl implements TgpuConst, } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const resolvedDataType = ctx.resolve(this.dataType).value; const resolvedValue = ctx.resolve(this.#value, this.dataType).value; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index cf4cda90f7..aa91d451f6 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -3,6 +3,7 @@ import { undecorate } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTypes.ts'; import { MissingLinksError } from '../../errors.ts'; +import { isValidIdentifier } from '../../nameUtils.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; @@ -65,21 +66,25 @@ export function createFnCore( applyExternals(externalMap, externals); } - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); if (typeof implementation === 'string') { if (!returnType) { throw new Error('Explicit return type is required for string implementation'); } - const validArgNames = entryInput - ? Object.fromEntries( - entryInput.positionalArgs.map((a) => [a.schemaKey, ctx.makeNameValid(a.schemaKey)]), - ) - : undefined; + if (entryInput) { + for (const arg of entryInput.positionalArgs) { + if (!isValidIdentifier(arg.schemaKey)) { + throw new Error(`Invalid argument name: ${arg.schemaKey}`); + } + } - if (validArgNames && Object.keys(validArgNames).length > 0) { - applyExternals(externalMap, { in: validArgNames }); + applyExternals(externalMap, { + in: Object.fromEntries( + entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey]), + ), + }); } const replacedImpl = replaceExternalsInWgsl(ctx, externalMap, implementation); @@ -87,15 +92,15 @@ export function createFnCore( let header = ''; let body = ''; - if (functionType !== 'normal' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { parts.push(`in: ${ctx.resolve(dataSchema).value}`); } for (const a of positionalArgs) { - const argName = validArgNames[a.schemaKey] ?? ''; - if (argName !== '' && isArgUsedInBody(argName, replacedImpl)) { + const argName = a.schemaKey; + if (isArgUsedInBody(argName, replacedImpl)) { parts.push(`${getAttributesString(a.type)}${argName}: ${ctx.resolve(a.type).value}`); } } diff --git a/packages/typegpu/src/core/resolve/namespace.ts b/packages/typegpu/src/core/resolve/namespace.ts index f112cd4fb0..90fcba08f7 100644 --- a/packages/typegpu/src/core/resolve/namespace.ts +++ b/packages/typegpu/src/core/resolve/namespace.ts @@ -1,6 +1,5 @@ import type { ResolvedSnippet } from '../../data/snippet.ts'; -import { type NameRegistry, RandomNameRegistry, StrictNameRegistry } from '../../nameRegistry.ts'; -import { getName } from '../../shared/meta.ts'; +import { bannedTokens, builtins } from '../../nameUtils.ts'; import { $internal } from '../../shared/symbols.ts'; import { ShelllessRepository } from '../../tgsl/shellless.ts'; import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; @@ -8,8 +7,9 @@ import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; type SlotToValueMap = Map, unknown>; export interface NamespaceInternal { - readonly nameRegistry: NameRegistry; + readonly takenGlobalIdentifiers: Set; readonly shelllessRepo: ShelllessRepository; + readonly strategy: 'random' | 'strict'; memoizedResolves: WeakMap< // WeakMap because if the item does not exist anymore, @@ -24,73 +24,32 @@ export interface NamespaceInternal { TgpuLazy, { slotToValueMap: SlotToValueMap; result: unknown }[] >; - - listeners: { - [K in keyof NamespaceEventMap]: Set<(event: NamespaceEventMap[K]) => void>; - }; } -type NamespaceEventMap = { - name: { target: object; name: string }; -}; - -type DetachListener = () => void; - export interface Namespace { readonly [$internal]: NamespaceInternal; - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener; } class NamespaceImpl implements Namespace { readonly [$internal]: NamespaceInternal; - constructor(nameRegistry: NameRegistry) { + constructor(strategy: 'random' | 'strict') { this[$internal] = { - nameRegistry, + strategy, + takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins]), shelllessRepo: new ShelllessRepository(), memoizedResolves: new WeakMap(), memoizedLazy: new WeakMap(), - listeners: { - name: new Set(), - }, }; } - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener { - if (event === 'name') { - const listeners = this[$internal].listeners.name; - listeners.add(listener); - - return () => listeners.delete(listener); - } - - throw new Error(`Unsupported event: ${event}`); - } } export interface NamespaceOptions { names?: 'random' | 'strict' | undefined; } -export function getUniqueName(namespace: NamespaceInternal, resource: object): string { - const name = namespace.nameRegistry.makeUnique(getName(resource), true); - for (const listener of namespace.listeners.name) { - listener({ target: resource, name }); - } - return name; -} - export function namespace(options?: NamespaceOptions): Namespace { const { names = 'strict' } = options ?? {}; - return new NamespaceImpl( - names === 'strict' ? new StrictNameRegistry() : new RandomNameRegistry(), - ); + return new NamespaceImpl(names); } diff --git a/packages/typegpu/src/core/resolve/resolveData.ts b/packages/typegpu/src/core/resolve/resolveData.ts index 19e5da347d..bc5320c80d 100644 --- a/packages/typegpu/src/core/resolve/resolveData.ts +++ b/packages/typegpu/src/core/resolve/resolveData.ts @@ -38,6 +38,7 @@ import type { WgslArray, WgslStruct, } from '../../data/wgslTypes.ts'; +import { getName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { assertExhaustive } from '../../shared/utilityTypes.ts'; import type { ResolutionCtx } from '../../types.ts'; @@ -127,7 +128,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) { if (struct[$internal].isAbstruct) { throw new Error('Cannot resolve abstract struct types to WGSL.'); } - const id = ctx.getUniqueName(struct); + const id = ctx.makeUniqueIdentifier(getName(struct), 'global'); ctx.addDeclaration(`\ struct ${id} { @@ -155,7 +156,7 @@ ${Object.entries(struct.propTypes) * ``` */ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) { - const id = ctx.getUniqueName(unstruct); + const id = ctx.makeUniqueIdentifier(getName(unstruct), 'global'); ctx.addDeclaration(`\ struct ${id} { diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index 4daaf1c917..adf10ee8ce 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -99,7 +99,7 @@ export class TgpuLaidOutSamplerImpl< } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( @@ -186,7 +186,7 @@ class TgpuFixedSamplerImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( this.schema.type === 'sampler_comparison' diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index c9d457c48b..78ae4cd582 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl implements TgpuExternalTexture, SelfResolva } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 27923995d6..fbe7295d83 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -600,7 +600,7 @@ class TgpuFixedTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( isWgslStorageTexture(this.schema) ? { @@ -642,7 +642,7 @@ export class TgpuLaidOutTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const pre = `var<${this.#scope}> ${id}: ${ctx.resolve(this.#dataType).value}`; if (this.#initialValue) { diff --git a/packages/typegpu/src/data/autoStruct.ts b/packages/typegpu/src/data/autoStruct.ts index 647d13b79c..c227b565fe 100644 --- a/packages/typegpu/src/data/autoStruct.ts +++ b/packages/typegpu/src/data/autoStruct.ts @@ -1,5 +1,5 @@ import { createIoSchema } from '../core/function/ioSchema.ts'; -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal, $repr, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; diff --git a/packages/typegpu/src/data/struct.ts b/packages/typegpu/src/data/struct.ts index c044da5f8f..c2d024acc3 100644 --- a/packages/typegpu/src/data/struct.ts +++ b/packages/typegpu/src/data/struct.ts @@ -1,4 +1,4 @@ -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { schemaCallWrapper } from './schemaCallWrapper.ts'; diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameUtils.ts similarity index 58% rename from packages/typegpu/src/nameRegistry.ts rename to packages/typegpu/src/nameUtils.ts index aa2cd1ada0..f66bfbb79a 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameUtils.ts @@ -1,6 +1,4 @@ -import { invariant } from './errors.ts'; - -const bannedTokens = new Set([ +export const bannedTokens = new Set([ // keywords 'alias', 'break', @@ -181,7 +179,7 @@ const bannedTokens = new Set([ 'storage', ]); -const builtins = new Set([ +export const builtins = new Set([ // constructors 'array', 'bool', @@ -361,37 +359,8 @@ const builtins = new Set([ 'quadSwapY', ]); -export interface NameRegistry { - /** - * Creates a valid WGSL identifier, each guaranteed to be unique - * in the lifetime of a single resolution process - * (excluding non-global identifiers from popped scopes). - * Should append "_" to primer, followed by some id. - * @param primer Used in the generation process, makes the identifier more recognizable. - * @param global Whether the name should be registered in the global scope (true), or in the current function scope (false) - */ - makeUnique(primer: string | undefined, global: boolean): string; - - /** - * Creates a valid WGSL identifier. - * Renames identifiers that are WGSL reserved words. - * @param primer Used in the generation process. - * - * @example - * makeValid("notAKeyword"); // "notAKeyword" - * makeValid("struct"); // makeUnique("struct") - * makeValid("struct_1"); // makeUnique("struct_1") (to avoid potential name collisions) - * makeValid("_"); // ERROR (too difficult to make valid to care) - */ - makeValid(primer: string): string; - - pushFunctionScope(): void; - popFunctionScope(): void; - pushBlockScope(): void; - popBlockScope(): void; -} - -function sanitizePrimer(primer: string | undefined) { +/*#__NO_SIDE_EFFECTS__*/ +export function sanitizePrimer(primer: string | undefined) { if (primer) { // sanitizing return primer @@ -411,7 +380,8 @@ function sanitizePrimer(primer: string | undefined) { * isValidIdentifier("_"); // ERROR * isValidIdentifier("my variable"); // ERROR */ -function isValidIdentifier(ident: string): boolean { +/*#__NO_SIDE_EFFECTS__*/ +export function isValidIdentifier(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( `Invalid identifier '${ident}'. Choose an identifier without whitespaces or leading underscores.`, @@ -424,6 +394,7 @@ function isValidIdentifier(ident: string): boolean { /** * Same as `isValidIdentifier`, except does not check for builtin clashes. */ +/*#__NO_SIDE_EFFECTS__*/ export function isValidProp(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( @@ -433,126 +404,3 @@ 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; - - readonly #usedNames: Set; - readonly #scopeStack: ScopeLayer[]; - - constructor() { - this.#usedNames = new Set([...bannedTokens, ...builtins]); - this.#scopeStack = []; - } - - get #usedBlockScopeNames(): Set | undefined { - return (this.#scopeStack[this.#scopeStack.length - 1] as BlockScopeLayer | undefined) - ?.usedBlockScopeNames; - } - - makeUnique(primer: string | undefined, global: boolean): string { - const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer); - - if (global) { - this.#usedNames.add(name); - } else { - 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.#isUsedInBlocksBefore(primer) - ) { - this.#usedBlockScopeNames?.add(primer); - return primer; - } - return this.makeUnique(primer, false); - } - - isUsed(name: string): boolean { - return this.#usedNames.has(name) || this.#isUsedInBlocksBefore(name); - } - - pushFunctionScope(): void { - this.#scopeStack.push({ type: 'functionScope' }); - this.#scopeStack.push({ - type: 'blockScope', - usedBlockScopeNames: new Set(), - }); - } - - popFunctionScope(): void { - const functionScopeIndex = this.#scopeStack.findLastIndex( - (scope) => scope.type === 'functionScope', - ); - - if (functionScopeIndex === -1) { - throw new Error('Tried to pop function scope when no scope was present.'); - } - - 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 { - let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name)) { - name = `${base}_${this.#lastUniqueId++}`; - } - return name; - } -} - -export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string): string { - let index = 0; - let name = base; - while (this.isUsed(name)) { - index++; - name = `${base}_${index}`; - } - return name; - } -} diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5eedec298d..57f87d7e84 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -1,5 +1,5 @@ import { isTgpuFn } from './core/function/tgpuFn.ts'; -import { getUniqueName, type Namespace, type NamespaceInternal } from './core/resolve/namespace.ts'; +import type { Namespace, NamespaceInternal } from './core/resolve/namespace.ts'; import { stitch } from './core/resolve/stitch.ts'; import { ConfigurableImpl } from './core/root/configurableImpl.ts'; import type { Configurable, ExperimentalTgpuRoot } from './core/root/rootTypes.ts'; @@ -37,6 +37,7 @@ import { coerceToSnippet, concretize, numericLiteralToSnippet } from './tgsl/gen import type { ShaderGenerator } from './tgsl/shaderGenerator.ts'; import wgslGenerator from './tgsl/wgslGenerator.ts'; import type { + BlockScopeLayer, ExecMode, ExecState, FnToWgslOptions, @@ -59,6 +60,7 @@ import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; +import { isValidIdentifier, sanitizePrimer } from './nameUtils.ts'; /** * Inserted into bind group entry definitions that belong @@ -99,6 +101,10 @@ class ItemStateStackImpl implements ItemStateStack { return this._stack.findLast((e) => e.type === 'functionScope'); } + get topBlockScope(): BlockScopeLayer | undefined { + return this._stack.findLast((e) => e.type === 'blockScope'); + } + pushItem() { this._itemDepth++; this._stack.push({ @@ -136,6 +142,7 @@ class ItemStateStackImpl implements ItemStateStack { pushBlockScope() { this._stack.push({ type: 'blockScope', + takenLocalIdentifiers: new Set(), declarations: new Map(), externals: new Map(), }); @@ -213,6 +220,26 @@ class ItemStateStackImpl implements ItemStateStack { return undefined; } + isIdentifierTakenLocally(id: string): boolean { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + + if (layer?.type === 'functionScope') { + // Since functions cannot access resources from the calling scope, we + // return early here. + return false; + } + + if (layer?.type === 'blockScope') { + if (layer.takenLocalIdentifiers.has(id)) { + return true; + } + } + } + + return false; + } + defineBlockVariable(id: string, snippet: Snippet): void { if (snippet.dataType === UnknownData) { throw Error(`Tried to define variable '${id}' of unknown type`); @@ -375,6 +402,11 @@ export class ResolutionCtxImpl implements ResolutionCtx { public readonly enableExtensions: WgslExtension[] | undefined; public expectedType: BaseData | undefined; + /** + * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy. + */ + #lastUniqueId = 0; + constructor(opts: ResolutionCtxImplOptions) { this.enableExtensions = opts.enableExtensions; this.gen = opts.shaderGenerator ?? wgslGenerator; @@ -382,12 +414,42 @@ export class ResolutionCtxImpl implements ResolutionCtx { this.#namespaceInternal = opts.namespace[$internal]; } - getUniqueName(resource: object): string { - return getUniqueName(this.#namespaceInternal, resource); + isIdentifierTaken(name: string): boolean { + return ( + this.#namespaceInternal.takenGlobalIdentifiers.has(name) || + !!this._itemStateStack.isIdentifierTakenLocally(name) + ); } - makeNameValid(name: string): string { - return this.#namespaceInternal.nameRegistry.makeValid(name); + makeUniqueIdentifier(primer: string = 'item', scope: 'global' | 'block'): string { + if (scope === 'block' && isValidIdentifier(primer) && !this.isIdentifierTaken(primer)) { + // Preserving local definitions as they are, provided they are valid and not already taken. + this.reserveIdentifier(primer, 'block'); + return primer; + } + + const base = sanitizePrimer(primer); + let index = 0; + const random = this.#namespaceInternal.strategy === 'random'; + let name = random ? `${base}_${this.#lastUniqueId++}` : base; + while (this.isIdentifierTaken(name)) { + name = random ? `${base}_${this.#lastUniqueId++}` : `${base}_${++index}`; + } + + this.reserveIdentifier(name, scope); + return name; + } + + reserveIdentifier(name: string, scope: 'global' | 'block'): void { + if (scope === 'block') { + const blockScope = this._itemStateStack.topBlockScope; + if (blockScope) { + blockScope.takenLocalIdentifiers.add(name); + return; + } + // Fall through if no block scope is present, treating as global. + } + this.#namespaceInternal.takenGlobalIdentifiers.add(name); } get pre(): string { @@ -441,12 +503,10 @@ export class ResolutionCtxImpl implements ResolutionCtx { } pushBlockScope() { - this.#namespaceInternal.nameRegistry.pushBlockScope(); this._itemStateStack.pushBlockScope(); } popBlockScope() { - this.#namespaceInternal.nameRegistry.popBlockScope(); this._itemStateStack.pop('blockScope'); } @@ -467,19 +527,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { } fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { - let fnScopePushed = false; - try { - this.#namespaceInternal.nameRegistry.pushFunctionScope(); + const scope = this._itemStateStack.pushFunctionScope( + options.functionType, + {}, + options.returnType, + options.externalMap, + ); + // Pushing a block scope as well, so that any identifiers declared at this point will be scoped to the function body. + this._itemStateStack.pushBlockScope(); + const args: FunctionArgument[] = []; - const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; const structArg = dataSchema - ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + ? createArgument(this.makeUniqueIdentifier('_arg_0', 'block'), dataSchema) : undefined; if (structArg) { @@ -491,31 +556,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const arg = createArgument(this.makeNameValid(alias), argInfo.type); + const arg = createArgument(this.makeUniqueIdentifier(alias, 'block'), argInfo.type); args.push(arg); - argAccess[alias] = arg.access; + scope.argAccess[alias] = arg.access; } else if (structArg) { - argAccess[alias] = createArgumentPropAccess(structArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } const router = new EntryInputRouter(structArg?.access, proxyEntries); - argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); + scope.argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); - argAccess[argName] = arg.access; + scope.argAccess[argName] = arg.access; } } } else { @@ -538,16 +603,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + const arg = createArgument( + this.makeUniqueIdentifier(astParam.name, 'block'), + argType, + origin, + ); args.push(arg); - argAccess[astParam.name] = arg.access; + scope.argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + const objArg = createArgument( + this.makeUniqueIdentifier(`_arg_${i}`, 'block'), + argType, + origin, + ); args.push(objArg); for (const { name, alias } of astParam.props) { - argAccess[alias] = createArgumentPropAccess(objArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(objArg.access, name); } break; } @@ -557,7 +630,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { // have any properties anyway. if (!(argType instanceof AutoStruct)) { args.push({ - name: this.makeNameValid(`_arg_${i}`), + name: this.makeUniqueIdentifier(`_arg_${i}`, 'block'), access: () => { throw new Error( `Unreachable: Accessing an argument that wasn't named in the function signature`, @@ -572,14 +645,6 @@ export class ResolutionCtxImpl implements ResolutionCtx { } } - const scope = this._itemStateStack.pushFunctionScope( - options.functionType, - argAccess, - options.returnType, - options.externalMap, - ); - fnScopePushed = true; - let returnType: BaseData | undefined; const code = this.gen.functionDefinition({ @@ -642,10 +707,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { returnType, }; } finally { - if (fnScopePushed) { - this._itemStateStack.pop('functionScope'); - } - this.#namespaceInternal.nameRegistry.popFunctionScope(); + this._itemStateStack.pop('blockScope'); + this._itemStateStack.pop('functionScope'); } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 16ecd07522..c795d78b9e 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -212,8 +212,12 @@ ${this.ctx.pre}}`; } } + public _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string { + return `${this.ctx.pre}${this._block(block, externalMap)}`; + } + public refVariable(id: string, dataType: wgsl.StorableData): string { - const varName = this.ctx.makeNameValid(id); + const varName = this.ctx.makeUniqueIdentifier(id, 'block'); const ptrType = ptrFn(dataType); const snippet = snip( new RefOperator(snip(varName, dataType, 'function'), ptrType), @@ -251,7 +255,11 @@ ${this.ctx.pre}}`; varOrigin = 'runtime'; } - const snippet = snip(this.ctx.makeNameValid(id), dataType, /* origin */ varOrigin); + const snippet = snip( + this.ctx.makeUniqueIdentifier(id, 'block'), + dataType, + /* origin */ varOrigin, + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -980,7 +988,7 @@ Try 'return ${typeStr}(${str});' instead. return this._statement(node); } // simplify 'if (true) {A} else {B}' to '{A}' - return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`; + return this._blockStatement(blockifySingleStatement(node)); } const consequent = this._block(blockifySingleStatement(consNode)); @@ -1113,7 +1121,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.block) { - return `${this.ctx.pre}${this._block(statement)}`; + return this._blockStatement(statement); } if (statement[0] === NODE.for) { @@ -1209,12 +1217,9 @@ ${this.ctx.pre}else ${alternate}`; const blocks = elements.map( (e, i) => - `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block( - blockified, - { - [originalLoopVarName]: e, - }, - )}`, + `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { + [originalLoopVarName]: e, + })}`, ); return blocks.join('\n'); @@ -1222,7 +1227,7 @@ ${this.ctx.pre}else ${alternate}`; this.#unrolling = false; - const index = this.ctx.makeNameValid('i'); + const index = this.ctx.makeUniqueIdentifier('i', 'block'); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`; @@ -1235,7 +1240,7 @@ ${this.ctx.pre}else ${alternate}`; } else { this.ctx.indent(); ctxIndent = true; - const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, snip(index, u32, 'runtime'), @@ -1249,7 +1254,7 @@ ${this.ctx.pre}else ${alternate}`; false, )};`; - bodyStr = `{\n${loopVarDeclStr}\n${this.ctx.pre}${this._block(blockified, { + bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin), })}\n`; this.ctx.dedent(); diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 75f9e71697..d2b7892faf 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -131,6 +131,7 @@ export type SlotBindingLayer = { export type BlockScopeLayer = { type: 'blockScope'; + takenLocalIdentifiers: Set; declarations: Map; externals: Map; }; @@ -140,6 +141,7 @@ export type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | Blo export interface ItemStateStack { readonly itemDepth: number; readonly topItem: ItemLayer; + readonly topBlockScope: BlockScopeLayer | undefined; readonly topFunctionScope: FunctionScopeLayer | undefined; pushItem(): void; @@ -330,8 +332,27 @@ export interface ResolutionCtx { */ withRenamed(item: object, name: string | undefined, callback: () => T): T; - getUniqueName(resource: object): string; - makeNameValid(name: string): string; + /** + * @param primer The basis for the unique identifier. Depending on the strategy, or + * the names already taken, this may be modified to ensure uniqueness. + * @param scope The scope in which to generate the identifier. 'global' means + * the identifier is meant to be unique across the entire program, while + * 'block' means it cannot shadow any existing identifiers visible from + * within the current block. After the block is popped, any identifiers + * defined within it are no longer visible. + * @returns an identifier that is unique within the given scope + */ + makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string; + + isIdentifierTaken(name: string): boolean; + + /** + * Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier} + * within the given scope. + * @param name The name to reserve + * @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter. + */ + reserveIdentifier(name: string, scope: 'global' | 'block'): void; } /** diff --git a/packages/typegpu/tests/namespace.test.ts b/packages/typegpu/tests/namespace.test.ts index ee6fe33099..7ac6407b43 100644 --- a/packages/typegpu/tests/namespace.test.ts +++ b/packages/typegpu/tests/namespace.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, vi } from 'vitest'; +import { describe, expect } from 'vitest'; import tgpu, { d } from '../src/index.js'; import { it } from 'typegpu-testing-utility'; @@ -71,34 +71,6 @@ describe('tgpu.namespace', () => { `); }); - it('fires "name" event', () => { - const Boid = d.struct({ - pos: d.vec3f, - }); - - const names = tgpu['~unstable'].namespace(); - - const listener = vi.fn((event) => {}); - names.on('name', listener); - - const code = tgpu.resolve([Boid], { names }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledWith({ name: 'Boid', target: Boid }); - - expect(code).toMatchInlineSnapshot(` - "struct Boid { - pos: vec3f, - }" - `); - - const code2 = tgpu.resolve([Boid], { names }); - - // No more events - expect(listener).toHaveBeenCalledTimes(1); - expect(code2).toMatchInlineSnapshot(`""`); - }); - it('handles name collision', () => { let code1: string, code2: string; const names = tgpu['~unstable'].namespace(); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 254730def2..83e8e1e787 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from 'vitest'; import tgpu, { d } from '../src/index.js'; -import { setName } from '../src/shared/meta.ts'; +import { getName, setName } from '../src/shared/meta.ts'; import { $gpuValueOf, $internal, $ownSnippet, $resolve } from '../src/shared/symbols.ts'; import type { ResolutionCtx } from '../src/types.ts'; import { it } from 'typegpu-testing-utility'; @@ -55,7 +55,7 @@ describe('tgpu resolve', () => { } as unknown as number, [$resolve](ctx: ResolutionCtx) { - const name = ctx.getUniqueName(this); + const name = ctx.makeUniqueIdentifier(getName(this), 'global'); ctx.addDeclaration(`@group(0) @binding(0) var ${name}: f32;`); return snip(name, d.f32, /* origin */ 'runtime'); }, From 46727696f49be14239fee5c3499eda7f3db1743b Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Sun, 12 Apr 2026 05:30:37 -0400 Subject: [PATCH 03/34] impr(docs): Improve orbit camera to better behave on mouse scroll-wheel (#2304) Fixes #2160 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- .../src/examples/common/setup-orbit-camera.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/typegpu-docs/src/examples/common/setup-orbit-camera.ts b/apps/typegpu-docs/src/examples/common/setup-orbit-camera.ts index fd71128e51..9cba0e0bfe 100644 --- a/apps/typegpu-docs/src/examples/common/setup-orbit-camera.ts +++ b/apps/typegpu-docs/src/examples/common/setup-orbit-camera.ts @@ -130,7 +130,19 @@ export function setupOrbitCamera( 'wheel', (event: WheelEvent) => { event.preventDefault(); - zoomCamera(event.deltaY); + let delta = event.deltaY; + // Normalize deltaY across input devices (touchpad vs mouse wheel). + // Mouse wheel (deltaMode LINE) reports ~3 per notch; convert to pixels. + // Touchpad (deltaMode PIXEL) reports small values directly. + if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) { + delta *= 16; + } else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + delta *= canvas.clientHeight; + } + // Clamp to prevent large jumps from discrete mouse wheel notches + // (Chrome reports deltaMode PIXEL with ~100 per mouse wheel notch). + delta = Math.sign(delta) * Math.min(Math.abs(delta), 60); + zoomCamera(delta); }, { passive: false }, ); From 74eaee6d8be97bbba9f7c10541b09ea9d4e56856 Mon Sep 17 00:00:00 2001 From: deluksic Date: Mon, 13 Apr 2026 11:19:31 +0200 Subject: [PATCH 04/34] @typegpu/geometry Simplify variable width lines implementation (#1935) --- .../src/examples/geometry/circles/index.ts | 10 +- .../geometry/lines-combinations/index.ts | 213 +++++------ .../geometry/lines-combinations/testCases.ts | 106 ++++-- .../src/examples/simulation/wind-map/index.ts | 56 +-- .../individual-example-tests/circles.test.ts | 93 +++++ .../global-wind-map.test.ts | 341 ++++++++++++++++++ .../lines-combinations.test.ts | 307 ++++++++++++++++ packages/typegpu-geometry/ArticlePlan.md | 52 +++ packages/typegpu-geometry/LinesExplanation.md | 107 ++++++ packages/typegpu-geometry/assets/basic.svg | 1 + .../typegpu-geometry/assets/triangulation.svg | 1 + packages/typegpu-geometry/src/circle.ts | 7 +- packages/typegpu-geometry/src/index.ts | 8 +- .../typegpu-geometry/src/lines/caps/arrow.ts | 38 +- .../typegpu-geometry/src/lines/caps/butt.ts | 41 +-- .../typegpu-geometry/src/lines/caps/common.ts | 8 - .../typegpu-geometry/src/lines/caps/index.ts | 21 +- .../typegpu-geometry/src/lines/caps/round.ts | 36 -- .../typegpu-geometry/src/lines/caps/square.ts | 39 +- .../src/lines/caps/swallowtail.ts | 19 - .../src/lines/caps/triangle.ts | 24 +- .../typegpu-geometry/src/lines/caps/wedge.ts | 15 + .../typegpu-geometry/src/lines/constants.ts | 10 +- .../src/lines/externalNormals.ts | 37 ++ packages/typegpu-geometry/src/lines/index.ts | 5 + .../typegpu-geometry/src/lines/indices.ts | 204 ++++------- .../src/lines/joins/common.ts | 34 -- .../typegpu-geometry/src/lines/joins/index.ts | 9 +- .../typegpu-geometry/src/lines/joins/miter.ts | 116 +++--- .../typegpu-geometry/src/lines/joins/round.ts | 76 +--- packages/typegpu-geometry/src/lines/lines.ts | 254 +++++-------- .../typegpu-geometry/src/lines/solveJoin.ts | 42 +++ packages/typegpu-geometry/src/lines/types.ts | 40 +- packages/typegpu-geometry/src/lines/utils.ts | 171 --------- packages/typegpu-geometry/src/utils.ts | 79 +++- 35 files changed, 1607 insertions(+), 1013 deletions(-) create mode 100644 apps/typegpu-docs/tests/individual-example-tests/circles.test.ts create mode 100644 apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts create mode 100644 apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts create mode 100644 packages/typegpu-geometry/ArticlePlan.md create mode 100644 packages/typegpu-geometry/LinesExplanation.md create mode 100644 packages/typegpu-geometry/assets/basic.svg create mode 100644 packages/typegpu-geometry/assets/triangulation.svg delete mode 100644 packages/typegpu-geometry/src/lines/caps/common.ts delete mode 100644 packages/typegpu-geometry/src/lines/caps/round.ts delete mode 100644 packages/typegpu-geometry/src/lines/caps/swallowtail.ts create mode 100644 packages/typegpu-geometry/src/lines/caps/wedge.ts create mode 100644 packages/typegpu-geometry/src/lines/externalNormals.ts create mode 100644 packages/typegpu-geometry/src/lines/index.ts delete mode 100644 packages/typegpu-geometry/src/lines/joins/common.ts create mode 100644 packages/typegpu-geometry/src/lines/solveJoin.ts delete mode 100644 packages/typegpu-geometry/src/lines/utils.ts diff --git a/apps/typegpu-docs/src/examples/geometry/circles/index.ts b/apps/typegpu-docs/src/examples/geometry/circles/index.ts index 5e46e4443a..549f4b0d4b 100644 --- a/apps/typegpu-docs/src/examples/geometry/circles/index.ts +++ b/apps/typegpu-docs/src/examples/geometry/circles/index.ts @@ -14,7 +14,6 @@ if (!context) { } const adapter = await navigator.gpu.requestAdapter(); -console.log(`Using ${adapter?.info.vendor} adapter`); const device = await adapter?.requestDevice({ requiredFeatures: ['timestamp-query'], }); @@ -29,7 +28,6 @@ context.configure({ alphaMode: 'premultiplied', }); -// Textures let msaaTexture: GPUTexture; let msaaTextureView: GPUTextureView; @@ -99,9 +97,10 @@ const mainVertexMaxArea = tgpu.vertexFn({ instanceIndex: d.interpolate('flat', d.u32), }, })(({ vertexIndex, instanceIndex }) => { + 'use gpu'; const C = bindGroupLayout.$.circles[instanceIndex]; const unit = circle(vertexIndex); - const pos = s.add(C.position, s.mul(unit, C.radius)); + const pos = C.position + unit * C.radius; return { outPos: d.vec4f(pos, 0.0, 1.0), uv: unit, @@ -116,6 +115,7 @@ const mainFragment = tgpu.fragmentFn({ }, out: d.vec4f, })(({ uv, instanceIndex }) => { + 'use gpu'; const color = d.vec3f(1, s.cos(d.f32(instanceIndex)), s.sin(5 * d.f32(instanceIndex))); const r = s.length(uv); return d.vec4f(s.mix(color, d.vec3f(), s.clamp((r - 0.9) * 20, 0, 0.5)), 1); @@ -148,6 +148,10 @@ setTimeout(() => { .draw(circleVertexCount(4), circleCount); }, 100); +// #region Example controls & Cleanup + export function onCleanup() { root.destroy(); } + +// #endregion diff --git a/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts b/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts index 9edb9e7e66..6943a1e79e 100644 --- a/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts +++ b/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts @@ -1,20 +1,13 @@ import { - addMul, + caps, endCapSlot, + joins, joinSlot, - lineCaps, - lineJoins, - lineSegmentIndicesCapLevel0, - lineSegmentIndicesCapLevel1, - lineSegmentIndicesCapLevel2, - lineSegmentIndicesCapLevel3, + lineSegmentIndices, + lineSegmentLeftIndices, lineSegmentVariableWidth, - lineSegmentWireframeIndicesCapLevel0, - lineSegmentWireframeIndicesCapLevel1, - lineSegmentWireframeIndicesCapLevel2, - lineSegmentWireframeIndicesCapLevel3, + lineSegmentWireframeIndices, startCapSlot, - uvToLineSegment, } from '@typegpu/geometry'; import tgpu, { type ColorAttachment } from 'typegpu'; import { @@ -29,9 +22,9 @@ import { vec3f, vec4f, } from 'typegpu/data'; -import { clamp, cos, min, mix, select, sin } from 'typegpu/std'; +import { clamp, cos, fwidth, min, mix, select, sin, smoothstep } from 'typegpu/std'; import { TEST_SEGMENT_COUNT } from './constants.ts'; -import * as testCases from './testCases.ts'; +import { testCases } from './testCases.ts'; import { defineControls } from '../../common/defineControls.ts'; const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); @@ -62,6 +55,26 @@ context.configure({ alphaMode: 'premultiplied', }); +let msaaTexture: GPUTexture; +let msaaTextureView: GPUTextureView; + +const createDepthAndMsaaTextures = () => { + if (msaaTexture) { + msaaTexture.destroy(); + } + msaaTexture = device.createTexture({ + size: [canvas.width, canvas.height, 1], + format: presentationFormat, + sampleCount: 4, + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + msaaTextureView = msaaTexture.createView(); +}; + +createDepthAndMsaaTextures(); +const resizeObserver = new ResizeObserver(createDepthAndMsaaTextures); +resizeObserver.observe(canvas); + const Uniforms = struct({ time: f32, fillType: u32, @@ -84,15 +97,19 @@ const uniformsBindGroup = root.createBindGroup(bindGroupLayout, { uniforms: uniformsBuffer, }); -const indexBuffer = root - .createBuffer(arrayOf(u16, lineSegmentIndicesCapLevel3.length), lineSegmentIndicesCapLevel3) +const MAX_JOIN_COUNT = 6; +const indices = lineSegmentIndices(MAX_JOIN_COUNT); +const indicesLeft = lineSegmentLeftIndices(MAX_JOIN_COUNT); +const wireframeIndices = lineSegmentWireframeIndices(MAX_JOIN_COUNT); + +const indexBuffer = root.createBuffer(arrayOf(u16, indices.length), indices).$usage('index'); + +const indexBufferLeft = root + .createBuffer(arrayOf(u16, indicesLeft.length), indicesLeft) .$usage('index'); const outlineIndexBuffer = root - .createBuffer( - arrayOf(u16, lineSegmentWireframeIndicesCapLevel3.length), - lineSegmentWireframeIndicesCapLevel3, - ) + .createBuffer(arrayOf(u16, wireframeIndices.length), wireframeIndices) .$usage('index'); const testCaseSlot = tgpu.slot(testCases.arms); @@ -110,7 +127,7 @@ const mainVertex = tgpu.vertexFn({ vertexIndex: interpolate('flat', u32), situationIndex: interpolate('flat', u32), }, -})(({ vertexIndex, instanceIndex }) => { +})(({ vertexIndex, instanceIndex }, Out) => { 'use gpu'; const t = bindGroupLayout.$.uniforms.time; const A = testCaseSlot.$(instanceIndex, t); @@ -120,31 +137,21 @@ const mainVertex = tgpu.vertexFn({ // disconnect lines if radius is < 0 if (A.radius < 0 || B.radius < 0 || C.radius < 0 || D.radius < 0) { - return { - outPos: vec4f(), - position: vec2f(), - uv: vec2f(), - instanceIndex: 0, - vertexIndex: 0, - situationIndex: 0, - }; + return Out(); } - const result = lineSegmentVariableWidth(vertexIndex, A, B, C, D); - const uv = uvToLineSegment(B.position, C.position, result.vertexPosition); + const result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, MAX_JOIN_COUNT); return { - outPos: vec4f(result.vertexPosition, 0, 1), + outPos: vec4f(result.vertexPosition * result.w, 0, result.w), position: result.vertexPosition, - uv: uv, + uv: vec2f(0, select(f32(0), f32(1), vertexIndex > 1)), instanceIndex, vertexIndex, - situationIndex: result.situationIndex, + situationIndex: 0, }; }); -console.log(tgpu.resolve({ externals: { lineSegmentVariableWidth } })); - const mainFragment = tgpu.fragmentFn({ in: { instanceIndex: interpolate('flat', u32), @@ -159,10 +166,6 @@ const mainFragment = tgpu.fragmentFn({ })(({ instanceIndex, vertexIndex, situationIndex, frontFacing, screenPosition, position, uv }) => { 'use gpu'; const fillType = bindGroupLayout.$.uniforms.fillType; - if (fillType === 1) { - // typegpu gradient - return mix(vec4f(0.77, 0.39, 1, 0.5), vec4f(0.11, 0.44, 0.94, 0.5), position.x * 0.5 + 0.5); - } let color = vec3f(); const colors = [ vec3f(1, 0, 0), // 0 @@ -175,18 +178,25 @@ const mainFragment = tgpu.fragmentFn({ vec3f(0.25, 0.75, 0.25), // 7 vec3f(0.25, 0.25, 0.75), // 8 ]; + if (fillType === 1) { + // typegpu gradient + color = mix(vec3f(0.77, 0.39, 1), vec3f(0.11, 0.44, 0.94), position.x * 0.5 + 0.5); + } if (fillType === 2) { - color = colors[instanceIndex % colors.length]; + let t = cos(uv.y * 10); + t = clamp(t / fwidth(t), 0, 1); + color = mix(vec3f(0.77, 0.39, 1), vec3f(0.11, 0.44, 0.94), t); } if (fillType === 3) { - color = colors[vertexIndex % colors.length]; + color = vec3f(colors[vertexIndex % colors.length]); } if (fillType === 4) { - color = colors[situationIndex % colors.length]; + color = vec3f(colors[instanceIndex % colors.length]); } if (fillType === 5) { - color = vec3f(uv.x, cos(uv.y * 100), 0); + color = vec3f(colors[situationIndex % colors.length]); } + color = color * (0.8 + 0.2 * smoothstep(1, 0.5, uv.y)); if (frontFacing) { return vec4f(color, 0.5); } @@ -204,6 +214,7 @@ const centerlineVertex = tgpu.vertexFn({ outPos: builtin.position, }, })(({ vertexIndex }) => { + 'use gpu'; const t = bindGroupLayout.$.uniforms.time; const vertex = testCaseSlot.$(vertexIndex, t); if (vertex.radius < 0) { @@ -222,6 +233,7 @@ const outlineFragment = tgpu.fragmentFn({ }, out: vec4f, })(() => { + 'use gpu'; return vec4f(0, 0, 0, 0.2); }); @@ -248,6 +260,7 @@ const circlesVertex = tgpu.vertexFn({ outPos: builtin.position, }, })(({ instanceIndex, vertexIndex }) => { + 'use gpu'; const t = bindGroupLayout.$.uniforms.time; const vertex = testCaseSlot.$(instanceIndex, t); if (vertex.radius < 0) { @@ -259,14 +272,14 @@ const circlesVertex = tgpu.vertexFn({ const angle = min(2 * Math.PI, step * f32(vertexIndex)); const unit = vec2f(cos(angle), sin(angle)); return { - outPos: vec4f(addMul(vertex.position, unit, vertex.radius), 0, 1), + outPos: vec4f(vertex.position + unit * vertex.radius, 0, 1), }; }); let testCase = testCases.arms; -let join = lineJoins.round; -let startCap = lineCaps.round; -let endCap = lineCaps.round; +let join = joins.round; +let startCap = caps.round; +let endCap = caps.round; function createPipelines() { const fill = root @@ -281,8 +294,9 @@ function createPipelines() { // primitive: { // cullMode: 'back', // }, + multisample: { count: multisample ? 4 : 1 }, }) - .withIndexBuffer(indexBuffer); + .withIndexBuffer(oneSided ? indexBufferLeft : indexBuffer); const outline = root .with(joinSlot, join) @@ -296,6 +310,7 @@ function createPipelines() { primitive: { topology: 'line-list', }, + multisample: { count: multisample ? 4 : 1 }, }) .withIndexBuffer(outlineIndexBuffer); @@ -306,6 +321,7 @@ function createPipelines() { primitive: { topology: 'line-strip', }, + multisample: { count: multisample ? 4 : 1 }, }); const circles = root.with(testCaseSlot, testCase).createRenderPipeline({ @@ -315,6 +331,7 @@ function createPipelines() { primitive: { topology: 'line-list', }, + multisample: { count: multisample ? 4 : 1 }, }); return { @@ -325,24 +342,23 @@ function createPipelines() { }; } -let pipelines = createPipelines(); - +let multisample = true; let showRadii = false; -let wireframe = true; +let wireframe = false; +let oneSided = false; let fillType = 1; let animationSpeed = 1; let reverse = false; -let subdiv = { - fillCount: lineSegmentIndicesCapLevel3.length, - wireframeCount: lineSegmentWireframeIndicesCapLevel3.length, -}; + +let pipelines = createPipelines(); const draw = (timeMs: number) => { uniformsBuffer.patch({ time: timeMs * 1e-3, }); const colorAttachment: ColorAttachment = { - view: context, + view: multisample ? msaaTextureView : context.getCurrentTexture().createView(), + resolveTarget: multisample ? context.getCurrentTexture().createView() : undefined, clearValue: [1, 1, 1, 1], loadOp: 'load', }; @@ -354,13 +370,16 @@ const draw = (timeMs: number) => { console.log(`${(Number(end - start) * 1e-6).toFixed(2)} ms`); } }) - .drawIndexed(subdiv.fillCount, fillType === 0 ? 0 : TEST_SEGMENT_COUNT); + .drawIndexed( + oneSided ? indicesLeft.length : indices.length, + fillType === 0 ? 0 : TEST_SEGMENT_COUNT, + ); if (wireframe) { pipelines.outline .with(uniformsBindGroup) .withColorAttachment(colorAttachment) - .drawIndexed(subdiv.wireframeCount, TEST_SEGMENT_COUNT); + .drawIndexed(wireframeIndices.length, TEST_SEGMENT_COUNT); } if (showRadii) { pipelines.circles @@ -390,32 +409,22 @@ runAnimationFrame(0); const fillOptions = { none: 0, solid: 1, - instance: 2, + distanceToSegment: 2, triangle: 3, - situation: 4, - distanceToSegment: 5, + instance: 4, + // situation: 5, }; -const subdivs = [ - { - fillCount: lineSegmentIndicesCapLevel0.length, - wireframeCount: lineSegmentWireframeIndicesCapLevel0.length, - }, - { - fillCount: lineSegmentIndicesCapLevel1.length, - wireframeCount: lineSegmentWireframeIndicesCapLevel1.length, - }, - { - fillCount: lineSegmentIndicesCapLevel2.length, - wireframeCount: lineSegmentWireframeIndicesCapLevel2.length, - }, - { - fillCount: lineSegmentIndicesCapLevel3.length, - wireframeCount: lineSegmentWireframeIndicesCapLevel3.length, - }, -]; +// #region Example controls & Cleanup export const controls = defineControls({ + 'MSAA x4': { + initial: multisample, + onToggleChange: (value: boolean) => { + multisample = value; + pipelines = createPipelines(); + }, + }, 'Test Case': { initial: Object.keys(testCases)[0], options: Object.keys(testCases), @@ -426,25 +435,25 @@ export const controls = defineControls({ }, 'Start Cap': { initial: 'round', - options: Object.keys(lineCaps), + options: Object.keys(caps), onSelectChange: async (selected) => { - startCap = lineCaps[selected as keyof typeof lineCaps]; + startCap = caps[selected as keyof typeof caps]; pipelines = createPipelines(); }, }, 'End Cap': { initial: 'round', - options: Object.keys(lineCaps), + options: Object.keys(caps), onSelectChange: async (selected) => { - endCap = lineCaps[selected as keyof typeof lineCaps]; + endCap = caps[selected as keyof typeof caps]; pipelines = createPipelines(); }, }, Join: { initial: 'round', - options: Object.keys(lineJoins), + options: Object.keys(joins), onSelectChange: async (selected) => { - join = lineJoins[selected as keyof typeof lineJoins]; + join = joins[selected as keyof typeof joins]; pipelines = createPipelines(); }, }, @@ -456,29 +465,27 @@ export const controls = defineControls({ uniformsBuffer.patch({ fillType }); }, }, - 'Subdiv. Level': { - initial: 2, - min: 0, - step: 1, - max: 3, - onSliderChange: (value) => { - subdiv = subdivs[value]; - }, - }, Wireframe: { - initial: true, - onToggleChange: (value) => { + initial: wireframe, + onToggleChange: (value: boolean) => { wireframe = value; }, }, + 'One sided': { + initial: oneSided, + onToggleChange: (value: boolean) => { + oneSided = value; + pipelines = createPipelines(); + }, + }, 'Radius and centerline': { - initial: false, - onToggleChange: (value) => { + initial: showRadii, + onToggleChange: (value: boolean) => { showRadii = value; }, }, 'Animation speed': { - initial: 1, + initial: animationSpeed, min: 0, step: 0.001, max: 5, @@ -487,8 +494,8 @@ export const controls = defineControls({ }, }, Reverse: { - initial: false, - onToggleChange: (value) => { + initial: reverse, + onToggleChange: (value: boolean) => { reverse = value; }, }, @@ -498,3 +505,5 @@ export function onCleanup() { root.destroy(); cancelAnimationFrame(frameId); } + +// #endregion diff --git a/apps/typegpu-docs/src/examples/geometry/lines-combinations/testCases.ts b/apps/typegpu-docs/src/examples/geometry/lines-combinations/testCases.ts index 0b241de23d..86ba518cdb 100644 --- a/apps/typegpu-docs/src/examples/geometry/lines-combinations/testCases.ts +++ b/apps/typegpu-docs/src/examples/geometry/lines-combinations/testCases.ts @@ -1,106 +1,106 @@ -import { LineSegmentVertex } from '@typegpu/geometry'; +import { LineControlPoint } from '@typegpu/geometry'; import { perlin2d, randf } from '@typegpu/noise'; import tgpu from 'typegpu'; import { arrayOf, f32, i32, mat2x2f, u32, vec2f } from 'typegpu/data'; -import { abs, add, clamp, cos, floor, mul, pow, select, sin } from 'typegpu/std'; +import { abs, clamp, cos, floor, max, pow, select, sin } from 'typegpu/std'; import { TEST_SEGMENT_COUNT } from './constants.ts'; -const testCaseShell = tgpu.fn([u32, f32], LineSegmentVertex); +const testCaseShell = tgpu.fn([u32, f32], LineControlPoint); const segmentSide = tgpu.const(arrayOf(f32, 4), [-1, -1, 1, 1]); -export const segmentAlternate = testCaseShell((vertexIndex, time) => { +const segmentAlternate = testCaseShell((vertexIndex, time) => { 'use gpu'; const side = segmentSide.$[vertexIndex]; const r = sin(time + select(0, Math.PI / 2, side === -1)); const radius = 0.4 * r * r; - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(0.5 * side * cos(time), 0.5 * side * sin(time)), radius, }); }); -export const segmentStretch = testCaseShell((vertexIndex, time) => { +const segmentStretch = testCaseShell((vertexIndex, time) => { 'use gpu'; const side = segmentSide.$[vertexIndex]; const distance = 0.5 * clamp(0.55 * sin(1.5 * time) + 0.5, 0, 1); - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(distance * side * cos(time), distance * side * sin(time)), radius: 0.25, }); }); -export const segmentContainsAnotherEnd = testCaseShell((vertexIndex, time) => { +const segmentContainsAnotherEnd = testCaseShell((vertexIndex, time) => { 'use gpu'; const side = segmentSide.$[vertexIndex]; - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(side * 0.25 * (1 + clamp(sin(time), -0.8, 1)), 0), radius: 0.25 + side * 0.125, }); }); -export const caseVShapeSmall = testCaseShell((vertexIndex, t) => { +const caseVShapeSmall = testCaseShell((vertexIndex, t) => { 'use gpu'; const side = clamp(f32(vertexIndex) - 2, -1, 1); const isMiddle = side === 0; - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(0.5 * side, select(0.5 * cos(t), 0, isMiddle)), radius: select(0.1, 0.2, isMiddle), }); }); -export const caseVShapeBig = testCaseShell((vertexIndex, time) => { +const caseVShapeBig = testCaseShell((vertexIndex, time) => { 'use gpu'; const side = clamp(f32(vertexIndex) - 2, -1, 1); const isMiddle = side === 0; - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(0.5 * side, select(0.5 * cos(time), 0, isMiddle)), radius: select(0.3, 0.2, isMiddle), }); }); -export const halfCircle = testCaseShell((vertexIndex, time) => { +const halfCircle = testCaseShell((vertexIndex, time) => { 'use gpu'; const angle = (Math.PI * clamp(f32(vertexIndex) - 1, 0, 50)) / 50; const radius = 0.5 * cos(time); - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(radius * cos(angle), radius * sin(angle)), radius: 0.2, }); }); -export const halfCircleThin = testCaseShell((vertexIndex, time) => { +const halfCircleThin = testCaseShell((vertexIndex, time) => { 'use gpu'; const result = halfCircle(vertexIndex, time); result.radius = 0.01; return result; }); -export const bending = testCaseShell((vertexIndex, time) => { +const bending = testCaseShell((vertexIndex, time) => { 'use gpu'; const i = clamp(f32(vertexIndex) - 1, 0, 48) / 48; const x = 2 * i - 1; const s = sin(time); const n = 10 * s * s * s * s + 0.25; const base = clamp(1 - pow(abs(x), n), 0, 1); - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(0.5 * x, 0.5 * pow(base, 1 / n)), radius: 0.2, }); }); -export const animateWidth = testCaseShell((vertexIndex, time) => { +const animateWidth = testCaseShell((vertexIndex, time) => { 'use gpu'; const i = (f32(vertexIndex) % TEST_SEGMENT_COUNT) / TEST_SEGMENT_COUNT; const x = cos(4 * 2 * Math.PI * i + Math.PI / 2); const y = cos(5 * 2 * Math.PI * i); - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(0.8 * x, 0.8 * y), radius: 0.05 * clamp(sin(8 * Math.PI * i - 3 * time), 0.1, 1), }); }); -export const perlinTraces = testCaseShell((vertexIndex, time) => { +const perlinTraces = testCaseShell((vertexIndex, time) => { 'use gpu'; const perLine = u32(200); const n = floor(f32(vertexIndex) / f32(perLine)); @@ -110,27 +110,27 @@ export const perlinTraces = testCaseShell((vertexIndex, time) => { 0.25 * perlin2d.sample(vec2f(4 * x, time + 100 + 0.1 * n)) + 0.125 * perlin2d.sample(vec2f(8 * x, time + 200 + 0.2 * n)) + 0.0625 * perlin2d.sample(vec2f(16 * x, time + 300 + 0.3 * n)); - const y = 0.125 * n - 0.5 + 0.5 * value; + const y = 0.15 * n - 0.75 + 0.5 * value; const radiusFactor = 0.025 * (n + 1); - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(0.8 * x, y), radius: select(radiusFactor * radiusFactor, -1, vertexIndex % perLine === 0), }); }); -export const bars = testCaseShell((vertexIndex, time) => { +const bars = testCaseShell((vertexIndex, time) => { 'use gpu'; const VERTS_PER_LINE = u32(5); const lineIndex = f32(u32(vertexIndex / VERTS_PER_LINE)); const y = f32(clamp(vertexIndex % VERTS_PER_LINE, 1, 2) - 1); const x = 20 * ((2 * f32(VERTS_PER_LINE) * lineIndex) / TEST_SEGMENT_COUNT - 1); - return LineSegmentVertex({ + return LineControlPoint({ position: vec2f(0.8 * x, 0.8 * y * sin(x + time)), radius: select(clamp(0.08 * abs(sin(x + time)), 0, 0.01), -1, vertexIndex % 5 === 4), }); }); -export const arms = testCaseShell((vertexIndex, time) => { +const arms = testCaseShell((vertexIndex, time) => { 'use gpu'; const s = sin(time); const c = cos(time); @@ -142,31 +142,31 @@ export const arms = testCaseShell((vertexIndex, time) => { vec2f(-r * s + 0.25, r * c), ]; const i = clamp(i32(vertexIndex) - 1, 0, 3); - return LineSegmentVertex({ + return LineControlPoint({ position: points[i], radius: 0.2, }); }); -export const armsSmall = testCaseShell((vertexIndex, time) => { +const armsSmall = testCaseShell((vertexIndex, time) => { 'use gpu'; const result = arms(vertexIndex, time); - return LineSegmentVertex({ + return LineControlPoint({ position: result.position, radius: select(0.1, 0.2, vertexIndex === 2 || vertexIndex === 3), }); }); -export const armsBig = testCaseShell((vertexIndex, time) => { +const armsBig = testCaseShell((vertexIndex, time) => { 'use gpu'; const result = arms(vertexIndex, time); - return LineSegmentVertex({ + return LineControlPoint({ position: result.position, - radius: select(0.275, 0.1, vertexIndex === 2 || vertexIndex === 3), + radius: select(0.2, 0.1, vertexIndex === 2 || vertexIndex === 3), }); }); -export const armsRotating = testCaseShell((vertexIndex, time) => { +const armsRotating = testCaseShell((vertexIndex, time) => { 'use gpu'; const s = sin(time); const c = cos(time); @@ -178,13 +178,13 @@ export const armsRotating = testCaseShell((vertexIndex, time) => { vec2f(-r * s + 0.25, -r * c), ]; const i = clamp(i32(vertexIndex) - 1, 0, 3); - return LineSegmentVertex({ + return LineControlPoint({ position: points[i], radius: 0.2, }); }); -export const flyingSquares = testCaseShell((vertexIndex, time) => { +const flyingSquares = testCaseShell((vertexIndex, time) => { 'use gpu'; const squareIndex = u32(vertexIndex / 8); randf.seed(f32(squareIndex + 5)); @@ -198,9 +198,41 @@ export const flyingSquares = testCaseShell((vertexIndex, time) => { const r = 0.1 + 0.05 * randf.sample(); const x = 2.0 * randf.sample() - 1; const y = 2.0 * randf.sample() - 1; - const transformedPoint = add(vec2f(x, y), mul(rotate, mul(point, r))); - return LineSegmentVertex({ + const transformedPoint = vec2f(x, y) + rotate * (point * r); + return LineControlPoint({ position: transformedPoint, radius: select(0.1 * r + 0.05 * randf.sample(), -1, pointIndex === 7 || squareIndex > 50), }); }); + +const spring = testCaseShell((vertexIndex, time) => { + 'use gpu'; + const i = clamp(i32(vertexIndex - 1), 0, 20); + return LineControlPoint({ + position: vec2f( + f32(max(0, i - 1)) * (0.1 + 0.09 * sin(time)) - 0.9, + select(-0.25, 0.25, (i & 0b1) === 0), + ), + radius: 0.05, + }); +}); + +export const testCases = { + animateWidth, + arms, + armsBig, + armsRotating, + armsSmall, + bars, + bending, + caseVShapeBig, + caseVShapeSmall, + flyingSquares, + halfCircle, + halfCircleThin, + perlinTraces, + segmentAlternate, + segmentContainsAnotherEnd, + segmentStretch, + spring, +}; diff --git a/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts b/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts index 7ea1cce64b..639d4914d8 100644 --- a/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts @@ -1,15 +1,14 @@ import { + caps, endCapSlot, - joinSlot, - lineSegmentIndicesCapLevel1, + LineControlPoint, + lineSegmentIndices, lineSegmentVariableWidth, - LineSegmentVertex, startCapSlot, } from '@typegpu/geometry'; import tgpu from 'typegpu'; import { arrayOf, builtin, f32, i32, struct, u16, u32, vec2f, vec4f } from 'typegpu/data'; -import { lineCaps, lineJoins } from '@typegpu/geometry'; -import { add, clamp, mix, mul, normalize, select } from 'typegpu/std'; +import { clamp, mix, normalize, select } from 'typegpu/std'; import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init({ @@ -85,9 +84,9 @@ const bindGroupWritable = root.createBindGroup(bindGroupLayoutWritable, { particles: particleTrailsBuffer, }); -const indexBuffer = root - .createBuffer(arrayOf(u16, lineSegmentIndicesCapLevel1.length), lineSegmentIndicesCapLevel1) - .$usage('index'); +const MAX_JOIN_COUNT = 3; +const indices = lineSegmentIndices(MAX_JOIN_COUNT); +const indexBuffer = root.createBuffer(arrayOf(u16, indices.length), indices).$usage('index'); // const vectorField = tgpu.fn([vec2f], vec2f)((pos) => { // return normalize(perlin2d.sampleWithGradient(pos).yz); @@ -97,6 +96,7 @@ const vectorField = tgpu.fn( [vec2f], vec2f, )((pos) => { + 'use gpu'; return normalize(vec2f(-pos.y, pos.x)); }); @@ -105,6 +105,7 @@ const advectCompute = tgpu.computeFn({ in: { globalInvocationId: builtin.globalInvocationId }, workgroupSize: [WORKGROUP_SIZE], })(({ globalInvocationId }) => { + 'use gpu'; const stepSize = bindGroupLayoutWritable.$.uniforms.stepSize; const frameCount = bindGroupLayoutWritable.$.uniforms.frameCount; const particleIndex = globalInvocationId.x; @@ -113,13 +114,19 @@ const advectCompute = tgpu.computeFn({ const prevPosIndex = (TRAIL_LENGTH + frameCount - 1) % TRAIL_LENGTH; const pos = particle.positions[prevPosIndex]; const v0 = vectorField(pos); - const v1 = vectorField(add(pos, mul(v0, 0.5 * stepSize))); - const newPos = add(pos, mul(v1, stepSize)); - particle.positions[currentPosIndex] = newPos; - bindGroupLayoutWritable.$.particles[particleIndex] = particle; + const v1 = vectorField(pos + v0 * (0.5 * stepSize)); + const newPos = pos + v1 * stepSize; + particle.positions[currentPosIndex] = vec2f(newPos); + bindGroupLayoutWritable.$.particles[particleIndex] = ParticleTrail(particle); }); -const lineWidth = tgpu.fn([f32], f32)((x) => 0.004 * (1 - x)); +const lineWidth = tgpu.fn( + [f32], + f32, +)((x) => { + 'use gpu'; + return 0.004 * (1 - x); +}); const mainVertex = tgpu.vertexFn({ in: { @@ -132,6 +139,7 @@ const mainVertex = tgpu.vertexFn({ trailPosition: f32, }, })(({ vertexIndex, instanceIndex }) => { + 'use gpu'; const frameCount = bindGroupLayout.$.uniforms.frameCount; const particleIndex = u32(instanceIndex / TRAIL_LENGTH); const trailIndexOriginal = instanceIndex % TRAIL_LENGTH; @@ -152,24 +160,24 @@ const mainVertex = tgpu.vertexFn({ const iB = trailIndex; const iC = (TRAIL_LENGTH + trailIndex - 1) % TRAIL_LENGTH; const iD = (TRAIL_LENGTH + trailIndex - 2) % TRAIL_LENGTH; - const A = LineSegmentVertex({ + const A = LineControlPoint({ position: particle.positions[iA], radius: lineWidth(f32(trailIndexOriginal) / (TRAIL_LENGTH - 1)), }); - const B = LineSegmentVertex({ + const B = LineControlPoint({ position: particle.positions[iB], radius: lineWidth(f32(trailIndexOriginal + 1) / (TRAIL_LENGTH - 1)), }); - const C = LineSegmentVertex({ + const C = LineControlPoint({ position: particle.positions[iC], radius: lineWidth(f32(trailIndexOriginal + 2) / (TRAIL_LENGTH - 1)), }); - const D = LineSegmentVertex({ + const D = LineControlPoint({ position: particle.positions[iD], radius: lineWidth(f32(trailIndexOriginal + 3) / (TRAIL_LENGTH - 1)), }); - const result = lineSegmentVariableWidth(vertexIndex, A, B, C, D); + const result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, MAX_JOIN_COUNT); return { outPos: vec4f(result.vertexPosition, 0, 1), @@ -185,6 +193,7 @@ const mainFragment = tgpu.fragmentFn({ }, out: vec4f, })(({ position, trailPosition }) => { + 'use gpu'; const opacity = clamp(f32(3) * (1 - trailPosition), 0, 1); return mix( vec4f(0.77, 0.39, 1, opacity), @@ -206,9 +215,8 @@ function createPipelines() { const advect = root.createComputePipeline({ compute: advectCompute }); const fill = root - .with(joinSlot, lineJoins.round) - .with(startCapSlot, lineCaps.arrow) - .with(endCapSlot, lineCaps.butt) + .with(startCapSlot, caps.arrow) + .with(endCapSlot, caps.butt) .createRenderPipeline({ vertex: mainVertex, fragment: mainFragment, @@ -237,7 +245,7 @@ const draw = () => { view: context, clearValue: [1, 1, 1, 1], }) - .drawIndexed(lineSegmentIndicesCapLevel1.length, PARTICLE_COUNT * TRAIL_LENGTH); + .drawIndexed(indices.length, PARTICLE_COUNT * TRAIL_LENGTH); }; let frameId = -1; @@ -259,6 +267,8 @@ const runAnimationFrame = () => { }; runAnimationFrame(); +// #region Example controls & Cleanup + export const controls = defineControls({ Play: { initial: true, @@ -273,3 +283,5 @@ export function onCleanup() { root.device.destroy(); cancelAnimationFrame(frameId); } + +// #endregion diff --git a/apps/typegpu-docs/tests/individual-example-tests/circles.test.ts b/apps/typegpu-docs/tests/individual-example-tests/circles.test.ts new file mode 100644 index 0000000000..4a527d3dca --- /dev/null +++ b/apps/typegpu-docs/tests/individual-example-tests/circles.test.ts @@ -0,0 +1,93 @@ +/** + * @vitest-environment jsdom + */ + +import { describe, expect } from 'vitest'; +import { it } from 'typegpu-testing-utility'; +import { runExampleTest, setupCommonMocks } from './utils/baseTest.ts'; +import { mockResizeObserver } from './utils/commonMocks.ts'; + +describe('circles example', () => { + setupCommonMocks(); + + it('should produce valid code', async ({ device }) => { + const shaderCodes = await runExampleTest( + { + category: 'geometry', + name: 'circles', + setupMocks: mockResizeObserver, + expectedCalls: 1, + }, + device, + ); + + expect(shaderCodes).toMatchInlineSnapshot(` + "struct Circle { + position: vec2f, + radius: f32, + } + + @group(0) @binding(0) var circles: array; + + struct SubdivLevelResult { + level: u32, + pointCount: u32, + vertexCountInLevel: u32, + vertexIndexInLevel: u32, + } + + fn getSubdivLevel(vertexIndex: u32) -> SubdivLevelResult { + var totalVertexCount = 0u; + for (var level = 0u; (level < 8u); level += 1u) { + let pointCount = (3u * (1u << level)); + let triangleCount = select(1u, u32((3 * (1 << (level - 1u)))), (level > 0u)); + let vertexCountInLevel = (3u * triangleCount); + let newVertexCount = (totalVertexCount + vertexCountInLevel); + if ((vertexIndex < newVertexCount)) { + return SubdivLevelResult(level, pointCount, vertexCountInLevel, (vertexIndex - totalVertexCount)); + } + totalVertexCount = newVertexCount; + } + return SubdivLevelResult(0u, 0u, 0u, 0u); + } + + fn consecutiveTriangleVertexIndex(i: u32) -> u32 { + return u32((f32((2u * (i + 1u))) / 3f)); + } + + const PI: f32 = 3.141592653589793f; + + fn circle(vertexIndex: u32) -> vec2f { + var subdiv = getSubdivLevel(vertexIndex); + let i = consecutiveTriangleVertexIndex(subdiv.vertexIndexInLevel); + let pointCount = subdiv.pointCount; + let angle = (((2f * PI) * f32(i)) / f32(pointCount)); + return vec2f(cos(angle), sin(angle)); + } + + struct mainVertexMaxArea_Output { + @builtin(position) outPos: vec4f, + @location(0) uv: vec2f, + @location(1) @interpolate(flat) instanceIndex: u32, + } + + @vertex fn mainVertexMaxArea(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> mainVertexMaxArea_Output { + let C = (&circles[instanceIndex]); + var unit = circle(vertexIndex); + var pos = ((*C).position + (unit * (*C).radius)); + return mainVertexMaxArea_Output(vec4f(pos, 0f, 1f), unit, instanceIndex); + } + + struct mainFragment_Input { + @location(0) uv: vec2f, + @location(1) @interpolate(flat) instanceIndex: u32, + } + + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { + var color = vec3f(1f, cos(f32(_arg_0.instanceIndex)), sin((5f * f32(_arg_0.instanceIndex)))); + let r = length(_arg_0.uv); + return vec4f(mix(color, vec3f(), clamp(((r - 0.9f) * 20f), 0f, 0.5f)), 1f); + }" + `); + }); +}); diff --git a/apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts b/apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts new file mode 100644 index 0000000000..0853d247e9 --- /dev/null +++ b/apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts @@ -0,0 +1,341 @@ +/** + * @vitest-environment jsdom + */ + +import { describe, expect } from 'vitest'; +import { it } from 'typegpu-testing-utility'; +import { runExampleTest, setupCommonMocks } from './utils/baseTest.ts'; + +describe('global wind map example', () => { + setupCommonMocks(); + + it('should produce valid code', async ({ device }) => { + const shaderCodes = await runExampleTest( + { + category: 'simulation', + name: 'wind-map', + expectedCalls: 2, + }, + device, + ); + + expect(shaderCodes).toMatchInlineSnapshot(` + "struct Uniforms { + stepSize: f32, + frameCount: u32, + } + + @group(0) @binding(0) var uniforms: Uniforms; + + struct ParticleTrail { + positions: array, + } + + @group(0) @binding(1) var particles: array; + + fn vectorField(pos: vec2f) -> vec2f { + return normalize(vec2f(-(pos.y), pos.x)); + } + + @compute @workgroup_size(64) fn advectCompute(@builtin(global_invocation_id) globalInvocationId: vec3u) { + let stepSize = uniforms.stepSize; + let frameCount = uniforms.frameCount; + let particleIndex = globalInvocationId.x; + let particle = (&particles[particleIndex]); + let currentPosIndex = (frameCount % 20u); + let prevPosIndex = (((20u + frameCount) - 1u) % 20u); + let pos = (&(*particle).positions[prevPosIndex]); + var v0 = vectorField((*pos)); + var v1 = vectorField(((*pos) + (v0 * (0.5f * stepSize)))); + var newPos = ((*pos) + (v1 * stepSize)); + (*particle).positions[currentPosIndex] = newPos; + particles[particleIndex] = (*particle); + } + + struct Uniforms { + stepSize: f32, + frameCount: u32, + } + + @group(0) @binding(0) var uniforms: Uniforms; + + struct mainVertex_Output { + @builtin(position) outPos: vec4f, + @location(0) position: vec2f, + @location(1) trailPosition: f32, + } + + struct ParticleTrail { + positions: array, + } + + @group(0) @binding(1) var particles: array; + + fn lineWidth(x: f32) -> f32 { + return (4e-3f * (1f - x)); + } + + struct LineControlPoint { + position: vec2f, + radius: f32, + } + + struct LineSegmentOutput { + vertexPosition: vec2f, + w: f32, + } + + struct ExternalNormals { + nL: vec2f, + nR: vec2f, + } + + fn externalNormals(distance_1: vec2f, r1: f32, r2: f32) -> ExternalNormals { + let dist2Inv = (1f / dot(distance_1, distance_1)); + let cosMulLen = (r1 - r2); + let cosDivLen = (cosMulLen * dist2Inv); + let sinDivLen = sqrt((max(0f, (1f - (cosMulLen * cosDivLen))) * dist2Inv)); + let a = (distance_1.x * cosDivLen); + let b = (distance_1.y * sinDivLen); + let c = (distance_1.x * sinDivLen); + let d = (distance_1.y * cosDivLen); + var nL = vec2f((a - b), (c + d)); + var nR = vec2f((a + b), (-(c) + d)); + return ExternalNormals(nL, nR); + } + + fn miterPointNoCheck(a: vec2f, b: vec2f) -> vec2f { + var ab = (a + b); + return (ab * (2f / dot(ab, ab))); + } + + struct JoinResult { + dL: vec2f, + dR: vec2f, + shouldJoinL: bool, + shouldJoinR: bool, + isHairpin: bool, + } + + fn solveJoin(AB: vec2f, BC: vec2f, eAB: ExternalNormals, eBC: ExternalNormals, joinLimit: f32, isCap: bool) -> JoinResult { + let underLimitL = (dot(eAB.nL, BC) < joinLimit); + let underLimitR = (dot(eAB.nR, BC) < joinLimit); + let isHairpin = (((dot(AB, BC) < 0f) && (underLimitL == underLimitR)) || (dot(normalize(AB), normalize(BC)) < -0.99f)); + let tooCloseToJoinL = (dot(eAB.nL, eBC.nL) > 0.99f); + let tooCloseToJoinR = (dot(eAB.nR, eBC.nR) > 0.99f); + let shouldJoinL = (isHairpin || (underLimitL && !tooCloseToJoinL)); + let shouldJoinR = (isHairpin || (underLimitR && !tooCloseToJoinR)); + var dLMiter = miterPointNoCheck(eAB.nL, eBC.nL); + var dRMiter = miterPointNoCheck(eBC.nR, eAB.nR); + var dL = select(eBC.nL, dLMiter, (!isCap && !shouldJoinL)); + var dR = select(eBC.nR, dRMiter, (!isCap && !shouldJoinR)); + return JoinResult(dL, dR, shouldJoinL, shouldJoinR, isHairpin); + } + + fn cross2d(a: vec2f, b: vec2f) -> f32 { + return ((a.x * b.y) - (a.y * b.x)); + } + + struct Intersection { + valid: bool, + t: f32, + point: vec2f, + } + + fn intersectLines(A1: vec2f, A2: vec2f, B1: vec2f, B2: vec2f) -> Intersection { + var a = (A2 - A1); + var b = (B2 - B1); + let axb = cross2d(a, b); + var AB = (B1 - A1); + let t = (cross2d(AB, b) / axb); + return Intersection((((axb != 0f) && (t >= 0f)) && (t <= 1f)), t, (A1 + (a * t))); + } + + struct JoinInput { + C: LineControlPoint, + v: vec2f, + d: vec2f, + fw: vec2f, + start: vec2f, + end: vec2f, + shouldJoin: bool, + isCap: bool, + } + + fn rot90ccw(v: vec2f) -> vec2f { + return vec2f(-(v.y), v.x); + } + + fn arrow(join: JoinInput, joinVertexIndex: u32, _maxJoinCount: u32) -> vec2f { + var bw = -(normalize(join.fw)); + var vert = rot90ccw(bw); + let sgn = sign(cross2d(bw, join.d)); + var svert = (vert * sgn); + var v0 = (svert + (bw * 7.5f)); + var v1 = (v0 + ((bw + svert) * 1.5f)); + if ((joinVertexIndex == 0u)) { + return (join.C.position + (v0 * join.C.radius)); + } + if ((joinVertexIndex == 1u)) { + return (join.C.position + (v1 * join.C.radius)); + } + return join.C.position; + } + + fn butt(join: JoinInput, _joinVertexIndex: u32, _maxJoinCount: u32) -> vec2f { + var fw = normalize(join.fw); + var vert = rot90ccw(fw); + let sgn = sign(cross2d(fw, join.d)); + var svert = (vert * sgn); + return (join.C.position + (svert * join.C.radius)); + } + + fn rot90cw(v: vec2f) -> vec2f { + return vec2f(v.y, -(v.x)); + } + + fn bisectCcw(a: vec2f, b: vec2f) -> vec2f { + let sin_1 = cross2d(a, b); + let sinSign = select(-1f, 1f, (sin_1 >= 0f)); + var orthoA = rot90ccw(a); + var orthoB = rot90cw(b); + var dir = select(((a + b) * sinSign), (orthoA + orthoB), (dot(a, b) < 0f)); + return normalize(dir); + } + + fn bisectNoCheck(a: vec2f, b: vec2f) -> vec2f { + return normalize((a + b)); + } + + fn slerpApprox(a: vec2f, b: vec2f, t: f32) -> vec2f { + var mid = bisectNoCheck(a, b); + var a_ = a; + var b_ = mid; + var t_ = (2f * t); + if ((t > 0.5f)) { + a_ = mid; + b_ = b; + t_ -= 1f; + } + return normalize(mix(a_, b_, t_)); + } + + fn round_1(join: JoinInput, joinVertexIndex: u32, maxJoinCount: u32) -> vec2f { + if ((joinVertexIndex == 0u)) { + return join.v; + } + var dir = slerpApprox(join.d, bisectCcw(join.start, join.end), (f32(joinVertexIndex) / f32(maxJoinCount))); + return (join.C.position + (dir * join.C.radius)); + } + + fn lineSegmentVariableWidth(vertexIndex: u32, A: LineControlPoint, B: LineControlPoint, C: LineControlPoint, D: LineControlPoint, maxJoinCount: u32) -> LineSegmentOutput { + var AB = (B.position - A.position); + var BC = (C.position - B.position); + var DC = (C.position - D.position); + var CB = -(BC); + let radiusABDelta = (A.radius - B.radius); + let radiusBCDelta = (B.radius - C.radius); + let radiusCDDelta = (C.radius - D.radius); + if ((dot(BC, BC) <= (radiusBCDelta * radiusBCDelta))) { + return LineSegmentOutput(vec2f(), 1f); + } + let isCapB = (dot(AB, AB) <= (radiusABDelta * radiusABDelta)); + let isCapC = (dot(DC, DC) <= (radiusCDDelta * radiusCDDelta)); + var eAB = externalNormals(AB, A.radius, B.radius); + var eBC = externalNormals(BC, B.radius, C.radius); + var eCB = ExternalNormals(eBC.nR, eBC.nL); + var eDC = externalNormals(DC, D.radius, C.radius); + let joinLimit = dot(eBC.nL, BC); + var joinB = solveJoin(AB, BC, eAB, eBC, joinLimit, isCapB); + var joinC = solveJoin(DC, CB, eDC, eCB, -(joinLimit), isCapC); + let d2 = (&joinB.dL); + let d3 = (&joinB.dR); + let d4 = (&joinC.dL); + let d5 = (&joinC.dR); + var v2orig = (B.position + ((*d2) * B.radius)); + var v3orig = (B.position + ((*d3) * B.radius)); + var v4orig = (C.position + ((*d4) * C.radius)); + var v5orig = (C.position + ((*d5) * C.radius)); + var limL = intersectLines(B.position, v2orig, C.position, v5orig); + var limR = intersectLines(B.position, v3orig, C.position, v4orig); + var v2 = select(v2orig, limL.point, limL.valid); + var v5 = select(v5orig, limL.point, limL.valid); + var v3 = select(v3orig, limR.point, limR.valid); + var v4 = select(v4orig, limR.point, limR.valid); + if ((vertexIndex == 0u)) { + return LineSegmentOutput(B.position, (1f / B.radius)); + } + if ((vertexIndex == 1u)) { + return LineSegmentOutput(C.position, (1f / C.radius)); + } + let coreVertexIndex = ((vertexIndex - 2u) & 3u); + let joinVertexIndex = ((vertexIndex - 2u) >> 2u); + var join = JoinInput(); + if ((coreVertexIndex == 0u)) { + join = JoinInput(B, v2, (*d2), CB, (*d2), select(eAB.nL, (*d3), (joinB.isHairpin || isCapB)), joinB.shouldJoinL, isCapB); + } + else { + if ((coreVertexIndex == 1u)) { + join = JoinInput(B, v3, (*d3), CB, select(eAB.nR, (*d2), (joinB.isHairpin || isCapB)), (*d3), joinB.shouldJoinR, isCapB); + } + else { + if ((coreVertexIndex == 2u)) { + join = JoinInput(C, v4, (*d4), BC, (*d4), select(eDC.nL, (*d5), (joinC.isHairpin || isCapC)), joinC.shouldJoinL, isCapC); + } + else { + join = JoinInput(C, v5, (*d5), BC, select(eDC.nR, (*d4), (joinC.isHairpin || isCapC)), (*d5), joinC.shouldJoinR, isCapC); + } + } + } + var vertexPosition = join.v; + if (join.isCap) { + if ((coreVertexIndex < 2u)) { + vertexPosition = arrow(join, joinVertexIndex, maxJoinCount); + } + else { + vertexPosition = butt(join, joinVertexIndex, maxJoinCount); + } + } + else { + if (join.shouldJoin) { + vertexPosition = round_1(join, joinVertexIndex, maxJoinCount); + } + } + let w = select((1f / B.radius), (1f / C.radius), (coreVertexIndex >= 2u)); + return LineSegmentOutput(vertexPosition, w); + } + + @vertex fn mainVertex(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> mainVertex_Output { + let frameCount = uniforms.frameCount; + let particleIndex = u32((f32(instanceIndex) / 20f)); + let trailIndexOriginal = (instanceIndex % 20u); + let currentPosIndex = (frameCount % 20u); + let trailIndex = (i32(((20u + currentPosIndex) - trailIndexOriginal)) % 20i); + if ((trailIndexOriginal == 19u)) { + return mainVertex_Output(vec4f(), vec2f(), 0f); + } + let particle = (&particles[particleIndex]); + let iA = select(((trailIndex + 1i) % 20i), trailIndex, (trailIndexOriginal == 0u)); + let iB = trailIndex; + let iC = (((20i + trailIndex) - 1i) % 20i); + let iD = (((20i + trailIndex) - 2i) % 20i); + var A = LineControlPoint((*particle).positions[iA], lineWidth((f32(trailIndexOriginal) / 19f))); + var B = LineControlPoint((*particle).positions[iB], lineWidth((f32((trailIndexOriginal + 1u)) / 19f))); + var C = LineControlPoint((*particle).positions[iC], lineWidth((f32((trailIndexOriginal + 2u)) / 19f))); + var D = LineControlPoint((*particle).positions[iD], lineWidth((f32((trailIndexOriginal + 3u)) / 19f))); + var result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, 3u); + return mainVertex_Output(vec4f(result.vertexPosition, 0f, 1f), result.vertexPosition, (f32(trailIndexOriginal) / 19f)); + } + + struct mainFragment_Input { + @location(0) position: vec2f, + @location(1) trailPosition: f32, + } + + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { + let opacity = clamp((3f * (1f - _arg_0.trailPosition)), 0f, 1f); + return mix(vec4f(0.77f, 0.39f, 1f, opacity), vec4f(0.11f, 0.44f, 0.94f, opacity), ((_arg_0.position.x * 0.5f) + 0.5f)); + }" + `); + }); +}); diff --git a/apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts b/apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts new file mode 100644 index 0000000000..f17d94772f --- /dev/null +++ b/apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts @@ -0,0 +1,307 @@ +/** + * @vitest-environment jsdom + */ + +import { describe, expect } from 'vitest'; +import { it } from 'typegpu-testing-utility'; +import { runExampleTest, setupCommonMocks } from './utils/baseTest.ts'; +import { mockResizeObserver } from './utils/commonMocks.ts'; + +describe('lines combinations example', () => { + setupCommonMocks(); + + it('should produce valid code', async ({ device }) => { + const shaderCodes = await runExampleTest( + { + category: 'geometry', + name: 'lines-combinations', + setupMocks: mockResizeObserver, + expectedCalls: 14, + controlTriggers: ['Test Resolution'], + }, + device, + ); + + expect(shaderCodes).toMatchInlineSnapshot(` + "struct Uniforms { + time: f32, + fillType: u32, + } + + @group(0) @binding(0) var uniforms: Uniforms; + + struct LineControlPoint { + position: vec2f, + radius: f32, + } + + fn item(vertexIndex: u32, time: f32) -> LineControlPoint { + let s = sin(time); + let c = cos(time); + const r = 0.25; + var points = array(vec2f(((r * s) - 0.25f), (r * c)), vec2f(-0.25, 0), vec2f(0.25, 0), vec2f(((-(r) * s) + 0.25f), (r * c))); + let i = clamp((i32(vertexIndex) - 1i), 0i, 3i); + return LineControlPoint(points[i], 0.2f); + } + + struct mainVertex_Output { + @builtin(position) outPos: vec4f, + @location(0) position: vec2f, + @location(1) uv: vec2f, + @location(2) @interpolate(flat) instanceIndex: u32, + @location(3) @interpolate(flat) vertexIndex: u32, + @location(4) @interpolate(flat) situationIndex: u32, + } + + struct LineSegmentOutput { + vertexPosition: vec2f, + w: f32, + } + + struct ExternalNormals { + nL: vec2f, + nR: vec2f, + } + + fn externalNormals(distance_1: vec2f, r1: f32, r2: f32) -> ExternalNormals { + let dist2Inv = (1f / dot(distance_1, distance_1)); + let cosMulLen = (r1 - r2); + let cosDivLen = (cosMulLen * dist2Inv); + let sinDivLen = sqrt((max(0f, (1f - (cosMulLen * cosDivLen))) * dist2Inv)); + let a = (distance_1.x * cosDivLen); + let b = (distance_1.y * sinDivLen); + let c = (distance_1.x * sinDivLen); + let d = (distance_1.y * cosDivLen); + var nL = vec2f((a - b), (c + d)); + var nR = vec2f((a + b), (-(c) + d)); + return ExternalNormals(nL, nR); + } + + fn miterPointNoCheck(a: vec2f, b: vec2f) -> vec2f { + var ab = (a + b); + return (ab * (2f / dot(ab, ab))); + } + + struct JoinResult { + dL: vec2f, + dR: vec2f, + shouldJoinL: bool, + shouldJoinR: bool, + isHairpin: bool, + } + + fn solveJoin(AB: vec2f, BC: vec2f, eAB: ExternalNormals, eBC: ExternalNormals, joinLimit: f32, isCap: bool) -> JoinResult { + let underLimitL = (dot(eAB.nL, BC) < joinLimit); + let underLimitR = (dot(eAB.nR, BC) < joinLimit); + let isHairpin = (((dot(AB, BC) < 0f) && (underLimitL == underLimitR)) || (dot(normalize(AB), normalize(BC)) < -0.99f)); + let tooCloseToJoinL = (dot(eAB.nL, eBC.nL) > 0.99f); + let tooCloseToJoinR = (dot(eAB.nR, eBC.nR) > 0.99f); + let shouldJoinL = (isHairpin || (underLimitL && !tooCloseToJoinL)); + let shouldJoinR = (isHairpin || (underLimitR && !tooCloseToJoinR)); + var dLMiter = miterPointNoCheck(eAB.nL, eBC.nL); + var dRMiter = miterPointNoCheck(eBC.nR, eAB.nR); + var dL = select(eBC.nL, dLMiter, (!isCap && !shouldJoinL)); + var dR = select(eBC.nR, dRMiter, (!isCap && !shouldJoinR)); + return JoinResult(dL, dR, shouldJoinL, shouldJoinR, isHairpin); + } + + fn cross2d(a: vec2f, b: vec2f) -> f32 { + return ((a.x * b.y) - (a.y * b.x)); + } + + struct Intersection { + valid: bool, + t: f32, + point: vec2f, + } + + fn intersectLines(A1: vec2f, A2: vec2f, B1: vec2f, B2: vec2f) -> Intersection { + var a = (A2 - A1); + var b = (B2 - B1); + let axb = cross2d(a, b); + var AB = (B1 - A1); + let t = (cross2d(AB, b) / axb); + return Intersection((((axb != 0f) && (t >= 0f)) && (t <= 1f)), t, (A1 + (a * t))); + } + + struct JoinInput { + C: LineControlPoint, + v: vec2f, + d: vec2f, + fw: vec2f, + start: vec2f, + end: vec2f, + shouldJoin: bool, + isCap: bool, + } + + fn rot90ccw(v: vec2f) -> vec2f { + return vec2f(-(v.y), v.x); + } + + fn rot90cw(v: vec2f) -> vec2f { + return vec2f(v.y, -(v.x)); + } + + fn bisectCcw(a: vec2f, b: vec2f) -> vec2f { + let sin_1 = cross2d(a, b); + let sinSign = select(-1f, 1f, (sin_1 >= 0f)); + var orthoA = rot90ccw(a); + var orthoB = rot90cw(b); + var dir = select(((a + b) * sinSign), (orthoA + orthoB), (dot(a, b) < 0f)); + return normalize(dir); + } + + fn bisectNoCheck(a: vec2f, b: vec2f) -> vec2f { + return normalize((a + b)); + } + + fn slerpApprox(a: vec2f, b: vec2f, t: f32) -> vec2f { + var mid = bisectNoCheck(a, b); + var a_ = a; + var b_ = mid; + var t_ = (2f * t); + if ((t > 0.5f)) { + a_ = mid; + b_ = b; + t_ -= 1f; + } + return normalize(mix(a_, b_, t_)); + } + + fn round_1(join: JoinInput, joinVertexIndex: u32, maxJoinCount: u32) -> vec2f { + if ((joinVertexIndex == 0u)) { + return join.v; + } + var dir = slerpApprox(join.d, bisectCcw(join.start, join.end), (f32(joinVertexIndex) / f32(maxJoinCount))); + return (join.C.position + (dir * join.C.radius)); + } + + fn lineSegmentVariableWidth(vertexIndex: u32, A: LineControlPoint, B: LineControlPoint, C: LineControlPoint, D: LineControlPoint, maxJoinCount: u32) -> LineSegmentOutput { + var AB = (B.position - A.position); + var BC = (C.position - B.position); + var DC = (C.position - D.position); + var CB = -(BC); + let radiusABDelta = (A.radius - B.radius); + let radiusBCDelta = (B.radius - C.radius); + let radiusCDDelta = (C.radius - D.radius); + if ((dot(BC, BC) <= (radiusBCDelta * radiusBCDelta))) { + return LineSegmentOutput(vec2f(), 1f); + } + let isCapB = (dot(AB, AB) <= (radiusABDelta * radiusABDelta)); + let isCapC = (dot(DC, DC) <= (radiusCDDelta * radiusCDDelta)); + var eAB = externalNormals(AB, A.radius, B.radius); + var eBC = externalNormals(BC, B.radius, C.radius); + var eCB = ExternalNormals(eBC.nR, eBC.nL); + var eDC = externalNormals(DC, D.radius, C.radius); + let joinLimit = dot(eBC.nL, BC); + var joinB = solveJoin(AB, BC, eAB, eBC, joinLimit, isCapB); + var joinC = solveJoin(DC, CB, eDC, eCB, -(joinLimit), isCapC); + let d2 = (&joinB.dL); + let d3 = (&joinB.dR); + let d4 = (&joinC.dL); + let d5 = (&joinC.dR); + var v2orig = (B.position + ((*d2) * B.radius)); + var v3orig = (B.position + ((*d3) * B.radius)); + var v4orig = (C.position + ((*d4) * C.radius)); + var v5orig = (C.position + ((*d5) * C.radius)); + var limL = intersectLines(B.position, v2orig, C.position, v5orig); + var limR = intersectLines(B.position, v3orig, C.position, v4orig); + var v2 = select(v2orig, limL.point, limL.valid); + var v5 = select(v5orig, limL.point, limL.valid); + var v3 = select(v3orig, limR.point, limR.valid); + var v4 = select(v4orig, limR.point, limR.valid); + if ((vertexIndex == 0u)) { + return LineSegmentOutput(B.position, (1f / B.radius)); + } + if ((vertexIndex == 1u)) { + return LineSegmentOutput(C.position, (1f / C.radius)); + } + let coreVertexIndex = ((vertexIndex - 2u) & 3u); + let joinVertexIndex = ((vertexIndex - 2u) >> 2u); + var join = JoinInput(); + if ((coreVertexIndex == 0u)) { + join = JoinInput(B, v2, (*d2), CB, (*d2), select(eAB.nL, (*d3), (joinB.isHairpin || isCapB)), joinB.shouldJoinL, isCapB); + } + else { + if ((coreVertexIndex == 1u)) { + join = JoinInput(B, v3, (*d3), CB, select(eAB.nR, (*d2), (joinB.isHairpin || isCapB)), (*d3), joinB.shouldJoinR, isCapB); + } + else { + if ((coreVertexIndex == 2u)) { + join = JoinInput(C, v4, (*d4), BC, (*d4), select(eDC.nL, (*d5), (joinC.isHairpin || isCapC)), joinC.shouldJoinL, isCapC); + } + else { + join = JoinInput(C, v5, (*d5), BC, select(eDC.nR, (*d4), (joinC.isHairpin || isCapC)), (*d5), joinC.shouldJoinR, isCapC); + } + } + } + var vertexPosition = join.v; + if (join.isCap) { + if ((coreVertexIndex < 2u)) { + vertexPosition = round_1(join, joinVertexIndex, maxJoinCount); + } + else { + vertexPosition = round_1(join, joinVertexIndex, maxJoinCount); + } + } + else { + if (join.shouldJoin) { + vertexPosition = round_1(join, joinVertexIndex, maxJoinCount); + } + } + let w = select((1f / B.radius), (1f / C.radius), (coreVertexIndex >= 2u)); + return LineSegmentOutput(vertexPosition, w); + } + + @vertex fn mainVertex(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> mainVertex_Output { + let t = uniforms.time; + var A = item(instanceIndex, t); + var B = item((instanceIndex + 1u), t); + var C = item((instanceIndex + 2u), t); + var D = item((instanceIndex + 3u), t); + if (((((A.radius < 0f) || (B.radius < 0f)) || (C.radius < 0f)) || (D.radius < 0f))) { + return mainVertex_Output(); + } + var result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, 6u); + return mainVertex_Output(vec4f((result.vertexPosition * result.w), 0f, result.w), result.vertexPosition, vec2f(0f, select(0f, 1f, (vertexIndex > 1u))), instanceIndex, vertexIndex, 0u); + } + + struct mainFragment_Input { + @location(2) @interpolate(flat) instanceIndex: u32, + @location(3) @interpolate(flat) vertexIndex: u32, + @location(4) @interpolate(flat) situationIndex: u32, + @location(0) position: vec2f, + @location(1) uv: vec2f, + } + + @fragment fn mainFragment(_arg_0: mainFragment_Input, @builtin(front_facing) frontFacing: bool, @builtin(position) screenPosition: vec4f) -> @location(0) vec4f { + let fillType = uniforms.fillType; + var color = vec3f(); + var colors = array(vec3f(1, 0, 0), vec3f(0, 1, 0), vec3f(0, 0, 1), vec3f(1, 0, 1), vec3f(1, 1, 0), vec3f(0, 1, 1), vec3f(0.75, 0.25, 0.25), vec3f(0.25, 0.75, 0.25), vec3f(0.25, 0.25, 0.75)); + if ((fillType == 1u)) { + color = mix(vec3f(0.7699999809265137, 0.38999998569488525, 1), vec3f(0.10999999940395355, 0.4399999976158142, 0.9399999976158142), ((_arg_0.position.x * 0.5f) + 0.5f)); + } + if ((fillType == 2u)) { + var t = cos((_arg_0.uv.y * 10f)); + t = clamp((t / fwidth(t)), 0f, 1f); + color = mix(vec3f(0.7699999809265137, 0.38999998569488525, 1), vec3f(0.10999999940395355, 0.4399999976158142, 0.9399999976158142), t); + } + if ((fillType == 3u)) { + color = colors[(_arg_0.vertexIndex % 9u)]; + } + if ((fillType == 4u)) { + color = colors[(_arg_0.instanceIndex % 9u)]; + } + if ((fillType == 5u)) { + color = colors[(_arg_0.situationIndex % 9u)]; + } + color = (color * (0.8f + (0.2f * smoothstep(1f, 0.5f, _arg_0.uv.y)))); + if (frontFacing) { + return vec4f(color, 0.5f); + } + return vec4f(color, select(0f, 1f, (((u32(screenPosition.x) >> 3u) % 2u) != ((u32(screenPosition.y) >> 3u) % 2u)))); + }" + `); + }); +}); diff --git a/packages/typegpu-geometry/ArticlePlan.md b/packages/typegpu-geometry/ArticlePlan.md new file mode 100644 index 0000000000..99b32ae184 --- /dev/null +++ b/packages/typegpu-geometry/ArticlePlan.md @@ -0,0 +1,52 @@ +This is a plan for a future article. + +## Sources + +- https://mattdesl.svbtle.com/drawing-lines-is-hard +- https://blog.mapbox.com/drawing-antialiased-lines-with-opengl-8766f34192dc +- https://wwwtyro.net/2019/11/18/instanced-lines.html +- https://wwwtyro.net/2021/10/01/instanced-lines-part-2.html + +## TODO + +- joining segments +- triangulation + +## Basic example + +- show how to implement a simple render command which renders a line of segments + which change width based on where the mouse cursor is, with a nice shading + +## Overlaps + +- explain the problem +- reverse miter solution +- intersecting reverse miters + +## Edge cases + +- enumerate situations a join can be in +- detecting whether to join or reverse miter +- collapsing joins into miters +- hairpin detection and what is done in this case +- degenerate lines (start point "inside" end point) + +## Example showing off reverse miter + +- tbd + +## Shading + +- naive approach and the shortcomings +- homogeneous coordinates and "perspective" correction +- contour level sets +- constant width outline +- dashes + +## Single-sided expansion + +- thick outline / box shadow example + +## Miter join math + +## Arrow cap math diff --git a/packages/typegpu-geometry/LinesExplanation.md b/packages/typegpu-geometry/LinesExplanation.md new file mode 100644 index 0000000000..c2a0397d27 --- /dev/null +++ b/packages/typegpu-geometry/LinesExplanation.md @@ -0,0 +1,107 @@ +# Variable Width Line Rendering + +by @deluksic + +High quality line rendering is a basic need in many graphics applications. +Charts, maps, graphs, particle systems, scientific visualizations, you name it. +Unfortunately, a high quality line primitive is not available in most graphics +APIs, especially low-level ones like WebGPU. You might think "I will slap +`topology: 'line-strip'` and be done with it". However, this primitive is very +limited. It allows only single pixel width lines! Good for debugging, but +terrible for anything user-facing. With TypeGPU, it is easier than ever to +create reusable, composable libraries. And now, high quality line rendering is +just an `npm install` away! This article serves as documentation for the library +source code. It should also give you an appreciation for how complex line +rendering can actually get. + +## Goals + +As already described in many online articles, drawing lines using GPU can be +notoriously difficult to do well. There are many different, sometimes +conflicting, goals you might have when it comes to line rendering. Following +goals are considered by this article and implementation: + +| | | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Variable width | Line width can be specified per-vertex. This is a core goal which complicates the math quite a bit, and forces us to have 4 triangles per-segment. But it makes the implementation elegant and general. | +| Joins, caps | Ability to choose how to join and cap off segments. Separate start and end caps. | +| Minimal overlaps | When rendering transparent lines, we should avoid producing overlaps which cause doubling-up. | +| Single draw call | Having the ability to do everything (caps, joins, segment) in one draw call makes using the library very easy. | +| Coloring based on contour level sets | | +| Coloring based on distance along line (dashes) | | +| Half-fill | When rendering outlines, you might want to render only one half of the line in order to avoid covering the content of what is outlined. | + +### Non-goals + +- maximum performance +- triangle counts +- minimizing quad-overdraw (having max-area triangles) + +## Single Line Segment + +We start with a single segment. Two vertices, `C1` and `C2` (C for center), and +radii `r1` and `r2`. + +![-](./assets/basic.svg) + +Two most important directions to compute are `nL` and `nR`, left (CCW) and right +(CW) external tangent **normals**. + +```ts +x = (r1 - r2) / distance(C1, C2); +y = sqrt(1 - x ^ 2); +nL = vec2(x, y); +nR = vec2(x, -y); +``` + +NOTE: in `externalNormals.ts`, additional care is taken to return `nL` and `nR` +rotated relative to the `distance` vector between the circles. + +Using these two directions, it is trivial to compute all other points necessary +for triangulation: + +![-](./assets/triangulation.svg) + +**Core** (red) vertices 0-5 are: + +```ts +v0 = C1; +v1 = C2; +v2 = C1 + r1 * nL; +v3 = C1 + r1 * nR; +v4 = C2 + r2 * nR; +v5 = C2 + r2 * nL; +``` + +Vertices 6+ are called `join` vertices in the code, however they are used for +both `joins` and `caps`. Their computation will depend on the type of `join` or +`cap` used. Here, a `round` cap is shown. It is important to note their +distribution. Just like the 4 core vertices 2-5, they are distributed CCW +progressively further away from their respective core vertex. This makes it +possible to dynamically vary the number of segments in the joins, while using +the same index buffer. Each join vertex is identified by: + +- `coreVertexIndex` which core vertex it belongs to (`v2,v6,v10=0`, + `v3,v7,v11=1` etc.) +- `joinVertexIndex` number of vertices away from the core vertex (`v2=0`, + `v6=1`, `v10=2`) + +```ts +coreVertexIndex = (vertexIndex - 2) % 4; +joinVertexIndex = (vertexIndex - 2) / 4; +``` + +NOTE: instead of the slow `% 4` and `/ 4`, real code uses `& 0b11` and `>> 2`. +Probably can be optimized away by wgsl compilers, but you never know. + +Cap functions can then use `joinVertexIndex` and `maxJoinCount` to compute the +final position of each join vertex. + +If all you want to do is render single line segments, you should use +`singleLineSegmentVariableWidth(A, B)` function. It does exactly what we just +discussed and nothing more. + +## Joining Line Segments + +Easy part is done. Joining segments is where the difficulties and edge cases +start. First, lets consider how in theory the joining should work: diff --git a/packages/typegpu-geometry/assets/basic.svg b/packages/typegpu-geometry/assets/basic.svg new file mode 100644 index 0000000000..96567d5fd3 --- /dev/null +++ b/packages/typegpu-geometry/assets/basic.svg @@ -0,0 +1 @@ +Expression 7Expression 8Expression 9Expression 10Expression 12Expression 13Expression 14Expression 15Expression 16Expression 17nLnRC1C2nLnLnRnRC1C1C2C2 \ No newline at end of file diff --git a/packages/typegpu-geometry/assets/triangulation.svg b/packages/typegpu-geometry/assets/triangulation.svg new file mode 100644 index 0000000000..633e23143c --- /dev/null +++ b/packages/typegpu-geometry/assets/triangulation.svg @@ -0,0 +1 @@ +Expression 7Expression 8Expression 9Expression 10Expression 23Expression 24Expression 25Expression 26Expression 27Expression 28Expression 29Expression 30Expression 31Expression 34Expression 35Expression 36Expression 37Expression 38Expression 39Expression 40Expression 41Expression 42Expression 43Expression 44Expression 45Expression 46Expression 47Expression 48Expression 49v6v7v8v9v10v11v12v13v0v1v2v3v4v5v6v6v7v7v8v8v9v9v10v10v11v11v12v12v13v13v0v0v1v1v2v2v3v3v4v4v5v5 \ No newline at end of file diff --git a/packages/typegpu-geometry/src/circle.ts b/packages/typegpu-geometry/src/circle.ts index 195a97e285..9b0e876b05 100644 --- a/packages/typegpu-geometry/src/circle.ts +++ b/packages/typegpu-geometry/src/circle.ts @@ -15,6 +15,7 @@ const getSubdivLevel = tgpu.fn( [u32], SubdivLevelResult, )((vertexIndex) => { + 'use gpu'; let totalVertexCount = u32(0); for (let level = u32(0); level < 8; level += 1) { const pointCount = u32(3) * (u32(1) << level); @@ -44,7 +45,8 @@ const consecutiveTriangleVertexIndex = tgpu.fn( [u32], u32, )((i) => { - return (2 * (i + 1)) / 3; + 'use gpu'; + return u32((2 * (i + 1)) / 3); }); /** @@ -61,6 +63,7 @@ export const circle = tgpu.fn( [u32], vec2f, )((vertexIndex) => { + 'use gpu'; const subdiv = getSubdivLevel(vertexIndex); const i = consecutiveTriangleVertexIndex(subdiv.vertexIndexInLevel); const pointCount = subdiv.pointCount; @@ -70,7 +73,7 @@ export const circle = tgpu.fn( export function circleVertexCount(subdivLevel: number) { let totalVertexCount = 3; - for (let level = 0; level < subdivLevel; level += 1) { + for (let level = u32(0); level < subdivLevel; level += 1) { totalVertexCount += 9 * (1 << level); } return totalVertexCount; diff --git a/packages/typegpu-geometry/src/index.ts b/packages/typegpu-geometry/src/index.ts index 0ffe08fe16..33f48fa5c3 100644 --- a/packages/typegpu-geometry/src/index.ts +++ b/packages/typegpu-geometry/src/index.ts @@ -1,8 +1,2 @@ export * from './circle.ts'; -export * from './lines/caps/index.ts'; -export * from './lines/indices.ts'; -export * from './lines/joins/index.ts'; -export * from './lines/lines.ts'; -export * from './lines/types.ts'; -export { uvToLineSegment } from './lines/utils.ts'; -export { addMul } from './utils.ts'; +export * from './lines/index.ts'; diff --git a/packages/typegpu-geometry/src/lines/caps/arrow.ts b/packages/typegpu-geometry/src/lines/caps/arrow.ts index 405267d925..c15d78c28f 100644 --- a/packages/typegpu-geometry/src/lines/caps/arrow.ts +++ b/packages/typegpu-geometry/src/lines/caps/arrow.ts @@ -1,25 +1,21 @@ import { vec2f } from 'typegpu/data'; -import type { v2f } from 'typegpu/data'; -import { addMul, rot90ccw, rot90cw } from '../../utils.ts'; -import { capShell } from './common.ts'; +import { neg, normalize, sign } from 'typegpu/std'; +import { cross2d, rot90ccw } from '../../utils.ts'; +import type { JoinInput } from '../types.ts'; -export const arrowCap = capShell((vertexIndex, joinPath, V, vu, vd, _right, dir, _left) => { +export function arrow(join: JoinInput, joinVertexIndex: number, _maxJoinCount: number) { 'use gpu'; - const dirRight = rot90cw(dir); - const dirLeft = rot90ccw(dir); - - const v0 = addMul(vu, dir, -7.5 * V.radius); - const v1 = addMul(V.position, addMul(dirRight, dir, -3), 3 * V.radius); - const v2 = addMul(V.position, vec2f(0, 0), 2 * V.radius); - const v3 = addMul(V.position, addMul(dirLeft, dir, -3), 3 * V.radius); - const v4 = addMul(vd, dir, -7.5 * V.radius); - const points = [v0, v1, v2, v3, v4]; - - if (joinPath.depth >= 0) { - const remove = [v0, v4]; - const dm = remove[joinPath.joinIndex & 0x1] as v2f; - return dm; + const bw = neg(normalize(join.fw)); + const vert = rot90ccw(bw); + const sgn = sign(cross2d(bw, join.d)); + const svert = vert * sgn; + const v0 = svert + bw * 7.5; + const v1 = v0 + (bw + svert) * 1.5; + if (joinVertexIndex === 0) { + return join.C.position + v0 * join.C.radius; } - - return points[vertexIndex % 5] as v2f; -}); + if (joinVertexIndex === 1) { + return join.C.position + v1 * join.C.radius; + } + return vec2f(join.C.position); +} diff --git a/packages/typegpu-geometry/src/lines/caps/butt.ts b/packages/typegpu-geometry/src/lines/caps/butt.ts index 0ff62d2fb5..1385fb24fa 100644 --- a/packages/typegpu-geometry/src/lines/caps/butt.ts +++ b/packages/typegpu-geometry/src/lines/caps/butt.ts @@ -1,33 +1,12 @@ -import { vec2f } from 'typegpu/data'; -import type { v2f } from 'typegpu/data'; -import { dot, select } from 'typegpu/std'; -import { addMul, rot90ccw, rot90cw } from '../../utils.ts'; -import { intersectTangent, miterPointNoCheck } from '../utils.ts'; -import { capShell } from './common.ts'; +import { normalize, sign } from 'typegpu/std'; +import { cross2d, rot90ccw } from '../../utils.ts'; +import type { JoinInput } from '../types.ts'; -export const buttCap = capShell((vertexIndex, joinPath, V, vu, vd, right, dir, left) => { +export function butt(join: JoinInput, _joinVertexIndex: number, _maxJoinCount: number) { 'use gpu'; - const shouldJoin = dot(dir, right) < 0; - const dirRight = rot90cw(dir); - const dirLeft = rot90ccw(dir); - const u = select(intersectTangent(right, dirRight), dirRight, shouldJoin); - const c = vec2f(0, 0); - const d = select(intersectTangent(left, dirLeft), dirLeft, shouldJoin); - - const joinIndex = joinPath.joinIndex; - if (joinPath.depth >= 0) { - const miterR = select(u, miterPointNoCheck(right, dirRight), shouldJoin); - const miterL = select(d, miterPointNoCheck(dirLeft, left), shouldJoin); - const parents = [miterR, miterL]; - const dm = parents[joinIndex & 0b1] as v2f; - return addMul(V.position, dm, V.radius); - } - - const v1 = addMul(V.position, u, V.radius); - const v0 = select(v1, vu, shouldJoin); - const v2 = addMul(V.position, c, V.radius); - const v3 = addMul(V.position, d, V.radius); - const v4 = select(v3, vd, shouldJoin); - const points = [v0, v1, v2, v3, v4]; - return points[vertexIndex % 5] as v2f; -}); + const fw = normalize(join.fw); + const vert = rot90ccw(fw); + const sgn = sign(cross2d(fw, join.d)); + const svert = vert * sgn; + return join.C.position + svert * join.C.radius; +} diff --git a/packages/typegpu-geometry/src/lines/caps/common.ts b/packages/typegpu-geometry/src/lines/caps/common.ts deleted file mode 100644 index bd71beded6..0000000000 --- a/packages/typegpu-geometry/src/lines/caps/common.ts +++ /dev/null @@ -1,8 +0,0 @@ -import tgpu from 'typegpu'; -import { u32, vec2f } from 'typegpu/data'; -import { JoinPath, LineSegmentVertex } from '../types.ts'; - -export const capShell = tgpu.fn( - [u32, JoinPath, LineSegmentVertex, vec2f, vec2f, vec2f, vec2f, vec2f], - vec2f, -); diff --git a/packages/typegpu-geometry/src/lines/caps/index.ts b/packages/typegpu-geometry/src/lines/caps/index.ts index 2b969d8b26..673e5ac147 100644 --- a/packages/typegpu-geometry/src/lines/caps/index.ts +++ b/packages/typegpu-geometry/src/lines/caps/index.ts @@ -1,15 +1,6 @@ -import { buttCap } from './butt.ts'; -import { squareCap } from './square.ts'; -import { roundCap } from './round.ts'; -import { triangleCap } from './triangle.ts'; -import { arrowCap } from './arrow.ts'; -import { swallowtailCap } from './swallowtail.ts'; - -export const lineCaps = { - butt: buttCap, - square: squareCap, - round: roundCap, - triangle: triangleCap, - arrow: arrowCap, - swallowtail: swallowtailCap, -}; +export { round } from '../joins/round.ts'; +export { arrow } from './arrow.ts'; +export { butt } from './butt.ts'; +export { square } from './square.ts'; +export { triangle } from './triangle.ts'; +export { wedge } from './wedge.ts'; diff --git a/packages/typegpu-geometry/src/lines/caps/round.ts b/packages/typegpu-geometry/src/lines/caps/round.ts deleted file mode 100644 index db8a55c613..0000000000 --- a/packages/typegpu-geometry/src/lines/caps/round.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { v2f } from 'typegpu/data'; -import { select } from 'typegpu/std'; -import { addMul, bisectCcw, bisectNoCheck } from '../../utils.ts'; -import { capShell } from './common.ts'; - -export const roundCap = capShell((vertexIndex, joinPath, V, vu, vd, right, dir, left) => { - 'use gpu'; - const uR = right; - const u = dir; - const c = dir; - const d = dir; - const dR = left; - - const joinIndex = joinPath.joinIndex; - if (joinPath.depth >= 0) { - const parents = [uR, u, d, dR]; - let d0 = parents[(joinIndex * 2) & 3] as v2f; - let d1 = parents[(joinIndex * 2 + 1) & 3] as v2f; - let dm = bisectCcw(d0, d1); - let path = joinPath.path; - for (let depth = joinPath.depth; depth > 0; depth -= 1) { - const isLeftChild = (path & 1) === 0; - d0 = select(dm, d0, isLeftChild); - d1 = select(d1, dm, isLeftChild); - dm = bisectNoCheck(d0, d1); - path >>= 1; - } - return addMul(V.position, dm, V.radius); - } - - const v1 = addMul(V.position, u, V.radius); - const v2 = addMul(V.position, c, V.radius); - const v3 = addMul(V.position, d, V.radius); - const points = [vu, v1, v2, v3, vd]; - return points[vertexIndex % 5] as v2f; -}); diff --git a/packages/typegpu-geometry/src/lines/caps/square.ts b/packages/typegpu-geometry/src/lines/caps/square.ts index 7d4ea83e55..8637948847 100644 --- a/packages/typegpu-geometry/src/lines/caps/square.ts +++ b/packages/typegpu-geometry/src/lines/caps/square.ts @@ -1,30 +1,15 @@ -import type { v2f } from 'typegpu/data'; -import { add, dot, select } from 'typegpu/std'; -import { addMul, rot90ccw, rot90cw } from '../../utils.ts'; -import { miterPointNoCheck } from '../utils.ts'; -import { capShell } from './common.ts'; +import { vec2f } from 'typegpu/data'; +import { normalize, select, sign } from 'typegpu/std'; +import { cross2d, rot90ccw } from '../../utils.ts'; +import type { JoinInput } from '../types.ts'; -export const squareCap = capShell((vertexIndex, joinPath, V, vu, vd, right, dir, left) => { +export function square(join: JoinInput, joinVertexIndex: number, _maxJoinCount: number) { 'use gpu'; - const shouldJoin = dot(dir, right) < 0; - const dirRight = rot90cw(dir); - const dirLeft = rot90ccw(dir); - const u = select(miterPointNoCheck(right, dir), add(dir, dirRight), shouldJoin); - const c = dir; - const d = select(miterPointNoCheck(dir, left), add(dir, dirLeft), shouldJoin); - - const joinIndex = joinPath.joinIndex; - if (joinPath.depth >= 0) { - const miterR = select(right, miterPointNoCheck(right, dirRight), shouldJoin); - const miterL = select(left, miterPointNoCheck(dirLeft, left), shouldJoin); - const parents = [miterR, miterL]; - const dm = parents[joinIndex & 0b1] as v2f; - return addMul(V.position, dm, V.radius); + if (joinVertexIndex === 0) { + return vec2f(join.v); } - - const v1 = addMul(V.position, u, V.radius); - const v2 = addMul(V.position, c, V.radius); - const v3 = addMul(V.position, d, V.radius); - const points = [vu, v1, v2, v3, vd]; - return points[vertexIndex % 5] as v2f; -}); + const fw = normalize(join.fw); + const vert = rot90ccw(fw); + const sgn = sign(cross2d(fw, join.d)); + return join.C.position + select(fw + vert * sgn, fw, joinVertexIndex > 1) * join.C.radius; +} diff --git a/packages/typegpu-geometry/src/lines/caps/swallowtail.ts b/packages/typegpu-geometry/src/lines/caps/swallowtail.ts deleted file mode 100644 index 856581389f..0000000000 --- a/packages/typegpu-geometry/src/lines/caps/swallowtail.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { v2f } from 'typegpu/data'; -import { addMul, midPoint } from '../../utils.ts'; -import { add } from 'typegpu/std'; -import { capShell } from './common.ts'; - -export const swallowtailCap = capShell((vertexIndex, joinPath, V, vu, vd, right, dir, left) => { - 'use gpu'; - if (joinPath.depth >= 0) { - const remove = [right, left]; - const dm = remove[joinPath.joinIndex & 0x1] as v2f; - return addMul(V.position, dm, V.radius); - } - - const v1 = addMul(V.position, add(right, dir), V.radius); - const v2 = addMul(V.position, midPoint(right, left), V.radius); - const v3 = addMul(V.position, add(left, dir), V.radius); - const points = [vu, v1, v2, v3, vd]; - return points[vertexIndex % 5] as v2f; -}); diff --git a/packages/typegpu-geometry/src/lines/caps/triangle.ts b/packages/typegpu-geometry/src/lines/caps/triangle.ts index af7a0644bd..0d51a385e1 100644 --- a/packages/typegpu-geometry/src/lines/caps/triangle.ts +++ b/packages/typegpu-geometry/src/lines/caps/triangle.ts @@ -1,18 +1,12 @@ -import type { v2f } from 'typegpu/data'; -import { addMul } from '../../utils.ts'; -import { capShell } from './common.ts'; +import { vec2f } from 'typegpu/data'; +import { normalize } from 'typegpu/std'; +import type { JoinInput } from '../types.ts'; -export const triangleCap = capShell((vertexIndex, joinPath, V, vu, vd, right, dir, left) => { +export function triangle(join: JoinInput, joinVertexIndex: number, _maxJoinCount: number) { 'use gpu'; - if (joinPath.depth >= 0) { - const remove = [right, left]; - const dm = remove[joinPath.joinIndex & 0x1] as v2f; - return addMul(V.position, dm, V.radius); + if (joinVertexIndex === 0) { + return vec2f(join.v); } - - const v1 = addMul(V.position, right, V.radius); - const v2 = addMul(V.position, dir, V.radius); - const v3 = addMul(V.position, left, V.radius); - const points = [vu, v1, v2, v3, vd]; - return points[vertexIndex % 5] as v2f; -}); + const fw = normalize(join.fw); + return join.C.position + fw * join.C.radius; +} diff --git a/packages/typegpu-geometry/src/lines/caps/wedge.ts b/packages/typegpu-geometry/src/lines/caps/wedge.ts new file mode 100644 index 0000000000..43d3457380 --- /dev/null +++ b/packages/typegpu-geometry/src/lines/caps/wedge.ts @@ -0,0 +1,15 @@ +import { vec2f } from 'typegpu/data'; +import { normalize, sign } from 'typegpu/std'; +import { cross2d, rot90ccw } from '../../utils.ts'; +import type { JoinInput } from '../types.ts'; + +export function wedge(join: JoinInput, joinVertexIndex: number, _maxJoinCount: number) { + 'use gpu'; + if (joinVertexIndex === 0) { + return vec2f(join.v); + } + const fw = normalize(join.fw); + const vert = rot90ccw(fw); + const sgn = sign(cross2d(fw, join.d)); + return join.C.position + (fw + vert * sgn) * join.C.radius; +} diff --git a/packages/typegpu-geometry/src/lines/constants.ts b/packages/typegpu-geometry/src/lines/constants.ts index eac34ff307..2f8707ee80 100644 --- a/packages/typegpu-geometry/src/lines/constants.ts +++ b/packages/typegpu-geometry/src/lines/constants.ts @@ -1,4 +1,6 @@ -import tgpu from 'typegpu'; -import { f32 } from 'typegpu/data'; - -export const JOIN_LIMIT = tgpu.const(f32, 0.999); +/** + * Limit between two neighboring tangent normals beyond which + * a miter is used for the join. This prevents tiny join triangles + * at almost-straight segment pairs. + */ +export const MITER_DOT_PRODUCT_LIMIT = 0.99; diff --git a/packages/typegpu-geometry/src/lines/externalNormals.ts b/packages/typegpu-geometry/src/lines/externalNormals.ts new file mode 100644 index 0000000000..4ff671ad64 --- /dev/null +++ b/packages/typegpu-geometry/src/lines/externalNormals.ts @@ -0,0 +1,37 @@ +import tgpu from 'typegpu'; +import type { Infer } from 'typegpu/data'; +import { f32, struct, vec2f } from 'typegpu/data'; +import { dot, max, sqrt } from 'typegpu/std'; + +export type ExternalNormals = Infer; +export const ExternalNormals = struct({ + /** Normal which is CW (left of) the distance vector */ + nL: vec2f, + /** Normal which is CCW (right of) the distance vector */ + nR: vec2f, +}); + +/** + * Given two circles at a `distance` of radii `r1` and `r2`, + * computes the two external tangent normals, which correspond to + * line segment edges. + */ +export const externalNormals = tgpu.fn( + [vec2f, f32, f32], + ExternalNormals, +)((distance, r1, r2) => { + 'use gpu'; + // Distance squared inverse is used to avoid taking square root more than necessary. + // This way we only need to take it once! + const dist2Inv = 1 / dot(distance, distance); + const cosMulLen = r1 - r2; + const cosDivLen = cosMulLen * dist2Inv; + const sinDivLen = sqrt(max(0, 1 - cosMulLen * cosDivLen) * dist2Inv); + const a = distance.x * cosDivLen; + const b = distance.y * sinDivLen; + const c = distance.x * sinDivLen; + const d = distance.y * cosDivLen; + const nL = vec2f(a - b, c + d); + const nR = vec2f(a + b, -c + d); + return ExternalNormals({ nL, nR }); +}); diff --git a/packages/typegpu-geometry/src/lines/index.ts b/packages/typegpu-geometry/src/lines/index.ts new file mode 100644 index 0000000000..779d96c99d --- /dev/null +++ b/packages/typegpu-geometry/src/lines/index.ts @@ -0,0 +1,5 @@ +export * as caps from './caps/index.ts'; +export * from './indices.ts'; +export * as joins from './joins/index.ts'; +export * from './lines.ts'; +export * from './types.ts'; diff --git a/packages/typegpu-geometry/src/lines/indices.ts b/packages/typegpu-geometry/src/lines/indices.ts index da7daec610..2215ca5a9e 100644 --- a/packages/typegpu-geometry/src/lines/indices.ts +++ b/packages/typegpu-geometry/src/lines/indices.ts @@ -1,146 +1,88 @@ -// oxfmt-ignore -export const lineSegmentIndicesCapLevel0 = [ - 0, 4, 5, - 1, 2, 0, - 2, 3, 4, - 4, 0, 2, - 5, 9, 0, - 6, 7, 5, - 7, 8, 9, - 9, 5, 7, -] - -// oxfmt-ignore -export const lineSegmentIndicesCapLevel1 = [ - ...lineSegmentIndicesCapLevel0, - 10, 1, 0, - 11, 4, 3, - 12, 6, 5, - 13, 9, 8, -] - -// oxfmt-ignore -export const lineSegmentIndicesCapLevel2 = [ - ...lineSegmentIndicesCapLevel1, - 14, 10, 0, - 15, 1, 10, - 16, 11, 3, - 17, 4, 11, - 18, 12, 5, - 19, 6, 12, - 20, 13, 8, - 21, 9, 13, -]; +/** + * Line segment triangulation: + * 0 + * 3/ | \2 + * |\ |\ | + * | \| \| + * 4\ | /5 + * 1 + * + * Joins are added like: (only top shown) + * 15---0---14 + * |/ /|\ \| + * 11 / | \ 10 + * |/ /|\ \| + * 7 / | \ 6 + * |/ | \| + * 3 | 2 + */ // oxfmt-ignore -export const lineSegmentIndicesCapLevel3 = [ - ...lineSegmentIndicesCapLevel2, - 22, 14, 0, - 23, 10, 14, - 24, 15, 10, - 25, 1, 15, - 26, 16, 3, - 27, 11, 16, - 28, 17, 11, - 29, 4, 17, - 30, 18, 5, - 31, 12, 18, - 32, 19, 12, - 33, 6, 19, - 34, 20, 8, - 35, 13, 20, - 36, 21, 13, - 37, 9, 21, +const lineSegmentIndicesBase = [ + 0, 5, 2, + 0, 3, 1, + 1, 3, 4, + 1, 5, 0, ]; // oxfmt-ignore -export const lineSegmentWireframeIndicesCapLevel0 = [ +const lineSegmentWireframeIndicesBase = [ 0, 1, 0, 2, - 0, 4, + 0, 3, 0, 5, - 0, 9, - 1, 2, - 2, 3, - 2, 4, + 1, 3, + 1, 4, + 1, 5, + 2, 5, 3, 4, - 4, 5, - 5, 6, - 5, 7, - 5, 9, - 6, 7, - 7, 8, - 7, 9, - 8, 9, ]; -// oxfmt-ignore -export const lineSegmentWireframeIndicesCapLevel1 = [ - ...lineSegmentWireframeIndicesCapLevel0, - 0, 10, - 1, 10, - 3, 11, - 4, 11, - 5, 12, - 6, 12, - 8, 13, - 9, 13, -] +export function lineSegmentIndices(joinTriangleCount: number) { + const indices = [...lineSegmentIndicesBase]; + for (let i = 0; i < joinTriangleCount; ++i) { + for (let j = 0; j < 4; ++j) { + const vertexIndex = i * 4 + j + 6; + const end = j < 2 ? 0 : 1; + const a = j % 2 === 0 ? end : vertexIndex - 4; + const b = j % 2 === 0 ? vertexIndex - 4 : end; + indices.push(vertexIndex, a, b); + } + } + return indices; +} -// oxfmt-ignore -export const lineSegmentWireframeIndicesCapLevel2 = [ - ...lineSegmentWireframeIndicesCapLevel1, - 0, 14, - 14, 10, - 10, 15, - 15, 1, - 3, 16, - 16, 11, - 11, 17, - 17, 4, - 5, 18, - 18, 12, - 12, 19, - 19, 6, - 8, 20, - 20, 13, - 13, 21, - 21, 9, -]; +export function lineSegmentWireframeIndices(joinTriangleCount: number) { + const wireframeIndices = [...lineSegmentWireframeIndicesBase]; + for (let i = 0; i < joinTriangleCount; ++i) { + for (let j = 0; j < 4; ++j) { + const vertexIndex = i * 4 + j + 6; + const end = j < 2 ? 0 : 1; + const a = j % 2 === 0 ? end : vertexIndex - 4; + const b = j % 2 === 0 ? vertexIndex - 4 : end; + wireframeIndices.push(vertexIndex, a); + wireframeIndices.push(vertexIndex, b); + } + } + return wireframeIndices; +} // oxfmt-ignore -export const lineSegmentWireframeIndicesCapLevel3 = [ - ...lineSegmentWireframeIndicesCapLevel2, - 0, 22, - 22, 14, - 14, 23, - 23, 10, - 10, 24, - 24, 15, - 15, 25, - 25, 1, - 3, 26, - 26, 16, - 16, 27, - 27, 11, - 11, 28, - 28, 17, - 17, 29, - 29, 4, - 5, 30, - 30, 18, - 18, 31, - 31, 12, - 12, 32, - 32, 19, - 19, 33, - 33, 6, - 8, 34, - 34, 20, - 20, 35, - 35, 13, - 13, 36, - 36, 21, - 21, 37, - 37, 9, +const lineSegmentLeftIndicesBase = [ + 0, 3, 1, + 1, 3, 4, ]; + +export function lineSegmentLeftIndices(joinTriangleCount: number) { + const indices = [...lineSegmentLeftIndicesBase]; + for (let i = 0; i < joinTriangleCount; ++i) { + for (let j = 1; j < 3; ++j) { + const vertexIndex = i * 4 + j + 6; + const end = j < 2 ? 0 : 1; + const a = j % 2 === 0 ? end : vertexIndex - 4; + const b = j % 2 === 0 ? vertexIndex - 4 : end; + indices.push(vertexIndex, a, b); + } + } + return indices; +} diff --git a/packages/typegpu-geometry/src/lines/joins/common.ts b/packages/typegpu-geometry/src/lines/joins/common.ts deleted file mode 100644 index 53a5de77cf..0000000000 --- a/packages/typegpu-geometry/src/lines/joins/common.ts +++ /dev/null @@ -1,34 +0,0 @@ -import tgpu from 'typegpu'; -import { bool, u32, vec2f } from 'typegpu/data'; -import { dot } from 'typegpu/std'; -import { cross2d } from '../../utils.ts'; -import { isCCW, rank3 } from '../utils.ts'; -import { JoinPath, LineSegmentVertex } from '../types.ts'; - -export const joinShell = tgpu.fn( - [u32, u32, JoinPath, LineSegmentVertex, vec2f, vec2f, vec2f, vec2f, vec2f, vec2f, bool, bool], - vec2f, -); - -export const joinSituationIndex = tgpu.fn( - [vec2f, vec2f, vec2f, vec2f], - u32, -)((ul, ur, dl, dr) => { - // ur is the reference vector - // we find all 6 orderings of the remaining ul, dl, dr - const crossUL = cross2d(ur, ul); - const crossDL = cross2d(ur, dl); - const crossDR = cross2d(ur, dr); - const signUL = crossUL >= 0; - const signDL = crossDL >= 0; - const signDR = crossDR >= 0; - const dotUL = dot(ur, ul); - const dotDL = dot(ur, dl); - const dotDR = dot(ur, dr); - - return rank3( - isCCW(dotUL, signUL, dotDL, signDL), - isCCW(dotDL, signDL, dotDR, signDR), - isCCW(dotUL, signUL, dotDR, signDR), - ); -}); diff --git a/packages/typegpu-geometry/src/lines/joins/index.ts b/packages/typegpu-geometry/src/lines/joins/index.ts index 0c419d1691..d9e1565a4d 100644 --- a/packages/typegpu-geometry/src/lines/joins/index.ts +++ b/packages/typegpu-geometry/src/lines/joins/index.ts @@ -1,7 +1,2 @@ -import { miterJoin } from './miter.ts'; -import { roundJoin } from './round.ts'; - -export const lineJoins = { - miter: miterJoin, - round: roundJoin, -}; +export { miter } from './miter.ts'; +export { round } from './round.ts'; diff --git a/packages/typegpu-geometry/src/lines/joins/miter.ts b/packages/typegpu-geometry/src/lines/joins/miter.ts index ee7765e03f..7a83cdab69 100644 --- a/packages/typegpu-geometry/src/lines/joins/miter.ts +++ b/packages/typegpu-geometry/src/lines/joins/miter.ts @@ -1,76 +1,60 @@ import tgpu from 'typegpu'; -import { add, dot, mul, normalize, select } from 'typegpu/std'; -import { addMul, bisectCcw } from '../../utils.ts'; -import { intersectLines, miterPoint } from '../utils.ts'; -import { joinShell } from './common.ts'; -import { f32, type v2f, vec2f } from 'typegpu/data'; +import type { v2f } from 'typegpu/data'; +import { vec2f } from 'typegpu/data'; +import { dot, normalize, select } from 'typegpu/std'; +import { bisectCcw, cross2d, miterPointNoCheck, rot90ccw } from '../../utils.ts'; +import type { JoinInput } from '../types.ts'; export const miterJoinLimitSlot = tgpu.slot(2); /** - * Limits the miter point to the given limit ratio, which is - * a length relative to the line vertex radius. + * Finds the miter point of tangents to two points on a circle. */ -export const miterLimit = tgpu.fn( - [vec2f, f32], - vec2f, -)((miter, limitRatio) => { - const m2 = dot(miter, miter); - if (m2 > limitRatio * limitRatio) { - return mul(normalize(miter), ((limitRatio - 1) * (limitRatio * limitRatio - 1)) / (m2 - 1) + 1); +function miterPointBisectorWhenClockwise(a: v2f, b: v2f) { + 'use gpu'; + const sin_ = cross2d(a, b); + if (sin_ <= 0) { + return bisectCcw(a, b); } - return miter; -}); - -export const miterJoin = joinShell( - (situationIndex, vertexIndex, joinPath, V, vu, vd, ul, ur, dl, dr, joinU, joinD) => { - 'use gpu'; - let miterU = miterPoint(ur, ul); - let miterD = miterPoint(dl, dr); - miterU = miterLimit(miterU, miterJoinLimitSlot.$); - miterD = miterLimit(miterD, miterJoinLimitSlot.$); - - const shouldCross = situationIndex === 1 || situationIndex === 4; - const crossCenter = intersectLines(ul, dl, ur, dr).point; - const averageCenter = mul(add(normalize(miterU), normalize(miterD)), 0.5); + return miterPointNoCheck(a, b); +} - let uR = ur; - let u = miterU; - let c = select(averageCenter, crossCenter, shouldCross); - let d = miterD; - let dR = dr; - - if (situationIndex === 2) { - const mid = bisectCcw(ur, dr); - uR = ur; - u = mid; - c = mid; - d = mid; - dR = dr; - } - - if (situationIndex === 3) { - const mid = bisectCcw(dl, ul); - uR = ur; - u = mid; - c = mid; - d = mid; - dR = dr; - } +/** + * Finds the miter point of tangents to two points on respective circles. + */ +function miterPoint(a: v2f, b: v2f) { + 'use gpu'; + const sin_ = cross2d(a, b); + const b2 = dot(b, b); + const cos_ = dot(a, b); + const diff = b2 - cos_; + const t = diff / sin_; + return a + rot90ccw(a) * t; +} - const joinIndex = joinPath.joinIndex; - if (joinPath.depth >= 0) { - const parents = [uR, u, d, dR]; - const d0 = parents[(joinIndex * 2) & 3] as v2f; - const d1 = parents[(joinIndex * 2 + 1) & 3] as v2f; - const dm = miterPoint(d0, d1); - return addMul(V.position, dm, V.radius); - } +/** + * Limits the miter point to the given limit ratio, which is + * a length relative to the control point radius. + */ +function miterLimit(miter: v2f, limitRatio: number) { + 'use gpu'; + const m2 = dot(miter, miter); + const l2 = limitRatio * limitRatio; + if (m2 > l2) { + return normalize(miter) * (((limitRatio - 1) * (l2 - 1)) / (m2 - 1) + 1); + } + return vec2f(miter); +} - const v1 = select(vu, addMul(V.position, u, V.radius), joinU); - const v2 = select(vu, addMul(V.position, c, V.radius), joinU || joinD); - const v3 = select(vd, addMul(V.position, d, V.radius), joinD); - const points = [vu, v1, v2, v3, vd]; - return points[vertexIndex % 5] as v2f; - }, -); +export function miter(join: JoinInput, joinVertexIndex: number, _maxJoinCount: number) { + 'use gpu'; + if (joinVertexIndex === 0) { + return vec2f(join.v); + } + const miter = miterLimit( + miterPointBisectorWhenClockwise(join.start, join.end), + miterJoinLimitSlot.$, + ); + const dir = select(miterPoint(join.d, miter), miter, joinVertexIndex > 1); + return join.C.position + dir * join.C.radius; +} diff --git a/packages/typegpu-geometry/src/lines/joins/round.ts b/packages/typegpu-geometry/src/lines/joins/round.ts index 112eda027c..6e8e6dc25c 100644 --- a/packages/typegpu-geometry/src/lines/joins/round.ts +++ b/packages/typegpu-geometry/src/lines/joins/round.ts @@ -1,64 +1,12 @@ -import type { v2f } from 'typegpu/data'; -import { add, mul, select } from 'typegpu/std'; -import { addMul, bisectCcw, bisectNoCheck } from '../../utils.ts'; -import { intersectLines } from '../utils.ts'; -import { joinShell } from './common.ts'; - -export const roundJoin = joinShell( - (situationIndex, vertexIndex, joinPath, V, vu, vd, ul, ur, dl, dr, joinU, joinD) => { - 'use gpu'; - const midU = bisectCcw(ur, ul); - const midD = bisectCcw(dl, dr); - const midR = bisectCcw(ur, dr); - const midL = bisectCcw(dl, ul); - - const shouldCross = situationIndex === 1 || situationIndex === 4; - const crossCenter = intersectLines(ul, dl, ur, dr).point; - const averageCenter = mul(add(add(ur, ul), add(dl, dr)), 0.25); - - let uR = ur; - let u = midU; - let c = select(averageCenter, crossCenter, shouldCross); - let d = midD; - let dR = dr; - - if (situationIndex === 2) { - uR = ur; - u = midR; - c = midR; - d = midR; - dR = dr; - } - - if (situationIndex === 3) { - uR = ur; - u = midL; - c = midL; - d = midL; - dR = dr; - } - - const joinIndex = joinPath.joinIndex; - if (joinPath.depth >= 0) { - const parents = [uR, u, d, dR]; - let d0 = parents[(joinIndex * 2) & 3] as v2f; - let d1 = parents[(joinIndex * 2 + 1) & 3] as v2f; - let dm = bisectCcw(d0, d1); - let path = joinPath.path; - for (let depth = joinPath.depth; depth > 0; depth -= 1) { - const isLeftChild = (path & 1) === 0; - d0 = select(dm, d0, isLeftChild); - d1 = select(d1, dm, isLeftChild); - dm = bisectNoCheck(d0, d1); - path >>= 1; - } - return addMul(V.position, dm, V.radius); - } - - const v1 = select(vu, addMul(V.position, u, V.radius), joinU); - const v2 = select(vu, addMul(V.position, c, V.radius), joinU || joinD); - const v3 = select(vd, addMul(V.position, d, V.radius), joinD); - const points = [vu, v1, v2, v3, vd]; - return points[vertexIndex % 5] as v2f; - }, -); +import { vec2f } from 'typegpu/data'; +import { bisectCcw, slerpApprox } from '../../utils.ts'; +import type { JoinInput } from '../types.ts'; + +export function round(join: JoinInput, joinVertexIndex: number, maxJoinCount: number) { + 'use gpu'; + if (joinVertexIndex === 0) { + return vec2f(join.v); + } + const dir = slerpApprox(join.d, bisectCcw(join.start, join.end), joinVertexIndex / maxJoinCount); + return join.C.position + dir * join.C.radius; +} diff --git a/packages/typegpu-geometry/src/lines/lines.ts b/packages/typegpu-geometry/src/lines/lines.ts index eb99c089fd..aca1f14f1a 100644 --- a/packages/typegpu-geometry/src/lines/lines.ts +++ b/packages/typegpu-geometry/src/lines/lines.ts @@ -1,59 +1,25 @@ import tgpu from 'typegpu'; -import { struct, u32, vec2f } from 'typegpu/data'; -import type { v2f } from 'typegpu/data'; -import { dot, mul, normalize, sub } from 'typegpu/std'; -import { addMul, midPoint, rot90ccw, rot90cw } from '../utils.ts'; -import { externalNormals, limitTowardsMiddle, miterPoint } from './utils.ts'; -import { JoinPath, LineSegmentVertex } from './types.ts'; -import { joinSituationIndex } from './joins/common.ts'; -import { roundJoin } from './joins/round.ts'; -import { roundCap } from './caps/round.ts'; -import { JOIN_LIMIT } from './constants.ts'; - -export const joinSlot = tgpu.slot(roundJoin); -export const startCapSlot = tgpu.slot(roundCap); -export const endCapSlot = tgpu.slot(roundCap); - -const getJoinParent = tgpu.fn([u32], u32)((i) => (i - 4) >> 1); - -const getJoinVertexPath = tgpu.fn( - [u32], - JoinPath, -)((vertexIndex) => { - // oxfmt-ignore - const lookup = [u32(0), u32(0), /* dont care */u32(0), u32(1), u32(1), u32(2), u32(2), /* dont care */u32(2), u32(3), u32(3)]; - if (vertexIndex < 10) { - return JoinPath({ - joinIndex: lookup[vertexIndex] as number, - path: 0, - depth: -1, - }); - } - let joinIndex = vertexIndex - 10; - let depth = 0; - let path = u32(0); - while (joinIndex >= 4) { - path = (path << 1) | (joinIndex & 1); - joinIndex = getJoinParent(joinIndex); - depth += 1; - } - return JoinPath({ joinIndex, path, depth }); -}); - -const LineSegmentOutput = struct({ - vertexPosition: vec2f, - situationIndex: u32, -}); +import { u32, vec2f } from 'typegpu/data'; +import { dot, neg, select } from 'typegpu/std'; +import { intersectLines } from '../utils.ts'; +import { ExternalNormals, externalNormals } from './externalNormals.ts'; +import { round } from './joins/round.ts'; +import { solveJoin } from './solveJoin.ts'; +import { JoinInput, LineControlPoint, LineSegmentOutput } from './types.ts'; + +export const joinSlot = tgpu.slot(round); +export const startCapSlot = tgpu.slot(round); +export const endCapSlot = tgpu.slot(round); export const lineSegmentVariableWidth = tgpu.fn( - [u32, LineSegmentVertex, LineSegmentVertex, LineSegmentVertex, LineSegmentVertex], + [u32, LineControlPoint, LineControlPoint, LineControlPoint, LineControlPoint, u32], LineSegmentOutput, -)((vertexIndex, A, B, C, D) => { - const joinPath = getJoinVertexPath(vertexIndex); - - const AB = sub(B.position, A.position); - const BC = sub(C.position, B.position); - const CD = sub(D.position, C.position); +)((vertexIndex, A, B, C, D, maxJoinCount) => { + 'use gpu'; + const AB = B.position - A.position; + const BC = C.position - B.position; + const DC = C.position - D.position; + const CB = neg(BC); const radiusABDelta = A.radius - B.radius; const radiusBCDelta = B.radius - C.radius; @@ -62,133 +28,89 @@ export const lineSegmentVariableWidth = tgpu.fn( // segments where one end completely contains the other are skipped // TODO: we should probably render a circle in some cases if (dot(BC, BC) <= radiusBCDelta * radiusBCDelta) { - return { - vertexPosition: vec2f(0, 0), - uv: vec2f(0, 0), - situationIndex: 0, - }; + return { vertexPosition: vec2f(0, 0), w: 1 }; } - const isCapB = dot(AB, AB) <= radiusABDelta * radiusABDelta + 1e-12; - const isCapC = dot(CD, CD) <= radiusCDDelta * radiusCDDelta + 1e-12; + const isCapB = dot(AB, AB) <= radiusABDelta * radiusABDelta; + const isCapC = dot(DC, DC) <= radiusCDDelta * radiusCDDelta; const eAB = externalNormals(AB, A.radius, B.radius); const eBC = externalNormals(BC, B.radius, C.radius); - const eCD = externalNormals(CD, C.radius, D.radius); - - const nBC = normalize(BC); - const nCB = mul(nBC, -1); - - let d0 = eBC.n1; - let d4 = eBC.n2; - let d5 = eBC.n2; - let d9 = eBC.n1; - - const situationIndexB = joinSituationIndex(eAB.n1, eBC.n1, eAB.n2, eBC.n2); - const situationIndexC = joinSituationIndex(eCD.n2, eBC.n2, eCD.n1, eBC.n1); - let joinBu = true; - let joinBd = true; - let joinCu = true; - let joinCd = true; - if (!isCapB) { - if (situationIndexB === 1 || situationIndexB === 5 || dot(eBC.n2, eAB.n2) > JOIN_LIMIT.$) { - d4 = miterPoint(eBC.n2, eAB.n2); - joinBd = false; - } - if (situationIndexB === 4 || situationIndexB === 5 || dot(eAB.n1, eBC.n1) > JOIN_LIMIT.$) { - d0 = miterPoint(eAB.n1, eBC.n1); - joinBu = false; - } + const eCB = ExternalNormals({ nL: eBC.nR, nR: eBC.nL }); + const eDC = externalNormals(DC, D.radius, C.radius); + + const joinLimit = dot(eBC.nL, BC); + const joinB = solveJoin(AB, BC, eAB, eBC, joinLimit, isCapB); + const joinC = solveJoin(DC, CB, eDC, eCB, -joinLimit, isCapC); + const d2 = joinB.dL; + const d3 = joinB.dR; + const d4 = joinC.dL; + const d5 = joinC.dR; + + const v2orig = B.position + d2 * B.radius; + const v3orig = B.position + d3 * B.radius; + const v4orig = C.position + d4 * C.radius; + const v5orig = C.position + d5 * C.radius; + + const limL = intersectLines(B.position, v2orig, C.position, v5orig); + const limR = intersectLines(B.position, v3orig, C.position, v4orig); + + const v2 = select(v2orig, limL.point, limL.valid); + const v5 = select(v5orig, limL.point, limL.valid); + const v3 = select(v3orig, limR.point, limR.valid); + const v4 = select(v4orig, limR.point, limR.valid); + + if (vertexIndex === 0) { + return { vertexPosition: B.position, w: 1 / B.radius }; } - if (!isCapC) { - if (situationIndexC === 4 || situationIndexC === 5 || dot(eCD.n2, eBC.n2) > JOIN_LIMIT.$) { - d5 = miterPoint(eCD.n2, eBC.n2); - joinCd = false; - } - if (situationIndexC === 1 || situationIndexC === 5 || dot(eBC.n1, eCD.n1) > JOIN_LIMIT.$) { - d9 = miterPoint(eBC.n1, eCD.n1); - joinCu = false; - } + if (vertexIndex === 1) { + return { vertexPosition: C.position, w: 1 / C.radius }; } - let v0 = addMul(B.position, d0, B.radius); - let v4 = addMul(B.position, d4, B.radius); - let v5 = addMul(C.position, d5, C.radius); - let v9 = addMul(C.position, d9, C.radius); - - const midBC = midPoint(B.position, C.position); - const tBC1 = rot90cw(eBC.n1); - const tBC2 = rot90ccw(eBC.n2); - - const limU = limitTowardsMiddle(midBC, tBC1, v0, v9); - const limD = limitTowardsMiddle(midBC, tBC2, v4, v5); - v0 = limU.a; - v9 = limU.b; - v4 = limD.a; - v5 = limD.b; - - // after this point we need to process only one of the joins! - const isCSide = joinPath.joinIndex >= 2; - - let situationIndex = situationIndexB; - let V = B; - let isCap = isCapB; - let j1 = eAB.n1; - let j2 = eBC.n1; - let j3 = eAB.n2; - let j4 = eBC.n2; - let vu = v0; - let vd = v4; - let joinU = joinBu; - let joinD = joinBd; - if (isCSide) { - situationIndex = situationIndexC; - V = C; - isCap = isCapC; - j4 = eBC.n1; - j3 = eCD.n1; - j2 = eBC.n2; - j1 = eCD.n2; - vu = v5; - vd = v9; - joinU = joinCd; - joinD = joinCu; - } + const coreVertexIndex = (vertexIndex - 2) & 0b11; + const joinVertexIndex = (vertexIndex - 2) >> 2; + let join = JoinInput(); - const joinIndex = joinPath.joinIndex; - if (vertexIndex >= 10) { - const shouldJoin = [u32(joinBu), u32(joinBd), u32(joinCd), u32(joinCu)]; - if (shouldJoin[joinIndex] === 0) { - const noJoinPoints = [v0, v4, v5, v9]; - const vertexPosition = noJoinPoints[joinIndex] as v2f; - return { - situationIndex, - vertexPosition, - }; - } + // oxfmt-ignore + if (coreVertexIndex === 0) { + join = JoinInput({ + C: B, v: v2, d: d2, shouldJoin: joinB.shouldJoinL, isCap: isCapB, fw: CB, + start: d2, + end: select(eAB.nL, d3, joinB.isHairpin || isCapB), + }); + } else if (coreVertexIndex === 1) { + join = JoinInput({ + C: B, v: v3, d: d3, shouldJoin: joinB.shouldJoinR, isCap: isCapB, fw: CB, + start: select(eAB.nR, d2, joinB.isHairpin || isCapB), + end: d3, + }); + } else if (coreVertexIndex === 2) { + join = JoinInput({ + C: C, v: v4, d: d4, shouldJoin: joinC.shouldJoinL, isCap: isCapC, fw: BC, + start: d4, + end: select(eDC.nL, d5, joinC.isHairpin || isCapC), + }); + } else { + join = JoinInput({ + C: C, v: v5, d: d5, shouldJoin: joinC.shouldJoinR, isCap: isCapC, fw: BC, + start: select(eDC.nR, d4, joinC.isHairpin || isCapC), + end: d5, + }); } - let vertexPosition = vec2f(); - - if (isCap) { - if (isCSide) { - vertexPosition = endCapSlot.$(vertexIndex, joinPath, V, vu, vd, j2, nBC, j4); + let vertexPosition = vec2f(join.v); + if (join.isCap) { + if (coreVertexIndex < 2) { + vertexPosition = startCapSlot.$(join, joinVertexIndex, maxJoinCount); } else { - vertexPosition = startCapSlot.$(vertexIndex, joinPath, V, vu, vd, j2, nCB, j4); + vertexPosition = endCapSlot.$(join, joinVertexIndex, maxJoinCount); } - } else { - // oxfmt-ignore - vertexPosition = joinSlot.$( - situationIndex, vertexIndex, - joinPath, - V, vu, vd, - j1, j2, j3, j4, - joinU, joinD - ); + } else if (join.shouldJoin) { + vertexPosition = joinSlot.$(join, joinVertexIndex, maxJoinCount); } - return { - situationIndex, - vertexPosition, - }; + // TODO: adjust for reverse miter + const w = select(1 / B.radius, 1 / C.radius, coreVertexIndex >= 2); + + return { vertexPosition, w }; }); diff --git a/packages/typegpu-geometry/src/lines/solveJoin.ts b/packages/typegpu-geometry/src/lines/solveJoin.ts new file mode 100644 index 0000000000..838cccd1cf --- /dev/null +++ b/packages/typegpu-geometry/src/lines/solveJoin.ts @@ -0,0 +1,42 @@ +import type { Infer, v2f } from 'typegpu/data'; +import { bool, struct, vec2f } from 'typegpu/data'; +import { dot, normalize, select } from 'typegpu/std'; +import { miterPointNoCheck } from '../utils.ts'; +import { MITER_DOT_PRODUCT_LIMIT } from './constants.ts'; +import type { ExternalNormals } from './externalNormals.ts'; + +type JoinResult = Infer; +const JoinResult = struct({ + dL: vec2f, + dR: vec2f, + shouldJoinL: bool, + shouldJoinR: bool, + isHairpin: bool, +}); + +export function solveJoin( + AB: v2f, + BC: v2f, + eAB: ExternalNormals, + eBC: ExternalNormals, + joinLimit: number, + isCap: boolean, +) { + 'use gpu'; + const underLimitL = dot(eAB.nL, BC) < joinLimit; + const underLimitR = dot(eAB.nR, BC) < joinLimit; + const isHairpin = + (dot(AB, BC) < 0 && underLimitL === underLimitR) || + dot(normalize(AB), normalize(BC)) < -MITER_DOT_PRODUCT_LIMIT; + const tooCloseToJoinL = dot(eAB.nL, eBC.nL) > MITER_DOT_PRODUCT_LIMIT; + const tooCloseToJoinR = dot(eAB.nR, eBC.nR) > MITER_DOT_PRODUCT_LIMIT; + const shouldJoinL = isHairpin || (underLimitL && !tooCloseToJoinL); + const shouldJoinR = isHairpin || (underLimitR && !tooCloseToJoinR); + + const dLMiter = miterPointNoCheck(eAB.nL, eBC.nL); + const dRMiter = miterPointNoCheck(eBC.nR, eAB.nR); + const dL = select(eBC.nL, dLMiter, !isCap && !shouldJoinL); + const dR = select(eBC.nR, dRMiter, !isCap && !shouldJoinR); + + return JoinResult({ dL, dR, shouldJoinL, shouldJoinR, isHairpin }); +} diff --git a/packages/typegpu-geometry/src/lines/types.ts b/packages/typegpu-geometry/src/lines/types.ts index 0be8e18467..feafae1d86 100644 --- a/packages/typegpu-geometry/src/lines/types.ts +++ b/packages/typegpu-geometry/src/lines/types.ts @@ -1,16 +1,34 @@ -import { f32, i32, struct, u32, vec2f } from 'typegpu/data'; import type { Infer } from 'typegpu/data'; +import { bool, f32, struct, vec2f } from 'typegpu/data'; -export type JoinPath = Infer; -export const JoinPath = struct({ - joinIndex: u32, - path: u32, - /** -1 for vertices on the original segment, >=0 for vertices inside the join */ - depth: i32, -}); - -export type LineSegmentVertex = Infer; -export const LineSegmentVertex = struct({ +export type LineControlPoint = Infer; +export const LineControlPoint = struct({ position: vec2f, radius: f32, }); + +export type LineSegmentOutput = Infer; +export const LineSegmentOutput = struct({ + vertexPosition: vec2f, + /** Homogeneous coordinate for scaling surface values */ + w: f32, +}); + +export type LineSegmentVertexData = Infer; +export const LineSegmentVertexData = struct({ + along: f32, + cross: f32, + join: f32, +}); + +export type JoinInput = Infer; +export const JoinInput = struct({ + C: LineControlPoint, + v: vec2f, + d: vec2f, + fw: vec2f, + start: vec2f, + end: vec2f, + shouldJoin: bool, + isCap: bool, +}); diff --git a/packages/typegpu-geometry/src/lines/utils.ts b/packages/typegpu-geometry/src/lines/utils.ts deleted file mode 100644 index 63177377ae..0000000000 --- a/packages/typegpu-geometry/src/lines/utils.ts +++ /dev/null @@ -1,171 +0,0 @@ -import tgpu from 'typegpu'; -import { arrayOf, bool, f32, struct, u32, vec2f } from 'typegpu/data'; -import { add, clamp, dot, length, max, mix, mul, normalize, select, sqrt, sub } from 'typegpu/std'; -import { addMul, bisectCcw, cross2d, midPoint, rot90ccw } from '../utils.ts'; - -/** Intersects tangent to point on a circle `a` with line from center in direction `n`. */ -export const intersectTangent = tgpu.fn( - [vec2f, vec2f], - vec2f, -)((a, n) => { - const cos_ = dot(a, n); - return mul(n, 1 / cos_); -}); - -/** - * Finds the miter point of tangents to two points on a circle. - * The miter point is on the smaller arc. - */ -export const miterPointNoCheck = tgpu.fn( - [vec2f, vec2f], - vec2f, -)((a, b) => { - const ab = add(a, b); - return mul(ab, 2 / dot(ab, ab)); -}); - -/** - * Finds the miter point of tangents to two points on respective circles. - * The miter point is on the counter-clockwise arc between the circles if possible, - * otherwise at "infinity". - */ -export const miterPoint = tgpu.fn( - [vec2f, vec2f], - vec2f, -)((a, b) => { - const sin_ = cross2d(a, b); - const bisection = bisectCcw(a, b); - const b2 = dot(b, b); - const cos_ = dot(a, b); - const diff = b2 - cos_; - // TODO: make this check relative - if (diff * diff < 1e-4) { - // the vectors are almost colinear - return midPoint(a, b); - } - if (sin_ < 0) { - // if the miter is at infinity, just make it super far - return mul(bisection, -1e6); - } - const t = diff / sin_; - return addMul(a, rot90ccw(a), t); -}); - -const ExternalNormals = struct({ - n1: vec2f, - n2: vec2f, -}); - -/** - * Computes external tangent directions (normals to tangent) - * for two circles at a `distance` with radii `r1` and `r2`. - */ -export const externalNormals = tgpu.fn( - [vec2f, f32, f32], - ExternalNormals, -)((distance, r1, r2) => { - const dNorm = normalize(distance); - const expCos = (r1 - r2) / length(distance); - const expSin = sqrt(max(0, 1 - expCos * expCos)); - const a = dNorm.x * expCos; - const b = dNorm.y * expSin; - const c = dNorm.x * expSin; - const d = dNorm.y * expCos; - const n1 = vec2f(a - b, c + d); - const n2 = vec2f(a + b, -c + d); - return ExternalNormals({ n1, n2 }); -}); - -const Intersection = struct({ - valid: bool, - t: f32, - point: vec2f, -}); - -export const intersectLines = tgpu.fn( - [vec2f, vec2f, vec2f, vec2f], - Intersection, -)((A1, A2, B1, B2) => { - const a = sub(A2, A1); - const b = sub(B2, B1); - const axb = cross2d(a, b); - const AB = sub(B1, A1); - const t = cross2d(AB, b) / axb; - return { - valid: axb !== 0, - t, - point: addMul(A1, a, t), - }; -}); - -const LimitAlongResult = struct({ - a: vec2f, - b: vec2f, - limitWasHit: bool, -}); - -/** - * Leaves a and b separate if no collision, otherwise merges them towards "middle". - */ -export const limitTowardsMiddle = tgpu.fn( - [vec2f, vec2f, vec2f, vec2f], - LimitAlongResult, -)((middle, dir, p1, p2) => { - const t1 = dot(sub(p1, middle), dir); - const t2 = dot(sub(p2, middle), dir); - if (t1 <= t2) { - return LimitAlongResult({ a: p1, b: p2, limitWasHit: false }); - } - const t = clamp(t1 / (t1 - t2), 0, 1); - const p = mix(p1, p2, t); - return LimitAlongResult({ a: p, b: p, limitWasHit: true }); -}); - -export const projectToLineSegment = tgpu.fn( - [vec2f, vec2f, vec2f], - vec2f, -)((A, B, point) => { - const p = sub(point, A); - const AB = sub(B, A); - const t = clamp(dot(p, AB) / dot(AB, AB), 0, 1); - const projP = addMul(A, AB, t); - return projP; -}); - -export const uvToLineSegment = tgpu.fn( - [vec2f, vec2f, vec2f], - vec2f, -)((A, B, point) => { - const p = sub(point, A); - const AB = sub(B, A); - const x = dot(p, AB) / dot(AB, AB); - const y = cross2d(normalize(AB), p); - return vec2f(x, y); -}); - -const lookup = tgpu.const(arrayOf(u32, 8), [ - 5, // 000 c >= b >= a - 3, // 001 INVALID - 4, // 010 b > c >= a - 3, // 011 b >= a > c - 2, // 100 c >= a > b - 1, // 101 a > c >= b - 0, // 110 INVALID - 0, // 111 a > b > c -]); - -export const rank3 = tgpu.fn( - [bool, bool, bool], - u32, -)((aGb, bGc, aGc) => { - const code = (u32(aGb) << 2) | (u32(bGc) << 1) | u32(aGc); - return lookup.$[code] as number; -}); - -export const isCCW = tgpu.fn( - [f32, bool, f32, bool], - bool, -)((aX, aYSign, bX, bYSign) => { - const sameSide = aYSign === bYSign; - return select(aYSign, aYSign === aX >= bX, sameSide); -}); diff --git a/packages/typegpu-geometry/src/utils.ts b/packages/typegpu-geometry/src/utils.ts index e8e49b01ba..11c58732bf 100644 --- a/packages/typegpu-geometry/src/utils.ts +++ b/packages/typegpu-geometry/src/utils.ts @@ -1,20 +1,13 @@ import tgpu from 'typegpu'; -import { f32, vec2f } from 'typegpu/data'; -import { add, dot, mul, normalize, select } from 'typegpu/std'; - -/** Shorthand for `add(a, mul(b, f))` due to lack of operators */ -export const addMul = tgpu.fn( - [vec2f, vec2f, f32], - vec2f, -)((a, b, f) => { - return add(a, mul(b, f)); -}); +import { bool, f32, struct, vec2f } from 'typegpu/data'; +import { dot, mix, normalize, select } from 'typegpu/std'; /** Rotates a 2D vector counter-clockwise by 90 degrees */ export const rot90ccw = tgpu.fn( [vec2f], vec2f, )((v) => { + 'use gpu'; return vec2f(-v.y, v.x); }); @@ -23,6 +16,7 @@ export const rot90cw = tgpu.fn( [vec2f], vec2f, )((v) => { + 'use gpu'; return vec2f(v.y, -v.x); }); @@ -34,6 +28,7 @@ export const cross2d = tgpu.fn( [vec2f, vec2f], f32, )((a, b) => { + 'use gpu'; return a.x * b.y - a.y * b.x; }); @@ -46,29 +41,85 @@ export const bisectCcw = tgpu.fn( [vec2f, vec2f], vec2f, )((a, b) => { + 'use gpu'; const sin = cross2d(a, b); const sinSign = select(f32(-1), f32(1), sin >= 0); const orthoA = rot90ccw(a); const orthoB = rot90cw(b); - const dir = select(mul(add(a, b), sinSign), add(orthoA, orthoB), dot(a, b) < 0); + const dir = select((a + b) * sinSign, orthoA + orthoB, dot(a, b) < 0); return normalize(dir); }); +/** + * Finds the miter point of tangents to two points on a circle. + * The miter point is on the smaller arc. + */ +export const miterPointNoCheck = tgpu.fn( + [vec2f, vec2f], + vec2f, +)((a, b) => { + 'use gpu'; + const ab = a + b; + return ab * (2 / dot(ab, ab)); +}); + /** * Finds bisector direction between two vectors. * There is no check done to be on the CW part, instead - * it is assumed that a and b are well less than 180 degrees apart. + * it is assumed that a and b are significantly less than 180 degrees apart. */ export const bisectNoCheck = tgpu.fn( [vec2f, vec2f], vec2f, )((a, b) => { - return normalize(add(a, b)); + 'use gpu'; + return normalize(a + b); }); export const midPoint = tgpu.fn( [vec2f, vec2f], vec2f, )((a, b) => { - return mul(0.5, add(a, b)); + 'use gpu'; + return (a + b) * 0.5; +}); + +export const slerpApprox = tgpu.fn( + [vec2f, vec2f, f32], + vec2f, +)((a, b, t) => { + 'use gpu'; + const mid = bisectNoCheck(a, b); + let a_ = vec2f(a); + let b_ = vec2f(mid); + let t_ = 2 * t; + if (t > 0.5) { + a_ = vec2f(mid); + b_ = vec2f(b); + t_ -= 1; + } + return normalize(mix(a_, b_, t_)); +}); + +const Intersection = struct({ + valid: bool, + t: f32, + point: vec2f, +}); + +export const intersectLines = tgpu.fn( + [vec2f, vec2f, vec2f, vec2f], + Intersection, +)((A1, A2, B1, B2) => { + 'use gpu'; + const a = A2 - A1; + const b = B2 - B1; + const axb = cross2d(a, b); + const AB = B1 - A1; + const t = cross2d(AB, b) / axb; + return { + valid: axb !== 0 && t >= 0 && t <= 1, + t, + point: A1 + a * t, + }; }); From f1da8b5436794895b691660142c8ef98d8734f89 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:32:25 +0200 Subject: [PATCH 05/34] Add some basic tests --- .../typegpu/tests/mutabilityTracking.test.ts | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 packages/typegpu/tests/mutabilityTracking.test.ts diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts new file mode 100644 index 0000000000..f568f82238 --- /dev/null +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -0,0 +1,140 @@ +import { describe, expect, it } from 'vitest'; +import * as d from '../src/data/index.ts'; +import tgpu from '../src/index.js'; + +const fnShell = tgpu.fn([d.vec4u], d.u32); + +describe('mutability tracking', () => { + describe('resolves modified to var', () => { + it('resolves modified primitive let arg to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = arg.x; + a += 1; + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg.x; + a += 1u; + return a; + }" + `); + }); + + it('resolves modified reference const arg to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + const a = d.vec4u(arg); + a.x = 1; + return a.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = arg'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg; + a.x = 1u; + return a.x; + }" + `); + }); + + it('resolves modified reference let arg to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = d.vec4u(arg); + a += 1; + return a.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = arg'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg; + a += 1; + return a.x; + }" + `); + }); + + it('resolves reassigned reference let arg to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = d.vec4u(arg); + a = d.vec4u(); + return a.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = arg'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg; + a = vec4u(); + return a.x; + }" + `); + }); + }); + + describe('resolves unmodified to let', () => { + it('resolves unmodified primitive const arg', () => { + const fn = fnShell((arg) => { + 'use gpu'; + const a = arg.x; + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('let a = arg.x'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + let a = arg.x; + return a; + }" + `); + }); + + it('resolves unmodified primitive let arg', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = arg.x; + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('let a = arg.x'); + expect(resolved).toMatchInlineSnapshot(); + }); + + it('resolves unmodified reference const arg', () => { + const fn = fnShell((arg) => { + 'use gpu'; + const a = d.vec4u(arg); + return a.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('let a = arg'); + expect(resolved).toMatchInlineSnapshot(); + }); + + it('resolves unmodified reference let arg', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = d.vec4u(arg); + return a.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('let a = arg'); + expect(resolved).toMatchInlineSnapshot(); + }); + }); +}); From 591298fe20a84f5f27dc2d66b96d168dd5b497b0 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:38:51 +0200 Subject: [PATCH 06/34] Add more tests --- .../typegpu/tests/mutabilityTracking.test.ts | 86 +++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index f568f82238..b2874b834a 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -6,7 +6,47 @@ const fnShell = tgpu.fn([d.vec4u], d.u32); describe('mutability tracking', () => { describe('resolves modified to var', () => { - it('resolves modified primitive let arg to var', () => { + it('resolves reassigned primitive let to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = arg.x; + a = 1; + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg.x; + a += 1u; + return a; + }" + `); + }); + + it('resolves conditionally reassigned primitive let to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = arg.x; + if (a < 1) { + a = 1; + } + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg.x; + a += 1u; + return a; + }" + `); + }); + + it('resolves mutated primitive let to var', () => { const fn = fnShell((arg) => { 'use gpu'; let a = arg.x; @@ -25,7 +65,26 @@ describe('mutability tracking', () => { `); }); - it('resolves modified reference const arg to var', () => { + it('resolves incremented primitive let to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = arg.x; + a++; + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg.x; + a += 1u; + return a; + }" + `); + }); + + it('resolves modified reference const to var', () => { const fn = fnShell((arg) => { 'use gpu'; const a = d.vec4u(arg); @@ -44,11 +103,11 @@ describe('mutability tracking', () => { `); }); - it('resolves modified reference let arg to var', () => { + it('resolves modified reference let to var', () => { const fn = fnShell((arg) => { 'use gpu'; let a = d.vec4u(arg); - a += 1; + a.x = 1; return a.x; }); @@ -63,7 +122,7 @@ describe('mutability tracking', () => { `); }); - it('resolves reassigned reference let arg to var', () => { + it('resolves reassigned reference let to var', () => { const fn = fnShell((arg) => { 'use gpu'; let a = d.vec4u(arg); @@ -137,4 +196,21 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(); }); }); + + it('resolves shadowed variables correctly', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = d.vec4u(arg); + { + let a = d.vec4u(arg); + a.x++; + } + return a.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('let a = arg'); + expect(resolved).toContain('var a = arg'); + expect(resolved).toMatchInlineSnapshot(); + }); }); From 8f27d44688c71869ed8e7e25340ce1d2e70ff17e Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:54:39 +0200 Subject: [PATCH 07/34] Add reference tests --- .../typegpu/tests/mutabilityTracking.test.ts | 90 ++++++++++++++++++- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index b2874b834a..7e385593ac 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -19,7 +19,7 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg.x; - a += 1u; + a = 1u; return a; }" `); @@ -40,7 +40,9 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg.x; - a += 1u; + if ((a < 1u)) { + a = 1u; + } return a; }" `); @@ -78,7 +80,7 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg.x; - a += 1u; + a++; return a; }" `); @@ -116,7 +118,7 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg; - a += 1; + a.x = 1u; return a.x; }" `); @@ -213,4 +215,84 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = arg'); expect(resolved).toMatchInlineSnapshot(); }); + + describe('references', () => { + it('resolves a primitive variable used with d.ref to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + const a = d.ref(0); + return a.$; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = 0'); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = 0; + return u32(a); + }" + `); + }); + + it('resolves a complex variable used with d.ref to var', () => { + const fn = () => { + 'use gpu'; + const a = d.ref(d.vec4u()); + return a.$.x; + }; + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var a = vec4u()'); + expect(resolved).toMatchInlineSnapshot(` + "fn fn_1() -> u32 { + var a = vec4u(); + return a.x; + }" + `); + }); + + it('resolves a struct to var when its prop is referenced', () => { + const Struct = d.struct({ prop: d.vec4f }); + const fn = () => { + 'use gpu'; + const struct = Struct(); + const prop = struct.prop; + }; + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('var struct_1 = Struct()'); + expect(resolved).toMatchInlineSnapshot(` + "struct Struct { + prop: vec4f, + } + + fn fn_1() { + var struct_1 = Struct(); + let prop = (&struct_1.prop); + }" + `); + }); + + it('resolves a struct to let when its prop is only read', () => { + const Struct = d.struct({ prop: d.vec4f }); + const fn = () => { + 'use gpu'; + const struct = Struct(); + return struct.prop; + }; + + const resolved = tgpu.resolve([fn]); + expect(resolved).toContain('let struct_1 = Struct()'); + expect(resolved).toMatchInlineSnapshot(` + "struct Struct { + prop: vec4f, + } + + fn fn_1() -> vec4f { + var struct_1 = Struct(); + return struct_1.prop; + }" + `); + }); + }); }); From 08650b3f4295920ce4d741fd58e2e54067f4f534 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:31:18 +0200 Subject: [PATCH 08/34] Move snapshots higher in tests --- .../typegpu/tests/mutabilityTracking.test.ts | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 7e385593ac..6cf1155883 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -15,7 +15,6 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = arg.x'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg.x; @@ -23,6 +22,7 @@ describe('mutability tracking', () => { return a; }" `); + expect(resolved).toContain('var a = arg.x'); }); it('resolves conditionally reassigned primitive let to var', () => { @@ -36,7 +36,6 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = arg.x'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg.x; @@ -46,6 +45,7 @@ describe('mutability tracking', () => { return a; }" `); + expect(resolved).toContain('var a = arg.x'); }); it('resolves mutated primitive let to var', () => { @@ -57,7 +57,6 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = arg.x'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg.x; @@ -65,6 +64,7 @@ describe('mutability tracking', () => { return a; }" `); + expect(resolved).toContain('var a = arg.x'); }); it('resolves incremented primitive let to var', () => { @@ -76,7 +76,6 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = arg.x'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg.x; @@ -84,6 +83,7 @@ describe('mutability tracking', () => { return a; }" `); + expect(resolved).toContain('var a = arg.x'); }); it('resolves modified reference const to var', () => { @@ -95,7 +95,6 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = arg'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg; @@ -103,6 +102,7 @@ describe('mutability tracking', () => { return a.x; }" `); + expect(resolved).toContain('var a = arg'); }); it('resolves modified reference let to var', () => { @@ -114,7 +114,6 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = arg'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg; @@ -122,6 +121,7 @@ describe('mutability tracking', () => { return a.x; }" `); + expect(resolved).toContain('var a = arg'); }); it('resolves reassigned reference let to var', () => { @@ -133,7 +133,6 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = arg'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg; @@ -141,6 +140,7 @@ describe('mutability tracking', () => { return a.x; }" `); + expect(resolved).toContain('var a = arg'); }); }); @@ -153,13 +153,13 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('let a = arg.x'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { let a = arg.x; return a; }" `); + expect(resolved).toContain('let a = arg.x'); }); it('resolves unmodified primitive let arg', () => { @@ -170,8 +170,13 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg.x; + return a; + }" + `); expect(resolved).toContain('let a = arg.x'); - expect(resolved).toMatchInlineSnapshot(); }); it('resolves unmodified reference const arg', () => { @@ -182,8 +187,13 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg; + return a.x; + }" + `); expect(resolved).toContain('let a = arg'); - expect(resolved).toMatchInlineSnapshot(); }); it('resolves unmodified reference let arg', () => { @@ -194,8 +204,13 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg; + return a.x; + }" + `); expect(resolved).toContain('let a = arg'); - expect(resolved).toMatchInlineSnapshot(); }); }); @@ -211,9 +226,18 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg; + { + var a_1 = arg; + a_1.x++; + } + return a.x; + }" + `); expect(resolved).toContain('let a = arg'); expect(resolved).toContain('var a = arg'); - expect(resolved).toMatchInlineSnapshot(); }); describe('references', () => { @@ -225,13 +249,13 @@ describe('mutability tracking', () => { }); const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = 0'); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = 0; return u32(a); }" `); + expect(resolved).toContain('var a = 0'); }); it('resolves a complex variable used with d.ref to var', () => { @@ -242,13 +266,13 @@ describe('mutability tracking', () => { }; const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var a = vec4u()'); expect(resolved).toMatchInlineSnapshot(` "fn fn_1() -> u32 { var a = vec4u(); return a.x; }" `); + expect(resolved).toContain('var a = vec4u()'); }); it('resolves a struct to var when its prop is referenced', () => { @@ -260,7 +284,6 @@ describe('mutability tracking', () => { }; const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('var struct_1 = Struct()'); expect(resolved).toMatchInlineSnapshot(` "struct Struct { prop: vec4f, @@ -271,6 +294,7 @@ describe('mutability tracking', () => { let prop = (&struct_1.prop); }" `); + expect(resolved).toContain('var struct_1 = Struct()'); }); it('resolves a struct to let when its prop is only read', () => { @@ -282,7 +306,6 @@ describe('mutability tracking', () => { }; const resolved = tgpu.resolve([fn]); - expect(resolved).toContain('let struct_1 = Struct()'); expect(resolved).toMatchInlineSnapshot(` "struct Struct { prop: vec4f, @@ -293,6 +316,7 @@ describe('mutability tracking', () => { return struct_1.prop; }" `); + expect(resolved).toContain('let struct_1 = Struct()'); }); }); }); From 52f52c0c3e5397b6ff812e64d141d92d771c3369 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:34:36 +0200 Subject: [PATCH 09/34] Add a stub of implementation --- packages/typegpu/src/resolutionCtx.ts | 2 ++ packages/typegpu/src/tgsl/wgslGenerator.ts | 30 +++++++++++++++++-- packages/typegpu/src/types.ts | 8 +++++ .../typegpu/tests/mutabilityTracking.test.ts | 5 ++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 57f87d7e84..f9183d4995 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -133,6 +133,8 @@ class ItemStateStackImpl implements ItemStateStack { returnType, externalMap, reportedReturnTypes: new Set(), + placeholderForVariable: new Map(), + modifiedVariables: new Set(), }; this._stack.push(scope); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index c795d78b9e..3782ae6bb2 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -845,7 +845,20 @@ ${this.ctx.pre}}`; public functionDefinition(options: FunctionDefinitionOptions): string { // Function body - const body = this._block(options.body); + let body = this._block(options.body); + const scope = this.ctx.topFunctionScope; + invariant(scope, 'Expected function scope to be present'); + // TODO: optimize this to one pass + body = scope.modifiedVariables.values().reduce((body: string, variable: Snippet) => { + const placeholder = scope.placeholderForVariable.get(variable); + invariant( + placeholder, + `Expected placeholder (like #VAR_3#) to be present for ${variable.value}`, + ); + return body.replaceAll(placeholder, 'var'); + }, body); + // TODO: replace with 'let' once we actually report modified variables + body = body.replaceAll(/#VAR_[0-9]+#/g, 'var'); // Function header const returnType = options.determineReturnType(); @@ -1004,7 +1017,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.let || statement[0] === NODE.const) { - let varType: 'var' | 'let' | 'const' = 'var'; + let varType: 'var' | 'let' | 'const' | undefined; const [stmtType, rawId, rawValue] = statement; const eq = rawValue !== undefined ? this._expression(rawValue) : undefined; @@ -1108,7 +1121,20 @@ ${this.ctx.pre}else ${alternate}`; } } + // TODO: move this inside block + if (varType === undefined) { + const scope = this.ctx.topFunctionScope; + invariant(scope, 'Expected function scope to be present'); + const index = scope.placeholderForVariable.size; + varType = `#VAR_${index}#`; + } const snippet = this.blockVariable(varType, rawId, concretize(dataType), eq.origin); + if (varType?.startsWith('#')) { + const scope = this.ctx.topFunctionScope; + invariant(scope, 'Expected function scope to be present'); + scope.placeholderForVariable.set(snippet, varType); + } + const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false); const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value; return this.emitVarDecl( diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index d2b7892faf..0d5e488bb3 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -122,6 +122,14 @@ export type FunctionScopeLayer = { * All types used in `return` statements. */ reportedReturnTypes: Set; + /** + * Maps variables to their modifier placeholders + */ + placeholderForVariable: Map; + /** + * Local variables that need `var` modifier. + */ + modifiedVariables: Set; }; export type SlotBindingLayer = { diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 6cf1155883..700f9d0f7c 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -217,10 +217,11 @@ describe('mutability tracking', () => { it('resolves shadowed variables correctly', () => { const fn = fnShell((arg) => { 'use gpu'; - let a = d.vec4u(arg); + const a = d.vec4u(arg); { - let a = d.vec4u(arg); + const a = d.vec4u(arg); a.x++; + const b = a; } return a.x; }); From 87e3cd4a889f747f5dcb3ebf92586762ec1fa32d Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:31:53 +0200 Subject: [PATCH 10/34] Mark assignments as modified --- packages/typegpu/src/tgsl/wgslGenerator.ts | 71 ++++++++++++++----- .../typegpu/tests/mutabilityTracking.test.ts | 21 +++--- 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 3782ae6bb2..f6720ff45c 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -229,7 +229,7 @@ ${this.ctx.pre}}`; } public blockVariable( - varType: 'var' | 'let' | 'const', + varType: 'var' | 'let' | 'const' | undefined, id: string, dataType: wgsl.BaseData | UnknownData, origin: Origin, @@ -264,9 +264,13 @@ ${this.ctx.pre}}`; return snippet; } + /** + * Creates a variable declaration string. + * `keyword` may be a placeholder filled in later. + */ protected emitVarDecl( pre: string, - keyword: 'var' | 'let' | 'const', + keyword: string, name: string, _dataType: wgsl.BaseData | UnknownData, rhsStr: string, @@ -406,6 +410,8 @@ ${this.ctx.pre}}`; ); } + this.tryMarkModified(lhs); + // Compound assignment operators are okay, e.g. +=, -=, *=, /=, ... if ( op === '=' && @@ -857,8 +863,7 @@ ${this.ctx.pre}}`; ); return body.replaceAll(placeholder, 'var'); }, body); - // TODO: replace with 'let' once we actually report modified variables - body = body.replaceAll(/#VAR_[0-9]+#/g, 'var'); + body = body.replaceAll(/#VAR_[0-9]+#/g, 'let'); // Function header const returnType = options.determineReturnType(); @@ -1121,25 +1126,23 @@ ${this.ctx.pre}else ${alternate}`; } } - // TODO: move this inside block - if (varType === undefined) { - const scope = this.ctx.topFunctionScope; - invariant(scope, 'Expected function scope to be present'); - const index = scope.placeholderForVariable.size; - varType = `#VAR_${index}#`; - } const snippet = this.blockVariable(varType, rawId, concretize(dataType), eq.origin); - if (varType?.startsWith('#')) { + const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false); + const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value; + + let emittedVarType: string | undefined = varType; + if (emittedVarType === undefined) { const scope = this.ctx.topFunctionScope; - invariant(scope, 'Expected function scope to be present'); - scope.placeholderForVariable.set(snippet, varType); + const snippet = this.ctx.getById(rawId); + invariant(scope, `Expected function scope to be present for ${rawId}`); + invariant(snippet, `Expected snippet to be present for ${rawId}`); + emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`; + scope.placeholderForVariable.set(snippet, emittedVarType); } - const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false); - const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value; return this.emitVarDecl( this.ctx.pre, - varType, + emittedVarType, snippet.value as string, concretize(dataType), rhsStr, @@ -1317,6 +1320,30 @@ ${this.ctx.pre}else ${alternate}`; // oxlint-disable-next-line typescript/no-base-to-string return resolved ? `${this.ctx.pre}${resolved};` : ''; } + + /** + * Attempts a member access lookup to mark a variable as modified. + * @example + * // given `let a; a = 1;` + * tryMarkModified('a') // `a` is marked in the function scope + * + * // given `const obj; obj.prop = 1;` + * tryMarkModified('obj.prop') // `obj` is marked in the function scope + * + * // given `this.buffer.$;` + * tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it + */ + private tryMarkModified(expr: tinyest.Expression) { + const maybeObject = extractObject(expr); + if (maybeObject !== undefined) { + const snippet = this.ctx.getById(maybeObject); + const scope = this.ctx.topFunctionScope; + if (snippet && scope && scope.placeholderForVariable.has(snippet)) { + console.log(`Adding ${maybeObject} (${snippet.value}) to modified variables.`); + this.ctx.topFunctionScope?.modifiedVariables.add(snippet); + } + } + } } function assertExhaustive(value: never): never { @@ -1343,5 +1370,15 @@ function blockifySingleStatement(statement: tinyest.Statement): tinyest.Block { : statement; } +function extractObject(expr: tinyest.Expression): string | undefined { + let object = expr; + while (Array.isArray(object) && object[0] === NODE.memberAccess) { + object = object[1]; + } + if (typeof object === 'string') { + return object; + } +} + const wgslGenerator: WgslGenerator = new WgslGenerator(); export default wgslGenerator; diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 700f9d0f7c..0d55285a0d 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -78,7 +78,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = arg.x; + let a = arg.x; a++; return a; }" @@ -172,7 +172,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = arg.x; + let a = arg.x; return a; }" `); @@ -189,7 +189,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = arg; + let a = arg; return a.x; }" `); @@ -206,7 +206,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = arg; + let a = arg; return a.x; }" `); @@ -220,7 +220,7 @@ describe('mutability tracking', () => { const a = d.vec4u(arg); { const a = d.vec4u(arg); - a.x++; + a.x = 2; const b = a; } return a.x; @@ -229,16 +229,17 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = arg; + let a = arg; { var a_1 = arg; - a_1.x++; + a_1.x = 2u; + let b = (&a_1); } return a.x; }" `); expect(resolved).toContain('let a = arg'); - expect(resolved).toContain('var a = arg'); + expect(resolved).toContain('var a_1 = arg'); }); describe('references', () => { @@ -291,7 +292,7 @@ describe('mutability tracking', () => { } fn fn_1() { - var struct_1 = Struct(); + let struct_1 = Struct(); let prop = (&struct_1.prop); }" `); @@ -313,7 +314,7 @@ describe('mutability tracking', () => { } fn fn_1() -> vec4f { - var struct_1 = Struct(); + let struct_1 = Struct(); return struct_1.prop; }" `); From d612682eecc9075ae8ad4817a9f9892c28e90f53 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:02:29 +0200 Subject: [PATCH 11/34] Add more tests and handle update --- packages/typegpu/src/tgsl/wgslGenerator.ts | 2 + .../typegpu/tests/mutabilityTracking.test.ts | 111 +++++++++++++++++- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index f6720ff45c..fbdc3a6aab 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -451,6 +451,8 @@ ${this.ctx.pre}}`; const argExpr = this._expression(arg); const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value; + this.tryMarkModified(arg); + // Result of an operation, so not a reference to anything return snip(`${argStr}${op}`, argExpr.dataType, /* origin */ 'runtime'); } diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 0d55285a0d..e37376c564 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -5,6 +5,115 @@ import tgpu from '../src/index.js'; const fnShell = tgpu.fn([d.vec4u], d.u32); describe('mutability tracking', () => { + describe('different nodes', () => { + it('leaves unmodified variables as let', () => { + const fn = fnShell((arg) => { + 'use gpu'; + const a = arg.x; + const b = d.vec4u(arg); + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + let a = arg.x; + let b = arg; + return a; + }" + `); + expect(resolved).toContain('let a = arg.x'); + expect(resolved).toContain('let b = arg'); + }); + + it('resolves reassigned variable to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = arg.x; + const b = d.vec4u(arg); + + a = 1; + b.x = 1; + + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg.x; + var b = arg; + a = 1u; + b.x = 1u; + return a; + }" + `); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toContain('var b = arg'); + }); + + it('resolves incremented variable to var', () => { + const fn = fnShell((arg) => { + 'use gpu'; + let a = arg.x; + const b = d.vec4u(arg); + + a++; + b.x++; + + return a; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = arg.x; + var b = arg; + a++; + b.x++; + return a; + }" + `); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toContain('var b = arg'); + }); + + it('resolves pointed variable to var', () => { + const modify = tgpu.fn([d.ptrFn(d.u32), d.ptrFn(d.vec4u)])((num, vec) => { + 'use gpu'; + num.$ += 1; + vec.$ += 1; + }); + + const fn = fnShell((arg) => { + 'use gpu'; + let a = d.ref(d.u32(arg.x)); + const b = d.vec4u(arg); + + modify(a, d.ref(b)); + + return a.$; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn modify(num: ptr, vec: ptr) { + (*num) += 1u; + (*vec) += 1; + } + + fn item(arg: vec4u) -> u32 { + var a = arg.x; + let b = arg; + modify((&a), (&b)); + return a; + }" + `); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toContain('var b = arg'); + }); + }); + describe('resolves modified to var', () => { it('resolves reassigned primitive let to var', () => { const fn = fnShell((arg) => { @@ -78,7 +187,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - let a = arg.x; + var a = arg.x; a++; return a; }" From 613c3ee6a2db3ff120a40c78ed0447a6b2ad86cd Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:39:09 +0200 Subject: [PATCH 12/34] Handle refs --- packages/typegpu/src/tgsl/wgslGenerator.ts | 6 +++++- packages/typegpu/tests/mutabilityTracking.test.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index fbdc3a6aab..d18eb58397 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -41,7 +41,7 @@ import { accessProp } from './accessProp.ts'; import type { ShaderGenerator } from './shaderGenerator.ts'; import { resolveData } from '../core/resolve/resolveData.ts'; import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; -import { RefOperator } from '../data/ref.ts'; +import { _ref, RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; import { UnrollableIterable } from '../core/unroll/tgpuUnroll.ts'; import { isGenericFn } from '../core/function/tgpuFn.ts'; @@ -637,6 +637,10 @@ ${this.ctx.pre}}`; ); } + if (callee.value === _ref && argNodes[0]) { + this.tryMarkModified(argNodes[0]); + } + if (isGPUCallable(callee.value)) { const callable = callee.value[$gpuCallable]; const strictSignature = callable.strictSignature; diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index e37376c564..13339f9455 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -104,7 +104,7 @@ describe('mutability tracking', () => { fn item(arg: vec4u) -> u32 { var a = arg.x; - let b = arg; + var b = arg; modify((&a), (&b)); return a; }" From bcb1735fe4743f3e7a22b9d63c2e5e09f01097f8 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:51:59 +0200 Subject: [PATCH 13/34] Handle js references --- packages/typegpu/src/tgsl/wgslGenerator.ts | 11 ++- .../typegpu/tests/mutabilityTracking.test.ts | 69 ++++++++++++------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index d18eb58397..d36dd6c3ba 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1100,6 +1100,7 @@ ${this.ctx.pre}else ${alternate}`; // If what we're assigning is something preceded by `&`, then it's a value // created using `d.ref()`. Otherwise, it's an implicit pointer dataType = implicitFrom(dataType as wgsl.Ptr); + this.tryMarkModified(rawValue); } } } else { @@ -1339,7 +1340,10 @@ ${this.ctx.pre}else ${alternate}`; * // given `this.buffer.$;` * tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it */ - private tryMarkModified(expr: tinyest.Expression) { + private tryMarkModified(expr?: tinyest.Expression) { + if (!expr) { + return; + } const maybeObject = extractObject(expr); if (maybeObject !== undefined) { const snippet = this.ctx.getById(maybeObject); @@ -1378,7 +1382,10 @@ function blockifySingleStatement(statement: tinyest.Statement): tinyest.Block { function extractObject(expr: tinyest.Expression): string | undefined { let object = expr; - while (Array.isArray(object) && object[0] === NODE.memberAccess) { + while ( + Array.isArray(object) && + (object[0] === NODE.memberAccess || object[0] === NODE.indexAccess) + ) { object = object[1]; } if (typeof object === 'string') { diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 13339f9455..8e1b4cb99c 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -78,39 +78,25 @@ describe('mutability tracking', () => { expect(resolved).toContain('var b = arg'); }); - it('resolves pointed variable to var', () => { - const modify = tgpu.fn([d.ptrFn(d.u32), d.ptrFn(d.vec4u)])((num, vec) => { - 'use gpu'; - num.$ += 1; - vec.$ += 1; - }); - + it('resolves index-accessed variable to var', () => { const fn = fnShell((arg) => { 'use gpu'; - let a = d.ref(d.u32(arg.x)); - const b = d.vec4u(arg); + const a = [1, 2, 3]; - modify(a, d.ref(b)); + a[0] = 1; - return a.$; + return a[0]; }); const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` - "fn modify(num: ptr, vec: ptr) { - (*num) += 1u; - (*vec) += 1; - } - - fn item(arg: vec4u) -> u32 { - var a = arg.x; - var b = arg; - modify((&a), (&b)); - return a; + "fn item(arg: vec4u) -> u32 { + var a = array(1, 2, 3); + a[0i] = 1i; + return u32(a[0i]); }" `); - expect(resolved).toContain('var a = arg.x'); - expect(resolved).toContain('var b = arg'); + expect(resolved).toContain('var a ='); }); }); @@ -386,6 +372,41 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = vec4u()'); }); + it('resolves pointed variable to var', () => { + const modify = tgpu.fn([d.ptrFn(d.u32), d.ptrFn(d.vec4u)])((num, vec) => { + 'use gpu'; + num.$ += 1; + vec.$ += 1; + }); + + const fn = fnShell((arg) => { + 'use gpu'; + let a = d.ref(d.u32(arg.x)); + const b = d.vec4u(arg); + + modify(a, d.ref(b)); + + return a.$; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn modify(num: ptr, vec: ptr) { + (*num) += 1u; + (*vec) += 1; + } + + fn item(arg: vec4u) -> u32 { + var a = arg.x; + var b = arg; + modify((&a), (&b)); + return a; + }" + `); + expect(resolved).toContain('var a = arg.x'); + expect(resolved).toContain('var b = arg'); + }); + it('resolves a struct to var when its prop is referenced', () => { const Struct = d.struct({ prop: d.vec4f }); const fn = () => { @@ -401,7 +422,7 @@ describe('mutability tracking', () => { } fn fn_1() { - let struct_1 = Struct(); + var struct_1 = Struct(); let prop = (&struct_1.prop); }" `); From a15476b42f93bc95790124430d9f2bf26358c6ec Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:19:13 +0200 Subject: [PATCH 14/34] Reorganize tests --- packages/typegpu/src/tgsl/wgslGenerator.ts | 1 - .../typegpu/tests/mutabilityTracking.test.ts | 221 ++++++++---------- 2 files changed, 101 insertions(+), 121 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index d36dd6c3ba..a7b69d8da8 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1349,7 +1349,6 @@ ${this.ctx.pre}else ${alternate}`; const snippet = this.ctx.getById(maybeObject); const scope = this.ctx.topFunctionScope; if (snippet && scope && scope.placeholderForVariable.has(snippet)) { - console.log(`Adding ${maybeObject} (${snippet.value}) to modified variables.`); this.ctx.topFunctionScope?.modifiedVariables.add(snippet); } } diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 8e1b4cb99c..683d4c97f3 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -5,12 +5,11 @@ import tgpu from '../src/index.js'; const fnShell = tgpu.fn([d.vec4u], d.u32); describe('mutability tracking', () => { - describe('different nodes', () => { - it('leaves unmodified variables as let', () => { + describe('resolves unmodified to let', () => { + it('resolves unmodified primitive const', () => { const fn = fnShell((arg) => { 'use gpu'; const a = arg.x; - const b = d.vec4u(arg); return a; }); @@ -18,90 +17,87 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { let a = arg.x; - let b = arg; return a; }" `); expect(resolved).toContain('let a = arg.x'); - expect(resolved).toContain('let b = arg'); }); - it('resolves reassigned variable to var', () => { + it('resolves unmodified primitive let', () => { const fn = fnShell((arg) => { 'use gpu'; let a = arg.x; - const b = d.vec4u(arg); - - a = 1; - b.x = 1; - return a; }); const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = arg.x; - var b = arg; - a = 1u; - b.x = 1u; + let a = arg.x; return a; }" `); - expect(resolved).toContain('var a = arg.x'); - expect(resolved).toContain('var b = arg'); + expect(resolved).toContain('let a = arg.x'); }); - it('resolves incremented variable to var', () => { + it('resolves unmodified reference const', () => { const fn = fnShell((arg) => { 'use gpu'; - let a = arg.x; - const b = d.vec4u(arg); - - a++; - b.x++; - - return a; + const a = d.vec4u(arg); + return a.x; }); const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = arg.x; - var b = arg; - a++; - b.x++; - return a; + let a = arg; + return a.x; }" `); - expect(resolved).toContain('var a = arg.x'); - expect(resolved).toContain('var b = arg'); + expect(resolved).toContain('let a = arg'); }); - it('resolves index-accessed variable to var', () => { + it('resolves unmodified reference let', () => { const fn = fnShell((arg) => { 'use gpu'; - const a = [1, 2, 3]; - - a[0] = 1; - - return a[0]; + let a = d.vec4u(arg); + return a.x; }); const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - var a = array(1, 2, 3); - a[0i] = 1i; - return u32(a[0i]); + let a = arg; + return a.x; }" `); - expect(resolved).toContain('var a ='); + expect(resolved).toContain('let a = arg'); + }); + + it('resolves reassigned in pruned branch', () => { + const myAccess = tgpu.accessor(d.bool); + const fn = () => { + 'use gpu'; + let a = 0; + if (myAccess.$) { + a = 1; + } + return a; + }; + + const resolved = tgpu.resolve([tgpu.fn(fn).with(myAccess, false)]); + expect(resolved).toMatchInlineSnapshot(` + "fn fn_1() -> i32 { + let a = 0; + return a; + }" + `); + expect(resolved).toContain('let a = 0'); }); }); describe('resolves modified to var', () => { - it('resolves reassigned primitive let to var', () => { + it('resolves reassigned primitive let', () => { const fn = fnShell((arg) => { 'use gpu'; let a = arg.x; @@ -120,7 +116,7 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = arg.x'); }); - it('resolves conditionally reassigned primitive let to var', () => { + it('resolves conditionally reassigned primitive let', () => { const fn = fnShell((arg) => { 'use gpu'; let a = arg.x; @@ -143,7 +139,7 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = arg.x'); }); - it('resolves mutated primitive let to var', () => { + it('resolves mutated primitive let', () => { const fn = fnShell((arg) => { 'use gpu'; let a = arg.x; @@ -162,7 +158,7 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = arg.x'); }); - it('resolves incremented primitive let to var', () => { + it('resolves incremented primitive let', () => { const fn = fnShell((arg) => { 'use gpu'; let a = arg.x; @@ -181,11 +177,11 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = arg.x'); }); - it('resolves modified reference const to var', () => { + it('resolves incremented reference const', () => { const fn = fnShell((arg) => { 'use gpu'; const a = d.vec4u(arg); - a.x = 1; + a.x++; return a.x; }); @@ -193,17 +189,17 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg; - a.x = 1u; + a.x++; return a.x; }" `); expect(resolved).toContain('var a = arg'); }); - it('resolves modified reference let to var', () => { + it('resolves modified reference const', () => { const fn = fnShell((arg) => { 'use gpu'; - let a = d.vec4u(arg); + const a = d.vec4u(arg); a.x = 1; return a.x; }); @@ -219,11 +215,11 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = arg'); }); - it('resolves reassigned reference let to var', () => { + it('resolves modified reference let', () => { const fn = fnShell((arg) => { 'use gpu'; let a = d.vec4u(arg); - a = d.vec4u(); + a.x = 1; return a.x; }); @@ -231,81 +227,51 @@ describe('mutability tracking', () => { expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { var a = arg; - a = vec4u(); + a.x = 1u; return a.x; }" `); expect(resolved).toContain('var a = arg'); }); - }); - describe('resolves unmodified to let', () => { - it('resolves unmodified primitive const arg', () => { + it('resolves reassigned reference let', () => { const fn = fnShell((arg) => { 'use gpu'; - const a = arg.x; - return a; - }); - - const resolved = tgpu.resolve([fn]); - expect(resolved).toMatchInlineSnapshot(` - "fn item(arg: vec4u) -> u32 { - let a = arg.x; - return a; - }" - `); - expect(resolved).toContain('let a = arg.x'); - }); - - it('resolves unmodified primitive let arg', () => { - const fn = fnShell((arg) => { - 'use gpu'; - let a = arg.x; - return a; - }); - - const resolved = tgpu.resolve([fn]); - expect(resolved).toMatchInlineSnapshot(` - "fn item(arg: vec4u) -> u32 { - let a = arg.x; - return a; - }" - `); - expect(resolved).toContain('let a = arg.x'); - }); - - it('resolves unmodified reference const arg', () => { - const fn = fnShell((arg) => { - 'use gpu'; - const a = d.vec4u(arg); + let a = d.vec4u(arg); + a = d.vec4u(); return a.x; }); const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - let a = arg; + var a = arg; + a = vec4u(); return a.x; }" `); - expect(resolved).toContain('let a = arg'); + expect(resolved).toContain('var a = arg'); }); - it('resolves unmodified reference let arg', () => { + it('resolves index-accessed reference const', () => { const fn = fnShell((arg) => { 'use gpu'; - let a = d.vec4u(arg); - return a.x; + const a = [1, 2, 3]; + + a[0] = 1; + + return a[0]; }); const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - let a = arg; - return a.x; + var a = array(1, 2, 3); + a[0i] = 1i; + return u32(a[0i]); }" `); - expect(resolved).toContain('let a = arg'); + expect(resolved).toContain('var a ='); }); }); @@ -316,46 +282,44 @@ describe('mutability tracking', () => { { const a = d.vec4u(arg); a.x = 2; - const b = a; } return a.x; }); const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` - "fn item(arg: vec4u) -> u32 { - let a = arg; - { - var a_1 = arg; - a_1.x = 2u; - let b = (&a_1); - } - return a.x; - }" - `); + "fn item(arg: vec4u) -> u32 { + let a = arg; + { + var a_1 = arg; + a_1.x = 2u; + } + return a.x; + }" + `); expect(resolved).toContain('let a = arg'); expect(resolved).toContain('var a_1 = arg'); }); - describe('references', () => { - it('resolves a primitive variable used with d.ref to var', () => { - const fn = fnShell((arg) => { + describe('d.ref and pointers', () => { + it('resolves a primitive variable used with d.ref', () => { + const fn = () => { 'use gpu'; const a = d.ref(0); return a.$; - }); + }; const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` - "fn item(arg: vec4u) -> u32 { + "fn fn_1() -> i32 { var a = 0; - return u32(a); + return a; }" `); expect(resolved).toContain('var a = 0'); }); - it('resolves a complex variable used with d.ref to var', () => { + it('resolves a referential variable used with d.ref', () => { const fn = () => { 'use gpu'; const a = d.ref(d.vec4u()); @@ -407,7 +371,24 @@ describe('mutability tracking', () => { expect(resolved).toContain('var b = arg'); }); - it('resolves a struct to var when its prop is referenced', () => { + it('resolves a referenced reference', () => { + const fn = () => { + 'use gpu'; + const a = d.vec4f(); + const b = a; + }; + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn fn_1() { + var a = vec4f(); + let b = (&a); + }" + `); + expect(resolved).toContain('var a = vec4f()'); + }); + + it('resolves a struct with its prop referenced', () => { const Struct = d.struct({ prop: d.vec4f }); const fn = () => { 'use gpu'; @@ -429,7 +410,7 @@ describe('mutability tracking', () => { expect(resolved).toContain('var struct_1 = Struct()'); }); - it('resolves a struct to let when its prop is only read', () => { + it('resolves a struct when its prop is only read', () => { const Struct = d.struct({ prop: d.vec4f }); const fn = () => { 'use gpu'; From f073da4573dceadc2e7f98f50c7b88717e5babd5 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:49:43 +0200 Subject: [PATCH 15/34] Add more tests --- .../typegpu/tests/mutabilityTracking.test.ts | 100 +++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 683d4c97f3..49ae6ec26b 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import * as d from '../src/data/index.ts'; -import tgpu from '../src/index.js'; +import tgpu, { std } from '../src/index.js'; const fnShell = tgpu.fn([d.vec4u], d.u32); @@ -122,6 +122,7 @@ describe('mutability tracking', () => { let a = arg.x; if (a < 1) { a = 1; + } else { } return a; }); @@ -273,6 +274,65 @@ describe('mutability tracking', () => { `); expect(resolved).toContain('var a ='); }); + + it('resolves for loops', () => { + const fn = fnShell((arg) => { + 'use gpu'; + + for (const i of std.range(3)) { + } + for (let j = 0; j < 3; j++) {} + + return 0; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + for (var i = 0i; i < 3i; i += 1i) { + + } + for (var j = 0; (j < 3i); j++) { + + } + return 0u; + }" + `); + }); + + it('resolves deeply nested struct modification', () => { + const Struct = d.struct({ + a: d.arrayOf(d.struct({ b: d.struct({ c: d.vec4f }) }), 4), + }); + const fn = fnShell((arg) => { + 'use gpu'; + const struct = Struct(); + struct.a[0]!.b.c.x += 1; + return 0; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "struct b { + c: vec4f, + } + + struct item_1 { + b: b, + } + + struct Struct { + a: array, + } + + fn item(arg: vec4u) -> u32 { + var struct_1 = Struct(); + struct_1.a[0i].b.c.x += 1f; + return 0u; + }" + `); + expect(resolved).toContain('var struct_1 = Struct()'); + }); }); it('resolves shadowed variables correctly', () => { @@ -336,7 +396,7 @@ describe('mutability tracking', () => { expect(resolved).toContain('var a = vec4u()'); }); - it('resolves pointed variable to var', () => { + it('resolves pointed variable', () => { const modify = tgpu.fn([d.ptrFn(d.u32), d.ptrFn(d.vec4u)])((num, vec) => { 'use gpu'; num.$ += 1; @@ -371,6 +431,42 @@ describe('mutability tracking', () => { expect(resolved).toContain('var b = arg'); }); + it('resolves d.ref to a struct prop', () => { + const Struct = d.struct({ prop: d.vec4u }); + + const modify = tgpu.fn([d.ptrFn(d.vec4u)])((vec) => { + 'use gpu'; + vec.$ += 1; + }); + + const fn = fnShell((arg) => { + 'use gpu'; + const struct = Struct(); + + modify(d.ref(struct.prop)); + + return struct.prop.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "struct Struct { + prop: vec4u, + } + + fn modify(vec: ptr) { + (*vec) += 1; + } + + fn item(arg: vec4u) -> u32 { + var struct_1 = Struct(); + modify((&struct_1.prop)); + return struct_1.prop.x; + }" + `); + expect(resolved).toContain('var struct_1 = Struct()'); + }); + it('resolves a referenced reference', () => { const fn = () => { 'use gpu'; From 88dfb648d70748360057277104965448e139940c Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 10 Apr 2026 12:30:13 +0200 Subject: [PATCH 16/34] fix: Improvement to argument usage tracking --- .../individual-example-tests/3d-fish.test.ts | 18 +- .../bitonic-sort.test.ts | 4 +- .../individual-example-tests/blur.test.ts | 8 +- .../camera-thresholding.test.ts | 8 +- .../individual-example-tests/caustics.test.ts | 8 +- .../chroma-keying.test.ts | 8 +- .../individual-example-tests/confetti.test.ts | 6 +- .../cubemap-reflection.test.ts | 18 +- .../individual-example-tests/disco.test.ts | 16 +- .../fluid-double-buffering.test.ts | 8 +- .../game-of-life.test.ts | 8 +- .../gradient-tiles.test.ts | 4 +- .../individual-example-tests/gravity.test.ts | 20 +- .../image-tuning.test.ts | 8 +- .../jelly-slider.test.ts | 16 +- .../jelly-switch.test.ts | 16 +- .../jump-flood-distance.test.ts | 8 +- .../liquid-glass.test.ts | 8 +- .../individual-example-tests/oklab.test.ts | 8 +- .../phong-reflection.test.ts | 10 +- .../point-light-shadow.test.ts | 16 +- .../ray-marching.test.ts | 8 +- .../ripple-cube.test.ts | 8 +- .../simple-shadow.test.ts | 10 +- .../slime-mold-3d.test.ts | 8 +- .../slime-mold.test.ts | 8 +- .../stable-fluid.test.ts | 8 +- .../uniformity.test.ts | 16 +- .../vaporrave.test.ts | 8 +- .../xor-dev-centrifuge-2.test.ts | 8 +- .../xor-dev-runner.test.ts | 8 +- packages/typegpu/src/core/function/autoIO.ts | 4 +- .../src/core/function/entryInputRouter.ts | 39 +- packages/typegpu/src/core/function/fnCore.ts | 51 +- .../src/core/function/shelllessImpl.ts | 2 +- .../src/core/function/tgpuComputeFn.ts | 5 +- packages/typegpu/src/core/function/tgpuFn.ts | 2 +- .../src/core/function/tgpuFragmentFn.ts | 2 +- .../typegpu/src/core/function/tgpuVertexFn.ts | 2 +- packages/typegpu/src/data/snippet.ts | 7 +- packages/typegpu/src/resolutionCtx.ts | 257 +++---- packages/typegpu/src/tgsl/accessProp.ts | 11 +- packages/typegpu/src/tgsl/conversion.ts | 2 +- packages/typegpu/src/tgsl/shaderGenerator.ts | 4 +- .../src/tgsl/shaderGenerator_members.ts | 14 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 25 +- packages/typegpu/src/types.ts | 18 +- packages/typegpu/tests/renderPipeline.test.ts | 33 +- .../tests/tgsl/extensionEnabled.test.ts | 14 +- .../typegpu/tests/tgsl/nameClashes.test.ts | 16 +- .../tests/tgsl/ternaryOperator.test.ts | 68 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 676 +++--------------- packages/typegpu/tests/tgslFn.test.ts | 24 +- packages/typegpu/tests/utils/parseResolved.ts | 1 - 54 files changed, 579 insertions(+), 1012 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts index dca6c22f4e..6430284a2e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts @@ -305,15 +305,6 @@ describe('3d fish example', () => { return vertexShader_Output(worldPosition.xyz, worldNormal, canvasPosition, (*currentModelData).variant, _arg_textureUV, (*currentModelData).applySeaFog, (*currentModelData).applySeaDesaturation); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - @location(2) variant: f32, - @location(3) textureUV: vec2f, - @location(4) @interpolate(flat) applySeaFog: u32, - @location(5) @interpolate(flat) applySeaDesaturation: u32, - } - @group(0) @binding(1) var modelTexture: texture_2d; @group(0) @binding(3) var sampler_1: sampler; @@ -418,6 +409,15 @@ describe('3d fish example', () => { return vec3f(r, g, b); } + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + @location(2) variant: f32, + @location(3) textureUV: vec2f, + @location(4) @interpolate(flat) applySeaFog: u32, + @location(5) @interpolate(flat) applySeaDesaturation: u32, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var textureColorWithAlpha = textureSample(modelTexture, sampler_1, _arg_0.textureUV); var textureColor = textureColorWithAlpha.rgb; diff --git a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts index 06b1c586cb..6368fd715f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts @@ -118,12 +118,12 @@ describe('bitonic sort example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var data_1: array; + struct fragmentFn_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var data_1: array; - @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { let data = (&data_1); let arrayLength_1 = arrayLength(&(*data)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts index 00df4923ed..7f81d96515 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts @@ -470,14 +470,14 @@ describe('blur example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct renderFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var renderView: texture_2d; @group(0) @binding(1) var sampler_1: sampler; + struct renderFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn renderFragment(_arg_0: renderFragment_Input) -> @location(0) vec4f { return textureSample(renderView, sampler_1, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts index 43e10c97b2..783bb59558 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts @@ -32,10 +32,6 @@ describe('camera thresholding example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFrag_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransformUniform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('camera thresholding example', () => { @group(0) @binding(3) var thresholdBuffer: f32; + struct mainFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { var uv2 = ((uvTransformUniform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts index a1938d5c34..9d011f7a74 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts @@ -31,10 +31,6 @@ describe('caustics example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var tileDensity: f32; fn tilePattern(uv: vec2f) -> f32 { @@ -120,6 +116,10 @@ describe('caustics example', () => { return mat2x2f(vec2f(cos(angle), sin(angle)), vec2f(-(sin(angle)), cos(angle))); } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f((-1.9866933079506122f + (_arg_0.uv.x * 3f)), 4.900332889206208f)); var skewedUv = (skewMat * _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts index 9b25129771..60175ac939 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts @@ -32,10 +32,6 @@ describe('chroma keying example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('chroma keying example', () => { @group(0) @binding(3) var threshold: f32; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var uv2 = ((uvTransform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts index 3e39636f7a..921721055b 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts @@ -59,10 +59,10 @@ describe('confetti example', () => { struct VertexIn { @location(0) tilt: f32, - @location(1) angle: f32, - @location(2) color: vec4f, - @location(3) center: vec2f, @builtin(vertex_index) vertexIndex: u32, + @location(1) angle: f32, + @location(2) center: vec2f, + @location(3) color: vec4f, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { diff --git a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts index c77faffba2..fe28f8827d 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts @@ -257,14 +257,14 @@ describe('cubemap reflection example', () => { return cubeVertexFn_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct cubeFragmentFn_Input { - @location(0) texCoord: vec3f, - } - @group(1) @binding(0) var cubemap: texture_cube; @group(1) @binding(1) var texSampler: sampler; + struct cubeFragmentFn_Input { + @location(0) texCoord: vec3f, + } + @fragment fn cubeFragmentFn(_arg_0: cubeFragmentFn_Input) -> @location(0) vec4f { return textureSample(cubemap, texSampler, normalize(_arg_0.texCoord)); } @@ -287,11 +287,6 @@ describe('cubemap reflection example', () => { return vertexFn_Output((camera.projection * (camera.view * _arg_position)), _arg_normal, _arg_position); } - struct fragmentFn_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec4f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -314,6 +309,11 @@ describe('cubemap reflection example', () => { @group(1) @binding(1) var texSampler: sampler; + struct fragmentFn_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec4f, + } + @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { var normalizedNormal = normalize(_arg_0.normal.xyz); var normalizedLightDir = normalize(light.direction); diff --git a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts index 8ea4e8d7f7..ba6d090535 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts @@ -32,10 +32,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment2_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -65,6 +61,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment2_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment2(_arg_0: mainFragment2_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; @@ -243,10 +243,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment1_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -276,6 +272,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment1_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment1(_arg_0: mainFragment1_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; diff --git a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts index ef73e74189..df9620d6ed 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts @@ -558,10 +558,6 @@ describe('fluid double buffering example', () => { return vertexMain_Output(vec4f(pos[_arg_idx].x, pos[_arg_idx].y, 0f, 1f), uv[_arg_idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - fn coordsToIndex(x: i32, y: i32) -> i32 { return (x + (y * 256i)); } @@ -595,6 +591,10 @@ describe('fluid double buffering example', () => { return false; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let x = i32((_arg_0.uv.x * 256f)); let y = i32((_arg_0.uv.y * 256f)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts index 3028c19bf5..516692dc41 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts @@ -214,10 +214,6 @@ describe('game of life example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct displayFragment_Input { - @location(0) uv: vec2f, - } - struct ZoomParams { enabled: u32, level: f32, @@ -242,6 +238,10 @@ describe('game of life example', () => { @group(0) @binding(2) var viewModeUniform: u32; + struct displayFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn displayFragment(_arg_0: displayFragment_Input) -> @location(0) vec4f { let zoom = (&zoomUniform); let gs = f32(gameSizeUniform); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts index f0d3016198..d3c209eb18 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts @@ -32,12 +32,12 @@ describe('gradient tiles example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var spanUniform: vec2f; + struct fragment_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var spanUniform: vec2f; - @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { let red = (floor((_arg_0.uv.x * spanUniform.x)) / spanUniform.x); let green = (floor((_arg_0.uv.y * spanUniform.y)) / spanUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts index 55ed0d2fb8..899b6cb76e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts @@ -192,14 +192,14 @@ describe('gravity example', () => { return skyBoxVertex_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct skyBoxFragment_Input { - @location(0) texCoord: vec3f, - } - @group(0) @binding(1) var skyBox: texture_cube; @group(0) @binding(2) var sampler_1: sampler; + struct skyBoxFragment_Input { + @location(0) texCoord: vec3f, + } + @fragment fn skyBoxFragment(_arg_0: skyBoxFragment_Input) -> @location(0) vec4f { return textureSample(skyBox, sampler_1, normalize(_arg_0.texCoord)); } @@ -250,6 +250,12 @@ describe('gravity example', () => { return mainVertex_Output(positionOnCanvas, _arg_uv, _arg_normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); } + @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; + + @group(0) @binding(1) var sampler_1: sampler; + + @group(0) @binding(2) var lightSource: vec3f; + struct mainFragment_Input { @location(0) uv: vec2f, @location(1) normals: vec3f, @@ -259,12 +265,6 @@ describe('gravity example', () => { @location(5) ambientLightFactor: f32, } - @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; - - @group(0) @binding(1) var sampler_1: sampler; - - @group(0) @binding(2) var lightSource: vec3f; - @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { if ((_arg_0.destroyed == 1u)) { discard;; diff --git a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts index a9fa1865c8..059828005e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts @@ -37,10 +37,6 @@ describe('image tuning example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var imageView: texture_2d; @group(0) @binding(1) var imageSampler: sampler; @@ -68,6 +64,10 @@ describe('image tuning example', () => { @group(0) @binding(4) var adjustments: Adjustments; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var color = textureSample(imageView, imageSampler, _arg_0.uv).rgb; let inputLuminance = dot(color, vec3f(0.29899999499320984, 0.5870000123977661, 0.11400000005960464)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts index 941ee300b4..976bdbaaf2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts @@ -66,10 +66,6 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -583,6 +579,10 @@ describe('jelly-slider example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -713,14 +713,14 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts index 7a192b5f54..6e507a2602 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts @@ -37,10 +37,6 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -385,6 +381,10 @@ describe('jelly switch example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -505,14 +505,14 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts index 3fb0e7d685..dcf73b0927 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts @@ -303,10 +303,6 @@ describe('jump flood (distance) example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct distanceFrag_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var distTexture: texture_2d; @group(1) @binding(1) var sampler_1: sampler; @@ -322,6 +318,10 @@ describe('jump flood (distance) example', () => { const insideGradient: array = array(vec3f(0.05000000074505806, 0.05000000074505806, 0.15000000596046448), vec3f(0.10000000149011612, 0.20000000298023224, 0.30000001192092896), vec3f(0.20000000298023224, 0.44999998807907104, 0.550000011920929), vec3f(0.4000000059604645, 0.75, 0.699999988079071), vec3f(0.8999999761581421, 1, 0.949999988079071)); + struct distanceFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn distanceFrag(_arg_0: distanceFrag_Input) -> @location(0) vec4f { var size = textureDimensions(distTexture); var dist = textureSample(distTexture, sampler_1, _arg_0.uv).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts index e247d26a22..56a5b4ecf5 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts @@ -64,10 +64,6 @@ describe('liquid-glass example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var mousePosUniform: vec2f; struct Params { @@ -137,6 +133,10 @@ describe('liquid-glass example', () => { return mix(vec4f(color, 1f), vec4f(tint.color, 1f), tint.strength); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var posInBoxSpace = (_arg_0.uv - mousePosUniform); let sdfDist = sdRoundedBox2d(posInBoxSpace, paramsUniform.rectDims, paramsUniform.radius); diff --git a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts index b8e64d3654..23abf35258 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts @@ -32,10 +32,6 @@ describe('oklab example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - struct item { hue: f32, alpha: f32, @@ -226,6 +222,10 @@ describe('oklab example', () => { return 1f; } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv - 0.5f) * vec2f(2, -2)); let hue = uniforms.hue; diff --git a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts index 1cfbf878ea..0240d9ae44 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts @@ -54,11 +54,6 @@ describe('phong reflection example', () => { return vertexShader_Output(_arg_modelPosition, _arg_modelNormal, canvasPosition); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - } - struct ExampleControls { lightColor: vec3f, lightDirection: vec3f, @@ -69,6 +64,11 @@ describe('phong reflection example', () => { @group(0) @binding(1) var exampleControlsUniform: ExampleControls; + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var lightColor = normalize(exampleControlsUniform.lightColor); var lightDirection = normalize(exampleControlsUniform.lightDirection); diff --git a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts index 283d8d652b..2ce441c6b2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts @@ -41,12 +41,12 @@ describe('point light shadow example', () => { return vertexDepth_Output(pos, worldPos); } + @group(0) @binding(1) var lightPosition: vec3f; + struct fragmentDepth_Input { @location(0) worldPos: vec3f, } - @group(0) @binding(1) var lightPosition: vec3f; - @fragment fn fragmentDepth(_arg_0: fragmentDepth_Input) -> @builtin(frag_depth) f32 { let dist = length((_arg_0.worldPos - lightPosition)); return (dist / 100f); @@ -74,12 +74,6 @@ describe('point light shadow example', () => { return vertexMain_Output(pos, worldPos, uv, worldNormal); } - struct fragmentMain_Input { - @location(0) worldPos: vec3f, - @location(1) uv: vec2f, - @location(2) normal: vec3f, - } - @group(1) @binding(3) var lightPosition: vec3f; struct item { @@ -97,6 +91,12 @@ describe('point light shadow example', () => { @group(1) @binding(2) var shadowSampler: sampler_comparison; + struct fragmentMain_Input { + @location(0) worldPos: vec3f, + @location(1) uv: vec2f, + @location(2) normal: vec3f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let lightPos = (&lightPosition); var toLight = ((*lightPos) - _arg_0.worldPos); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts index 1d56a2d307..bdfa96686f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts @@ -31,10 +31,6 @@ describe('ray-marching example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolution: vec2f; struct Shape { @@ -148,6 +144,10 @@ describe('ray-marching example', () => { return res; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolution.x / resolution.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts index e73568d898..24dcc7ee1c 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts @@ -765,10 +765,6 @@ describe('ripple-cube example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var colorTexture: texture_2d; @group(1) @binding(2) var sampler_1: sampler; @@ -782,6 +778,10 @@ describe('ripple-cube example', () => { @group(0) @binding(0) var bloomUniform: BloomParams; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var color = textureSample(colorTexture, sampler_1, _arg_0.uv); var bloomColor = textureSample(bloomTexture, sampler_1, _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts index f1f6775cc0..e6d2de8222 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts @@ -89,11 +89,6 @@ describe('simple shadow example', () => { return mainVert_Output(clipPos, transformedNormal, worldPos.xyz); } - struct mainFrag_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec3f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -118,6 +113,11 @@ describe('simple shadow example', () => { @group(0) @binding(3) var paramsUniform: VisParams; + struct mainFrag_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec3f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { let instanceInfo_1 = (&instanceInfo); var N = normalize(_arg_0.normal.xyz); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts index 16ec9b7e12..daab5433e6 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts @@ -382,10 +382,6 @@ describe('slime mold 3d example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - var seed: vec2f; fn seed2(value: vec2f) { @@ -438,6 +434,10 @@ describe('slime mold 3d example', () => { @group(0) @binding(1) var sampler_1: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { randSeed2(_arg_0.uv); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), (1f - (_arg_0.uv.y * 2f))); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts index 44d050241e..4b77dc41f4 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts @@ -308,14 +308,14 @@ describe('slime mold example', () => { return fullScreenTriangle_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), uv[_arg_vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var state: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { return textureSample(state, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts index 0e97b0d9e9..b94b01c01f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts @@ -259,16 +259,16 @@ describe('stable-fluid example', () => { return renderFn_Output(vec4f(vertices[_arg_idx], 0f, 1f), texCoords[_arg_idx]); } - struct fragmentImageFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var result: texture_2d; @group(0) @binding(2) var linSampler: sampler; @group(0) @binding(1) var background: texture_2d; + struct fragmentImageFn_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentImageFn(_arg_0: fragmentImageFn_Input) -> @location(0) vec4f { const pixelStep = 0.001953125f; let leftSample = textureSample(result, linSampler, vec2f((_arg_0.uv.x - pixelStep), _arg_0.uv.y)).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts index 21ccd861f6..11a61fe114 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts @@ -35,10 +35,6 @@ describe('uniformity test example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var canvasRatioUniform: f32; @group(0) @binding(1) var gridSizeUniform: f32; @@ -65,6 +61,10 @@ describe('uniformity test example', () => { return sample(); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); @@ -72,10 +72,6 @@ describe('uniformity test example', () => { return vec4f(vec3f(randFloat01()), 1f); } - struct fragmentShader_Input_1 { - @location(0) uv: vec2f, - } - var seed_1: u32; fn seed2_1(value: vec2f) { @@ -102,6 +98,10 @@ describe('uniformity test example', () => { return sample_1(); } + struct fragmentShader_Input_1 { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader_1(_arg_0: fragmentShader_Input_1) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts index d7834afa66..dafd303f91 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts @@ -79,10 +79,6 @@ describe('vaporrave example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; struct Ray { @@ -219,6 +215,10 @@ describe('vaporrave example', () => { @group(0) @binding(5) var glowIntensityUniform: f32; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolutionUniform.x / resolutionUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts index 9abffaf6b8..894d6e4d67 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts @@ -30,10 +30,6 @@ describe('xor dev centrifuge example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - struct Params { time: f32, aspectRatio: f32, @@ -51,6 +47,10 @@ describe('xor dev centrifuge example', () => { return select(tanh(v), sign(v), (abs(v) > vec3f(10))); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let params = (¶msUniform); var ratio = vec2f((*params).aspectRatio, 1f); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts index 419c95807c..ec9b1c27a0 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts @@ -34,10 +34,6 @@ describe('xor dev runner example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var colorUniform: vec3f; struct Camera { @@ -82,6 +78,10 @@ describe('xor dev runner example', () => { return select(tanh(v), sign(v), (abs(v) > 10f)); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var icolor = (colorUniform * 4f); var ray = getRayForUV(_arg_0.uv); diff --git a/packages/typegpu/src/core/function/autoIO.ts b/packages/typegpu/src/core/function/autoIO.ts index 6feece51d5..b35a092b1a 100644 --- a/packages/typegpu/src/core/function/autoIO.ts +++ b/packages/typegpu/src/core/function/autoIO.ts @@ -85,7 +85,7 @@ export class AutoFragmentFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'fragmentFn'); } - this.#core = createFnCore(impl, '@fragment '); + this.#core = createFnCore(impl, 'fragment'); this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations); setName(this.autoIn, 'FragmentIn'); this.autoOut = new AutoStruct(builtinFragmentOut, vec4f); @@ -131,7 +131,7 @@ export class AutoVertexFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'vertexFn'); } - this.#core = createFnCore(impl, '@vertex '); + this.#core = createFnCore(impl, 'vertex'); this.autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations); setName(this.autoIn, 'VertexIn'); this.autoOut = new AutoStruct(builtinVertexOut, undefined); diff --git a/packages/typegpu/src/core/function/entryInputRouter.ts b/packages/typegpu/src/core/function/entryInputRouter.ts index 8ba6669372..b756366c4b 100644 --- a/packages/typegpu/src/core/function/entryInputRouter.ts +++ b/packages/typegpu/src/core/function/entryInputRouter.ts @@ -1,12 +1,7 @@ -import { undecorate } from '../../data/dataTypes.ts'; -import { snip, type Snippet } from '../../data/snippet.ts'; +import { type Snippet } from '../../data/snippet.ts'; import { $internal, $repr } from '../../shared/symbols.ts'; import { type BaseData, isWgslStruct } from '../../data/wgslTypes.ts'; - -interface PositionalArgEntry { - argName: string; - type: BaseData; -} +import type { FunctionArgumentAccess } from '../../types.ts'; /** * Routes `(input) => { input.x }` style property access to the correct WGSL @@ -18,38 +13,32 @@ export class EntryInputRouter implements BaseData { readonly type = 'entry-input-router' as const; // Type-token only, not present at runtime: declare readonly [$repr]: never; - readonly structArgName: string; - readonly dataSchema: BaseData | undefined; + + readonly structArg: FunctionArgumentAccess | undefined; /** Maps schemaKey → { WGSL arg name, type } */ - readonly positionalArgsMap: Map; + readonly positionalArgsMap: Map; constructor( - structArgName: string, - dataSchema: BaseData | undefined, - positionalArgs: { schemaKey: string; argName: string; type: BaseData }[], + structArg: FunctionArgumentAccess | undefined, + positionalArgs: { schemaKey: string; arg: FunctionArgumentAccess }[], ) { - this.structArgName = structArgName; - this.dataSchema = dataSchema; - this.positionalArgsMap = new Map( - positionalArgs.map((a) => [a.schemaKey, { argName: a.argName, type: a.type }]), - ); + this.structArg = structArg; + this.positionalArgsMap = new Map(positionalArgs.map((a) => [a.schemaKey, a.arg])); } toString(): string { return 'entry-input-router'; } - accessProp(propName: string): Snippet | undefined { + accessProp(propName: string): Snippet | { target: Snippet; prop: string } | undefined { const positionalEntry = this.positionalArgsMap.get(propName); if (positionalEntry) { - return snip(positionalEntry.argName, positionalEntry.type, 'argument'); + return positionalEntry(); } - if (this.dataSchema && isWgslStruct(this.dataSchema)) { - const propType = this.dataSchema.propTypes[propName]; - if (propType) { - return snip(`${this.structArgName}.${propName}`, undecorate(propType), 'argument'); - } + const structSnippet = this.structArg?.(); + if (structSnippet && isWgslStruct(structSnippet.dataType)) { + return { target: structSnippet, prop: propName }; } return undefined; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 66429067e5..cf4cda90f7 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -5,7 +5,7 @@ import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTy import { MissingLinksError } from '../../errors.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; -import type { ResolutionCtx } from '../../types.ts'; +import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; import { applyExternals, type ExternalMap, replaceExternalsInWgsl } from '../resolve/externals.ts'; import { extractArgs } from './extractArgs.ts'; import type { Implementation, SeparatedEntryArgs } from './fnTypes.ts'; @@ -32,7 +32,11 @@ export interface FnCore { ): ResolvedSnippet; } -export function createFnCore(implementation: Implementation, fnAttribute = ''): FnCore { +export function createFnCore( + implementation: Implementation, + functionType: 'normal' | TgpuShaderStage, + workgroupSize?: number[], +): FnCore { /** * External application has to be deferred until resolution because * some externals can reference the owner function which has not been @@ -83,7 +87,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): let header = ''; let body = ''; - if (fnAttribute !== '' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput && validArgNames) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { @@ -140,7 +144,17 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): body = replacedImpl.slice(providedArgs.range.end); } - ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); + let attributes = ''; + if (functionType === 'compute') { + attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `; + } else if (functionType === 'vertex') { + attributes = `@vertex `; + } else if (functionType === 'fragment') { + attributes = `@fragment `; + } + + ctx.addDeclaration(`${attributes}fn ${id}${header}${body}`); + return snip(id, returnType, /* origin */ 'runtime'); } @@ -178,7 +192,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // If an entrypoint implementation has a second argument, it represents the output schema. // We look at the identifier chosen by the user and add it to externals. const maybeSecondArg = ast.params[1]; - if (maybeSecondArg && maybeSecondArg.type === 'i' && fnAttribute !== '') { + if (maybeSecondArg && maybeSecondArg.type === 'i' && functionType !== 'normal') { applyExternals(externalMap, { // oxlint-disable-next-line typescript/no-non-null-assertion -- entry functions cannot be shellless [maybeSecondArg.name]: undecorate(returnType!), @@ -187,18 +201,8 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // generate wgsl string - const { - head, - body, - returnType: actualReturnType, - } = ctx.fnToWgsl({ - functionType: fnAttribute.includes('@compute') - ? 'compute' - : fnAttribute.includes('@vertex') - ? 'vertex' - : fnAttribute.includes('@fragment') - ? 'fragment' - : 'normal', + const { code, returnType: actualReturnType } = ctx.fnToWgsl({ + functionType, argTypes, entryInput, params: ast.params, @@ -207,9 +211,16 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): externalMap, }); - ctx.addDeclaration( - `${fnAttribute}fn ${id}${ctx.resolve(head).value}${ctx.resolve(body).value}`, - ); + let attributes = ''; + if (functionType === 'compute') { + attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `; + } else if (functionType === 'vertex') { + attributes = `@vertex `; + } else if (functionType === 'fragment') { + attributes = `@fragment `; + } + + ctx.addDeclaration(`${attributes}fn ${id}${code}`); return snip(id, actualReturnType, /* origin */ 'runtime'); }, diff --git a/packages/typegpu/src/core/function/shelllessImpl.ts b/packages/typegpu/src/core/function/shelllessImpl.ts index 23b60bdde1..9263303a31 100644 --- a/packages/typegpu/src/core/function/shelllessImpl.ts +++ b/packages/typegpu/src/core/function/shelllessImpl.ts @@ -31,7 +31,7 @@ export function createShelllessImpl( argTypes: BaseData[], implementation: (...args: never[]) => unknown, ): ShelllessImpl { - const core = createFnCore(implementation, ''); + const core = createFnCore(implementation, 'normal'); return { [$internal]: true, diff --git a/packages/typegpu/src/core/function/tgpuComputeFn.ts b/packages/typegpu/src/core/function/tgpuComputeFn.ts index 6f48cbe643..9cef2d49cc 100644 --- a/packages/typegpu/src/core/function/tgpuComputeFn.ts +++ b/packages/typegpu/src/core/function/tgpuComputeFn.ts @@ -114,10 +114,7 @@ function createComputeFn>( [$getNameForward]: FnCore; }; - const core = createFnCore( - implementation, - `@compute @workgroup_size(${workgroupSize.join(', ')}) `, - ); + const core = createFnCore(implementation, 'compute', workgroupSize); const result: This = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index 65ea52de65..40f6548922 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -173,7 +173,7 @@ function createFn( implementation = _implementation; } - const core = createFnCore(implementation as Implementation, ''); + const core = createFnCore(implementation as Implementation, 'normal'); const fnBase = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFragmentFn.ts b/packages/typegpu/src/core/function/tgpuFragmentFn.ts index 631617bcdc..cb3b4c990f 100644 --- a/packages/typegpu/src/core/function/tgpuFragmentFn.ts +++ b/packages/typegpu/src/core/function/tgpuFragmentFn.ts @@ -183,7 +183,7 @@ function createFragmentFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@fragment '); + const core = createFnCore(implementation, 'fragment'); const outputType = shell.returnType; if (typeof implementation === 'string') { addReturnTypeToExternals(implementation, outputType, (externals) => diff --git a/packages/typegpu/src/core/function/tgpuVertexFn.ts b/packages/typegpu/src/core/function/tgpuVertexFn.ts index a261634b3d..8c5b230891 100644 --- a/packages/typegpu/src/core/function/tgpuVertexFn.ts +++ b/packages/typegpu/src/core/function/tgpuVertexFn.ts @@ -158,7 +158,7 @@ function createVertexFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@vertex '); + const core = createFnCore(implementation, 'vertex'); const entryInput: SeparatedEntryArgs = separateAllAsPositional(shell.in ?? {}); const result: This = { diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index fe658ef99d..9c2e954f0d 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -74,14 +74,9 @@ export interface Snippet { readonly origin: Origin; } -export interface ResolvedSnippet { +export interface ResolvedSnippet extends Snippet { readonly value: string; - /** - * The type that `value` is assignable to (not necessary exactly inferred as). - * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float - */ readonly dataType: BaseData; - readonly origin: Origin; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5b3daa0067..5eedec298d 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -12,10 +12,9 @@ import { type TgpuLazy, type TgpuSlot, } from './core/slot/slotTypes.ts'; -import { getAttributesString } from './data/attributes.ts'; -import { isData, undecorate, UnknownData } from './data/dataTypes.ts'; +import { isData, UnknownData } from './data/dataTypes.ts'; import { bool } from './data/numeric.ts'; -import { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; +import { type Origin, type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; import { type BaseData, isPtr, isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts'; import { invariant, MissingSlotValueError, ResolutionError, WgslTypeError } from './errors.ts'; import { provideCtx, topLevelState } from './execMode.ts'; @@ -41,6 +40,7 @@ import type { ExecMode, ExecState, FnToWgslOptions, + FunctionArgumentAccess, FunctionScopeLayer, ItemLayer, ItemStateStack, @@ -58,6 +58,7 @@ import { createIoSchema } from './core/function/ioSchema.ts'; import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; +import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; /** * Inserted into bind group entry definitions that belong @@ -115,16 +116,14 @@ class ItemStateStackImpl implements ItemStateStack { pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, returnType: BaseData | undefined, externalMap: Record, ): FunctionScopeLayer { const scope: FunctionScopeLayer = { type: 'functionScope', functionType, - args, - argAliases, + argAccess, returnType, externalMap, reportedReturnTypes: new Set(), @@ -184,13 +183,9 @@ class ItemStateStackImpl implements ItemStateStack { const layer = this._stack[i]; if (layer?.type === 'functionScope') { - const arg = layer.args.find((a) => a.value === id); - if (arg !== undefined) { - return arg; - } - - if (layer.argAliases[id]) { - return layer.argAliases[id]; + const access = layer.argAccess[id]; + if (access) { + return access(); } const external = layer.externalMap[id]; @@ -311,6 +306,39 @@ interface FixedBindingConfig { resource: object; } +function createArgument( + name: string, + type: BaseData, + origin: Origin = 'argument', +): FunctionArgument { + let used = false; + + return { + name, + access: () => { + used = true; + return snip(name, type, origin); + }, + decoratedType: type, + get used() { + return used; + }, + }; +} + +function createArgumentPropAccess( + argAccess: FunctionArgumentAccess, + prop: string, +): FunctionArgumentAccess { + return () => { + const argSnippet = argAccess(); + if (!argSnippet) { + return undefined; + } + return accessProp(argSnippet, prop); + }; +} + export class ResolutionCtxImpl implements ResolutionCtx { readonly #namespaceInternal: NamespaceInternal; @@ -438,28 +466,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { return this.#logGenerator.logResources; } - fnToWgsl(options: FnToWgslOptions): { head: Wgsl; body: Wgsl; returnType: BaseData } { + fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { let fnScopePushed = false; try { this.#namespaceInternal.nameRegistry.pushFunctionScope(); - const args: Snippet[] = []; - const argAliases: [string, Snippet][] = []; - // For entry functions: collect pending header entries to be filtered after body generation. - const pendingHeaderEntries: { argName: string; header: string }[] = []; + const args: FunctionArgument[] = []; + const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; - const structArgName = this.makeNameValid('_arg_0'); - const structArg = dataSchema ? snip(structArgName, dataSchema, 'argument') : undefined; + const structArg = dataSchema + ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + : undefined; + if (structArg) { args.push(structArg); - pendingHeaderEntries.push({ - argName: structArgName, - header: `${structArgName}: ${this.resolve(dataSchema).value}`, - }); } if (firstParam?.type === FuncParameterType.destructuredObject) { @@ -467,45 +491,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const argName = this.makeNameValid(alias); - const argSnippet = snip(argName, argInfo.type, 'argument'); - args.push(argSnippet); - argAliases.push([alias, argSnippet]); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(argInfo.type)}${argName}: ${this.resolve(undecorate(argInfo.type)).value}`, - }); + const arg = createArgument(this.makeNameValid(alias), argInfo.type); + args.push(arg); + argAccess[alias] = arg.access; } else if (structArg) { - const propSnippet = accessProp(structArg, name); - if (propSnippet) { - argAliases.push([alias, propSnippet]); - } + argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. - const proxyEntries: Array<{ schemaKey: string; argName: string; type: BaseData }> = []; + const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - const s = snip(argName, a.type, 'argument'); - args.push(s); - proxyEntries.push({ schemaKey: a.schemaKey, argName, type: a.type }); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } - const router = new EntryInputRouter(structArgName, dataSchema, proxyEntries); - argAliases.push([firstParam.name, snip(firstParam.name, router, 'argument')]); + const router = new EntryInputRouter(structArg?.access, proxyEntries); + argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - args.push(snip(argName, a.type, 'argument')); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + argAccess[argName] = arg.access; } } } else { @@ -528,22 +538,17 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const rawName = astParam.name; - const snippet = snip(this.makeNameValid(rawName), argType, origin); - args.push(snippet); - if (snippet.value !== rawName) { - argAliases.push([rawName, snippet]); - } + const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + args.push(arg); + argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objSnippet = snip(`_arg_${i}`, argType, origin); - args.push(objSnippet); - argAliases.push( - ...astParam.props.map( - ({ name, alias }) => [alias, accessProp(objSnippet, name)] as [string, Snippet], - ), - ); + const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + args.push(objArg); + for (const { name, alias } of astParam.props) { + argAccess[alias] = createArgumentPropAccess(objArg.access, name); + } break; } case undefined: { @@ -551,7 +556,16 @@ export class ResolutionCtxImpl implements ResolutionCtx { // If we're not using an auto-struct, it's not going to // have any properties anyway. if (!(argType instanceof AutoStruct)) { - args.push(snip(`_arg_${i}`, argType, origin)); + args.push({ + name: this.makeNameValid(`_arg_${i}`), + access: () => { + throw new Error( + `Unreachable: Accessing an argument that wasn't named in the function signature`, + ); + }, + decoratedType: argType, + used: false, + }); } } } @@ -560,68 +574,71 @@ export class ResolutionCtxImpl implements ResolutionCtx { const scope = this._itemStateStack.pushFunctionScope( options.functionType, - args, - Object.fromEntries(argAliases), + argAccess, options.returnType, options.externalMap, ); fnScopePushed = true; - const body = this.gen.functionDefinition(options.body); + let returnType: BaseData | undefined; - let returnType = options.returnType; - if (returnType instanceof AutoStruct) { - // We're expecting an "auto" return type, so if there were structs returned, - // we accept the struct, otherwise we let the rest of the code unify on a - // primitive type. - if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { - returnType = returnType.completeStruct; - } else { - returnType = undefined; - } - } + const code = this.gen.functionDefinition({ + functionType: options.functionType, + args, + body: options.body, + determineReturnType: () => { + if (returnType) { + // Already determined + return returnType; + } - if (!returnType) { - const returnTypes = [...scope.reportedReturnTypes]; - if (returnTypes.length === 0) { - returnType = Void; - } else { - const conversion = getBestConversion(returnTypes); - if (conversion && !conversion.hasImplicitConversions) { - returnType = conversion.targetType; + returnType = options.returnType; + if (returnType instanceof AutoStruct) { + // We're expecting an "auto" return type, so if there were structs returned, + // we accept the struct, otherwise we let the rest of the code unify on a + // primitive type. + if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { + returnType = returnType.completeStruct; + } else { + returnType = undefined; + } } - } - if (!returnType) { - throw new Error( - `Expected function to have a single return type, got [${returnTypes.join( - ', ', - )}]. Cast explicitly to the desired type.`, - ); - } + if (!returnType) { + const returnTypes = [...scope.reportedReturnTypes]; + if (returnTypes.length === 0) { + returnType = Void; + } else { + const conversion = getBestConversion(returnTypes); + if (conversion && !conversion.hasImplicitConversions) { + returnType = conversion.targetType; + } + } - returnType = concretize(returnType); + if (!returnType) { + throw new Error( + `Expected function to have a single return type, got [${returnTypes.join( + ', ', + )}]. Cast explicitly to the desired type.`, + ); + } - if (options.functionType === 'vertex' || options.functionType === 'fragment') { - returnType = createIoSchema(returnType as IOData); - } - } + returnType = concretize(returnType); - if (options.entryInput) { - const headerParts = pendingHeaderEntries - .filter(({ argName }) => isArgUsedInBody(argName, body)) - .map(({ header }) => header); - const argList = headerParts.join(', '); - const returnStr = - returnType.type !== 'void' - ? `-> ${getAttributesString(returnType)}${this.resolve(returnType).value} ` - : ''; - return { head: `(${argList}) ${returnStr}`, body, returnType }; + if (options.functionType === 'vertex' || options.functionType === 'fragment') { + returnType = createIoSchema(returnType as IOData); + } + } + return returnType; + }, + }); + + if (!returnType) { + throw new Error(`Failed to determine return type`); } return { - head: resolveFunctionHeader(this, args, returnType), - body, + code, returnType, }; } finally { @@ -1079,17 +1096,3 @@ export function resolve(item: Wgsl, options: ResolutionCtxImplOptions): Resoluti logResources: ctx.logResources, }; } - -function isArgUsedInBody(argName: string, body: string): boolean { - return new RegExp(`\\b${argName}\\b`).test(body); -} - -function resolveFunctionHeader(ctx: ResolutionCtx, args: Snippet[], returnType: BaseData) { - const argList = args - .map((arg) => `${arg.value}: ${ctx.resolve(arg.dataType as BaseData).value}`) - .join(', '); - - return returnType.type !== 'void' - ? `(${argList}) -> ${getAttributesString(returnType)}${ctx.resolve(returnType).value} ` - : `(${argList}) `; -} diff --git a/packages/typegpu/src/tgsl/accessProp.ts b/packages/typegpu/src/tgsl/accessProp.ts index b264a154ba..3410d3e383 100644 --- a/packages/typegpu/src/tgsl/accessProp.ts +++ b/packages/typegpu/src/tgsl/accessProp.ts @@ -10,7 +10,7 @@ import { } from '../data/dataTypes.ts'; import { abstractInt, bool, f16, f32, i32, u32 } from '../data/numeric.ts'; import { derefSnippet } from '../data/ref.ts'; -import { isEphemeralSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { isEphemeralSnippet, isSnippet, snip, type Snippet } from '../data/snippet.ts'; import { vec2b, vec2f, @@ -160,7 +160,14 @@ export function accessProp(target: Snippet, propName: string): Snippet | undefin } if (target.dataType instanceof EntryInputRouter) { - return target.dataType.accessProp(propName); + const result = target.dataType.accessProp(propName); + if (isSnippet(result)) { + return result; + } + if (result) { + return accessProp(result.target, result.prop); + } + return undefined; } if (isPtr(target.dataType)) { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 0d8ae68163..a11954784a 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -294,7 +294,7 @@ export function convertToCommonType( if ((TEST || DEV) && verbose && conversion.hasImplicitConversions) { console.warn( `Implicit conversions from [\n${values - .map((v) => ` ${v.value}: ${safeStringify(v.dataType)}`) + .map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`) .join(',\n')}\n] to ${conversion.targetType.type} are supported, but not recommended. Consider using explicit conversions instead.`, ); diff --git a/packages/typegpu/src/tgsl/shaderGenerator.ts b/packages/typegpu/src/tgsl/shaderGenerator.ts index e482b03cc8..adde48bf00 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator.ts @@ -1,7 +1,7 @@ -import type { Block } from 'tinyest'; import type { BaseData } from '../data/wgslTypes.ts'; import type { GenerationCtx } from './generationHelpers.ts'; import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; /** * **NOTE: This is an unstable API and may change in the future.** @@ -12,7 +12,7 @@ import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; export interface ShaderGenerator { initGenerator(ctx: GenerationCtx): void; - functionDefinition(body: Block): string; + functionDefinition(options: FunctionDefinitionOptions): string; typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet; typeAnnotation(schema: BaseData): string; } diff --git a/packages/typegpu/src/tgsl/shaderGenerator_members.ts b/packages/typegpu/src/tgsl/shaderGenerator_members.ts index a33ac92b24..8dfc3eae57 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator_members.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator_members.ts @@ -1,4 +1,16 @@ +import type { Block } from 'tinyest'; +import type { BaseData } from '../data/wgslTypes.ts'; +import type { FunctionArgument, TgpuShaderStage } from '../types.ts'; + export { UnknownData } from '../data/dataTypes.ts'; // types -export type { ResolutionCtx } from '../types.ts'; +export type { ResolutionCtx, FunctionArgument } from '../types.ts'; + +export interface FunctionDefinitionOptions { + readonly functionType: 'normal' | TgpuShaderStage; + readonly args: readonly FunctionArgument[]; + readonly body: Block; + + determineReturnType(): BaseData; +} diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 7c7c06d7c8..153ca5ed0b 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -51,6 +51,8 @@ import { mathToStd } from './math.ts'; import type { ExternalMap } from '../core/resolve/externals.ts'; import * as forOfUtils from './forOfUtils.ts'; import { isTgpuRange } from '../std/range.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; +import { getAttributesString } from '../data/attributes.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -887,8 +889,27 @@ ${this.ctx.pre}}`; assertExhaustive(expression); } - public functionDefinition(body: tinyest.Block): string { - return this._block(body); + public functionDefinition(options: FunctionDefinitionOptions): string { + // Function body + const body = this._block(options.body); + + // Function header + const returnType = options.determineReturnType(); + + const argList = options.args + // Stripping out unused arguments in entry functions + .filter((arg) => arg.used || options.functionType === 'normal') + .map((arg) => { + return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`; + }) + .join(', '); + + const head = + returnType.type !== 'void' + ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` + : `(${argList}) `; + + return `${head}${body}`; } /** diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 1c92f8a43f..75f9e71697 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -99,11 +99,19 @@ export type ItemLayer = { usedSlots: Set>; }; +export type FunctionArgumentAccess = () => Snippet | undefined; + +export interface FunctionArgument { + name: string; + access: FunctionArgumentAccess; + decoratedType: BaseData; + used: boolean; +} + export type FunctionScopeLayer = { type: 'functionScope'; functionType: 'normal' | 'compute' | 'vertex' | 'fragment'; - args: Snippet[]; - argAliases: Record; + argAccess: Record; externalMap: Record; /** * The return type of the function. If undefined, the type should be inferred @@ -138,8 +146,7 @@ export interface ItemStateStack { pushSlotBindings(pairs: SlotValuePair[]): void; pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, /** * The return type of the function. If undefined, the type should be inferred * from the implementation (relevant for shellless functions). @@ -305,8 +312,7 @@ export interface ResolutionCtx { resolveSnippet(snippet: Snippet): ResolvedSnippet; fnToWgsl(options: FnToWgslOptions): { - head: Wgsl; - body: Wgsl; + code: string; returnType: BaseData; }; diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 316f2d0e02..be0c2888e4 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -277,13 +277,6 @@ describe('root.withVertex(...).withFragment(...)', () => { return vertexMain_Output(vec3f(), vec3f(), vec3f(), 0f, 0u, vec4f()); } - struct fragmentMain_Input { - @location(3) baz3: u32, - @location(1) bar: vec3f, - @location(2) foo: vec3f, - @location(5) baz2: f32, - } - @fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(); }" @@ -1568,8 +1561,8 @@ describe('root.createRenderPipeline', () => { } struct VertexIn { - @builtin(vertex_index) vertexIndex: u32, @location(0) localPos: vec3f, + @builtin(vertex_index) vertexIndex: u32, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { @@ -1712,18 +1705,13 @@ describe('root.createRenderPipeline', () => { { "arrayStride": 16, "attributes": [ - { - "format": "float32", - "offset": 12, - "shaderLocation": 0, - }, { "format": "float32x3", "offset": 0, - "shaderLocation": 1, + "shaderLocation": 0, }, ], - "stepMode": "instance", + "stepMode": "vertex", }, { "arrayStride": 16, @@ -1731,10 +1719,15 @@ describe('root.createRenderPipeline', () => { { "format": "float32x3", "offset": 0, + "shaderLocation": 1, + }, + { + "format": "float32", + "offset": 12, "shaderLocation": 2, }, ], - "stepMode": "vertex", + "stepMode": "instance", }, ], "module": "mockShaderModule", @@ -1751,10 +1744,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "instanceBuffer", + "label": "vertexBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 16, + "size": 48, "unmap": [MockFunction], "usage": 44, }, @@ -1766,10 +1759,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "vertexBuffer", + "label": "instanceBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 48, + "size": 16, "unmap": [MockFunction], "usage": 44, }, diff --git a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts index 727e9fed70..58150fbee5 100644 --- a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts +++ b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts @@ -17,14 +17,14 @@ describe('extension based pruning', () => { }); expect(tgpu.resolve([someFn], { enableExtensions: ['f16'] })).toMatchInlineSnapshot(` - "enable f16; + "enable f16; - fn someFn() -> f32 { - { - return 6.599609375f; - } - }" - `); + fn someFn() -> f32 { + { + return 6.599609375f; + } + }" + `); expect(tgpu.resolve([someFn])).toMatchInlineSnapshot(` "fn someFn() -> f32 { diff --git a/packages/typegpu/tests/tgsl/nameClashes.test.ts b/packages/typegpu/tests/tgsl/nameClashes.test.ts index 86167245ed..586e8e3bf1 100644 --- a/packages/typegpu/tests/tgsl/nameClashes.test.ts +++ b/packages/typegpu/tests/tgsl/nameClashes.test.ts @@ -223,14 +223,14 @@ test('should allow duplicate name after block end', () => { }; expect(tgpu.resolve([main])).toMatchInlineSnapshot(` - "fn main() -> u32 { - for (var i = 0; (i < 3i); i++) { - let foo = (i + 1i); - } - const foo = 7u; - return foo; - }" - `); + "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', () => { diff --git a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts index fd37903d12..c41cc6b27c 100644 --- a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts +++ b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts @@ -18,14 +18,14 @@ describe('ternary operator', () => { myFn.with(mySlot, false).$name('falseFn'), ]), ).toMatchInlineSnapshot(` - "fn trueFn() -> u32 { - return 10u; - } - - fn falseFn() -> u32 { - return 20u; - }" - `); + "fn trueFn() -> u32 { + return 10u; + } + + fn falseFn() -> u32 { + return 20u; + }" + `); }); it('should work for different comptime known expressions', () => { @@ -72,22 +72,22 @@ describe('ternary operator', () => { myFn.with(mySlot, 3).$name('threeFn'), ]), ).toMatchInlineSnapshot(` - "fn myFn() -> u32 { - return -1u; - } - - fn oneFn() -> u32 { - return 10u; - } - - fn twoFn() -> u32 { - return 20u; - } - - fn threeFn() -> u32 { - return 30u; - }" - `); + "fn myFn() -> u32 { + return -1u; + } + + fn oneFn() -> u32 { + return 10u; + } + + fn twoFn() -> u32 { + return 20u; + } + + fn threeFn() -> u32 { + return 30u; + }" + `); }); it('should not include unused dependencies', ({ root }) => { @@ -103,20 +103,20 @@ describe('ternary operator', () => { }); expect(tgpu.resolve([myFn.with(mySlot, true).$name('trueFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myUniform: u32; + "@group(0) @binding(0) var myUniform: u32; - fn trueFn() -> u32 { - return myUniform; - }" - `); + fn trueFn() -> u32 { + return myUniform; + }" + `); expect(tgpu.resolve([myFn.with(mySlot, false).$name('falseFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myReadonly: u32; + "@group(0) @binding(0) var myReadonly: u32; - fn falseFn() -> u32 { - return myReadonly; - }" - `); + fn falseFn() -> u32 { + return myReadonly; + }" + `); }); it('should handle undefined', ({ root }) => { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 62c96fe428..89e9e50218 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -3,8 +3,7 @@ import { beforeEach, describe, expect, vi } from 'vitest'; import { namespace } from '../../src/core/resolve/namespace.ts'; import * as d from '../../src/data/index.ts'; import { abstractFloat, abstractInt } from '../../src/data/numeric.ts'; -import { snip } from '../../src/data/snippet.ts'; -import { Void, type WgslArray } from '../../src/data/wgslTypes.ts'; +import { type WgslArray } from '../../src/data/wgslTypes.ts'; import { provideCtx } from '../../src/execMode.ts'; import tgpu from '../../src/index.js'; import { ResolutionCtxImpl } from '../../src/resolutionCtx.ts'; @@ -15,8 +14,7 @@ import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from 'typegpu-testing-utility'; import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; -import { extractSnippetFromFn } from '../utils/parseResolved.ts'; -import { UnknownData } from '../../src/tgsl/shaderGenerator_members.ts'; +import { expectDataTypeOf, extractSnippetFromFn } from '../utils/parseResolved.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -41,19 +39,11 @@ describe('wgslGenerator', () => { return true; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot(`"[0,[[10,true]]]"`); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.bool, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - return true; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> bool { + return true; + }" + `); }); it('creates a function body', () => { @@ -64,23 +54,13 @@ describe('wgslGenerator', () => { return a; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot( - `"[0,[[12,"a",[5,"12"]],[2,"a","+=",[5,"21"]],[10,"a"]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.i32, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - var a = 12; - a += 21i; - return a; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> i32 { + var a = 12; + a += 21i; + return a; + }" + `); }); it('creates correct resources for numeric literals', () => { @@ -135,57 +115,22 @@ describe('wgslGenerator', () => { }); const testBuffer = root.createBuffer(TestStruct).$usage('storage'); - const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$.a + testUsage.$.b.x; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - if (!astInfo) { - throw new Error('Expected prebuilt AST to be present'); - } + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.a; + }).toStrictEqual(d.u32); - expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( - `"[0,[[10,[1,[7,[7,"testUsage","$"],"a"],"+",[7,[7,[7,"testUsage","$"],"b"],"x"]]]]]"`, - ); - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.b.x; + }).toStrictEqual(d.u32); - provideCtx(ctx, () => { - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res1 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[1], - ); - - expect(res1.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res2 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[3], - ); - expect(res2.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const sum = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - expect(sum.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.a + testUsage.$.b.x; + }).toStrictEqual(d.u32); }); it('generates correct resources for external resource array index access', ({ root }) => { @@ -193,42 +138,10 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('uniform'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$[3] as number; - }); - - 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,[[10,[8,[7,"testUsage","$"],[5,"3"]]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return testUsage.$[3]; - // ^ this should be a u32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$[3]; + }).toStrictEqual(d.u32); }); it('generates correct resources for nested struct with atomics in a complex expression', ({ @@ -254,156 +167,29 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [d.u32], - d.vec4f, - )((idx) => { - const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - const vec = std.mix(d.vec4f(), testUsage.$.a, value); - std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - return vec; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - - if (!astInfo?.ast) { - throw new Error('Expected prebuilt AST to be present'); - } - - expect(JSON.stringify(astInfo.ast.body)).toMatchInlineSnapshot( - `"[0,[[13,"value",[6,[7,"std","atomicLoad"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"y"]]]],[13,"vec",[6,[7,"std","mix"],[[6,[7,"d","vec4f"],[]],[7,[7,"testUsage","$"],"a"],"value"]]],[6,[7,"std","atomicStore"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"x"],[7,"vec","y"]]],[10,"vec"]]]"`, - ); - - if (astInfo.ast.params.filter((arg) => arg.type !== 'i').length > 0) { - throw new Error('Expected arguments as identifier names in ast'); - } - - const args = astInfo.ast.params.map((arg) => - snip((arg as { type: 'i'; name: string }).name, d.u32, /* origin */ 'runtime'), - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - args, - {}, - d.vec4f, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - // ^ this part should be a i32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.i32); - - // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); - // ^ this part should be a vec4f - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'value', d.i32, 'runtime'); - const res2 = wgslGenerator._expression( - (astInfo.ast?.body[1][1] as tinyest.Const)[2] as tinyest.Expression, - ); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res2.dataType).toStrictEqual(d.vec4f); - - // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - // ^ this part should be an atomic u32 - // ^ this part should be void - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'function'); - const res3 = wgslGenerator._expression( - (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, - ); - const res4 = wgslGenerator._expression(astInfo.ast?.body[1][2] as tinyest.Expression); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res3.dataType).toStrictEqual(d.atomic(d.u32)); - expect(res4.dataType).toStrictEqual(Void); - }); - }); - - it('creates correct code for for statements', () => { - const main = () => { + // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); + // ^ this part should be a i32 + expectDataTypeOf(() => { 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[14,[12,"i",[5,"0"]],[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); + const idx = d.u32(0); + std.atomicLoad(testUsage.$.b.aa[idx]!.y); + }).toStrictEqual(d.i32); - it('creates correct code for for statements with outside init', () => { - const main = () => { + // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); + // ^ this part should be a vec4f + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - for (; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[14,null,[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - for (; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); + const value = std.atomicLoad(testUsage.$.b.aa[0]!.y); + std.mix(d.vec4f(), testUsage.$.a, value); + }).toStrictEqual(d.vec4f); - it('creates correct code for while statements', () => { - const main = () => { + // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); + // ^ this part should be an atomic u32 + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - while (i < 10) { - i += 1; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[15,[1,"i","<",[5,"10"]],[0,[[2,"i","+=",[5,"1"]]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - while ((i < 10i)) { - i += 1i; - } - }" - `); + const idx = d.u32(0); + testUsage.$.b.aa[idx]!.x; + }).toStrictEqual(d.atomic(d.u32)); }); it('parses correctly "for ... of ..." statements', () => { @@ -1004,6 +790,11 @@ describe('wgslGenerator', () => { }); it('creates correct resources for lazy values and slots', () => { + expectDataTypeOf(() => { + 'use gpu'; + lazyV4u.$; + }).toStrictEqual(d.vec4u); + const testFn = tgpu.fn([], d.vec4u)(() => lazyV4u.$); expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` @@ -1011,76 +802,14 @@ describe('wgslGenerator', () => { return vec4u(44, 88, 132, 176); }" `); - - 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,[[10,[7,"lazyV4u","$"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.vec4u, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return lazyV4u.$; - // ^ this should be a vec4u - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.vec4u); - }); }); it('creates correct resources for indexing into a lazy value', () => { - const testFn = tgpu.fn( - [d.u32], - d.f32, - )((idx) => { - return lazyV2f.$[idx] as number; - }); - - 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,[[10,[8,[7,"lazyV2f","$"],"idx"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [snip('idx', d.u32, /* origin */ 'runtime')], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return lazyV2f.$[idx]; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + const idx = d.u32(0); + lazyV2f.$[idx]; + }).toStrictEqual(d.f32); }); it('creates intermediate representation for array expression', () => { @@ -1106,44 +835,11 @@ describe('wgslGenerator', () => { }); 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( - (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); - }); + "fn testFn() -> u32 { + var arr = array(1u, 2u, 3u); + return arr[1i]; + }" + `); }); it('generates correct code for complex array expressions', () => { @@ -1165,39 +861,6 @@ 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( - (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); - }); }); it('does not autocast lhs of an assignment', () => { @@ -1248,38 +911,15 @@ describe('wgslGenerator', () => { }" `); - 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,"TestStruct",[[104,{"x":[5,"1"],"y":[5,"2"]}]]],[6,"TestStruct",[[104,{"x":[5,"3"],"y":[5,"4"]}]]]]]],[10,[7,[8,"arr",[5,"1"]],"y"]]]]"`, - ); - - const res = provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; - // ^ this should be an array - wgslGenerator.initGenerator(ctx); - return wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); + const arraySnippet = extractSnippetFromFn(() => { + 'use gpu'; + const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; + arr; }); - 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); + expect(d.isWgslArray(arraySnippet.dataType)).toBe(true); + expect((arraySnippet.dataType as unknown as WgslArray).elementCount).toBe(2); + expect((arraySnippet.dataType as unknown as WgslArray).elementType).toBe(TestStruct); }); it('generates correct code for array expressions with lazy elements', () => { @@ -1297,37 +937,6 @@ describe('wgslGenerator', () => { return arr[1i].y; }" `); - - 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,[[7,"lazyV2f","$"],[6,[7,"std","mul"],[[7,"lazyV2f","$"],[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); }); it('allows for member access on values returned from function calls', () => { @@ -1365,34 +974,10 @@ describe('wgslGenerator', () => { }" `); - const astInfo = getMetaData(fnTwo[$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,[[10,[7,[7,[6,"fnOne",[]],"y"],"x"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return fnOne().y.x; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + fnOne().y.x; + }).toStrictEqual(d.f32); }); it('generates correct code for conditional with single statement', () => { @@ -1465,27 +1050,6 @@ describe('wgslGenerator', () => { `); }); - it('generates correct code for for loops with single statements', () => { - const main = () => { - 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const gen = provideCtx(ctx, () => - wgslGenerator.functionDefinition(getMetaData(main)?.ast?.body as tinyest.Block), - ); - - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); - it('generates correct code for while loops with single statements', () => { const main = tgpu.fn([])(() => { let i = 0; @@ -1641,7 +1205,7 @@ describe('wgslGenerator', () => { }); it('does not cause identifier clashes when renaming parameters', () => { - const main = tgpu.fn([d.u32, d.u32])((extern, extern_1) => {}); + const main = tgpu.fn([d.u32, d.u32])((extern, extern_1) => { }); expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main(extern_1: u32, extern_1_1: u32) { @@ -1806,59 +1370,51 @@ describe('wgslGenerator', () => { it('block externals do not override identifiers', () => { const f = () => { 'use gpu'; - const y = 100; - const x = y; - return x; + const list = [1]; + for (const x of tgpu.unroll(list)) { + 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); - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var list = array(1); + // unrolled iteration #0 + { + const y = 100; + const x = y; + return x; + } + }" + `); }); it('block externals are injected correctly', () => { const f = () => { 'use gpu'; - for (const x of []) { + for (const x of tgpu.unroll([1])) { 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; - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + // unrolled iteration #0 + { + const y = 1; + } + }" + `); }); 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) { + const list = [1]; + for (const elem of tgpu.unroll(list)) { { // We use the `elem` in a nested block result += elem; @@ -1866,24 +1422,18 @@ describe('wgslGenerator', () => { } }; - 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(` - "{ + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var result = 0i; + var list = array(1); + // unrolled iteration #0 + { { - result += 7i; + result += list[0u]; } - }" - `); - }); + } + }" + `); }); it('prunes comptime if/else', () => { diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 529e9f0cfa..9278a82a00 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -363,11 +363,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -410,11 +406,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -444,11 +436,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - @fragment fn fragmentFn() -> @location(0) vec4f { + "@fragment fn fragmentFn() -> @location(0) vec4f { var hmm = vec4f(1.25); return hmm; }" @@ -481,11 +469,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index b798ff9f8c..9c94a073c0 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -29,7 +29,6 @@ export function extractSnippetFromFn(cb: () => unknown): Snippet { ctx[$internal].itemStateStack.pushItem(); ctx[$internal].itemStateStack.pushFunctionScope( 'normal', - [], {}, undefined, (meta.externals as () => Record)() ?? {}, From 5b7b6ebfcf863215ab9830bfcb96c9e899a42ae5 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 11 Apr 2026 13:31:41 +0200 Subject: [PATCH 17/34] refactor: Naming cleanup --- .../typegpu/src/core/buffer/bufferUsage.ts | 4 +- .../typegpu/src/core/constant/tgpuConstant.ts | 2 +- packages/typegpu/src/core/function/fnCore.ts | 27 +-- .../typegpu/src/core/resolve/namespace.ts | 55 +----- .../typegpu/src/core/resolve/resolveData.ts | 5 +- packages/typegpu/src/core/sampler/sampler.ts | 4 +- .../src/core/texture/externalTexture.ts | 2 +- packages/typegpu/src/core/texture/texture.ts | 4 +- .../typegpu/src/core/variable/tgpuVariable.ts | 2 +- packages/typegpu/src/data/autoStruct.ts | 2 +- packages/typegpu/src/data/struct.ts | 2 +- .../src/{nameRegistry.ts => nameUtils.ts} | 166 +----------------- packages/typegpu/src/resolutionCtx.ts | 135 ++++++++++---- packages/typegpu/src/tgsl/wgslGenerator.ts | 31 ++-- packages/typegpu/src/types.ts | 25 ++- packages/typegpu/tests/namespace.test.ts | 30 +--- packages/typegpu/tests/resolve.test.ts | 4 +- 17 files changed, 187 insertions(+), 313 deletions(-) rename packages/typegpu/src/{nameRegistry.ts => nameUtils.ts} (58%) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 24f28dcbca..d8a1028e78 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -118,7 +118,7 @@ class TgpuFixedBufferImpl implements TgpuConst, } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const resolvedDataType = ctx.resolve(this.dataType).value; const resolvedValue = ctx.resolve(this.#value, this.dataType).value; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index cf4cda90f7..aa91d451f6 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -3,6 +3,7 @@ import { undecorate } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTypes.ts'; import { MissingLinksError } from '../../errors.ts'; +import { isValidIdentifier } from '../../nameUtils.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; @@ -65,21 +66,25 @@ export function createFnCore( applyExternals(externalMap, externals); } - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); if (typeof implementation === 'string') { if (!returnType) { throw new Error('Explicit return type is required for string implementation'); } - const validArgNames = entryInput - ? Object.fromEntries( - entryInput.positionalArgs.map((a) => [a.schemaKey, ctx.makeNameValid(a.schemaKey)]), - ) - : undefined; + if (entryInput) { + for (const arg of entryInput.positionalArgs) { + if (!isValidIdentifier(arg.schemaKey)) { + throw new Error(`Invalid argument name: ${arg.schemaKey}`); + } + } - if (validArgNames && Object.keys(validArgNames).length > 0) { - applyExternals(externalMap, { in: validArgNames }); + applyExternals(externalMap, { + in: Object.fromEntries( + entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey]), + ), + }); } const replacedImpl = replaceExternalsInWgsl(ctx, externalMap, implementation); @@ -87,15 +92,15 @@ export function createFnCore( let header = ''; let body = ''; - if (functionType !== 'normal' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { parts.push(`in: ${ctx.resolve(dataSchema).value}`); } for (const a of positionalArgs) { - const argName = validArgNames[a.schemaKey] ?? ''; - if (argName !== '' && isArgUsedInBody(argName, replacedImpl)) { + const argName = a.schemaKey; + if (isArgUsedInBody(argName, replacedImpl)) { parts.push(`${getAttributesString(a.type)}${argName}: ${ctx.resolve(a.type).value}`); } } diff --git a/packages/typegpu/src/core/resolve/namespace.ts b/packages/typegpu/src/core/resolve/namespace.ts index f112cd4fb0..90fcba08f7 100644 --- a/packages/typegpu/src/core/resolve/namespace.ts +++ b/packages/typegpu/src/core/resolve/namespace.ts @@ -1,6 +1,5 @@ import type { ResolvedSnippet } from '../../data/snippet.ts'; -import { type NameRegistry, RandomNameRegistry, StrictNameRegistry } from '../../nameRegistry.ts'; -import { getName } from '../../shared/meta.ts'; +import { bannedTokens, builtins } from '../../nameUtils.ts'; import { $internal } from '../../shared/symbols.ts'; import { ShelllessRepository } from '../../tgsl/shellless.ts'; import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; @@ -8,8 +7,9 @@ import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; type SlotToValueMap = Map, unknown>; export interface NamespaceInternal { - readonly nameRegistry: NameRegistry; + readonly takenGlobalIdentifiers: Set; readonly shelllessRepo: ShelllessRepository; + readonly strategy: 'random' | 'strict'; memoizedResolves: WeakMap< // WeakMap because if the item does not exist anymore, @@ -24,73 +24,32 @@ export interface NamespaceInternal { TgpuLazy, { slotToValueMap: SlotToValueMap; result: unknown }[] >; - - listeners: { - [K in keyof NamespaceEventMap]: Set<(event: NamespaceEventMap[K]) => void>; - }; } -type NamespaceEventMap = { - name: { target: object; name: string }; -}; - -type DetachListener = () => void; - export interface Namespace { readonly [$internal]: NamespaceInternal; - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener; } class NamespaceImpl implements Namespace { readonly [$internal]: NamespaceInternal; - constructor(nameRegistry: NameRegistry) { + constructor(strategy: 'random' | 'strict') { this[$internal] = { - nameRegistry, + strategy, + takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins]), shelllessRepo: new ShelllessRepository(), memoizedResolves: new WeakMap(), memoizedLazy: new WeakMap(), - listeners: { - name: new Set(), - }, }; } - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener { - if (event === 'name') { - const listeners = this[$internal].listeners.name; - listeners.add(listener); - - return () => listeners.delete(listener); - } - - throw new Error(`Unsupported event: ${event}`); - } } export interface NamespaceOptions { names?: 'random' | 'strict' | undefined; } -export function getUniqueName(namespace: NamespaceInternal, resource: object): string { - const name = namespace.nameRegistry.makeUnique(getName(resource), true); - for (const listener of namespace.listeners.name) { - listener({ target: resource, name }); - } - return name; -} - export function namespace(options?: NamespaceOptions): Namespace { const { names = 'strict' } = options ?? {}; - return new NamespaceImpl( - names === 'strict' ? new StrictNameRegistry() : new RandomNameRegistry(), - ); + return new NamespaceImpl(names); } diff --git a/packages/typegpu/src/core/resolve/resolveData.ts b/packages/typegpu/src/core/resolve/resolveData.ts index 19e5da347d..bc5320c80d 100644 --- a/packages/typegpu/src/core/resolve/resolveData.ts +++ b/packages/typegpu/src/core/resolve/resolveData.ts @@ -38,6 +38,7 @@ import type { WgslArray, WgslStruct, } from '../../data/wgslTypes.ts'; +import { getName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { assertExhaustive } from '../../shared/utilityTypes.ts'; import type { ResolutionCtx } from '../../types.ts'; @@ -127,7 +128,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) { if (struct[$internal].isAbstruct) { throw new Error('Cannot resolve abstract struct types to WGSL.'); } - const id = ctx.getUniqueName(struct); + const id = ctx.makeUniqueIdentifier(getName(struct), 'global'); ctx.addDeclaration(`\ struct ${id} { @@ -155,7 +156,7 @@ ${Object.entries(struct.propTypes) * ``` */ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) { - const id = ctx.getUniqueName(unstruct); + const id = ctx.makeUniqueIdentifier(getName(unstruct), 'global'); ctx.addDeclaration(`\ struct ${id} { diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index 4daaf1c917..adf10ee8ce 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -99,7 +99,7 @@ export class TgpuLaidOutSamplerImpl< } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( @@ -186,7 +186,7 @@ class TgpuFixedSamplerImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( this.schema.type === 'sampler_comparison' diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index c9d457c48b..78ae4cd582 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl implements TgpuExternalTexture, SelfResolva } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 27923995d6..fbe7295d83 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -600,7 +600,7 @@ class TgpuFixedTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( isWgslStorageTexture(this.schema) ? { @@ -642,7 +642,7 @@ export class TgpuLaidOutTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const pre = `var<${this.#scope}> ${id}: ${ctx.resolve(this.#dataType).value}`; if (this.#initialValue) { diff --git a/packages/typegpu/src/data/autoStruct.ts b/packages/typegpu/src/data/autoStruct.ts index 647d13b79c..c227b565fe 100644 --- a/packages/typegpu/src/data/autoStruct.ts +++ b/packages/typegpu/src/data/autoStruct.ts @@ -1,5 +1,5 @@ import { createIoSchema } from '../core/function/ioSchema.ts'; -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal, $repr, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; diff --git a/packages/typegpu/src/data/struct.ts b/packages/typegpu/src/data/struct.ts index c044da5f8f..c2d024acc3 100644 --- a/packages/typegpu/src/data/struct.ts +++ b/packages/typegpu/src/data/struct.ts @@ -1,4 +1,4 @@ -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { schemaCallWrapper } from './schemaCallWrapper.ts'; diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameUtils.ts similarity index 58% rename from packages/typegpu/src/nameRegistry.ts rename to packages/typegpu/src/nameUtils.ts index aa2cd1ada0..f66bfbb79a 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameUtils.ts @@ -1,6 +1,4 @@ -import { invariant } from './errors.ts'; - -const bannedTokens = new Set([ +export const bannedTokens = new Set([ // keywords 'alias', 'break', @@ -181,7 +179,7 @@ const bannedTokens = new Set([ 'storage', ]); -const builtins = new Set([ +export const builtins = new Set([ // constructors 'array', 'bool', @@ -361,37 +359,8 @@ const builtins = new Set([ 'quadSwapY', ]); -export interface NameRegistry { - /** - * Creates a valid WGSL identifier, each guaranteed to be unique - * in the lifetime of a single resolution process - * (excluding non-global identifiers from popped scopes). - * Should append "_" to primer, followed by some id. - * @param primer Used in the generation process, makes the identifier more recognizable. - * @param global Whether the name should be registered in the global scope (true), or in the current function scope (false) - */ - makeUnique(primer: string | undefined, global: boolean): string; - - /** - * Creates a valid WGSL identifier. - * Renames identifiers that are WGSL reserved words. - * @param primer Used in the generation process. - * - * @example - * makeValid("notAKeyword"); // "notAKeyword" - * makeValid("struct"); // makeUnique("struct") - * makeValid("struct_1"); // makeUnique("struct_1") (to avoid potential name collisions) - * makeValid("_"); // ERROR (too difficult to make valid to care) - */ - makeValid(primer: string): string; - - pushFunctionScope(): void; - popFunctionScope(): void; - pushBlockScope(): void; - popBlockScope(): void; -} - -function sanitizePrimer(primer: string | undefined) { +/*#__NO_SIDE_EFFECTS__*/ +export function sanitizePrimer(primer: string | undefined) { if (primer) { // sanitizing return primer @@ -411,7 +380,8 @@ function sanitizePrimer(primer: string | undefined) { * isValidIdentifier("_"); // ERROR * isValidIdentifier("my variable"); // ERROR */ -function isValidIdentifier(ident: string): boolean { +/*#__NO_SIDE_EFFECTS__*/ +export function isValidIdentifier(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( `Invalid identifier '${ident}'. Choose an identifier without whitespaces or leading underscores.`, @@ -424,6 +394,7 @@ function isValidIdentifier(ident: string): boolean { /** * Same as `isValidIdentifier`, except does not check for builtin clashes. */ +/*#__NO_SIDE_EFFECTS__*/ export function isValidProp(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( @@ -433,126 +404,3 @@ 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; - - readonly #usedNames: Set; - readonly #scopeStack: ScopeLayer[]; - - constructor() { - this.#usedNames = new Set([...bannedTokens, ...builtins]); - this.#scopeStack = []; - } - - get #usedBlockScopeNames(): Set | undefined { - return (this.#scopeStack[this.#scopeStack.length - 1] as BlockScopeLayer | undefined) - ?.usedBlockScopeNames; - } - - makeUnique(primer: string | undefined, global: boolean): string { - const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer); - - if (global) { - this.#usedNames.add(name); - } else { - 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.#isUsedInBlocksBefore(primer) - ) { - this.#usedBlockScopeNames?.add(primer); - return primer; - } - return this.makeUnique(primer, false); - } - - isUsed(name: string): boolean { - return this.#usedNames.has(name) || this.#isUsedInBlocksBefore(name); - } - - pushFunctionScope(): void { - this.#scopeStack.push({ type: 'functionScope' }); - this.#scopeStack.push({ - type: 'blockScope', - usedBlockScopeNames: new Set(), - }); - } - - popFunctionScope(): void { - const functionScopeIndex = this.#scopeStack.findLastIndex( - (scope) => scope.type === 'functionScope', - ); - - if (functionScopeIndex === -1) { - throw new Error('Tried to pop function scope when no scope was present.'); - } - - 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 { - let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name)) { - name = `${base}_${this.#lastUniqueId++}`; - } - return name; - } -} - -export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string): string { - let index = 0; - let name = base; - while (this.isUsed(name)) { - index++; - name = `${base}_${index}`; - } - return name; - } -} diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5eedec298d..57f87d7e84 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -1,5 +1,5 @@ import { isTgpuFn } from './core/function/tgpuFn.ts'; -import { getUniqueName, type Namespace, type NamespaceInternal } from './core/resolve/namespace.ts'; +import type { Namespace, NamespaceInternal } from './core/resolve/namespace.ts'; import { stitch } from './core/resolve/stitch.ts'; import { ConfigurableImpl } from './core/root/configurableImpl.ts'; import type { Configurable, ExperimentalTgpuRoot } from './core/root/rootTypes.ts'; @@ -37,6 +37,7 @@ import { coerceToSnippet, concretize, numericLiteralToSnippet } from './tgsl/gen import type { ShaderGenerator } from './tgsl/shaderGenerator.ts'; import wgslGenerator from './tgsl/wgslGenerator.ts'; import type { + BlockScopeLayer, ExecMode, ExecState, FnToWgslOptions, @@ -59,6 +60,7 @@ import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; +import { isValidIdentifier, sanitizePrimer } from './nameUtils.ts'; /** * Inserted into bind group entry definitions that belong @@ -99,6 +101,10 @@ class ItemStateStackImpl implements ItemStateStack { return this._stack.findLast((e) => e.type === 'functionScope'); } + get topBlockScope(): BlockScopeLayer | undefined { + return this._stack.findLast((e) => e.type === 'blockScope'); + } + pushItem() { this._itemDepth++; this._stack.push({ @@ -136,6 +142,7 @@ class ItemStateStackImpl implements ItemStateStack { pushBlockScope() { this._stack.push({ type: 'blockScope', + takenLocalIdentifiers: new Set(), declarations: new Map(), externals: new Map(), }); @@ -213,6 +220,26 @@ class ItemStateStackImpl implements ItemStateStack { return undefined; } + isIdentifierTakenLocally(id: string): boolean { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + + if (layer?.type === 'functionScope') { + // Since functions cannot access resources from the calling scope, we + // return early here. + return false; + } + + if (layer?.type === 'blockScope') { + if (layer.takenLocalIdentifiers.has(id)) { + return true; + } + } + } + + return false; + } + defineBlockVariable(id: string, snippet: Snippet): void { if (snippet.dataType === UnknownData) { throw Error(`Tried to define variable '${id}' of unknown type`); @@ -375,6 +402,11 @@ export class ResolutionCtxImpl implements ResolutionCtx { public readonly enableExtensions: WgslExtension[] | undefined; public expectedType: BaseData | undefined; + /** + * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy. + */ + #lastUniqueId = 0; + constructor(opts: ResolutionCtxImplOptions) { this.enableExtensions = opts.enableExtensions; this.gen = opts.shaderGenerator ?? wgslGenerator; @@ -382,12 +414,42 @@ export class ResolutionCtxImpl implements ResolutionCtx { this.#namespaceInternal = opts.namespace[$internal]; } - getUniqueName(resource: object): string { - return getUniqueName(this.#namespaceInternal, resource); + isIdentifierTaken(name: string): boolean { + return ( + this.#namespaceInternal.takenGlobalIdentifiers.has(name) || + !!this._itemStateStack.isIdentifierTakenLocally(name) + ); } - makeNameValid(name: string): string { - return this.#namespaceInternal.nameRegistry.makeValid(name); + makeUniqueIdentifier(primer: string = 'item', scope: 'global' | 'block'): string { + if (scope === 'block' && isValidIdentifier(primer) && !this.isIdentifierTaken(primer)) { + // Preserving local definitions as they are, provided they are valid and not already taken. + this.reserveIdentifier(primer, 'block'); + return primer; + } + + const base = sanitizePrimer(primer); + let index = 0; + const random = this.#namespaceInternal.strategy === 'random'; + let name = random ? `${base}_${this.#lastUniqueId++}` : base; + while (this.isIdentifierTaken(name)) { + name = random ? `${base}_${this.#lastUniqueId++}` : `${base}_${++index}`; + } + + this.reserveIdentifier(name, scope); + return name; + } + + reserveIdentifier(name: string, scope: 'global' | 'block'): void { + if (scope === 'block') { + const blockScope = this._itemStateStack.topBlockScope; + if (blockScope) { + blockScope.takenLocalIdentifiers.add(name); + return; + } + // Fall through if no block scope is present, treating as global. + } + this.#namespaceInternal.takenGlobalIdentifiers.add(name); } get pre(): string { @@ -441,12 +503,10 @@ export class ResolutionCtxImpl implements ResolutionCtx { } pushBlockScope() { - this.#namespaceInternal.nameRegistry.pushBlockScope(); this._itemStateStack.pushBlockScope(); } popBlockScope() { - this.#namespaceInternal.nameRegistry.popBlockScope(); this._itemStateStack.pop('blockScope'); } @@ -467,19 +527,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { } fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { - let fnScopePushed = false; - try { - this.#namespaceInternal.nameRegistry.pushFunctionScope(); + const scope = this._itemStateStack.pushFunctionScope( + options.functionType, + {}, + options.returnType, + options.externalMap, + ); + // Pushing a block scope as well, so that any identifiers declared at this point will be scoped to the function body. + this._itemStateStack.pushBlockScope(); + const args: FunctionArgument[] = []; - const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; const structArg = dataSchema - ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + ? createArgument(this.makeUniqueIdentifier('_arg_0', 'block'), dataSchema) : undefined; if (structArg) { @@ -491,31 +556,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const arg = createArgument(this.makeNameValid(alias), argInfo.type); + const arg = createArgument(this.makeUniqueIdentifier(alias, 'block'), argInfo.type); args.push(arg); - argAccess[alias] = arg.access; + scope.argAccess[alias] = arg.access; } else if (structArg) { - argAccess[alias] = createArgumentPropAccess(structArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } const router = new EntryInputRouter(structArg?.access, proxyEntries); - argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); + scope.argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); - argAccess[argName] = arg.access; + scope.argAccess[argName] = arg.access; } } } else { @@ -538,16 +603,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + const arg = createArgument( + this.makeUniqueIdentifier(astParam.name, 'block'), + argType, + origin, + ); args.push(arg); - argAccess[astParam.name] = arg.access; + scope.argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + const objArg = createArgument( + this.makeUniqueIdentifier(`_arg_${i}`, 'block'), + argType, + origin, + ); args.push(objArg); for (const { name, alias } of astParam.props) { - argAccess[alias] = createArgumentPropAccess(objArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(objArg.access, name); } break; } @@ -557,7 +630,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { // have any properties anyway. if (!(argType instanceof AutoStruct)) { args.push({ - name: this.makeNameValid(`_arg_${i}`), + name: this.makeUniqueIdentifier(`_arg_${i}`, 'block'), access: () => { throw new Error( `Unreachable: Accessing an argument that wasn't named in the function signature`, @@ -572,14 +645,6 @@ export class ResolutionCtxImpl implements ResolutionCtx { } } - const scope = this._itemStateStack.pushFunctionScope( - options.functionType, - argAccess, - options.returnType, - options.externalMap, - ); - fnScopePushed = true; - let returnType: BaseData | undefined; const code = this.gen.functionDefinition({ @@ -642,10 +707,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { returnType, }; } finally { - if (fnScopePushed) { - this._itemStateStack.pop('functionScope'); - } - this.#namespaceInternal.nameRegistry.popFunctionScope(); + this._itemStateStack.pop('blockScope'); + this._itemStateStack.pop('functionScope'); } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 153ca5ed0b..e419ff5814 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -241,8 +241,12 @@ ${this.ctx.pre}}`; } } + public _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string { + return `${this.ctx.pre}${this._block(block, externalMap)}`; + } + public refVariable(id: string, dataType: wgsl.StorableData): string { - const varName = this.ctx.makeNameValid(id); + const varName = this.ctx.makeUniqueIdentifier(id, 'block'); const ptrType = ptrFn(dataType); const snippet = snip( new RefOperator(snip(varName, dataType, 'function'), ptrType), @@ -280,7 +284,11 @@ ${this.ctx.pre}}`; varOrigin = 'runtime'; } - const snippet = snip(this.ctx.makeNameValid(id), dataType, /* origin */ varOrigin); + const snippet = snip( + this.ctx.makeUniqueIdentifier(id, 'block'), + dataType, + /* origin */ varOrigin, + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -1034,7 +1042,7 @@ Try 'return ${typeStr}(${str});' instead. return this._statement(node); } // simplify 'if (true) {A} else {B}' to '{A}' - return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`; + return this._blockStatement(blockifySingleStatement(node)); } const consequent = this._block(blockifySingleStatement(consNode)); @@ -1167,7 +1175,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.block) { - return `${this.ctx.pre}${this._block(statement)}`; + return this._blockStatement(statement); } if (statement[0] === NODE.for) { @@ -1263,12 +1271,9 @@ ${this.ctx.pre}else ${alternate}`; const blocks = elements.map( (e, i) => - `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block( - blockified, - { - [originalLoopVarName]: e, - }, - )}`, + `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { + [originalLoopVarName]: e, + })}`, ); return blocks.join('\n'); @@ -1276,7 +1281,7 @@ ${this.ctx.pre}else ${alternate}`; this.#unrolling = false; - const index = this.ctx.makeNameValid('i'); + const index = this.ctx.makeUniqueIdentifier('i', 'block'); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`; @@ -1289,7 +1294,7 @@ ${this.ctx.pre}else ${alternate}`; } else { this.ctx.indent(); ctxIndent = true; - const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, snip(index, u32, 'runtime'), @@ -1303,7 +1308,7 @@ ${this.ctx.pre}else ${alternate}`; false, )};`; - bodyStr = `{\n${loopVarDeclStr}\n${this.ctx.pre}${this._block(blockified, { + bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin), })}\n`; this.ctx.dedent(); diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 75f9e71697..d2b7892faf 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -131,6 +131,7 @@ export type SlotBindingLayer = { export type BlockScopeLayer = { type: 'blockScope'; + takenLocalIdentifiers: Set; declarations: Map; externals: Map; }; @@ -140,6 +141,7 @@ export type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | Blo export interface ItemStateStack { readonly itemDepth: number; readonly topItem: ItemLayer; + readonly topBlockScope: BlockScopeLayer | undefined; readonly topFunctionScope: FunctionScopeLayer | undefined; pushItem(): void; @@ -330,8 +332,27 @@ export interface ResolutionCtx { */ withRenamed(item: object, name: string | undefined, callback: () => T): T; - getUniqueName(resource: object): string; - makeNameValid(name: string): string; + /** + * @param primer The basis for the unique identifier. Depending on the strategy, or + * the names already taken, this may be modified to ensure uniqueness. + * @param scope The scope in which to generate the identifier. 'global' means + * the identifier is meant to be unique across the entire program, while + * 'block' means it cannot shadow any existing identifiers visible from + * within the current block. After the block is popped, any identifiers + * defined within it are no longer visible. + * @returns an identifier that is unique within the given scope + */ + makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string; + + isIdentifierTaken(name: string): boolean; + + /** + * Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier} + * within the given scope. + * @param name The name to reserve + * @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter. + */ + reserveIdentifier(name: string, scope: 'global' | 'block'): void; } /** diff --git a/packages/typegpu/tests/namespace.test.ts b/packages/typegpu/tests/namespace.test.ts index ee6fe33099..7ac6407b43 100644 --- a/packages/typegpu/tests/namespace.test.ts +++ b/packages/typegpu/tests/namespace.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, vi } from 'vitest'; +import { describe, expect } from 'vitest'; import tgpu, { d } from '../src/index.js'; import { it } from 'typegpu-testing-utility'; @@ -71,34 +71,6 @@ describe('tgpu.namespace', () => { `); }); - it('fires "name" event', () => { - const Boid = d.struct({ - pos: d.vec3f, - }); - - const names = tgpu['~unstable'].namespace(); - - const listener = vi.fn((event) => {}); - names.on('name', listener); - - const code = tgpu.resolve([Boid], { names }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledWith({ name: 'Boid', target: Boid }); - - expect(code).toMatchInlineSnapshot(` - "struct Boid { - pos: vec3f, - }" - `); - - const code2 = tgpu.resolve([Boid], { names }); - - // No more events - expect(listener).toHaveBeenCalledTimes(1); - expect(code2).toMatchInlineSnapshot(`""`); - }); - it('handles name collision', () => { let code1: string, code2: string; const names = tgpu['~unstable'].namespace(); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 254730def2..83e8e1e787 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from 'vitest'; import tgpu, { d } from '../src/index.js'; -import { setName } from '../src/shared/meta.ts'; +import { getName, setName } from '../src/shared/meta.ts'; import { $gpuValueOf, $internal, $ownSnippet, $resolve } from '../src/shared/symbols.ts'; import type { ResolutionCtx } from '../src/types.ts'; import { it } from 'typegpu-testing-utility'; @@ -55,7 +55,7 @@ describe('tgpu resolve', () => { } as unknown as number, [$resolve](ctx: ResolutionCtx) { - const name = ctx.getUniqueName(this); + const name = ctx.makeUniqueIdentifier(getName(this), 'global'); ctx.addDeclaration(`@group(0) @binding(0) var ${name}: f32;`); return snip(name, d.f32, /* origin */ 'runtime'); }, From 2afcfb830eab248345f7f1cc5b17fd2333279d49 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 11 Apr 2026 13:31:41 +0200 Subject: [PATCH 18/34] refactor: Naming cleanup --- .../typegpu/src/core/buffer/bufferUsage.ts | 4 +- .../typegpu/src/core/constant/tgpuConstant.ts | 2 +- packages/typegpu/src/core/function/fnCore.ts | 27 +-- .../typegpu/src/core/resolve/namespace.ts | 55 +----- .../typegpu/src/core/resolve/resolveData.ts | 5 +- packages/typegpu/src/core/sampler/sampler.ts | 4 +- .../src/core/texture/externalTexture.ts | 2 +- packages/typegpu/src/core/texture/texture.ts | 4 +- .../typegpu/src/core/variable/tgpuVariable.ts | 2 +- packages/typegpu/src/data/autoStruct.ts | 2 +- packages/typegpu/src/data/struct.ts | 2 +- .../src/{nameRegistry.ts => nameUtils.ts} | 166 +----------------- packages/typegpu/src/resolutionCtx.ts | 135 ++++++++++---- packages/typegpu/src/tgsl/wgslGenerator.ts | 31 ++-- packages/typegpu/src/types.ts | 25 ++- packages/typegpu/tests/namespace.test.ts | 30 +--- packages/typegpu/tests/resolve.test.ts | 4 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 2 +- 18 files changed, 188 insertions(+), 314 deletions(-) rename packages/typegpu/src/{nameRegistry.ts => nameUtils.ts} (58%) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 24f28dcbca..d8a1028e78 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -118,7 +118,7 @@ class TgpuFixedBufferImpl implements TgpuConst, } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const resolvedDataType = ctx.resolve(this.dataType).value; const resolvedValue = ctx.resolve(this.#value, this.dataType).value; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index cf4cda90f7..aa91d451f6 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -3,6 +3,7 @@ import { undecorate } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTypes.ts'; import { MissingLinksError } from '../../errors.ts'; +import { isValidIdentifier } from '../../nameUtils.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; @@ -65,21 +66,25 @@ export function createFnCore( applyExternals(externalMap, externals); } - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); if (typeof implementation === 'string') { if (!returnType) { throw new Error('Explicit return type is required for string implementation'); } - const validArgNames = entryInput - ? Object.fromEntries( - entryInput.positionalArgs.map((a) => [a.schemaKey, ctx.makeNameValid(a.schemaKey)]), - ) - : undefined; + if (entryInput) { + for (const arg of entryInput.positionalArgs) { + if (!isValidIdentifier(arg.schemaKey)) { + throw new Error(`Invalid argument name: ${arg.schemaKey}`); + } + } - if (validArgNames && Object.keys(validArgNames).length > 0) { - applyExternals(externalMap, { in: validArgNames }); + applyExternals(externalMap, { + in: Object.fromEntries( + entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey]), + ), + }); } const replacedImpl = replaceExternalsInWgsl(ctx, externalMap, implementation); @@ -87,15 +92,15 @@ export function createFnCore( let header = ''; let body = ''; - if (functionType !== 'normal' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { parts.push(`in: ${ctx.resolve(dataSchema).value}`); } for (const a of positionalArgs) { - const argName = validArgNames[a.schemaKey] ?? ''; - if (argName !== '' && isArgUsedInBody(argName, replacedImpl)) { + const argName = a.schemaKey; + if (isArgUsedInBody(argName, replacedImpl)) { parts.push(`${getAttributesString(a.type)}${argName}: ${ctx.resolve(a.type).value}`); } } diff --git a/packages/typegpu/src/core/resolve/namespace.ts b/packages/typegpu/src/core/resolve/namespace.ts index f112cd4fb0..90fcba08f7 100644 --- a/packages/typegpu/src/core/resolve/namespace.ts +++ b/packages/typegpu/src/core/resolve/namespace.ts @@ -1,6 +1,5 @@ import type { ResolvedSnippet } from '../../data/snippet.ts'; -import { type NameRegistry, RandomNameRegistry, StrictNameRegistry } from '../../nameRegistry.ts'; -import { getName } from '../../shared/meta.ts'; +import { bannedTokens, builtins } from '../../nameUtils.ts'; import { $internal } from '../../shared/symbols.ts'; import { ShelllessRepository } from '../../tgsl/shellless.ts'; import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; @@ -8,8 +7,9 @@ import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; type SlotToValueMap = Map, unknown>; export interface NamespaceInternal { - readonly nameRegistry: NameRegistry; + readonly takenGlobalIdentifiers: Set; readonly shelllessRepo: ShelllessRepository; + readonly strategy: 'random' | 'strict'; memoizedResolves: WeakMap< // WeakMap because if the item does not exist anymore, @@ -24,73 +24,32 @@ export interface NamespaceInternal { TgpuLazy, { slotToValueMap: SlotToValueMap; result: unknown }[] >; - - listeners: { - [K in keyof NamespaceEventMap]: Set<(event: NamespaceEventMap[K]) => void>; - }; } -type NamespaceEventMap = { - name: { target: object; name: string }; -}; - -type DetachListener = () => void; - export interface Namespace { readonly [$internal]: NamespaceInternal; - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener; } class NamespaceImpl implements Namespace { readonly [$internal]: NamespaceInternal; - constructor(nameRegistry: NameRegistry) { + constructor(strategy: 'random' | 'strict') { this[$internal] = { - nameRegistry, + strategy, + takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins]), shelllessRepo: new ShelllessRepository(), memoizedResolves: new WeakMap(), memoizedLazy: new WeakMap(), - listeners: { - name: new Set(), - }, }; } - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener { - if (event === 'name') { - const listeners = this[$internal].listeners.name; - listeners.add(listener); - - return () => listeners.delete(listener); - } - - throw new Error(`Unsupported event: ${event}`); - } } export interface NamespaceOptions { names?: 'random' | 'strict' | undefined; } -export function getUniqueName(namespace: NamespaceInternal, resource: object): string { - const name = namespace.nameRegistry.makeUnique(getName(resource), true); - for (const listener of namespace.listeners.name) { - listener({ target: resource, name }); - } - return name; -} - export function namespace(options?: NamespaceOptions): Namespace { const { names = 'strict' } = options ?? {}; - return new NamespaceImpl( - names === 'strict' ? new StrictNameRegistry() : new RandomNameRegistry(), - ); + return new NamespaceImpl(names); } diff --git a/packages/typegpu/src/core/resolve/resolveData.ts b/packages/typegpu/src/core/resolve/resolveData.ts index 19e5da347d..bc5320c80d 100644 --- a/packages/typegpu/src/core/resolve/resolveData.ts +++ b/packages/typegpu/src/core/resolve/resolveData.ts @@ -38,6 +38,7 @@ import type { WgslArray, WgslStruct, } from '../../data/wgslTypes.ts'; +import { getName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { assertExhaustive } from '../../shared/utilityTypes.ts'; import type { ResolutionCtx } from '../../types.ts'; @@ -127,7 +128,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) { if (struct[$internal].isAbstruct) { throw new Error('Cannot resolve abstract struct types to WGSL.'); } - const id = ctx.getUniqueName(struct); + const id = ctx.makeUniqueIdentifier(getName(struct), 'global'); ctx.addDeclaration(`\ struct ${id} { @@ -155,7 +156,7 @@ ${Object.entries(struct.propTypes) * ``` */ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) { - const id = ctx.getUniqueName(unstruct); + const id = ctx.makeUniqueIdentifier(getName(unstruct), 'global'); ctx.addDeclaration(`\ struct ${id} { diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index 4daaf1c917..adf10ee8ce 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -99,7 +99,7 @@ export class TgpuLaidOutSamplerImpl< } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( @@ -186,7 +186,7 @@ class TgpuFixedSamplerImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( this.schema.type === 'sampler_comparison' diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index c9d457c48b..78ae4cd582 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl implements TgpuExternalTexture, SelfResolva } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 27923995d6..fbe7295d83 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -600,7 +600,7 @@ class TgpuFixedTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( isWgslStorageTexture(this.schema) ? { @@ -642,7 +642,7 @@ export class TgpuLaidOutTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const pre = `var<${this.#scope}> ${id}: ${ctx.resolve(this.#dataType).value}`; if (this.#initialValue) { diff --git a/packages/typegpu/src/data/autoStruct.ts b/packages/typegpu/src/data/autoStruct.ts index 647d13b79c..c227b565fe 100644 --- a/packages/typegpu/src/data/autoStruct.ts +++ b/packages/typegpu/src/data/autoStruct.ts @@ -1,5 +1,5 @@ import { createIoSchema } from '../core/function/ioSchema.ts'; -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal, $repr, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; diff --git a/packages/typegpu/src/data/struct.ts b/packages/typegpu/src/data/struct.ts index c044da5f8f..c2d024acc3 100644 --- a/packages/typegpu/src/data/struct.ts +++ b/packages/typegpu/src/data/struct.ts @@ -1,4 +1,4 @@ -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { schemaCallWrapper } from './schemaCallWrapper.ts'; diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameUtils.ts similarity index 58% rename from packages/typegpu/src/nameRegistry.ts rename to packages/typegpu/src/nameUtils.ts index aa2cd1ada0..f66bfbb79a 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameUtils.ts @@ -1,6 +1,4 @@ -import { invariant } from './errors.ts'; - -const bannedTokens = new Set([ +export const bannedTokens = new Set([ // keywords 'alias', 'break', @@ -181,7 +179,7 @@ const bannedTokens = new Set([ 'storage', ]); -const builtins = new Set([ +export const builtins = new Set([ // constructors 'array', 'bool', @@ -361,37 +359,8 @@ const builtins = new Set([ 'quadSwapY', ]); -export interface NameRegistry { - /** - * Creates a valid WGSL identifier, each guaranteed to be unique - * in the lifetime of a single resolution process - * (excluding non-global identifiers from popped scopes). - * Should append "_" to primer, followed by some id. - * @param primer Used in the generation process, makes the identifier more recognizable. - * @param global Whether the name should be registered in the global scope (true), or in the current function scope (false) - */ - makeUnique(primer: string | undefined, global: boolean): string; - - /** - * Creates a valid WGSL identifier. - * Renames identifiers that are WGSL reserved words. - * @param primer Used in the generation process. - * - * @example - * makeValid("notAKeyword"); // "notAKeyword" - * makeValid("struct"); // makeUnique("struct") - * makeValid("struct_1"); // makeUnique("struct_1") (to avoid potential name collisions) - * makeValid("_"); // ERROR (too difficult to make valid to care) - */ - makeValid(primer: string): string; - - pushFunctionScope(): void; - popFunctionScope(): void; - pushBlockScope(): void; - popBlockScope(): void; -} - -function sanitizePrimer(primer: string | undefined) { +/*#__NO_SIDE_EFFECTS__*/ +export function sanitizePrimer(primer: string | undefined) { if (primer) { // sanitizing return primer @@ -411,7 +380,8 @@ function sanitizePrimer(primer: string | undefined) { * isValidIdentifier("_"); // ERROR * isValidIdentifier("my variable"); // ERROR */ -function isValidIdentifier(ident: string): boolean { +/*#__NO_SIDE_EFFECTS__*/ +export function isValidIdentifier(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( `Invalid identifier '${ident}'. Choose an identifier without whitespaces or leading underscores.`, @@ -424,6 +394,7 @@ function isValidIdentifier(ident: string): boolean { /** * Same as `isValidIdentifier`, except does not check for builtin clashes. */ +/*#__NO_SIDE_EFFECTS__*/ export function isValidProp(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( @@ -433,126 +404,3 @@ 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; - - readonly #usedNames: Set; - readonly #scopeStack: ScopeLayer[]; - - constructor() { - this.#usedNames = new Set([...bannedTokens, ...builtins]); - this.#scopeStack = []; - } - - get #usedBlockScopeNames(): Set | undefined { - return (this.#scopeStack[this.#scopeStack.length - 1] as BlockScopeLayer | undefined) - ?.usedBlockScopeNames; - } - - makeUnique(primer: string | undefined, global: boolean): string { - const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer); - - if (global) { - this.#usedNames.add(name); - } else { - 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.#isUsedInBlocksBefore(primer) - ) { - this.#usedBlockScopeNames?.add(primer); - return primer; - } - return this.makeUnique(primer, false); - } - - isUsed(name: string): boolean { - return this.#usedNames.has(name) || this.#isUsedInBlocksBefore(name); - } - - pushFunctionScope(): void { - this.#scopeStack.push({ type: 'functionScope' }); - this.#scopeStack.push({ - type: 'blockScope', - usedBlockScopeNames: new Set(), - }); - } - - popFunctionScope(): void { - const functionScopeIndex = this.#scopeStack.findLastIndex( - (scope) => scope.type === 'functionScope', - ); - - if (functionScopeIndex === -1) { - throw new Error('Tried to pop function scope when no scope was present.'); - } - - 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 { - let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name)) { - name = `${base}_${this.#lastUniqueId++}`; - } - return name; - } -} - -export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string): string { - let index = 0; - let name = base; - while (this.isUsed(name)) { - index++; - name = `${base}_${index}`; - } - return name; - } -} diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5eedec298d..2ed8dde06a 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -1,5 +1,5 @@ import { isTgpuFn } from './core/function/tgpuFn.ts'; -import { getUniqueName, type Namespace, type NamespaceInternal } from './core/resolve/namespace.ts'; +import type { Namespace, NamespaceInternal } from './core/resolve/namespace.ts'; import { stitch } from './core/resolve/stitch.ts'; import { ConfigurableImpl } from './core/root/configurableImpl.ts'; import type { Configurable, ExperimentalTgpuRoot } from './core/root/rootTypes.ts'; @@ -37,6 +37,7 @@ import { coerceToSnippet, concretize, numericLiteralToSnippet } from './tgsl/gen import type { ShaderGenerator } from './tgsl/shaderGenerator.ts'; import wgslGenerator from './tgsl/wgslGenerator.ts'; import type { + BlockScopeLayer, ExecMode, ExecState, FnToWgslOptions, @@ -59,6 +60,7 @@ import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; +import { isValidIdentifier, sanitizePrimer } from './nameUtils.ts'; /** * Inserted into bind group entry definitions that belong @@ -99,6 +101,10 @@ class ItemStateStackImpl implements ItemStateStack { return this._stack.findLast((e) => e.type === 'functionScope'); } + get topBlockScope(): BlockScopeLayer | undefined { + return this._stack.findLast((e) => e.type === 'blockScope'); + } + pushItem() { this._itemDepth++; this._stack.push({ @@ -136,6 +142,7 @@ class ItemStateStackImpl implements ItemStateStack { pushBlockScope() { this._stack.push({ type: 'blockScope', + takenLocalIdentifiers: new Set(), declarations: new Map(), externals: new Map(), }); @@ -213,6 +220,26 @@ class ItemStateStackImpl implements ItemStateStack { return undefined; } + isIdentifierTakenLocally(id: string): boolean { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + + if (layer?.type === 'functionScope') { + // Since functions cannot access resources from the calling scope, we + // return early here. + return false; + } + + if (layer?.type === 'blockScope') { + if (layer.takenLocalIdentifiers.has(id)) { + return true; + } + } + } + + return false; + } + defineBlockVariable(id: string, snippet: Snippet): void { if (snippet.dataType === UnknownData) { throw Error(`Tried to define variable '${id}' of unknown type`); @@ -375,6 +402,11 @@ export class ResolutionCtxImpl implements ResolutionCtx { public readonly enableExtensions: WgslExtension[] | undefined; public expectedType: BaseData | undefined; + /** + * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy. + */ + #lastUniqueId = 0; + constructor(opts: ResolutionCtxImplOptions) { this.enableExtensions = opts.enableExtensions; this.gen = opts.shaderGenerator ?? wgslGenerator; @@ -382,12 +414,42 @@ export class ResolutionCtxImpl implements ResolutionCtx { this.#namespaceInternal = opts.namespace[$internal]; } - getUniqueName(resource: object): string { - return getUniqueName(this.#namespaceInternal, resource); + isIdentifierTaken(name: string): boolean { + return ( + this.#namespaceInternal.takenGlobalIdentifiers.has(name) || + this._itemStateStack.isIdentifierTakenLocally(name) + ); } - makeNameValid(name: string): string { - return this.#namespaceInternal.nameRegistry.makeValid(name); + makeUniqueIdentifier(primer: string = 'item', scope: 'global' | 'block'): string { + if (scope === 'block' && isValidIdentifier(primer) && !this.isIdentifierTaken(primer)) { + // Preserving local definitions as they are, provided they are valid and not already taken. + this.reserveIdentifier(primer, 'block'); + return primer; + } + + const base = sanitizePrimer(primer); + let index = 0; + const random = this.#namespaceInternal.strategy === 'random'; + let name = random ? `${base}_${this.#lastUniqueId++}` : base; + while (this.isIdentifierTaken(name)) { + name = random ? `${base}_${this.#lastUniqueId++}` : `${base}_${++index}`; + } + + this.reserveIdentifier(name, scope); + return name; + } + + reserveIdentifier(name: string, scope: 'global' | 'block'): void { + if (scope === 'block') { + const blockScope = this._itemStateStack.topBlockScope; + if (blockScope) { + blockScope.takenLocalIdentifiers.add(name); + return; + } + // Fall through if no block scope is present, treating as global. + } + this.#namespaceInternal.takenGlobalIdentifiers.add(name); } get pre(): string { @@ -441,12 +503,10 @@ export class ResolutionCtxImpl implements ResolutionCtx { } pushBlockScope() { - this.#namespaceInternal.nameRegistry.pushBlockScope(); this._itemStateStack.pushBlockScope(); } popBlockScope() { - this.#namespaceInternal.nameRegistry.popBlockScope(); this._itemStateStack.pop('blockScope'); } @@ -467,19 +527,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { } fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { - let fnScopePushed = false; - try { - this.#namespaceInternal.nameRegistry.pushFunctionScope(); + const scope = this._itemStateStack.pushFunctionScope( + options.functionType, + {}, + options.returnType, + options.externalMap, + ); + // Pushing a block scope as well, so that any identifiers declared at this point will be scoped to the function body. + this._itemStateStack.pushBlockScope(); + const args: FunctionArgument[] = []; - const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; const structArg = dataSchema - ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + ? createArgument(this.makeUniqueIdentifier('_arg_0', 'block'), dataSchema) : undefined; if (structArg) { @@ -491,31 +556,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const arg = createArgument(this.makeNameValid(alias), argInfo.type); + const arg = createArgument(this.makeUniqueIdentifier(alias, 'block'), argInfo.type); args.push(arg); - argAccess[alias] = arg.access; + scope.argAccess[alias] = arg.access; } else if (structArg) { - argAccess[alias] = createArgumentPropAccess(structArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } const router = new EntryInputRouter(structArg?.access, proxyEntries); - argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); + scope.argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); - argAccess[argName] = arg.access; + scope.argAccess[argName] = arg.access; } } } else { @@ -538,16 +603,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + const arg = createArgument( + this.makeUniqueIdentifier(astParam.name, 'block'), + argType, + origin, + ); args.push(arg); - argAccess[astParam.name] = arg.access; + scope.argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + const objArg = createArgument( + this.makeUniqueIdentifier(`_arg_${i}`, 'block'), + argType, + origin, + ); args.push(objArg); for (const { name, alias } of astParam.props) { - argAccess[alias] = createArgumentPropAccess(objArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(objArg.access, name); } break; } @@ -557,7 +630,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { // have any properties anyway. if (!(argType instanceof AutoStruct)) { args.push({ - name: this.makeNameValid(`_arg_${i}`), + name: this.makeUniqueIdentifier(`_arg_${i}`, 'block'), access: () => { throw new Error( `Unreachable: Accessing an argument that wasn't named in the function signature`, @@ -572,14 +645,6 @@ export class ResolutionCtxImpl implements ResolutionCtx { } } - const scope = this._itemStateStack.pushFunctionScope( - options.functionType, - argAccess, - options.returnType, - options.externalMap, - ); - fnScopePushed = true; - let returnType: BaseData | undefined; const code = this.gen.functionDefinition({ @@ -642,10 +707,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { returnType, }; } finally { - if (fnScopePushed) { - this._itemStateStack.pop('functionScope'); - } - this.#namespaceInternal.nameRegistry.popFunctionScope(); + this._itemStateStack.pop('blockScope'); + this._itemStateStack.pop('functionScope'); } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 153ca5ed0b..e419ff5814 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -241,8 +241,12 @@ ${this.ctx.pre}}`; } } + public _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string { + return `${this.ctx.pre}${this._block(block, externalMap)}`; + } + public refVariable(id: string, dataType: wgsl.StorableData): string { - const varName = this.ctx.makeNameValid(id); + const varName = this.ctx.makeUniqueIdentifier(id, 'block'); const ptrType = ptrFn(dataType); const snippet = snip( new RefOperator(snip(varName, dataType, 'function'), ptrType), @@ -280,7 +284,11 @@ ${this.ctx.pre}}`; varOrigin = 'runtime'; } - const snippet = snip(this.ctx.makeNameValid(id), dataType, /* origin */ varOrigin); + const snippet = snip( + this.ctx.makeUniqueIdentifier(id, 'block'), + dataType, + /* origin */ varOrigin, + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -1034,7 +1042,7 @@ Try 'return ${typeStr}(${str});' instead. return this._statement(node); } // simplify 'if (true) {A} else {B}' to '{A}' - return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`; + return this._blockStatement(blockifySingleStatement(node)); } const consequent = this._block(blockifySingleStatement(consNode)); @@ -1167,7 +1175,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.block) { - return `${this.ctx.pre}${this._block(statement)}`; + return this._blockStatement(statement); } if (statement[0] === NODE.for) { @@ -1263,12 +1271,9 @@ ${this.ctx.pre}else ${alternate}`; const blocks = elements.map( (e, i) => - `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block( - blockified, - { - [originalLoopVarName]: e, - }, - )}`, + `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { + [originalLoopVarName]: e, + })}`, ); return blocks.join('\n'); @@ -1276,7 +1281,7 @@ ${this.ctx.pre}else ${alternate}`; this.#unrolling = false; - const index = this.ctx.makeNameValid('i'); + const index = this.ctx.makeUniqueIdentifier('i', 'block'); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`; @@ -1289,7 +1294,7 @@ ${this.ctx.pre}else ${alternate}`; } else { this.ctx.indent(); ctxIndent = true; - const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, snip(index, u32, 'runtime'), @@ -1303,7 +1308,7 @@ ${this.ctx.pre}else ${alternate}`; false, )};`; - bodyStr = `{\n${loopVarDeclStr}\n${this.ctx.pre}${this._block(blockified, { + bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin), })}\n`; this.ctx.dedent(); diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 75f9e71697..d2b7892faf 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -131,6 +131,7 @@ export type SlotBindingLayer = { export type BlockScopeLayer = { type: 'blockScope'; + takenLocalIdentifiers: Set; declarations: Map; externals: Map; }; @@ -140,6 +141,7 @@ export type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | Blo export interface ItemStateStack { readonly itemDepth: number; readonly topItem: ItemLayer; + readonly topBlockScope: BlockScopeLayer | undefined; readonly topFunctionScope: FunctionScopeLayer | undefined; pushItem(): void; @@ -330,8 +332,27 @@ export interface ResolutionCtx { */ withRenamed(item: object, name: string | undefined, callback: () => T): T; - getUniqueName(resource: object): string; - makeNameValid(name: string): string; + /** + * @param primer The basis for the unique identifier. Depending on the strategy, or + * the names already taken, this may be modified to ensure uniqueness. + * @param scope The scope in which to generate the identifier. 'global' means + * the identifier is meant to be unique across the entire program, while + * 'block' means it cannot shadow any existing identifiers visible from + * within the current block. After the block is popped, any identifiers + * defined within it are no longer visible. + * @returns an identifier that is unique within the given scope + */ + makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string; + + isIdentifierTaken(name: string): boolean; + + /** + * Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier} + * within the given scope. + * @param name The name to reserve + * @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter. + */ + reserveIdentifier(name: string, scope: 'global' | 'block'): void; } /** diff --git a/packages/typegpu/tests/namespace.test.ts b/packages/typegpu/tests/namespace.test.ts index ee6fe33099..7ac6407b43 100644 --- a/packages/typegpu/tests/namespace.test.ts +++ b/packages/typegpu/tests/namespace.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, vi } from 'vitest'; +import { describe, expect } from 'vitest'; import tgpu, { d } from '../src/index.js'; import { it } from 'typegpu-testing-utility'; @@ -71,34 +71,6 @@ describe('tgpu.namespace', () => { `); }); - it('fires "name" event', () => { - const Boid = d.struct({ - pos: d.vec3f, - }); - - const names = tgpu['~unstable'].namespace(); - - const listener = vi.fn((event) => {}); - names.on('name', listener); - - const code = tgpu.resolve([Boid], { names }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledWith({ name: 'Boid', target: Boid }); - - expect(code).toMatchInlineSnapshot(` - "struct Boid { - pos: vec3f, - }" - `); - - const code2 = tgpu.resolve([Boid], { names }); - - // No more events - expect(listener).toHaveBeenCalledTimes(1); - expect(code2).toMatchInlineSnapshot(`""`); - }); - it('handles name collision', () => { let code1: string, code2: string; const names = tgpu['~unstable'].namespace(); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 254730def2..83e8e1e787 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from 'vitest'; import tgpu, { d } from '../src/index.js'; -import { setName } from '../src/shared/meta.ts'; +import { getName, setName } from '../src/shared/meta.ts'; import { $gpuValueOf, $internal, $ownSnippet, $resolve } from '../src/shared/symbols.ts'; import type { ResolutionCtx } from '../src/types.ts'; import { it } from 'typegpu-testing-utility'; @@ -55,7 +55,7 @@ describe('tgpu resolve', () => { } as unknown as number, [$resolve](ctx: ResolutionCtx) { - const name = ctx.getUniqueName(this); + const name = ctx.makeUniqueIdentifier(getName(this), 'global'); ctx.addDeclaration(`@group(0) @binding(0) var ${name}: f32;`); return snip(name, d.f32, /* origin */ 'runtime'); }, diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 89e9e50218..eb377fbcab 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1205,7 +1205,7 @@ describe('wgslGenerator', () => { }); it('does not cause identifier clashes when renaming parameters', () => { - const main = tgpu.fn([d.u32, d.u32])((extern, extern_1) => { }); + const main = tgpu.fn([d.u32, d.u32])((extern, extern_1) => {}); expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main(extern_1: u32, extern_1_1: u32) { From 6a12e46b53a3e30c65b5784adfe512b8298bf79f Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 10 Apr 2026 12:30:13 +0200 Subject: [PATCH 19/34] fix: Improvement to argument usage tracking --- .../individual-example-tests/3d-fish.test.ts | 18 +- .../bitonic-sort.test.ts | 4 +- .../individual-example-tests/blur.test.ts | 8 +- .../camera-thresholding.test.ts | 8 +- .../individual-example-tests/caustics.test.ts | 8 +- .../chroma-keying.test.ts | 8 +- .../individual-example-tests/confetti.test.ts | 6 +- .../cubemap-reflection.test.ts | 18 +- .../individual-example-tests/disco.test.ts | 16 +- .../fluid-double-buffering.test.ts | 8 +- .../game-of-life.test.ts | 8 +- .../gradient-tiles.test.ts | 4 +- .../individual-example-tests/gravity.test.ts | 20 +- .../image-tuning.test.ts | 8 +- .../jelly-slider.test.ts | 16 +- .../jelly-switch.test.ts | 16 +- .../jump-flood-distance.test.ts | 8 +- .../liquid-glass.test.ts | 8 +- .../individual-example-tests/oklab.test.ts | 8 +- .../phong-reflection.test.ts | 10 +- .../point-light-shadow.test.ts | 16 +- .../ray-marching.test.ts | 8 +- .../ripple-cube.test.ts | 8 +- .../simple-shadow.test.ts | 10 +- .../slime-mold-3d.test.ts | 8 +- .../slime-mold.test.ts | 8 +- .../stable-fluid.test.ts | 8 +- .../uniformity.test.ts | 16 +- .../vaporrave.test.ts | 8 +- .../xor-dev-centrifuge-2.test.ts | 8 +- .../xor-dev-runner.test.ts | 8 +- packages/typegpu/src/core/function/autoIO.ts | 4 +- .../src/core/function/entryInputRouter.ts | 39 +- packages/typegpu/src/core/function/fnCore.ts | 51 +- .../src/core/function/shelllessImpl.ts | 2 +- .../src/core/function/tgpuComputeFn.ts | 5 +- packages/typegpu/src/core/function/tgpuFn.ts | 2 +- .../src/core/function/tgpuFragmentFn.ts | 2 +- .../typegpu/src/core/function/tgpuVertexFn.ts | 2 +- packages/typegpu/src/data/snippet.ts | 7 +- packages/typegpu/src/resolutionCtx.ts | 257 +++---- packages/typegpu/src/tgsl/accessProp.ts | 11 +- packages/typegpu/src/tgsl/conversion.ts | 2 +- packages/typegpu/src/tgsl/shaderGenerator.ts | 4 +- .../src/tgsl/shaderGenerator_members.ts | 14 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 25 +- packages/typegpu/src/types.ts | 18 +- packages/typegpu/tests/renderPipeline.test.ts | 33 +- .../tests/tgsl/extensionEnabled.test.ts | 14 +- .../typegpu/tests/tgsl/nameClashes.test.ts | 16 +- .../tests/tgsl/ternaryOperator.test.ts | 68 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 674 +++--------------- packages/typegpu/tests/tgslFn.test.ts | 24 +- packages/typegpu/tests/utils/parseResolved.ts | 1 - 54 files changed, 578 insertions(+), 1011 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts index dca6c22f4e..6430284a2e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts @@ -305,15 +305,6 @@ describe('3d fish example', () => { return vertexShader_Output(worldPosition.xyz, worldNormal, canvasPosition, (*currentModelData).variant, _arg_textureUV, (*currentModelData).applySeaFog, (*currentModelData).applySeaDesaturation); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - @location(2) variant: f32, - @location(3) textureUV: vec2f, - @location(4) @interpolate(flat) applySeaFog: u32, - @location(5) @interpolate(flat) applySeaDesaturation: u32, - } - @group(0) @binding(1) var modelTexture: texture_2d; @group(0) @binding(3) var sampler_1: sampler; @@ -418,6 +409,15 @@ describe('3d fish example', () => { return vec3f(r, g, b); } + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + @location(2) variant: f32, + @location(3) textureUV: vec2f, + @location(4) @interpolate(flat) applySeaFog: u32, + @location(5) @interpolate(flat) applySeaDesaturation: u32, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var textureColorWithAlpha = textureSample(modelTexture, sampler_1, _arg_0.textureUV); var textureColor = textureColorWithAlpha.rgb; diff --git a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts index 06b1c586cb..6368fd715f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts @@ -118,12 +118,12 @@ describe('bitonic sort example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var data_1: array; + struct fragmentFn_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var data_1: array; - @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { let data = (&data_1); let arrayLength_1 = arrayLength(&(*data)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts index 00df4923ed..7f81d96515 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts @@ -470,14 +470,14 @@ describe('blur example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct renderFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var renderView: texture_2d; @group(0) @binding(1) var sampler_1: sampler; + struct renderFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn renderFragment(_arg_0: renderFragment_Input) -> @location(0) vec4f { return textureSample(renderView, sampler_1, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts index 43e10c97b2..783bb59558 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts @@ -32,10 +32,6 @@ describe('camera thresholding example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFrag_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransformUniform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('camera thresholding example', () => { @group(0) @binding(3) var thresholdBuffer: f32; + struct mainFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { var uv2 = ((uvTransformUniform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts index a1938d5c34..9d011f7a74 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts @@ -31,10 +31,6 @@ describe('caustics example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var tileDensity: f32; fn tilePattern(uv: vec2f) -> f32 { @@ -120,6 +116,10 @@ describe('caustics example', () => { return mat2x2f(vec2f(cos(angle), sin(angle)), vec2f(-(sin(angle)), cos(angle))); } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f((-1.9866933079506122f + (_arg_0.uv.x * 3f)), 4.900332889206208f)); var skewedUv = (skewMat * _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts index 9b25129771..60175ac939 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts @@ -32,10 +32,6 @@ describe('chroma keying example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('chroma keying example', () => { @group(0) @binding(3) var threshold: f32; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var uv2 = ((uvTransform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts index 3e39636f7a..921721055b 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts @@ -59,10 +59,10 @@ describe('confetti example', () => { struct VertexIn { @location(0) tilt: f32, - @location(1) angle: f32, - @location(2) color: vec4f, - @location(3) center: vec2f, @builtin(vertex_index) vertexIndex: u32, + @location(1) angle: f32, + @location(2) center: vec2f, + @location(3) color: vec4f, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { diff --git a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts index c77faffba2..fe28f8827d 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts @@ -257,14 +257,14 @@ describe('cubemap reflection example', () => { return cubeVertexFn_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct cubeFragmentFn_Input { - @location(0) texCoord: vec3f, - } - @group(1) @binding(0) var cubemap: texture_cube; @group(1) @binding(1) var texSampler: sampler; + struct cubeFragmentFn_Input { + @location(0) texCoord: vec3f, + } + @fragment fn cubeFragmentFn(_arg_0: cubeFragmentFn_Input) -> @location(0) vec4f { return textureSample(cubemap, texSampler, normalize(_arg_0.texCoord)); } @@ -287,11 +287,6 @@ describe('cubemap reflection example', () => { return vertexFn_Output((camera.projection * (camera.view * _arg_position)), _arg_normal, _arg_position); } - struct fragmentFn_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec4f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -314,6 +309,11 @@ describe('cubemap reflection example', () => { @group(1) @binding(1) var texSampler: sampler; + struct fragmentFn_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec4f, + } + @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { var normalizedNormal = normalize(_arg_0.normal.xyz); var normalizedLightDir = normalize(light.direction); diff --git a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts index 8ea4e8d7f7..ba6d090535 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts @@ -32,10 +32,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment2_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -65,6 +61,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment2_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment2(_arg_0: mainFragment2_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; @@ -243,10 +243,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment1_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -276,6 +272,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment1_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment1(_arg_0: mainFragment1_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; diff --git a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts index ef73e74189..df9620d6ed 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts @@ -558,10 +558,6 @@ describe('fluid double buffering example', () => { return vertexMain_Output(vec4f(pos[_arg_idx].x, pos[_arg_idx].y, 0f, 1f), uv[_arg_idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - fn coordsToIndex(x: i32, y: i32) -> i32 { return (x + (y * 256i)); } @@ -595,6 +591,10 @@ describe('fluid double buffering example', () => { return false; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let x = i32((_arg_0.uv.x * 256f)); let y = i32((_arg_0.uv.y * 256f)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts index 3028c19bf5..516692dc41 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts @@ -214,10 +214,6 @@ describe('game of life example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct displayFragment_Input { - @location(0) uv: vec2f, - } - struct ZoomParams { enabled: u32, level: f32, @@ -242,6 +238,10 @@ describe('game of life example', () => { @group(0) @binding(2) var viewModeUniform: u32; + struct displayFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn displayFragment(_arg_0: displayFragment_Input) -> @location(0) vec4f { let zoom = (&zoomUniform); let gs = f32(gameSizeUniform); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts index f0d3016198..d3c209eb18 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts @@ -32,12 +32,12 @@ describe('gradient tiles example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var spanUniform: vec2f; + struct fragment_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var spanUniform: vec2f; - @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { let red = (floor((_arg_0.uv.x * spanUniform.x)) / spanUniform.x); let green = (floor((_arg_0.uv.y * spanUniform.y)) / spanUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts index 55ed0d2fb8..899b6cb76e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts @@ -192,14 +192,14 @@ describe('gravity example', () => { return skyBoxVertex_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct skyBoxFragment_Input { - @location(0) texCoord: vec3f, - } - @group(0) @binding(1) var skyBox: texture_cube; @group(0) @binding(2) var sampler_1: sampler; + struct skyBoxFragment_Input { + @location(0) texCoord: vec3f, + } + @fragment fn skyBoxFragment(_arg_0: skyBoxFragment_Input) -> @location(0) vec4f { return textureSample(skyBox, sampler_1, normalize(_arg_0.texCoord)); } @@ -250,6 +250,12 @@ describe('gravity example', () => { return mainVertex_Output(positionOnCanvas, _arg_uv, _arg_normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); } + @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; + + @group(0) @binding(1) var sampler_1: sampler; + + @group(0) @binding(2) var lightSource: vec3f; + struct mainFragment_Input { @location(0) uv: vec2f, @location(1) normals: vec3f, @@ -259,12 +265,6 @@ describe('gravity example', () => { @location(5) ambientLightFactor: f32, } - @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; - - @group(0) @binding(1) var sampler_1: sampler; - - @group(0) @binding(2) var lightSource: vec3f; - @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { if ((_arg_0.destroyed == 1u)) { discard;; diff --git a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts index a9fa1865c8..059828005e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts @@ -37,10 +37,6 @@ describe('image tuning example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var imageView: texture_2d; @group(0) @binding(1) var imageSampler: sampler; @@ -68,6 +64,10 @@ describe('image tuning example', () => { @group(0) @binding(4) var adjustments: Adjustments; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var color = textureSample(imageView, imageSampler, _arg_0.uv).rgb; let inputLuminance = dot(color, vec3f(0.29899999499320984, 0.5870000123977661, 0.11400000005960464)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts index 941ee300b4..976bdbaaf2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts @@ -66,10 +66,6 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -583,6 +579,10 @@ describe('jelly-slider example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -713,14 +713,14 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts index 7a192b5f54..6e507a2602 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts @@ -37,10 +37,6 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -385,6 +381,10 @@ describe('jelly switch example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -505,14 +505,14 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts index 3fb0e7d685..dcf73b0927 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts @@ -303,10 +303,6 @@ describe('jump flood (distance) example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct distanceFrag_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var distTexture: texture_2d; @group(1) @binding(1) var sampler_1: sampler; @@ -322,6 +318,10 @@ describe('jump flood (distance) example', () => { const insideGradient: array = array(vec3f(0.05000000074505806, 0.05000000074505806, 0.15000000596046448), vec3f(0.10000000149011612, 0.20000000298023224, 0.30000001192092896), vec3f(0.20000000298023224, 0.44999998807907104, 0.550000011920929), vec3f(0.4000000059604645, 0.75, 0.699999988079071), vec3f(0.8999999761581421, 1, 0.949999988079071)); + struct distanceFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn distanceFrag(_arg_0: distanceFrag_Input) -> @location(0) vec4f { var size = textureDimensions(distTexture); var dist = textureSample(distTexture, sampler_1, _arg_0.uv).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts index e247d26a22..56a5b4ecf5 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts @@ -64,10 +64,6 @@ describe('liquid-glass example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var mousePosUniform: vec2f; struct Params { @@ -137,6 +133,10 @@ describe('liquid-glass example', () => { return mix(vec4f(color, 1f), vec4f(tint.color, 1f), tint.strength); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var posInBoxSpace = (_arg_0.uv - mousePosUniform); let sdfDist = sdRoundedBox2d(posInBoxSpace, paramsUniform.rectDims, paramsUniform.radius); diff --git a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts index b8e64d3654..23abf35258 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts @@ -32,10 +32,6 @@ describe('oklab example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - struct item { hue: f32, alpha: f32, @@ -226,6 +222,10 @@ describe('oklab example', () => { return 1f; } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv - 0.5f) * vec2f(2, -2)); let hue = uniforms.hue; diff --git a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts index 1cfbf878ea..0240d9ae44 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts @@ -54,11 +54,6 @@ describe('phong reflection example', () => { return vertexShader_Output(_arg_modelPosition, _arg_modelNormal, canvasPosition); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - } - struct ExampleControls { lightColor: vec3f, lightDirection: vec3f, @@ -69,6 +64,11 @@ describe('phong reflection example', () => { @group(0) @binding(1) var exampleControlsUniform: ExampleControls; + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var lightColor = normalize(exampleControlsUniform.lightColor); var lightDirection = normalize(exampleControlsUniform.lightDirection); diff --git a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts index 283d8d652b..2ce441c6b2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts @@ -41,12 +41,12 @@ describe('point light shadow example', () => { return vertexDepth_Output(pos, worldPos); } + @group(0) @binding(1) var lightPosition: vec3f; + struct fragmentDepth_Input { @location(0) worldPos: vec3f, } - @group(0) @binding(1) var lightPosition: vec3f; - @fragment fn fragmentDepth(_arg_0: fragmentDepth_Input) -> @builtin(frag_depth) f32 { let dist = length((_arg_0.worldPos - lightPosition)); return (dist / 100f); @@ -74,12 +74,6 @@ describe('point light shadow example', () => { return vertexMain_Output(pos, worldPos, uv, worldNormal); } - struct fragmentMain_Input { - @location(0) worldPos: vec3f, - @location(1) uv: vec2f, - @location(2) normal: vec3f, - } - @group(1) @binding(3) var lightPosition: vec3f; struct item { @@ -97,6 +91,12 @@ describe('point light shadow example', () => { @group(1) @binding(2) var shadowSampler: sampler_comparison; + struct fragmentMain_Input { + @location(0) worldPos: vec3f, + @location(1) uv: vec2f, + @location(2) normal: vec3f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let lightPos = (&lightPosition); var toLight = ((*lightPos) - _arg_0.worldPos); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts index 1d56a2d307..bdfa96686f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts @@ -31,10 +31,6 @@ describe('ray-marching example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolution: vec2f; struct Shape { @@ -148,6 +144,10 @@ describe('ray-marching example', () => { return res; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolution.x / resolution.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts index e73568d898..24dcc7ee1c 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts @@ -765,10 +765,6 @@ describe('ripple-cube example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var colorTexture: texture_2d; @group(1) @binding(2) var sampler_1: sampler; @@ -782,6 +778,10 @@ describe('ripple-cube example', () => { @group(0) @binding(0) var bloomUniform: BloomParams; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var color = textureSample(colorTexture, sampler_1, _arg_0.uv); var bloomColor = textureSample(bloomTexture, sampler_1, _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts index f1f6775cc0..e6d2de8222 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts @@ -89,11 +89,6 @@ describe('simple shadow example', () => { return mainVert_Output(clipPos, transformedNormal, worldPos.xyz); } - struct mainFrag_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec3f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -118,6 +113,11 @@ describe('simple shadow example', () => { @group(0) @binding(3) var paramsUniform: VisParams; + struct mainFrag_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec3f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { let instanceInfo_1 = (&instanceInfo); var N = normalize(_arg_0.normal.xyz); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts index 16ec9b7e12..daab5433e6 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts @@ -382,10 +382,6 @@ describe('slime mold 3d example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - var seed: vec2f; fn seed2(value: vec2f) { @@ -438,6 +434,10 @@ describe('slime mold 3d example', () => { @group(0) @binding(1) var sampler_1: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { randSeed2(_arg_0.uv); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), (1f - (_arg_0.uv.y * 2f))); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts index 44d050241e..4b77dc41f4 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts @@ -308,14 +308,14 @@ describe('slime mold example', () => { return fullScreenTriangle_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), uv[_arg_vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var state: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { return textureSample(state, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts index 0e97b0d9e9..b94b01c01f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts @@ -259,16 +259,16 @@ describe('stable-fluid example', () => { return renderFn_Output(vec4f(vertices[_arg_idx], 0f, 1f), texCoords[_arg_idx]); } - struct fragmentImageFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var result: texture_2d; @group(0) @binding(2) var linSampler: sampler; @group(0) @binding(1) var background: texture_2d; + struct fragmentImageFn_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentImageFn(_arg_0: fragmentImageFn_Input) -> @location(0) vec4f { const pixelStep = 0.001953125f; let leftSample = textureSample(result, linSampler, vec2f((_arg_0.uv.x - pixelStep), _arg_0.uv.y)).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts index 21ccd861f6..11a61fe114 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts @@ -35,10 +35,6 @@ describe('uniformity test example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var canvasRatioUniform: f32; @group(0) @binding(1) var gridSizeUniform: f32; @@ -65,6 +61,10 @@ describe('uniformity test example', () => { return sample(); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); @@ -72,10 +72,6 @@ describe('uniformity test example', () => { return vec4f(vec3f(randFloat01()), 1f); } - struct fragmentShader_Input_1 { - @location(0) uv: vec2f, - } - var seed_1: u32; fn seed2_1(value: vec2f) { @@ -102,6 +98,10 @@ describe('uniformity test example', () => { return sample_1(); } + struct fragmentShader_Input_1 { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader_1(_arg_0: fragmentShader_Input_1) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts index d7834afa66..dafd303f91 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts @@ -79,10 +79,6 @@ describe('vaporrave example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; struct Ray { @@ -219,6 +215,10 @@ describe('vaporrave example', () => { @group(0) @binding(5) var glowIntensityUniform: f32; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolutionUniform.x / resolutionUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts index 9abffaf6b8..894d6e4d67 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts @@ -30,10 +30,6 @@ describe('xor dev centrifuge example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - struct Params { time: f32, aspectRatio: f32, @@ -51,6 +47,10 @@ describe('xor dev centrifuge example', () => { return select(tanh(v), sign(v), (abs(v) > vec3f(10))); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let params = (¶msUniform); var ratio = vec2f((*params).aspectRatio, 1f); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts index 419c95807c..ec9b1c27a0 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts @@ -34,10 +34,6 @@ describe('xor dev runner example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var colorUniform: vec3f; struct Camera { @@ -82,6 +78,10 @@ describe('xor dev runner example', () => { return select(tanh(v), sign(v), (abs(v) > 10f)); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var icolor = (colorUniform * 4f); var ray = getRayForUV(_arg_0.uv); diff --git a/packages/typegpu/src/core/function/autoIO.ts b/packages/typegpu/src/core/function/autoIO.ts index 6feece51d5..b35a092b1a 100644 --- a/packages/typegpu/src/core/function/autoIO.ts +++ b/packages/typegpu/src/core/function/autoIO.ts @@ -85,7 +85,7 @@ export class AutoFragmentFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'fragmentFn'); } - this.#core = createFnCore(impl, '@fragment '); + this.#core = createFnCore(impl, 'fragment'); this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations); setName(this.autoIn, 'FragmentIn'); this.autoOut = new AutoStruct(builtinFragmentOut, vec4f); @@ -131,7 +131,7 @@ export class AutoVertexFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'vertexFn'); } - this.#core = createFnCore(impl, '@vertex '); + this.#core = createFnCore(impl, 'vertex'); this.autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations); setName(this.autoIn, 'VertexIn'); this.autoOut = new AutoStruct(builtinVertexOut, undefined); diff --git a/packages/typegpu/src/core/function/entryInputRouter.ts b/packages/typegpu/src/core/function/entryInputRouter.ts index 8ba6669372..b756366c4b 100644 --- a/packages/typegpu/src/core/function/entryInputRouter.ts +++ b/packages/typegpu/src/core/function/entryInputRouter.ts @@ -1,12 +1,7 @@ -import { undecorate } from '../../data/dataTypes.ts'; -import { snip, type Snippet } from '../../data/snippet.ts'; +import { type Snippet } from '../../data/snippet.ts'; import { $internal, $repr } from '../../shared/symbols.ts'; import { type BaseData, isWgslStruct } from '../../data/wgslTypes.ts'; - -interface PositionalArgEntry { - argName: string; - type: BaseData; -} +import type { FunctionArgumentAccess } from '../../types.ts'; /** * Routes `(input) => { input.x }` style property access to the correct WGSL @@ -18,38 +13,32 @@ export class EntryInputRouter implements BaseData { readonly type = 'entry-input-router' as const; // Type-token only, not present at runtime: declare readonly [$repr]: never; - readonly structArgName: string; - readonly dataSchema: BaseData | undefined; + + readonly structArg: FunctionArgumentAccess | undefined; /** Maps schemaKey → { WGSL arg name, type } */ - readonly positionalArgsMap: Map; + readonly positionalArgsMap: Map; constructor( - structArgName: string, - dataSchema: BaseData | undefined, - positionalArgs: { schemaKey: string; argName: string; type: BaseData }[], + structArg: FunctionArgumentAccess | undefined, + positionalArgs: { schemaKey: string; arg: FunctionArgumentAccess }[], ) { - this.structArgName = structArgName; - this.dataSchema = dataSchema; - this.positionalArgsMap = new Map( - positionalArgs.map((a) => [a.schemaKey, { argName: a.argName, type: a.type }]), - ); + this.structArg = structArg; + this.positionalArgsMap = new Map(positionalArgs.map((a) => [a.schemaKey, a.arg])); } toString(): string { return 'entry-input-router'; } - accessProp(propName: string): Snippet | undefined { + accessProp(propName: string): Snippet | { target: Snippet; prop: string } | undefined { const positionalEntry = this.positionalArgsMap.get(propName); if (positionalEntry) { - return snip(positionalEntry.argName, positionalEntry.type, 'argument'); + return positionalEntry(); } - if (this.dataSchema && isWgslStruct(this.dataSchema)) { - const propType = this.dataSchema.propTypes[propName]; - if (propType) { - return snip(`${this.structArgName}.${propName}`, undecorate(propType), 'argument'); - } + const structSnippet = this.structArg?.(); + if (structSnippet && isWgslStruct(structSnippet.dataType)) { + return { target: structSnippet, prop: propName }; } return undefined; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 66429067e5..cf4cda90f7 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -5,7 +5,7 @@ import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTy import { MissingLinksError } from '../../errors.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; -import type { ResolutionCtx } from '../../types.ts'; +import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; import { applyExternals, type ExternalMap, replaceExternalsInWgsl } from '../resolve/externals.ts'; import { extractArgs } from './extractArgs.ts'; import type { Implementation, SeparatedEntryArgs } from './fnTypes.ts'; @@ -32,7 +32,11 @@ export interface FnCore { ): ResolvedSnippet; } -export function createFnCore(implementation: Implementation, fnAttribute = ''): FnCore { +export function createFnCore( + implementation: Implementation, + functionType: 'normal' | TgpuShaderStage, + workgroupSize?: number[], +): FnCore { /** * External application has to be deferred until resolution because * some externals can reference the owner function which has not been @@ -83,7 +87,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): let header = ''; let body = ''; - if (fnAttribute !== '' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput && validArgNames) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { @@ -140,7 +144,17 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): body = replacedImpl.slice(providedArgs.range.end); } - ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); + let attributes = ''; + if (functionType === 'compute') { + attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `; + } else if (functionType === 'vertex') { + attributes = `@vertex `; + } else if (functionType === 'fragment') { + attributes = `@fragment `; + } + + ctx.addDeclaration(`${attributes}fn ${id}${header}${body}`); + return snip(id, returnType, /* origin */ 'runtime'); } @@ -178,7 +192,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // If an entrypoint implementation has a second argument, it represents the output schema. // We look at the identifier chosen by the user and add it to externals. const maybeSecondArg = ast.params[1]; - if (maybeSecondArg && maybeSecondArg.type === 'i' && fnAttribute !== '') { + if (maybeSecondArg && maybeSecondArg.type === 'i' && functionType !== 'normal') { applyExternals(externalMap, { // oxlint-disable-next-line typescript/no-non-null-assertion -- entry functions cannot be shellless [maybeSecondArg.name]: undecorate(returnType!), @@ -187,18 +201,8 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // generate wgsl string - const { - head, - body, - returnType: actualReturnType, - } = ctx.fnToWgsl({ - functionType: fnAttribute.includes('@compute') - ? 'compute' - : fnAttribute.includes('@vertex') - ? 'vertex' - : fnAttribute.includes('@fragment') - ? 'fragment' - : 'normal', + const { code, returnType: actualReturnType } = ctx.fnToWgsl({ + functionType, argTypes, entryInput, params: ast.params, @@ -207,9 +211,16 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): externalMap, }); - ctx.addDeclaration( - `${fnAttribute}fn ${id}${ctx.resolve(head).value}${ctx.resolve(body).value}`, - ); + let attributes = ''; + if (functionType === 'compute') { + attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `; + } else if (functionType === 'vertex') { + attributes = `@vertex `; + } else if (functionType === 'fragment') { + attributes = `@fragment `; + } + + ctx.addDeclaration(`${attributes}fn ${id}${code}`); return snip(id, actualReturnType, /* origin */ 'runtime'); }, diff --git a/packages/typegpu/src/core/function/shelllessImpl.ts b/packages/typegpu/src/core/function/shelllessImpl.ts index 23b60bdde1..9263303a31 100644 --- a/packages/typegpu/src/core/function/shelllessImpl.ts +++ b/packages/typegpu/src/core/function/shelllessImpl.ts @@ -31,7 +31,7 @@ export function createShelllessImpl( argTypes: BaseData[], implementation: (...args: never[]) => unknown, ): ShelllessImpl { - const core = createFnCore(implementation, ''); + const core = createFnCore(implementation, 'normal'); return { [$internal]: true, diff --git a/packages/typegpu/src/core/function/tgpuComputeFn.ts b/packages/typegpu/src/core/function/tgpuComputeFn.ts index 6f48cbe643..9cef2d49cc 100644 --- a/packages/typegpu/src/core/function/tgpuComputeFn.ts +++ b/packages/typegpu/src/core/function/tgpuComputeFn.ts @@ -114,10 +114,7 @@ function createComputeFn>( [$getNameForward]: FnCore; }; - const core = createFnCore( - implementation, - `@compute @workgroup_size(${workgroupSize.join(', ')}) `, - ); + const core = createFnCore(implementation, 'compute', workgroupSize); const result: This = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index 65ea52de65..40f6548922 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -173,7 +173,7 @@ function createFn( implementation = _implementation; } - const core = createFnCore(implementation as Implementation, ''); + const core = createFnCore(implementation as Implementation, 'normal'); const fnBase = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFragmentFn.ts b/packages/typegpu/src/core/function/tgpuFragmentFn.ts index 631617bcdc..cb3b4c990f 100644 --- a/packages/typegpu/src/core/function/tgpuFragmentFn.ts +++ b/packages/typegpu/src/core/function/tgpuFragmentFn.ts @@ -183,7 +183,7 @@ function createFragmentFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@fragment '); + const core = createFnCore(implementation, 'fragment'); const outputType = shell.returnType; if (typeof implementation === 'string') { addReturnTypeToExternals(implementation, outputType, (externals) => diff --git a/packages/typegpu/src/core/function/tgpuVertexFn.ts b/packages/typegpu/src/core/function/tgpuVertexFn.ts index a261634b3d..8c5b230891 100644 --- a/packages/typegpu/src/core/function/tgpuVertexFn.ts +++ b/packages/typegpu/src/core/function/tgpuVertexFn.ts @@ -158,7 +158,7 @@ function createVertexFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@vertex '); + const core = createFnCore(implementation, 'vertex'); const entryInput: SeparatedEntryArgs = separateAllAsPositional(shell.in ?? {}); const result: This = { diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index fe658ef99d..9c2e954f0d 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -74,14 +74,9 @@ export interface Snippet { readonly origin: Origin; } -export interface ResolvedSnippet { +export interface ResolvedSnippet extends Snippet { readonly value: string; - /** - * The type that `value` is assignable to (not necessary exactly inferred as). - * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float - */ readonly dataType: BaseData; - readonly origin: Origin; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5b3daa0067..5eedec298d 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -12,10 +12,9 @@ import { type TgpuLazy, type TgpuSlot, } from './core/slot/slotTypes.ts'; -import { getAttributesString } from './data/attributes.ts'; -import { isData, undecorate, UnknownData } from './data/dataTypes.ts'; +import { isData, UnknownData } from './data/dataTypes.ts'; import { bool } from './data/numeric.ts'; -import { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; +import { type Origin, type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; import { type BaseData, isPtr, isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts'; import { invariant, MissingSlotValueError, ResolutionError, WgslTypeError } from './errors.ts'; import { provideCtx, topLevelState } from './execMode.ts'; @@ -41,6 +40,7 @@ import type { ExecMode, ExecState, FnToWgslOptions, + FunctionArgumentAccess, FunctionScopeLayer, ItemLayer, ItemStateStack, @@ -58,6 +58,7 @@ import { createIoSchema } from './core/function/ioSchema.ts'; import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; +import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; /** * Inserted into bind group entry definitions that belong @@ -115,16 +116,14 @@ class ItemStateStackImpl implements ItemStateStack { pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, returnType: BaseData | undefined, externalMap: Record, ): FunctionScopeLayer { const scope: FunctionScopeLayer = { type: 'functionScope', functionType, - args, - argAliases, + argAccess, returnType, externalMap, reportedReturnTypes: new Set(), @@ -184,13 +183,9 @@ class ItemStateStackImpl implements ItemStateStack { const layer = this._stack[i]; if (layer?.type === 'functionScope') { - const arg = layer.args.find((a) => a.value === id); - if (arg !== undefined) { - return arg; - } - - if (layer.argAliases[id]) { - return layer.argAliases[id]; + const access = layer.argAccess[id]; + if (access) { + return access(); } const external = layer.externalMap[id]; @@ -311,6 +306,39 @@ interface FixedBindingConfig { resource: object; } +function createArgument( + name: string, + type: BaseData, + origin: Origin = 'argument', +): FunctionArgument { + let used = false; + + return { + name, + access: () => { + used = true; + return snip(name, type, origin); + }, + decoratedType: type, + get used() { + return used; + }, + }; +} + +function createArgumentPropAccess( + argAccess: FunctionArgumentAccess, + prop: string, +): FunctionArgumentAccess { + return () => { + const argSnippet = argAccess(); + if (!argSnippet) { + return undefined; + } + return accessProp(argSnippet, prop); + }; +} + export class ResolutionCtxImpl implements ResolutionCtx { readonly #namespaceInternal: NamespaceInternal; @@ -438,28 +466,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { return this.#logGenerator.logResources; } - fnToWgsl(options: FnToWgslOptions): { head: Wgsl; body: Wgsl; returnType: BaseData } { + fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { let fnScopePushed = false; try { this.#namespaceInternal.nameRegistry.pushFunctionScope(); - const args: Snippet[] = []; - const argAliases: [string, Snippet][] = []; - // For entry functions: collect pending header entries to be filtered after body generation. - const pendingHeaderEntries: { argName: string; header: string }[] = []; + const args: FunctionArgument[] = []; + const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; - const structArgName = this.makeNameValid('_arg_0'); - const structArg = dataSchema ? snip(structArgName, dataSchema, 'argument') : undefined; + const structArg = dataSchema + ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + : undefined; + if (structArg) { args.push(structArg); - pendingHeaderEntries.push({ - argName: structArgName, - header: `${structArgName}: ${this.resolve(dataSchema).value}`, - }); } if (firstParam?.type === FuncParameterType.destructuredObject) { @@ -467,45 +491,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const argName = this.makeNameValid(alias); - const argSnippet = snip(argName, argInfo.type, 'argument'); - args.push(argSnippet); - argAliases.push([alias, argSnippet]); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(argInfo.type)}${argName}: ${this.resolve(undecorate(argInfo.type)).value}`, - }); + const arg = createArgument(this.makeNameValid(alias), argInfo.type); + args.push(arg); + argAccess[alias] = arg.access; } else if (structArg) { - const propSnippet = accessProp(structArg, name); - if (propSnippet) { - argAliases.push([alias, propSnippet]); - } + argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. - const proxyEntries: Array<{ schemaKey: string; argName: string; type: BaseData }> = []; + const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - const s = snip(argName, a.type, 'argument'); - args.push(s); - proxyEntries.push({ schemaKey: a.schemaKey, argName, type: a.type }); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } - const router = new EntryInputRouter(structArgName, dataSchema, proxyEntries); - argAliases.push([firstParam.name, snip(firstParam.name, router, 'argument')]); + const router = new EntryInputRouter(structArg?.access, proxyEntries); + argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - args.push(snip(argName, a.type, 'argument')); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + argAccess[argName] = arg.access; } } } else { @@ -528,22 +538,17 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const rawName = astParam.name; - const snippet = snip(this.makeNameValid(rawName), argType, origin); - args.push(snippet); - if (snippet.value !== rawName) { - argAliases.push([rawName, snippet]); - } + const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + args.push(arg); + argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objSnippet = snip(`_arg_${i}`, argType, origin); - args.push(objSnippet); - argAliases.push( - ...astParam.props.map( - ({ name, alias }) => [alias, accessProp(objSnippet, name)] as [string, Snippet], - ), - ); + const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + args.push(objArg); + for (const { name, alias } of astParam.props) { + argAccess[alias] = createArgumentPropAccess(objArg.access, name); + } break; } case undefined: { @@ -551,7 +556,16 @@ export class ResolutionCtxImpl implements ResolutionCtx { // If we're not using an auto-struct, it's not going to // have any properties anyway. if (!(argType instanceof AutoStruct)) { - args.push(snip(`_arg_${i}`, argType, origin)); + args.push({ + name: this.makeNameValid(`_arg_${i}`), + access: () => { + throw new Error( + `Unreachable: Accessing an argument that wasn't named in the function signature`, + ); + }, + decoratedType: argType, + used: false, + }); } } } @@ -560,68 +574,71 @@ export class ResolutionCtxImpl implements ResolutionCtx { const scope = this._itemStateStack.pushFunctionScope( options.functionType, - args, - Object.fromEntries(argAliases), + argAccess, options.returnType, options.externalMap, ); fnScopePushed = true; - const body = this.gen.functionDefinition(options.body); + let returnType: BaseData | undefined; - let returnType = options.returnType; - if (returnType instanceof AutoStruct) { - // We're expecting an "auto" return type, so if there were structs returned, - // we accept the struct, otherwise we let the rest of the code unify on a - // primitive type. - if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { - returnType = returnType.completeStruct; - } else { - returnType = undefined; - } - } + const code = this.gen.functionDefinition({ + functionType: options.functionType, + args, + body: options.body, + determineReturnType: () => { + if (returnType) { + // Already determined + return returnType; + } - if (!returnType) { - const returnTypes = [...scope.reportedReturnTypes]; - if (returnTypes.length === 0) { - returnType = Void; - } else { - const conversion = getBestConversion(returnTypes); - if (conversion && !conversion.hasImplicitConversions) { - returnType = conversion.targetType; + returnType = options.returnType; + if (returnType instanceof AutoStruct) { + // We're expecting an "auto" return type, so if there were structs returned, + // we accept the struct, otherwise we let the rest of the code unify on a + // primitive type. + if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { + returnType = returnType.completeStruct; + } else { + returnType = undefined; + } } - } - if (!returnType) { - throw new Error( - `Expected function to have a single return type, got [${returnTypes.join( - ', ', - )}]. Cast explicitly to the desired type.`, - ); - } + if (!returnType) { + const returnTypes = [...scope.reportedReturnTypes]; + if (returnTypes.length === 0) { + returnType = Void; + } else { + const conversion = getBestConversion(returnTypes); + if (conversion && !conversion.hasImplicitConversions) { + returnType = conversion.targetType; + } + } - returnType = concretize(returnType); + if (!returnType) { + throw new Error( + `Expected function to have a single return type, got [${returnTypes.join( + ', ', + )}]. Cast explicitly to the desired type.`, + ); + } - if (options.functionType === 'vertex' || options.functionType === 'fragment') { - returnType = createIoSchema(returnType as IOData); - } - } + returnType = concretize(returnType); - if (options.entryInput) { - const headerParts = pendingHeaderEntries - .filter(({ argName }) => isArgUsedInBody(argName, body)) - .map(({ header }) => header); - const argList = headerParts.join(', '); - const returnStr = - returnType.type !== 'void' - ? `-> ${getAttributesString(returnType)}${this.resolve(returnType).value} ` - : ''; - return { head: `(${argList}) ${returnStr}`, body, returnType }; + if (options.functionType === 'vertex' || options.functionType === 'fragment') { + returnType = createIoSchema(returnType as IOData); + } + } + return returnType; + }, + }); + + if (!returnType) { + throw new Error(`Failed to determine return type`); } return { - head: resolveFunctionHeader(this, args, returnType), - body, + code, returnType, }; } finally { @@ -1079,17 +1096,3 @@ export function resolve(item: Wgsl, options: ResolutionCtxImplOptions): Resoluti logResources: ctx.logResources, }; } - -function isArgUsedInBody(argName: string, body: string): boolean { - return new RegExp(`\\b${argName}\\b`).test(body); -} - -function resolveFunctionHeader(ctx: ResolutionCtx, args: Snippet[], returnType: BaseData) { - const argList = args - .map((arg) => `${arg.value}: ${ctx.resolve(arg.dataType as BaseData).value}`) - .join(', '); - - return returnType.type !== 'void' - ? `(${argList}) -> ${getAttributesString(returnType)}${ctx.resolve(returnType).value} ` - : `(${argList}) `; -} diff --git a/packages/typegpu/src/tgsl/accessProp.ts b/packages/typegpu/src/tgsl/accessProp.ts index b264a154ba..3410d3e383 100644 --- a/packages/typegpu/src/tgsl/accessProp.ts +++ b/packages/typegpu/src/tgsl/accessProp.ts @@ -10,7 +10,7 @@ import { } from '../data/dataTypes.ts'; import { abstractInt, bool, f16, f32, i32, u32 } from '../data/numeric.ts'; import { derefSnippet } from '../data/ref.ts'; -import { isEphemeralSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { isEphemeralSnippet, isSnippet, snip, type Snippet } from '../data/snippet.ts'; import { vec2b, vec2f, @@ -160,7 +160,14 @@ export function accessProp(target: Snippet, propName: string): Snippet | undefin } if (target.dataType instanceof EntryInputRouter) { - return target.dataType.accessProp(propName); + const result = target.dataType.accessProp(propName); + if (isSnippet(result)) { + return result; + } + if (result) { + return accessProp(result.target, result.prop); + } + return undefined; } if (isPtr(target.dataType)) { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 0d8ae68163..a11954784a 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -294,7 +294,7 @@ export function convertToCommonType( if ((TEST || DEV) && verbose && conversion.hasImplicitConversions) { console.warn( `Implicit conversions from [\n${values - .map((v) => ` ${v.value}: ${safeStringify(v.dataType)}`) + .map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`) .join(',\n')}\n] to ${conversion.targetType.type} are supported, but not recommended. Consider using explicit conversions instead.`, ); diff --git a/packages/typegpu/src/tgsl/shaderGenerator.ts b/packages/typegpu/src/tgsl/shaderGenerator.ts index e482b03cc8..adde48bf00 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator.ts @@ -1,7 +1,7 @@ -import type { Block } from 'tinyest'; import type { BaseData } from '../data/wgslTypes.ts'; import type { GenerationCtx } from './generationHelpers.ts'; import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; /** * **NOTE: This is an unstable API and may change in the future.** @@ -12,7 +12,7 @@ import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; export interface ShaderGenerator { initGenerator(ctx: GenerationCtx): void; - functionDefinition(body: Block): string; + functionDefinition(options: FunctionDefinitionOptions): string; typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet; typeAnnotation(schema: BaseData): string; } diff --git a/packages/typegpu/src/tgsl/shaderGenerator_members.ts b/packages/typegpu/src/tgsl/shaderGenerator_members.ts index a33ac92b24..8dfc3eae57 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator_members.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator_members.ts @@ -1,4 +1,16 @@ +import type { Block } from 'tinyest'; +import type { BaseData } from '../data/wgslTypes.ts'; +import type { FunctionArgument, TgpuShaderStage } from '../types.ts'; + export { UnknownData } from '../data/dataTypes.ts'; // types -export type { ResolutionCtx } from '../types.ts'; +export type { ResolutionCtx, FunctionArgument } from '../types.ts'; + +export interface FunctionDefinitionOptions { + readonly functionType: 'normal' | TgpuShaderStage; + readonly args: readonly FunctionArgument[]; + readonly body: Block; + + determineReturnType(): BaseData; +} diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 7c7c06d7c8..153ca5ed0b 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -51,6 +51,8 @@ import { mathToStd } from './math.ts'; import type { ExternalMap } from '../core/resolve/externals.ts'; import * as forOfUtils from './forOfUtils.ts'; import { isTgpuRange } from '../std/range.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; +import { getAttributesString } from '../data/attributes.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -887,8 +889,27 @@ ${this.ctx.pre}}`; assertExhaustive(expression); } - public functionDefinition(body: tinyest.Block): string { - return this._block(body); + public functionDefinition(options: FunctionDefinitionOptions): string { + // Function body + const body = this._block(options.body); + + // Function header + const returnType = options.determineReturnType(); + + const argList = options.args + // Stripping out unused arguments in entry functions + .filter((arg) => arg.used || options.functionType === 'normal') + .map((arg) => { + return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`; + }) + .join(', '); + + const head = + returnType.type !== 'void' + ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` + : `(${argList}) `; + + return `${head}${body}`; } /** diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 1c92f8a43f..75f9e71697 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -99,11 +99,19 @@ export type ItemLayer = { usedSlots: Set>; }; +export type FunctionArgumentAccess = () => Snippet | undefined; + +export interface FunctionArgument { + name: string; + access: FunctionArgumentAccess; + decoratedType: BaseData; + used: boolean; +} + export type FunctionScopeLayer = { type: 'functionScope'; functionType: 'normal' | 'compute' | 'vertex' | 'fragment'; - args: Snippet[]; - argAliases: Record; + argAccess: Record; externalMap: Record; /** * The return type of the function. If undefined, the type should be inferred @@ -138,8 +146,7 @@ export interface ItemStateStack { pushSlotBindings(pairs: SlotValuePair[]): void; pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, /** * The return type of the function. If undefined, the type should be inferred * from the implementation (relevant for shellless functions). @@ -305,8 +312,7 @@ export interface ResolutionCtx { resolveSnippet(snippet: Snippet): ResolvedSnippet; fnToWgsl(options: FnToWgslOptions): { - head: Wgsl; - body: Wgsl; + code: string; returnType: BaseData; }; diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 316f2d0e02..be0c2888e4 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -277,13 +277,6 @@ describe('root.withVertex(...).withFragment(...)', () => { return vertexMain_Output(vec3f(), vec3f(), vec3f(), 0f, 0u, vec4f()); } - struct fragmentMain_Input { - @location(3) baz3: u32, - @location(1) bar: vec3f, - @location(2) foo: vec3f, - @location(5) baz2: f32, - } - @fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(); }" @@ -1568,8 +1561,8 @@ describe('root.createRenderPipeline', () => { } struct VertexIn { - @builtin(vertex_index) vertexIndex: u32, @location(0) localPos: vec3f, + @builtin(vertex_index) vertexIndex: u32, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { @@ -1712,18 +1705,13 @@ describe('root.createRenderPipeline', () => { { "arrayStride": 16, "attributes": [ - { - "format": "float32", - "offset": 12, - "shaderLocation": 0, - }, { "format": "float32x3", "offset": 0, - "shaderLocation": 1, + "shaderLocation": 0, }, ], - "stepMode": "instance", + "stepMode": "vertex", }, { "arrayStride": 16, @@ -1731,10 +1719,15 @@ describe('root.createRenderPipeline', () => { { "format": "float32x3", "offset": 0, + "shaderLocation": 1, + }, + { + "format": "float32", + "offset": 12, "shaderLocation": 2, }, ], - "stepMode": "vertex", + "stepMode": "instance", }, ], "module": "mockShaderModule", @@ -1751,10 +1744,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "instanceBuffer", + "label": "vertexBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 16, + "size": 48, "unmap": [MockFunction], "usage": 44, }, @@ -1766,10 +1759,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "vertexBuffer", + "label": "instanceBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 48, + "size": 16, "unmap": [MockFunction], "usage": 44, }, diff --git a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts index 727e9fed70..58150fbee5 100644 --- a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts +++ b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts @@ -17,14 +17,14 @@ describe('extension based pruning', () => { }); expect(tgpu.resolve([someFn], { enableExtensions: ['f16'] })).toMatchInlineSnapshot(` - "enable f16; + "enable f16; - fn someFn() -> f32 { - { - return 6.599609375f; - } - }" - `); + fn someFn() -> f32 { + { + return 6.599609375f; + } + }" + `); expect(tgpu.resolve([someFn])).toMatchInlineSnapshot(` "fn someFn() -> f32 { diff --git a/packages/typegpu/tests/tgsl/nameClashes.test.ts b/packages/typegpu/tests/tgsl/nameClashes.test.ts index 86167245ed..586e8e3bf1 100644 --- a/packages/typegpu/tests/tgsl/nameClashes.test.ts +++ b/packages/typegpu/tests/tgsl/nameClashes.test.ts @@ -223,14 +223,14 @@ test('should allow duplicate name after block end', () => { }; expect(tgpu.resolve([main])).toMatchInlineSnapshot(` - "fn main() -> u32 { - for (var i = 0; (i < 3i); i++) { - let foo = (i + 1i); - } - const foo = 7u; - return foo; - }" - `); + "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', () => { diff --git a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts index fd37903d12..c41cc6b27c 100644 --- a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts +++ b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts @@ -18,14 +18,14 @@ describe('ternary operator', () => { myFn.with(mySlot, false).$name('falseFn'), ]), ).toMatchInlineSnapshot(` - "fn trueFn() -> u32 { - return 10u; - } - - fn falseFn() -> u32 { - return 20u; - }" - `); + "fn trueFn() -> u32 { + return 10u; + } + + fn falseFn() -> u32 { + return 20u; + }" + `); }); it('should work for different comptime known expressions', () => { @@ -72,22 +72,22 @@ describe('ternary operator', () => { myFn.with(mySlot, 3).$name('threeFn'), ]), ).toMatchInlineSnapshot(` - "fn myFn() -> u32 { - return -1u; - } - - fn oneFn() -> u32 { - return 10u; - } - - fn twoFn() -> u32 { - return 20u; - } - - fn threeFn() -> u32 { - return 30u; - }" - `); + "fn myFn() -> u32 { + return -1u; + } + + fn oneFn() -> u32 { + return 10u; + } + + fn twoFn() -> u32 { + return 20u; + } + + fn threeFn() -> u32 { + return 30u; + }" + `); }); it('should not include unused dependencies', ({ root }) => { @@ -103,20 +103,20 @@ describe('ternary operator', () => { }); expect(tgpu.resolve([myFn.with(mySlot, true).$name('trueFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myUniform: u32; + "@group(0) @binding(0) var myUniform: u32; - fn trueFn() -> u32 { - return myUniform; - }" - `); + fn trueFn() -> u32 { + return myUniform; + }" + `); expect(tgpu.resolve([myFn.with(mySlot, false).$name('falseFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myReadonly: u32; + "@group(0) @binding(0) var myReadonly: u32; - fn falseFn() -> u32 { - return myReadonly; - }" - `); + fn falseFn() -> u32 { + return myReadonly; + }" + `); }); it('should handle undefined', ({ root }) => { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 62c96fe428..eb377fbcab 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -3,8 +3,7 @@ import { beforeEach, describe, expect, vi } from 'vitest'; import { namespace } from '../../src/core/resolve/namespace.ts'; import * as d from '../../src/data/index.ts'; import { abstractFloat, abstractInt } from '../../src/data/numeric.ts'; -import { snip } from '../../src/data/snippet.ts'; -import { Void, type WgslArray } from '../../src/data/wgslTypes.ts'; +import { type WgslArray } from '../../src/data/wgslTypes.ts'; import { provideCtx } from '../../src/execMode.ts'; import tgpu from '../../src/index.js'; import { ResolutionCtxImpl } from '../../src/resolutionCtx.ts'; @@ -15,8 +14,7 @@ import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from 'typegpu-testing-utility'; import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; -import { extractSnippetFromFn } from '../utils/parseResolved.ts'; -import { UnknownData } from '../../src/tgsl/shaderGenerator_members.ts'; +import { expectDataTypeOf, extractSnippetFromFn } from '../utils/parseResolved.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -41,19 +39,11 @@ describe('wgslGenerator', () => { return true; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot(`"[0,[[10,true]]]"`); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.bool, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - return true; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> bool { + return true; + }" + `); }); it('creates a function body', () => { @@ -64,23 +54,13 @@ describe('wgslGenerator', () => { return a; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot( - `"[0,[[12,"a",[5,"12"]],[2,"a","+=",[5,"21"]],[10,"a"]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.i32, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - var a = 12; - a += 21i; - return a; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> i32 { + var a = 12; + a += 21i; + return a; + }" + `); }); it('creates correct resources for numeric literals', () => { @@ -135,57 +115,22 @@ describe('wgslGenerator', () => { }); const testBuffer = root.createBuffer(TestStruct).$usage('storage'); - const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$.a + testUsage.$.b.x; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - if (!astInfo) { - throw new Error('Expected prebuilt AST to be present'); - } + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.a; + }).toStrictEqual(d.u32); - expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( - `"[0,[[10,[1,[7,[7,"testUsage","$"],"a"],"+",[7,[7,[7,"testUsage","$"],"b"],"x"]]]]]"`, - ); - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.b.x; + }).toStrictEqual(d.u32); - provideCtx(ctx, () => { - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res1 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[1], - ); - - expect(res1.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res2 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[3], - ); - expect(res2.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const sum = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - expect(sum.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$.a + testUsage.$.b.x; + }).toStrictEqual(d.u32); }); it('generates correct resources for external resource array index access', ({ root }) => { @@ -193,42 +138,10 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('uniform'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$[3] as number; - }); - - 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,[[10,[8,[7,"testUsage","$"],[5,"3"]]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return testUsage.$[3]; - // ^ this should be a u32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + testUsage.$[3]; + }).toStrictEqual(d.u32); }); it('generates correct resources for nested struct with atomics in a complex expression', ({ @@ -254,156 +167,29 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [d.u32], - d.vec4f, - )((idx) => { - const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - const vec = std.mix(d.vec4f(), testUsage.$.a, value); - std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - return vec; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - - if (!astInfo?.ast) { - throw new Error('Expected prebuilt AST to be present'); - } - - expect(JSON.stringify(astInfo.ast.body)).toMatchInlineSnapshot( - `"[0,[[13,"value",[6,[7,"std","atomicLoad"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"y"]]]],[13,"vec",[6,[7,"std","mix"],[[6,[7,"d","vec4f"],[]],[7,[7,"testUsage","$"],"a"],"value"]]],[6,[7,"std","atomicStore"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"x"],[7,"vec","y"]]],[10,"vec"]]]"`, - ); - - if (astInfo.ast.params.filter((arg) => arg.type !== 'i').length > 0) { - throw new Error('Expected arguments as identifier names in ast'); - } - - const args = astInfo.ast.params.map((arg) => - snip((arg as { type: 'i'; name: string }).name, d.u32, /* origin */ 'runtime'), - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - args, - {}, - d.vec4f, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - // ^ this part should be a i32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.i32); - - // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); - // ^ this part should be a vec4f - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'value', d.i32, 'runtime'); - const res2 = wgslGenerator._expression( - (astInfo.ast?.body[1][1] as tinyest.Const)[2] as tinyest.Expression, - ); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res2.dataType).toStrictEqual(d.vec4f); - - // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - // ^ this part should be an atomic u32 - // ^ this part should be void - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'function'); - const res3 = wgslGenerator._expression( - (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, - ); - const res4 = wgslGenerator._expression(astInfo.ast?.body[1][2] as tinyest.Expression); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res3.dataType).toStrictEqual(d.atomic(d.u32)); - expect(res4.dataType).toStrictEqual(Void); - }); - }); - - it('creates correct code for for statements', () => { - const main = () => { + // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); + // ^ this part should be a i32 + expectDataTypeOf(() => { 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[14,[12,"i",[5,"0"]],[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); + const idx = d.u32(0); + std.atomicLoad(testUsage.$.b.aa[idx]!.y); + }).toStrictEqual(d.i32); - it('creates correct code for for statements with outside init', () => { - const main = () => { + // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); + // ^ this part should be a vec4f + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - for (; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[14,null,[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - for (; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); + const value = std.atomicLoad(testUsage.$.b.aa[0]!.y); + std.mix(d.vec4f(), testUsage.$.a, value); + }).toStrictEqual(d.vec4f); - it('creates correct code for while statements', () => { - const main = () => { + // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); + // ^ this part should be an atomic u32 + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - while (i < 10) { - i += 1; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[15,[1,"i","<",[5,"10"]],[0,[[2,"i","+=",[5,"1"]]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - while ((i < 10i)) { - i += 1i; - } - }" - `); + const idx = d.u32(0); + testUsage.$.b.aa[idx]!.x; + }).toStrictEqual(d.atomic(d.u32)); }); it('parses correctly "for ... of ..." statements', () => { @@ -1004,6 +790,11 @@ describe('wgslGenerator', () => { }); it('creates correct resources for lazy values and slots', () => { + expectDataTypeOf(() => { + 'use gpu'; + lazyV4u.$; + }).toStrictEqual(d.vec4u); + const testFn = tgpu.fn([], d.vec4u)(() => lazyV4u.$); expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` @@ -1011,76 +802,14 @@ describe('wgslGenerator', () => { return vec4u(44, 88, 132, 176); }" `); - - 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,[[10,[7,"lazyV4u","$"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.vec4u, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return lazyV4u.$; - // ^ this should be a vec4u - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.vec4u); - }); }); it('creates correct resources for indexing into a lazy value', () => { - const testFn = tgpu.fn( - [d.u32], - d.f32, - )((idx) => { - return lazyV2f.$[idx] as number; - }); - - 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,[[10,[8,[7,"lazyV2f","$"],"idx"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [snip('idx', d.u32, /* origin */ 'runtime')], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return lazyV2f.$[idx]; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + const idx = d.u32(0); + lazyV2f.$[idx]; + }).toStrictEqual(d.f32); }); it('creates intermediate representation for array expression', () => { @@ -1106,44 +835,11 @@ describe('wgslGenerator', () => { }); 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( - (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); - }); + "fn testFn() -> u32 { + var arr = array(1u, 2u, 3u); + return arr[1i]; + }" + `); }); it('generates correct code for complex array expressions', () => { @@ -1165,39 +861,6 @@ 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( - (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); - }); }); it('does not autocast lhs of an assignment', () => { @@ -1248,38 +911,15 @@ describe('wgslGenerator', () => { }" `); - 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,"TestStruct",[[104,{"x":[5,"1"],"y":[5,"2"]}]]],[6,"TestStruct",[[104,{"x":[5,"3"],"y":[5,"4"]}]]]]]],[10,[7,[8,"arr",[5,"1"]],"y"]]]]"`, - ); - - const res = provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; - // ^ this should be an array - wgslGenerator.initGenerator(ctx); - return wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); + const arraySnippet = extractSnippetFromFn(() => { + 'use gpu'; + const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; + arr; }); - 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); + expect(d.isWgslArray(arraySnippet.dataType)).toBe(true); + expect((arraySnippet.dataType as unknown as WgslArray).elementCount).toBe(2); + expect((arraySnippet.dataType as unknown as WgslArray).elementType).toBe(TestStruct); }); it('generates correct code for array expressions with lazy elements', () => { @@ -1297,37 +937,6 @@ describe('wgslGenerator', () => { return arr[1i].y; }" `); - - 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,[[7,"lazyV2f","$"],[6,[7,"std","mul"],[[7,"lazyV2f","$"],[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); }); it('allows for member access on values returned from function calls', () => { @@ -1365,34 +974,10 @@ describe('wgslGenerator', () => { }" `); - const astInfo = getMetaData(fnTwo[$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,[[10,[7,[7,[6,"fnOne",[]],"y"],"x"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return fnOne().y.x; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + fnOne().y.x; + }).toStrictEqual(d.f32); }); it('generates correct code for conditional with single statement', () => { @@ -1465,27 +1050,6 @@ describe('wgslGenerator', () => { `); }); - it('generates correct code for for loops with single statements', () => { - const main = () => { - 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const gen = provideCtx(ctx, () => - wgslGenerator.functionDefinition(getMetaData(main)?.ast?.body as tinyest.Block), - ); - - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); - it('generates correct code for while loops with single statements', () => { const main = tgpu.fn([])(() => { let i = 0; @@ -1806,59 +1370,51 @@ describe('wgslGenerator', () => { it('block externals do not override identifiers', () => { const f = () => { 'use gpu'; - const y = 100; - const x = y; - return x; + const list = [1]; + for (const x of tgpu.unroll(list)) { + 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); - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var list = array(1); + // unrolled iteration #0 + { + const y = 100; + const x = y; + return x; + } + }" + `); }); it('block externals are injected correctly', () => { const f = () => { 'use gpu'; - for (const x of []) { + for (const x of tgpu.unroll([1])) { 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; - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + // unrolled iteration #0 + { + const y = 1; + } + }" + `); }); 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) { + const list = [1]; + for (const elem of tgpu.unroll(list)) { { // We use the `elem` in a nested block result += elem; @@ -1866,24 +1422,18 @@ describe('wgslGenerator', () => { } }; - 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(` - "{ + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var result = 0i; + var list = array(1); + // unrolled iteration #0 + { { - result += 7i; + result += list[0u]; } - }" - `); - }); + } + }" + `); }); it('prunes comptime if/else', () => { diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 529e9f0cfa..9278a82a00 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -363,11 +363,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -410,11 +406,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -444,11 +436,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - @fragment fn fragmentFn() -> @location(0) vec4f { + "@fragment fn fragmentFn() -> @location(0) vec4f { var hmm = vec4f(1.25); return hmm; }" @@ -481,11 +469,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index b798ff9f8c..9c94a073c0 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -29,7 +29,6 @@ export function extractSnippetFromFn(cb: () => unknown): Snippet { ctx[$internal].itemStateStack.pushItem(); ctx[$internal].itemStateStack.pushFunctionScope( 'normal', - [], {}, undefined, (meta.externals as () => Record)() ?? {}, From d5d4be5183b15c9a016bd1e981e67fdaf84b0199 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 11 Apr 2026 13:31:41 +0200 Subject: [PATCH 20/34] refactor: Naming cleanup --- .../typegpu/src/core/buffer/bufferUsage.ts | 4 +- .../typegpu/src/core/constant/tgpuConstant.ts | 2 +- packages/typegpu/src/core/function/fnCore.ts | 27 +-- .../typegpu/src/core/resolve/namespace.ts | 55 +----- .../typegpu/src/core/resolve/resolveData.ts | 5 +- packages/typegpu/src/core/sampler/sampler.ts | 4 +- .../src/core/texture/externalTexture.ts | 2 +- packages/typegpu/src/core/texture/texture.ts | 4 +- .../typegpu/src/core/variable/tgpuVariable.ts | 2 +- packages/typegpu/src/data/autoStruct.ts | 2 +- packages/typegpu/src/data/struct.ts | 2 +- .../src/{nameRegistry.ts => nameUtils.ts} | 166 +----------------- packages/typegpu/src/resolutionCtx.ts | 135 ++++++++++---- packages/typegpu/src/tgsl/wgslGenerator.ts | 31 ++-- packages/typegpu/src/types.ts | 25 ++- packages/typegpu/tests/namespace.test.ts | 30 +--- packages/typegpu/tests/resolve.test.ts | 4 +- 17 files changed, 187 insertions(+), 313 deletions(-) rename packages/typegpu/src/{nameRegistry.ts => nameUtils.ts} (58%) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 24f28dcbca..d8a1028e78 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -118,7 +118,7 @@ class TgpuFixedBufferImpl implements TgpuConst, } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const resolvedDataType = ctx.resolve(this.dataType).value; const resolvedValue = ctx.resolve(this.#value, this.dataType).value; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index cf4cda90f7..aa91d451f6 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -3,6 +3,7 @@ import { undecorate } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTypes.ts'; import { MissingLinksError } from '../../errors.ts'; +import { isValidIdentifier } from '../../nameUtils.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; @@ -65,21 +66,25 @@ export function createFnCore( applyExternals(externalMap, externals); } - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); if (typeof implementation === 'string') { if (!returnType) { throw new Error('Explicit return type is required for string implementation'); } - const validArgNames = entryInput - ? Object.fromEntries( - entryInput.positionalArgs.map((a) => [a.schemaKey, ctx.makeNameValid(a.schemaKey)]), - ) - : undefined; + if (entryInput) { + for (const arg of entryInput.positionalArgs) { + if (!isValidIdentifier(arg.schemaKey)) { + throw new Error(`Invalid argument name: ${arg.schemaKey}`); + } + } - if (validArgNames && Object.keys(validArgNames).length > 0) { - applyExternals(externalMap, { in: validArgNames }); + applyExternals(externalMap, { + in: Object.fromEntries( + entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey]), + ), + }); } const replacedImpl = replaceExternalsInWgsl(ctx, externalMap, implementation); @@ -87,15 +92,15 @@ export function createFnCore( let header = ''; let body = ''; - if (functionType !== 'normal' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { parts.push(`in: ${ctx.resolve(dataSchema).value}`); } for (const a of positionalArgs) { - const argName = validArgNames[a.schemaKey] ?? ''; - if (argName !== '' && isArgUsedInBody(argName, replacedImpl)) { + const argName = a.schemaKey; + if (isArgUsedInBody(argName, replacedImpl)) { parts.push(`${getAttributesString(a.type)}${argName}: ${ctx.resolve(a.type).value}`); } } diff --git a/packages/typegpu/src/core/resolve/namespace.ts b/packages/typegpu/src/core/resolve/namespace.ts index f112cd4fb0..90fcba08f7 100644 --- a/packages/typegpu/src/core/resolve/namespace.ts +++ b/packages/typegpu/src/core/resolve/namespace.ts @@ -1,6 +1,5 @@ import type { ResolvedSnippet } from '../../data/snippet.ts'; -import { type NameRegistry, RandomNameRegistry, StrictNameRegistry } from '../../nameRegistry.ts'; -import { getName } from '../../shared/meta.ts'; +import { bannedTokens, builtins } from '../../nameUtils.ts'; import { $internal } from '../../shared/symbols.ts'; import { ShelllessRepository } from '../../tgsl/shellless.ts'; import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; @@ -8,8 +7,9 @@ import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; type SlotToValueMap = Map, unknown>; export interface NamespaceInternal { - readonly nameRegistry: NameRegistry; + readonly takenGlobalIdentifiers: Set; readonly shelllessRepo: ShelllessRepository; + readonly strategy: 'random' | 'strict'; memoizedResolves: WeakMap< // WeakMap because if the item does not exist anymore, @@ -24,73 +24,32 @@ export interface NamespaceInternal { TgpuLazy, { slotToValueMap: SlotToValueMap; result: unknown }[] >; - - listeners: { - [K in keyof NamespaceEventMap]: Set<(event: NamespaceEventMap[K]) => void>; - }; } -type NamespaceEventMap = { - name: { target: object; name: string }; -}; - -type DetachListener = () => void; - export interface Namespace { readonly [$internal]: NamespaceInternal; - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener; } class NamespaceImpl implements Namespace { readonly [$internal]: NamespaceInternal; - constructor(nameRegistry: NameRegistry) { + constructor(strategy: 'random' | 'strict') { this[$internal] = { - nameRegistry, + strategy, + takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins]), shelllessRepo: new ShelllessRepository(), memoizedResolves: new WeakMap(), memoizedLazy: new WeakMap(), - listeners: { - name: new Set(), - }, }; } - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener { - if (event === 'name') { - const listeners = this[$internal].listeners.name; - listeners.add(listener); - - return () => listeners.delete(listener); - } - - throw new Error(`Unsupported event: ${event}`); - } } export interface NamespaceOptions { names?: 'random' | 'strict' | undefined; } -export function getUniqueName(namespace: NamespaceInternal, resource: object): string { - const name = namespace.nameRegistry.makeUnique(getName(resource), true); - for (const listener of namespace.listeners.name) { - listener({ target: resource, name }); - } - return name; -} - export function namespace(options?: NamespaceOptions): Namespace { const { names = 'strict' } = options ?? {}; - return new NamespaceImpl( - names === 'strict' ? new StrictNameRegistry() : new RandomNameRegistry(), - ); + return new NamespaceImpl(names); } diff --git a/packages/typegpu/src/core/resolve/resolveData.ts b/packages/typegpu/src/core/resolve/resolveData.ts index 19e5da347d..bc5320c80d 100644 --- a/packages/typegpu/src/core/resolve/resolveData.ts +++ b/packages/typegpu/src/core/resolve/resolveData.ts @@ -38,6 +38,7 @@ import type { WgslArray, WgslStruct, } from '../../data/wgslTypes.ts'; +import { getName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { assertExhaustive } from '../../shared/utilityTypes.ts'; import type { ResolutionCtx } from '../../types.ts'; @@ -127,7 +128,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) { if (struct[$internal].isAbstruct) { throw new Error('Cannot resolve abstract struct types to WGSL.'); } - const id = ctx.getUniqueName(struct); + const id = ctx.makeUniqueIdentifier(getName(struct), 'global'); ctx.addDeclaration(`\ struct ${id} { @@ -155,7 +156,7 @@ ${Object.entries(struct.propTypes) * ``` */ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) { - const id = ctx.getUniqueName(unstruct); + const id = ctx.makeUniqueIdentifier(getName(unstruct), 'global'); ctx.addDeclaration(`\ struct ${id} { diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index 4daaf1c917..adf10ee8ce 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -99,7 +99,7 @@ export class TgpuLaidOutSamplerImpl< } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( @@ -186,7 +186,7 @@ class TgpuFixedSamplerImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( this.schema.type === 'sampler_comparison' diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index c9d457c48b..78ae4cd582 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl implements TgpuExternalTexture, SelfResolva } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 27923995d6..fbe7295d83 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -600,7 +600,7 @@ class TgpuFixedTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( isWgslStorageTexture(this.schema) ? { @@ -642,7 +642,7 @@ export class TgpuLaidOutTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const pre = `var<${this.#scope}> ${id}: ${ctx.resolve(this.#dataType).value}`; if (this.#initialValue) { diff --git a/packages/typegpu/src/data/autoStruct.ts b/packages/typegpu/src/data/autoStruct.ts index 647d13b79c..c227b565fe 100644 --- a/packages/typegpu/src/data/autoStruct.ts +++ b/packages/typegpu/src/data/autoStruct.ts @@ -1,5 +1,5 @@ import { createIoSchema } from '../core/function/ioSchema.ts'; -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal, $repr, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; diff --git a/packages/typegpu/src/data/struct.ts b/packages/typegpu/src/data/struct.ts index c044da5f8f..c2d024acc3 100644 --- a/packages/typegpu/src/data/struct.ts +++ b/packages/typegpu/src/data/struct.ts @@ -1,4 +1,4 @@ -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { schemaCallWrapper } from './schemaCallWrapper.ts'; diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameUtils.ts similarity index 58% rename from packages/typegpu/src/nameRegistry.ts rename to packages/typegpu/src/nameUtils.ts index aa2cd1ada0..f66bfbb79a 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameUtils.ts @@ -1,6 +1,4 @@ -import { invariant } from './errors.ts'; - -const bannedTokens = new Set([ +export const bannedTokens = new Set([ // keywords 'alias', 'break', @@ -181,7 +179,7 @@ const bannedTokens = new Set([ 'storage', ]); -const builtins = new Set([ +export const builtins = new Set([ // constructors 'array', 'bool', @@ -361,37 +359,8 @@ const builtins = new Set([ 'quadSwapY', ]); -export interface NameRegistry { - /** - * Creates a valid WGSL identifier, each guaranteed to be unique - * in the lifetime of a single resolution process - * (excluding non-global identifiers from popped scopes). - * Should append "_" to primer, followed by some id. - * @param primer Used in the generation process, makes the identifier more recognizable. - * @param global Whether the name should be registered in the global scope (true), or in the current function scope (false) - */ - makeUnique(primer: string | undefined, global: boolean): string; - - /** - * Creates a valid WGSL identifier. - * Renames identifiers that are WGSL reserved words. - * @param primer Used in the generation process. - * - * @example - * makeValid("notAKeyword"); // "notAKeyword" - * makeValid("struct"); // makeUnique("struct") - * makeValid("struct_1"); // makeUnique("struct_1") (to avoid potential name collisions) - * makeValid("_"); // ERROR (too difficult to make valid to care) - */ - makeValid(primer: string): string; - - pushFunctionScope(): void; - popFunctionScope(): void; - pushBlockScope(): void; - popBlockScope(): void; -} - -function sanitizePrimer(primer: string | undefined) { +/*#__NO_SIDE_EFFECTS__*/ +export function sanitizePrimer(primer: string | undefined) { if (primer) { // sanitizing return primer @@ -411,7 +380,8 @@ function sanitizePrimer(primer: string | undefined) { * isValidIdentifier("_"); // ERROR * isValidIdentifier("my variable"); // ERROR */ -function isValidIdentifier(ident: string): boolean { +/*#__NO_SIDE_EFFECTS__*/ +export function isValidIdentifier(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( `Invalid identifier '${ident}'. Choose an identifier without whitespaces or leading underscores.`, @@ -424,6 +394,7 @@ function isValidIdentifier(ident: string): boolean { /** * Same as `isValidIdentifier`, except does not check for builtin clashes. */ +/*#__NO_SIDE_EFFECTS__*/ export function isValidProp(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( @@ -433,126 +404,3 @@ 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; - - readonly #usedNames: Set; - readonly #scopeStack: ScopeLayer[]; - - constructor() { - this.#usedNames = new Set([...bannedTokens, ...builtins]); - this.#scopeStack = []; - } - - get #usedBlockScopeNames(): Set | undefined { - return (this.#scopeStack[this.#scopeStack.length - 1] as BlockScopeLayer | undefined) - ?.usedBlockScopeNames; - } - - makeUnique(primer: string | undefined, global: boolean): string { - const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer); - - if (global) { - this.#usedNames.add(name); - } else { - 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.#isUsedInBlocksBefore(primer) - ) { - this.#usedBlockScopeNames?.add(primer); - return primer; - } - return this.makeUnique(primer, false); - } - - isUsed(name: string): boolean { - return this.#usedNames.has(name) || this.#isUsedInBlocksBefore(name); - } - - pushFunctionScope(): void { - this.#scopeStack.push({ type: 'functionScope' }); - this.#scopeStack.push({ - type: 'blockScope', - usedBlockScopeNames: new Set(), - }); - } - - popFunctionScope(): void { - const functionScopeIndex = this.#scopeStack.findLastIndex( - (scope) => scope.type === 'functionScope', - ); - - if (functionScopeIndex === -1) { - throw new Error('Tried to pop function scope when no scope was present.'); - } - - 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 { - let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name)) { - name = `${base}_${this.#lastUniqueId++}`; - } - return name; - } -} - -export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string): string { - let index = 0; - let name = base; - while (this.isUsed(name)) { - index++; - name = `${base}_${index}`; - } - return name; - } -} diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5eedec298d..2ed8dde06a 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -1,5 +1,5 @@ import { isTgpuFn } from './core/function/tgpuFn.ts'; -import { getUniqueName, type Namespace, type NamespaceInternal } from './core/resolve/namespace.ts'; +import type { Namespace, NamespaceInternal } from './core/resolve/namespace.ts'; import { stitch } from './core/resolve/stitch.ts'; import { ConfigurableImpl } from './core/root/configurableImpl.ts'; import type { Configurable, ExperimentalTgpuRoot } from './core/root/rootTypes.ts'; @@ -37,6 +37,7 @@ import { coerceToSnippet, concretize, numericLiteralToSnippet } from './tgsl/gen import type { ShaderGenerator } from './tgsl/shaderGenerator.ts'; import wgslGenerator from './tgsl/wgslGenerator.ts'; import type { + BlockScopeLayer, ExecMode, ExecState, FnToWgslOptions, @@ -59,6 +60,7 @@ import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; +import { isValidIdentifier, sanitizePrimer } from './nameUtils.ts'; /** * Inserted into bind group entry definitions that belong @@ -99,6 +101,10 @@ class ItemStateStackImpl implements ItemStateStack { return this._stack.findLast((e) => e.type === 'functionScope'); } + get topBlockScope(): BlockScopeLayer | undefined { + return this._stack.findLast((e) => e.type === 'blockScope'); + } + pushItem() { this._itemDepth++; this._stack.push({ @@ -136,6 +142,7 @@ class ItemStateStackImpl implements ItemStateStack { pushBlockScope() { this._stack.push({ type: 'blockScope', + takenLocalIdentifiers: new Set(), declarations: new Map(), externals: new Map(), }); @@ -213,6 +220,26 @@ class ItemStateStackImpl implements ItemStateStack { return undefined; } + isIdentifierTakenLocally(id: string): boolean { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + + if (layer?.type === 'functionScope') { + // Since functions cannot access resources from the calling scope, we + // return early here. + return false; + } + + if (layer?.type === 'blockScope') { + if (layer.takenLocalIdentifiers.has(id)) { + return true; + } + } + } + + return false; + } + defineBlockVariable(id: string, snippet: Snippet): void { if (snippet.dataType === UnknownData) { throw Error(`Tried to define variable '${id}' of unknown type`); @@ -375,6 +402,11 @@ export class ResolutionCtxImpl implements ResolutionCtx { public readonly enableExtensions: WgslExtension[] | undefined; public expectedType: BaseData | undefined; + /** + * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy. + */ + #lastUniqueId = 0; + constructor(opts: ResolutionCtxImplOptions) { this.enableExtensions = opts.enableExtensions; this.gen = opts.shaderGenerator ?? wgslGenerator; @@ -382,12 +414,42 @@ export class ResolutionCtxImpl implements ResolutionCtx { this.#namespaceInternal = opts.namespace[$internal]; } - getUniqueName(resource: object): string { - return getUniqueName(this.#namespaceInternal, resource); + isIdentifierTaken(name: string): boolean { + return ( + this.#namespaceInternal.takenGlobalIdentifiers.has(name) || + this._itemStateStack.isIdentifierTakenLocally(name) + ); } - makeNameValid(name: string): string { - return this.#namespaceInternal.nameRegistry.makeValid(name); + makeUniqueIdentifier(primer: string = 'item', scope: 'global' | 'block'): string { + if (scope === 'block' && isValidIdentifier(primer) && !this.isIdentifierTaken(primer)) { + // Preserving local definitions as they are, provided they are valid and not already taken. + this.reserveIdentifier(primer, 'block'); + return primer; + } + + const base = sanitizePrimer(primer); + let index = 0; + const random = this.#namespaceInternal.strategy === 'random'; + let name = random ? `${base}_${this.#lastUniqueId++}` : base; + while (this.isIdentifierTaken(name)) { + name = random ? `${base}_${this.#lastUniqueId++}` : `${base}_${++index}`; + } + + this.reserveIdentifier(name, scope); + return name; + } + + reserveIdentifier(name: string, scope: 'global' | 'block'): void { + if (scope === 'block') { + const blockScope = this._itemStateStack.topBlockScope; + if (blockScope) { + blockScope.takenLocalIdentifiers.add(name); + return; + } + // Fall through if no block scope is present, treating as global. + } + this.#namespaceInternal.takenGlobalIdentifiers.add(name); } get pre(): string { @@ -441,12 +503,10 @@ export class ResolutionCtxImpl implements ResolutionCtx { } pushBlockScope() { - this.#namespaceInternal.nameRegistry.pushBlockScope(); this._itemStateStack.pushBlockScope(); } popBlockScope() { - this.#namespaceInternal.nameRegistry.popBlockScope(); this._itemStateStack.pop('blockScope'); } @@ -467,19 +527,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { } fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { - let fnScopePushed = false; - try { - this.#namespaceInternal.nameRegistry.pushFunctionScope(); + const scope = this._itemStateStack.pushFunctionScope( + options.functionType, + {}, + options.returnType, + options.externalMap, + ); + // Pushing a block scope as well, so that any identifiers declared at this point will be scoped to the function body. + this._itemStateStack.pushBlockScope(); + const args: FunctionArgument[] = []; - const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; const structArg = dataSchema - ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + ? createArgument(this.makeUniqueIdentifier('_arg_0', 'block'), dataSchema) : undefined; if (structArg) { @@ -491,31 +556,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const arg = createArgument(this.makeNameValid(alias), argInfo.type); + const arg = createArgument(this.makeUniqueIdentifier(alias, 'block'), argInfo.type); args.push(arg); - argAccess[alias] = arg.access; + scope.argAccess[alias] = arg.access; } else if (structArg) { - argAccess[alias] = createArgumentPropAccess(structArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } const router = new EntryInputRouter(structArg?.access, proxyEntries); - argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); + scope.argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); - argAccess[argName] = arg.access; + scope.argAccess[argName] = arg.access; } } } else { @@ -538,16 +603,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + const arg = createArgument( + this.makeUniqueIdentifier(astParam.name, 'block'), + argType, + origin, + ); args.push(arg); - argAccess[astParam.name] = arg.access; + scope.argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + const objArg = createArgument( + this.makeUniqueIdentifier(`_arg_${i}`, 'block'), + argType, + origin, + ); args.push(objArg); for (const { name, alias } of astParam.props) { - argAccess[alias] = createArgumentPropAccess(objArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(objArg.access, name); } break; } @@ -557,7 +630,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { // have any properties anyway. if (!(argType instanceof AutoStruct)) { args.push({ - name: this.makeNameValid(`_arg_${i}`), + name: this.makeUniqueIdentifier(`_arg_${i}`, 'block'), access: () => { throw new Error( `Unreachable: Accessing an argument that wasn't named in the function signature`, @@ -572,14 +645,6 @@ export class ResolutionCtxImpl implements ResolutionCtx { } } - const scope = this._itemStateStack.pushFunctionScope( - options.functionType, - argAccess, - options.returnType, - options.externalMap, - ); - fnScopePushed = true; - let returnType: BaseData | undefined; const code = this.gen.functionDefinition({ @@ -642,10 +707,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { returnType, }; } finally { - if (fnScopePushed) { - this._itemStateStack.pop('functionScope'); - } - this.#namespaceInternal.nameRegistry.popFunctionScope(); + this._itemStateStack.pop('blockScope'); + this._itemStateStack.pop('functionScope'); } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 153ca5ed0b..e419ff5814 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -241,8 +241,12 @@ ${this.ctx.pre}}`; } } + public _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string { + return `${this.ctx.pre}${this._block(block, externalMap)}`; + } + public refVariable(id: string, dataType: wgsl.StorableData): string { - const varName = this.ctx.makeNameValid(id); + const varName = this.ctx.makeUniqueIdentifier(id, 'block'); const ptrType = ptrFn(dataType); const snippet = snip( new RefOperator(snip(varName, dataType, 'function'), ptrType), @@ -280,7 +284,11 @@ ${this.ctx.pre}}`; varOrigin = 'runtime'; } - const snippet = snip(this.ctx.makeNameValid(id), dataType, /* origin */ varOrigin); + const snippet = snip( + this.ctx.makeUniqueIdentifier(id, 'block'), + dataType, + /* origin */ varOrigin, + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -1034,7 +1042,7 @@ Try 'return ${typeStr}(${str});' instead. return this._statement(node); } // simplify 'if (true) {A} else {B}' to '{A}' - return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`; + return this._blockStatement(blockifySingleStatement(node)); } const consequent = this._block(blockifySingleStatement(consNode)); @@ -1167,7 +1175,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.block) { - return `${this.ctx.pre}${this._block(statement)}`; + return this._blockStatement(statement); } if (statement[0] === NODE.for) { @@ -1263,12 +1271,9 @@ ${this.ctx.pre}else ${alternate}`; const blocks = elements.map( (e, i) => - `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block( - blockified, - { - [originalLoopVarName]: e, - }, - )}`, + `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { + [originalLoopVarName]: e, + })}`, ); return blocks.join('\n'); @@ -1276,7 +1281,7 @@ ${this.ctx.pre}else ${alternate}`; this.#unrolling = false; - const index = this.ctx.makeNameValid('i'); + const index = this.ctx.makeUniqueIdentifier('i', 'block'); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`; @@ -1289,7 +1294,7 @@ ${this.ctx.pre}else ${alternate}`; } else { this.ctx.indent(); ctxIndent = true; - const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, snip(index, u32, 'runtime'), @@ -1303,7 +1308,7 @@ ${this.ctx.pre}else ${alternate}`; false, )};`; - bodyStr = `{\n${loopVarDeclStr}\n${this.ctx.pre}${this._block(blockified, { + bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin), })}\n`; this.ctx.dedent(); diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 75f9e71697..d2b7892faf 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -131,6 +131,7 @@ export type SlotBindingLayer = { export type BlockScopeLayer = { type: 'blockScope'; + takenLocalIdentifiers: Set; declarations: Map; externals: Map; }; @@ -140,6 +141,7 @@ export type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | Blo export interface ItemStateStack { readonly itemDepth: number; readonly topItem: ItemLayer; + readonly topBlockScope: BlockScopeLayer | undefined; readonly topFunctionScope: FunctionScopeLayer | undefined; pushItem(): void; @@ -330,8 +332,27 @@ export interface ResolutionCtx { */ withRenamed(item: object, name: string | undefined, callback: () => T): T; - getUniqueName(resource: object): string; - makeNameValid(name: string): string; + /** + * @param primer The basis for the unique identifier. Depending on the strategy, or + * the names already taken, this may be modified to ensure uniqueness. + * @param scope The scope in which to generate the identifier. 'global' means + * the identifier is meant to be unique across the entire program, while + * 'block' means it cannot shadow any existing identifiers visible from + * within the current block. After the block is popped, any identifiers + * defined within it are no longer visible. + * @returns an identifier that is unique within the given scope + */ + makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string; + + isIdentifierTaken(name: string): boolean; + + /** + * Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier} + * within the given scope. + * @param name The name to reserve + * @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter. + */ + reserveIdentifier(name: string, scope: 'global' | 'block'): void; } /** diff --git a/packages/typegpu/tests/namespace.test.ts b/packages/typegpu/tests/namespace.test.ts index ee6fe33099..7ac6407b43 100644 --- a/packages/typegpu/tests/namespace.test.ts +++ b/packages/typegpu/tests/namespace.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, vi } from 'vitest'; +import { describe, expect } from 'vitest'; import tgpu, { d } from '../src/index.js'; import { it } from 'typegpu-testing-utility'; @@ -71,34 +71,6 @@ describe('tgpu.namespace', () => { `); }); - it('fires "name" event', () => { - const Boid = d.struct({ - pos: d.vec3f, - }); - - const names = tgpu['~unstable'].namespace(); - - const listener = vi.fn((event) => {}); - names.on('name', listener); - - const code = tgpu.resolve([Boid], { names }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledWith({ name: 'Boid', target: Boid }); - - expect(code).toMatchInlineSnapshot(` - "struct Boid { - pos: vec3f, - }" - `); - - const code2 = tgpu.resolve([Boid], { names }); - - // No more events - expect(listener).toHaveBeenCalledTimes(1); - expect(code2).toMatchInlineSnapshot(`""`); - }); - it('handles name collision', () => { let code1: string, code2: string; const names = tgpu['~unstable'].namespace(); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 254730def2..83e8e1e787 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from 'vitest'; import tgpu, { d } from '../src/index.js'; -import { setName } from '../src/shared/meta.ts'; +import { getName, setName } from '../src/shared/meta.ts'; import { $gpuValueOf, $internal, $ownSnippet, $resolve } from '../src/shared/symbols.ts'; import type { ResolutionCtx } from '../src/types.ts'; import { it } from 'typegpu-testing-utility'; @@ -55,7 +55,7 @@ describe('tgpu resolve', () => { } as unknown as number, [$resolve](ctx: ResolutionCtx) { - const name = ctx.getUniqueName(this); + const name = ctx.makeUniqueIdentifier(getName(this), 'global'); ctx.addDeclaration(`@group(0) @binding(0) var ${name}: f32;`); return snip(name, d.f32, /* origin */ 'runtime'); }, From eb7008b142f5548754c6e42b1e017571005d0977 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:05:53 +0200 Subject: [PATCH 21/34] Update example tests --- .../individual-example-tests/3d-fish.test.ts | 50 +-- .../ascii-filter.test.ts | 14 +- .../individual-example-tests/blur.test.ts | 4 +- .../individual-example-tests/boids.test.ts | 8 +- .../box-raytracing.test.ts | 26 +- .../camera-thresholding.test.ts | 6 +- .../individual-example-tests/caustics.test.ts | 44 +-- .../chroma-keying.test.ts | 8 +- .../individual-example-tests/circles.test.ts | 8 +- .../individual-example-tests/clouds.test.ts | 32 +- .../individual-example-tests/confetti.test.ts | 2 +- .../cubemap-reflection.test.ts | 44 +-- .../individual-example-tests/disco.test.ts | 60 ++-- .../fluid-double-buffering.test.ts | 58 ++-- .../function-visualizer.test.ts | 34 +- .../game-of-life.test.ts | 36 +-- .../global-wind-map.test.ts | 98 +++--- .../individual-example-tests/gravity.test.ts | 24 +- .../image-tuning.test.ts | 24 +- .../jelly-slider.test.ts | 296 +++++++++--------- .../jelly-switch.test.ts | 182 +++++------ .../jump-flood-distance.test.ts | 64 ++-- .../jump-flood-voronoi.test.ts | 38 +-- .../lines-combinations.test.ts | 80 ++--- .../liquid-glass.test.ts | 28 +- .../individual-example-tests/log-test.test.ts | 12 +- .../individual-example-tests/oklab.test.ts | 18 +- .../perlin-noise.test.ts | 18 +- .../phong-reflection.test.ts | 20 +- .../point-light-shadow.test.ts | 42 +-- .../probability.test.ts | 12 +- .../ray-marching.test.ts | 60 ++-- .../ripple-cube.test.ts | 226 ++++++------- .../simple-shadow.test.ts | 34 +- .../slime-mold-3d.test.ts | 156 ++++----- .../slime-mold.test.ts | 88 +++--- .../smoky-triangle.test.ts | 16 +- .../individual-example-tests/square.test.ts | 2 +- .../stable-fluid.test.ts | 104 +++--- .../individual-example-tests/stencil.test.ts | 4 +- .../tgsl-parsing-test.test.ts | 24 +- .../two-boxes.test.ts | 2 +- .../uniformity.test.ts | 8 +- .../vaporrave.test.ts | 48 +-- .../wgsl-resolution.test.ts | 6 +- .../xor-dev-centrifuge-2.test.ts | 10 +- .../xor-dev-runner.test.ts | 14 +- 47 files changed, 1096 insertions(+), 1096 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts index 6430284a2e..3ff3be2bca 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts @@ -72,7 +72,7 @@ describe('3d fish example', () => { fn wrappedCallback(x: u32, _arg_1: u32, _arg_2: u32) { randSeed2(vec2f(f32(x), seedUniform)); - var data = ModelData(vec3f(((randFloat01() * 10f) - 5f), ((randFloat01() * 4f) - 2f), ((randFloat01() * 10f) - 5f)), vec3f(((randFloat01() * 0.1f) - 0.05f), ((randFloat01() * 0.1f) - 0.05f), ((randFloat01() * 0.1f) - 0.05f)), (0.07f * (1f + ((randFloat01() - 0.5f) * 0.8f))), randFloat01(), 1u, 1u, 1u); + let data = ModelData(vec3f(((randFloat01() * 10f) - 5f), ((randFloat01() * 4f) - 2f), ((randFloat01() * 10f) - 5f)), vec3f(((randFloat01() * 0.1f) - 0.05f), ((randFloat01() * 0.1f) - 0.05f), ((randFloat01() * 0.1f) - 0.05f)), (0.07f * (1f + ((randFloat01() - 0.5f) * 0.8f))), randFloat01(), 1u, 1u, 1u); fish_data_0[x] = data; fish_data_1[x] = data; } @@ -115,7 +115,7 @@ describe('3d fish example', () => { } fn projectPointOnLine(point: vec3f, line: Line3) -> vec3f { - var pointVector = (point - line.origin); + let pointVector = (point - line.origin); let projection = dot(pointVector, line.dir); return (line.origin + (line.dir * projection)); } @@ -207,8 +207,8 @@ describe('3d fish example', () => { wallRepulsion = (wallRepulsion + (repulsion * str)); } } - var proj = projectPointOnLine((*fishData).position, mouseRay); - var diff = ((*fishData).position - proj); + let proj = projectPointOnLine((*fishData).position, mouseRay); + let diff = ((*fishData).position - proj); const limit = 1.2; let str = (pow(2f, clamp((limit - length(diff)), 0f, limit)) - 1f); rayRepulsion = (normalize(diff) * str); @@ -219,7 +219,7 @@ describe('3d fish example', () => { direction += (wallRepulsion * 1e-4f); direction += (rayRepulsion * 0.0015f); direction = (normalize(direction) * clamp(length((*fishData).direction), 0f, 0.01f)); - var translation = (direction * (min(999f, timePassed) / 8f)); + let translation = (direction * (min(999f, timePassed) / 8f)); let nextFishData_1 = (&nextFishData[fishIndex]); (*nextFishData_1).position = ((*fishData).position + translation); (*nextFishData_1).direction = direction; @@ -256,11 +256,11 @@ describe('3d fish example', () => { var posMod = vec3f(); posMod.z = (sin((f32(index) + (((time / a) + vertex.position.x) / b))) / c); let coeff = (cos((f32(index) + (((time / a) + vertex.position.x) / b))) / c); - var newOX = normalize(vec3f(1f, 0f, coeff)); - var newOZ = vec3f(-(newOX.z), 0f, newOX.x); - var newNormalXZ = ((newOX * vertex.normal.x) + (newOZ * vertex.normal.z)); - var wavedNormal = vec3f(newNormalXZ.x, vertex.normal.y, newNormalXZ.z); - var wavedPosition = (vertex.position + posMod); + let newOX = normalize(vec3f(1f, 0f, coeff)); + let newOZ = vec3f(-(newOX.z), 0f, newOX.x); + let newNormalXZ = ((newOX * vertex.normal.x) + (newOZ * vertex.normal.z)); + let wavedNormal = vec3f(newNormalXZ.x, vertex.normal.y, newNormalXZ.z); + let wavedPosition = (vertex.position + posMod); return PosAndNormal(wavedPosition, wavedNormal); } @@ -291,17 +291,17 @@ describe('3d fish example', () => { if (((*currentModelData).applySinWave == 1u)) { wavedVertex = applySinWave(_arg_instanceIndex, PosAndNormal(_arg_modelPosition, _arg_modelNormal), currentTime); } - var direction = normalize((*currentModelData).direction); + let direction = normalize((*currentModelData).direction); let yaw = (-(atan2(direction.z, direction.x)) + 3.141592653589793f); let pitch = asin(-(direction.y)); - var scaleMatrix = mat4x4f(vec3f((*currentModelData).scale).x, 0, 0, 0, 0, vec3f((*currentModelData).scale).y, 0, 0, 0, 0, vec3f((*currentModelData).scale).z, 0, 0, 0, 0, 1); - var pitchMatrix = mat4x4f(cos(pitch), sin(pitch), 0, 0, -sin(pitch), cos(pitch), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - var yawMatrix = mat4x4f(cos(yaw), 0, -sin(yaw), 0, 0, 1, 0, 0, sin(yaw), 0, cos(yaw), 0, 0, 0, 0, 1); - var translationMatrix = mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, (*currentModelData).position.x, (*currentModelData).position.y, (*currentModelData).position.z, 1); + let scaleMatrix = mat4x4f(vec3f((*currentModelData).scale).x, 0, 0, 0, 0, vec3f((*currentModelData).scale).y, 0, 0, 0, 0, vec3f((*currentModelData).scale).z, 0, 0, 0, 0, 1); + let pitchMatrix = mat4x4f(cos(pitch), sin(pitch), 0, 0, -sin(pitch), cos(pitch), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + let yawMatrix = mat4x4f(cos(yaw), 0, -sin(yaw), 0, 0, 1, 0, 0, sin(yaw), 0, cos(yaw), 0, 0, 0, 0, 1); + let translationMatrix = mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, (*currentModelData).position.x, (*currentModelData).position.y, (*currentModelData).position.z, 1); var worldPosition = ((((translationMatrix * yawMatrix) * pitchMatrix) * scaleMatrix) * vec4f(wavedVertex.position, 1f)); - var worldNormal = normalize(((yawMatrix * pitchMatrix) * vec4f(wavedVertex.normal, 1f)).xyz); + let worldNormal = normalize(((yawMatrix * pitchMatrix) * vec4f(wavedVertex.normal, 1f)).xyz); let worldPositionUniform = (&worldPosition); - var canvasPosition = ((camera.projection * camera.view) * (*worldPositionUniform)); + let canvasPosition = ((camera.projection * camera.view) * (*worldPositionUniform)); return vertexShader_Output(worldPosition.xyz, worldNormal, canvasPosition, (*currentModelData).variant, _arg_textureUV, (*currentModelData).applySeaFog, (*currentModelData).applySeaDesaturation); } @@ -419,16 +419,16 @@ describe('3d fish example', () => { } @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { - var textureColorWithAlpha = textureSample(modelTexture, sampler_1, _arg_0.textureUV); - var textureColor = textureColorWithAlpha.rgb; - var ambient = ((0.5f * textureColor) * vec3f(0.800000011920929, 0.800000011920929, 1)); + let textureColorWithAlpha = textureSample(modelTexture, sampler_1, _arg_0.textureUV); + let textureColor = textureColorWithAlpha.rgb; + let ambient = ((0.5f * textureColor) * vec3f(0.800000011920929, 0.800000011920929, 1)); let cosTheta = dot(_arg_0.worldNormal, vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)); - var diffuse = ((max(0f, cosTheta) * textureColor) * vec3f(0.800000011920929, 0.800000011920929, 1)); - var viewSource = normalize((camera.position.xyz - _arg_0.worldPosition)); - var reflectSource = normalize(reflect(vec3f(0.2357022613286972, -0.9428090453147888, 0.2357022613286972), _arg_0.worldNormal)); + let diffuse = ((max(0f, cosTheta) * textureColor) * vec3f(0.800000011920929, 0.800000011920929, 1)); + let viewSource = normalize((camera.position.xyz - _arg_0.worldPosition)); + let reflectSource = normalize(reflect(vec3f(0.2357022613286972, -0.9428090453147888, 0.2357022613286972), _arg_0.worldNormal)); let specularStrength = pow(max(0f, dot(viewSource, reflectSource)), 16f); - var specular = (specularStrength * vec3f(0.800000011920929, 0.800000011920929, 1)); - var lightedColor = ((ambient + diffuse) + specular); + let specular = (specularStrength * vec3f(0.800000011920929, 0.800000011920929, 1)); + let lightedColor = ((ambient + diffuse) + specular); let distanceFromCamera = length((camera.position.xyz - _arg_0.worldPosition)); var desaturatedColor = lightedColor; if ((_arg_0.applySeaDesaturation == 1u)) { diff --git a/apps/typegpu-docs/tests/individual-example-tests/ascii-filter.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ascii-filter.test.ts index d9748aa2db..85ede7a576 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ascii-filter.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ascii-filter.test.ts @@ -45,7 +45,7 @@ describe('ascii filter example', () => { @group(0) @binding(4) var charsetExtended: u32; fn characterFn(n: u32, p: vec2f) -> f32 { - var pos = floor(((p * vec2f(-4, 4)) + 2.5f)); + let pos = floor(((p * vec2f(-4, 4)) + 2.5f)); if (((((pos.x < 0f) || (pos.x > 4f)) || (pos.y < 0f)) || (pos.y > 4f))) { return 0f; } @@ -60,13 +60,13 @@ describe('ascii filter example', () => { } @fragment fn fragment(_arg_0: FragmentIn) -> @location(0) vec4f { - var uv2 = ((uvTransformBuffer * (_arg_0.uv - 0.5f)) + 0.5f); - var textureSize = vec2f(textureDimensions(externalTexture)); - var pix = (uv2 * textureSize); + let uv2 = ((uvTransformBuffer * (_arg_0.uv - 0.5f)) + 0.5f); + let textureSize = vec2f(textureDimensions(externalTexture)); + let pix = (uv2 * textureSize); let cellSize = f32(glyphSize); let halfCell = (cellSize * 0.5f); - var blockCoord = ((floor((pix / cellSize)) * cellSize) / textureSize); - var color = textureSampleBaseClampToEdge(externalTexture, shaderSampler, blockCoord); + let blockCoord = ((floor((pix / cellSize)) * cellSize) / textureSize); + let color = textureSampleBaseClampToEdge(externalTexture, shaderSampler, blockCoord); let rawGray = (((0.3f * color.x) + (0.59f * color.y)) + (0.11f * color.z)); let gray = pow(rawGray, gammaCorrection); var n = 4096u; @@ -221,7 +221,7 @@ describe('ascii filter example', () => { n = 11512810u; } } - var p = vec2f((((pix.x / halfCell) % 2f) - 1f), (((pix.y / halfCell) % 2f) - 1f)); + let p = vec2f((((pix.x / halfCell) % 2f) - 1f), (((pix.y / halfCell) % 2f) - 1f)); let charValue = characterFn(n, p); var resultColor = vec3f(1); if ((displayMode == 0u)) { diff --git a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts index 7f81d96515..6e9617f7c7 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts @@ -45,8 +45,8 @@ describe('blur example', () => { @compute @workgroup_size(32, 1, 1) fn computeFn(@builtin(workgroup_id) wid: vec3u, @builtin(local_invocation_id) lid: vec3u) { let settings = (&settingsUniform); let filterOffset = i32((f32(((*settings).filterDim - 1i)) / 2f)); - var dims = vec2i(textureDimensions(inTexture)); - var baseIndex = (vec2i(((wid.xy * vec2u((*settings).blockDim, 4u)) + (lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0i)); + let dims = vec2i(textureDimensions(inTexture)); + let baseIndex = (vec2i(((wid.xy * vec2u((*settings).blockDim, 4u)) + (lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0i)); // unrolled iteration #0 { // unrolled iteration #0 diff --git a/apps/typegpu-docs/tests/individual-example-tests/boids.test.ts b/apps/typegpu-docs/tests/individual-example-tests/boids.test.ts index 67d0886c76..410c70f767 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/boids.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/boids.test.ts @@ -73,7 +73,7 @@ describe('boids example', () => { cohesion /= f32(cohesionCount); cohesion -= self_1.position; } - var velocity = (((paramsBuffer.separationStrength * separation) + (paramsBuffer.alignmentStrength * alignment)) + (paramsBuffer.cohesionStrength * cohesion)); + let velocity = (((paramsBuffer.separationStrength * separation) + (paramsBuffer.alignmentStrength * alignment)) + (paramsBuffer.cohesionStrength * cohesion)); self_1.velocity += velocity; self_1.velocity = (clamp(length(self_1.velocity), 0f, 0.01f) * normalize(self_1.velocity)); self_1.position += self_1.velocity; @@ -108,9 +108,9 @@ describe('boids example', () => { @vertex fn mainVert(@location(0) _arg_v: vec2f, @location(1) _arg_center: vec2f, @location(2) _arg_velocity: vec2f) -> mainVert_Output { let angle = getRotationFromVelocity(_arg_velocity); - var rotated = rotate(_arg_v, angle); - var pos = vec4f((rotated + _arg_center), 0f, 1f); - var color = vec4f(((sin((colorPalette + angle)) * 0.45f) + 0.45f), 1f); + let rotated = rotate(_arg_v, angle); + let pos = vec4f((rotated + _arg_center), 0f, 1f); + let color = vec4f(((sin((colorPalette + angle)) * 0.45f) + 0.45f), 1f); return mainVert_Output(pos, color); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/box-raytracing.test.ts b/apps/typegpu-docs/tests/individual-example-tests/box-raytracing.test.ts index 838cdac465..915c2723e0 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/box-raytracing.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/box-raytracing.test.ts @@ -39,8 +39,8 @@ describe('box raytracing example', () => { } @vertex fn mainVertex(_arg_0: VertexIn) -> VertexOut { - var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); - var rayWorldOrigin = (uniforms.invViewMatrix * vec4f(0, 0, 0, 1)).xyz; + let pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); + let rayWorldOrigin = (uniforms.invViewMatrix * vec4f(0, 0, 0, 1)).xyz; return VertexOut(vec4f(pos[_arg_0.vertexIndex], 0f, 1f), rayWorldOrigin); } @@ -136,13 +136,13 @@ describe('box raytracing example', () => { } @fragment fn fragmentFunction(_arg_0: FragmentIn) -> @location(0) vec4f { - var boxSize3 = vec3f(uniforms.boxSize); - var halfBoxSize3 = (0.5f * boxSize3); - var halfCanvasDims = (0.5f * uniforms.canvasDims); + let boxSize3 = vec3f(uniforms.boxSize); + let halfBoxSize3 = (0.5f * boxSize3); + let halfCanvasDims = (0.5f * uniforms.canvasDims); let minDim = min(uniforms.canvasDims.x, uniforms.canvasDims.y); - var viewCoords = ((_arg_0.position.xy - halfCanvasDims) / minDim); - var ray = Ray(_arg_0.rayWorldOrigin, (uniforms.invViewMatrix * vec4f(normalize(vec3f(viewCoords, 1f)), 0f)).xyz); - var bigBoxIntersection = getBoxIntersection(AxisAlignedBounds((-1f * halfBoxSize3), (vec3f(7) + halfBoxSize3)), ray); + let viewCoords = ((_arg_0.position.xy - halfCanvasDims) / minDim); + let ray = Ray(_arg_0.rayWorldOrigin, (uniforms.invViewMatrix * vec4f(normalize(vec3f(viewCoords, 1f)), 0f)).xyz); + let bigBoxIntersection = getBoxIntersection(AxisAlignedBounds((-1f * halfBoxSize3), (vec3f(7) + halfBoxSize3)), ray); if (!bigBoxIntersection.intersects) { discard;; return vec4f(); @@ -156,8 +156,8 @@ describe('box raytracing example', () => { if ((boxMatrix[i][j][k].isActive == 0u)) { continue; } - var ijkScaled = vec3f(f32(i), f32(j), f32(k)); - var intersection = getBoxIntersection(AxisAlignedBounds((ijkScaled - halfBoxSize3), (ijkScaled + halfBoxSize3)), ray); + let ijkScaled = vec3f(f32(i), f32(j), f32(k)); + let intersection = getBoxIntersection(AxisAlignedBounds((ijkScaled - halfBoxSize3), (ijkScaled + halfBoxSize3)), ray); if (intersection.intersects) { let boxDensity = (max(0f, (intersection.tMax - intersection.tMin)) * pow(uniforms.materialDensity, 2f)); density += boxDensity; @@ -167,10 +167,10 @@ describe('box raytracing example', () => { } } } - var linear = (1f / invColor); - var srgb = linearToSrgb(linear); + let linear = (1f / invColor); + let srgb = linearToSrgb(linear); const gamma = 2.2; - var corrected = pow(srgb, vec3f((1f / gamma))); + let corrected = pow(srgb, vec3f((1f / gamma))); if (intersectionFound) { return (min(density, 1f) * vec4f(min(corrected, vec3f(1)), 1f)); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts index 783bb59558..349dc06379 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts @@ -49,10 +49,10 @@ describe('camera thresholding example', () => { } @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { - var uv2 = ((uvTransformUniform * (_arg_0.uv - 0.5f)) + 0.5f); + let uv2 = ((uvTransformUniform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); - var ycbcr = (col.rgb * rgbToYcbcrMatrix); - var colycbcr = (colorUniform * rgbToYcbcrMatrix); + let ycbcr = (col.rgb * rgbToYcbcrMatrix); + let colycbcr = (colorUniform * rgbToYcbcrMatrix); let crDiff = abs((ycbcr.y - colycbcr.y)); let cbDiff = abs((ycbcr.z - colycbcr.z)); let distance_1 = length(vec2f(crDiff, cbDiff)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts index 9d011f7a74..ea519c5db7 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts @@ -26,16 +26,16 @@ describe('caustics example', () => { } @vertex fn mainVertex(@builtin(vertex_index) vertexIndex: u32) -> mainVertex_Output { - var pos = array(vec2f(0, 0.800000011920929), vec2f(-0.800000011920929), vec2f(0.800000011920929, -0.800000011920929)); - var uv = array(vec2f(0.5, 1), vec2f(), vec2f(1, 0)); + let pos = array(vec2f(0, 0.800000011920929), vec2f(-0.800000011920929), vec2f(0.800000011920929, -0.800000011920929)); + let uv = array(vec2f(0.5, 1), vec2f(), vec2f(1, 0)); return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } @group(0) @binding(0) var tileDensity: f32; fn tilePattern(uv: vec2f) -> f32 { - var tiledUv = fract(uv); - var proximity = abs(((tiledUv * 2f) - 1f)); + let tiledUv = fract(uv); + let proximity = abs(((tiledUv * 2f) - 1f)); let maxProximity = max(proximity.x, proximity.y); return saturate((pow((1f - maxProximity), 0.6f) * 5f)); } @@ -75,8 +75,8 @@ describe('caustics example', () => { } fn dotProdGrid(pos: vec3f, junction: vec3f) -> f32 { - var relative = (pos - junction); - var gridVector = computeJunctionGradient(vec3i(junction)); + let relative = (pos - junction); + let gridVector = computeJunctionGradient(vec3i(junction)); return dot(relative, gridVector); } @@ -85,7 +85,7 @@ describe('caustics example', () => { } fn sample(pos: vec3f) -> f32 { - var minJunction = floor(pos); + let minJunction = floor(pos); let xyz = dotProdGrid(pos, minJunction); let xyZ = dotProdGrid(pos, (minJunction + vec3f(0, 0, 1))); let xYz = dotProdGrid(pos, (minJunction + vec3f(0, 1, 0))); @@ -94,8 +94,8 @@ describe('caustics example', () => { let XyZ = dotProdGrid(pos, (minJunction + vec3f(1, 0, 1))); let XYz = dotProdGrid(pos, (minJunction + vec3f(1, 1, 0))); let XYZ = dotProdGrid(pos, (minJunction + vec3f(1))); - var partial = (pos - minJunction); - var smoothPartial = quinticInterpolation(partial); + let partial = (pos - minJunction); + let smoothPartial = quinticInterpolation(partial); let xy = mix(xyz, xyZ, smoothPartial.z); let xY = mix(xYz, xYZ, smoothPartial.z); let Xy = mix(Xyz, XyZ, smoothPartial.z); @@ -107,7 +107,7 @@ describe('caustics example', () => { fn caustics(uv: vec2f, time_1: f32, profile: vec3f) -> vec3f { let distortion = sample(vec3f((uv * 0.5f), (time_1 * 0.2f))); - var uv2 = (uv + distortion); + let uv2 = (uv + distortion); let noise = abs(sample(vec3f((uv2 * 5f), time_1))); return pow(vec3f((1f - noise)), profile); } @@ -121,22 +121,22 @@ describe('caustics example', () => { } @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { - var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f((-1.9866933079506122f + (_arg_0.uv.x * 3f)), 4.900332889206208f)); - var skewedUv = (skewMat * _arg_0.uv); + let skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f((-1.9866933079506122f + (_arg_0.uv.x * 3f)), 4.900332889206208f)); + let skewedUv = (skewMat * _arg_0.uv); let tile = tilePattern((skewedUv * tileDensity)); - var albedo = mix(vec3f(0.10000000149011612), vec3f(1), tile); - var cuv = vec2f(((_arg_0.uv.x * (pow((_arg_0.uv.y * 1.5f), 3f) + 0.1f)) * 5f), (pow((((_arg_0.uv.y * 1.5f) + 0.1f) * 1.5f), 3f) * 1f)); - var c1 = (caustics(cuv, (time * 0.2f), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); - var c2 = (caustics((cuv * 2f), (time * 0.4f), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); - var blendCoord = vec3f((_arg_0.uv * vec2f(5, 10)), ((time * 0.2f) + 5f)); + let albedo = mix(vec3f(0.10000000149011612), vec3f(1), tile); + let cuv = vec2f(((_arg_0.uv.x * (pow((_arg_0.uv.y * 1.5f), 3f) + 0.1f)) * 5f), (pow((((_arg_0.uv.y * 1.5f) + 0.1f) * 1.5f), 3f) * 1f)); + let c1 = (caustics(cuv, (time * 0.2f), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); + let c2 = (caustics((cuv * 2f), (time * 0.4f), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); + let blendCoord = vec3f((_arg_0.uv * vec2f(5, 10)), ((time * 0.2f) + 5f)); let blend = saturate((sample(blendCoord) + 0.3f)); - var noFogColor = (albedo * mix(vec3f(0.20000000298023224, 0.5, 1), (c1 + c2), blend)); + let noFogColor = (albedo * mix(vec3f(0.20000000298023224, 0.5, 1), (c1 + c2), blend)); let fog = min((pow(_arg_0.uv.y, 0.5f) * 1.2f), 1f); - var godRayUv = ((rotateXY(-0.3f) * _arg_0.uv) * vec2f(15, 3)); + let godRayUv = ((rotateXY(-0.3f) * _arg_0.uv) * vec2f(15, 3)); let godRayFactor = _arg_0.uv.y; - var godRay1 = (((sample(vec3f(godRayUv, (time * 0.5f))) + 1f) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)) * godRayFactor); - var godRay2 = ((((sample(vec3f((godRayUv * 2f), (time * 0.3f))) + 1f) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)) * godRayFactor) * 0.4f); - var godRays = (godRay1 + godRay2); + let godRay1 = (((sample(vec3f(godRayUv, (time * 0.5f))) + 1f) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)) * godRayFactor); + let godRay2 = ((((sample(vec3f((godRayUv * 2f), (time * 0.3f))) + 1f) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)) * godRayFactor) * 0.4f); + let godRays = (godRay1 + godRay2); return vec4f((mix(noFogColor, vec3f(0.05000000074505806, 0.20000000298023224, 0.699999988079071), fog) + godRays), 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts index 60175ac939..d48c80f5b0 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts @@ -49,10 +49,10 @@ describe('chroma keying example', () => { } @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { - var uv2 = ((uvTransform * (_arg_0.uv - 0.5f)) + 0.5f); - var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); - var ycbcr = (col.rgb * rgbToYcbcrMatrix); - var colycbcr = (color * rgbToYcbcrMatrix); + let uv2 = ((uvTransform * (_arg_0.uv - 0.5f)) + 0.5f); + let col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); + let ycbcr = (col.rgb * rgbToYcbcrMatrix); + let colycbcr = (color * rgbToYcbcrMatrix); let crDiff = abs((ycbcr.y - colycbcr.y)); let cbDiff = abs((ycbcr.z - colycbcr.z)); let distance_1 = length(vec2f(crDiff, cbDiff)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/circles.test.ts b/apps/typegpu-docs/tests/individual-example-tests/circles.test.ts index 4a527d3dca..f9c7e9e239 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/circles.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/circles.test.ts @@ -58,7 +58,7 @@ describe('circles example', () => { const PI: f32 = 3.141592653589793f; fn circle(vertexIndex: u32) -> vec2f { - var subdiv = getSubdivLevel(vertexIndex); + let subdiv = getSubdivLevel(vertexIndex); let i = consecutiveTriangleVertexIndex(subdiv.vertexIndexInLevel); let pointCount = subdiv.pointCount; let angle = (((2f * PI) * f32(i)) / f32(pointCount)); @@ -73,8 +73,8 @@ describe('circles example', () => { @vertex fn mainVertexMaxArea(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> mainVertexMaxArea_Output { let C = (&circles[instanceIndex]); - var unit = circle(vertexIndex); - var pos = ((*C).position + (unit * (*C).radius)); + let unit = circle(vertexIndex); + let pos = ((*C).position + (unit * (*C).radius)); return mainVertexMaxArea_Output(vec4f(pos, 0f, 1f), unit, instanceIndex); } @@ -84,7 +84,7 @@ describe('circles example', () => { } @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { - var color = vec3f(1f, cos(f32(_arg_0.instanceIndex)), sin((5f * f32(_arg_0.instanceIndex)))); + let color = vec3f(1f, cos(f32(_arg_0.instanceIndex)), sin((5f * f32(_arg_0.instanceIndex)))); let r = length(_arg_0.uv); return vec4f(mix(color, vec3f(), clamp(((r - 0.9f) * 20f), 0f, 0.5f)), 1f); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/clouds.test.ts b/apps/typegpu-docs/tests/individual-example-tests/clouds.test.ts index 870effa75b..d7da120837 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/clouds.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/clouds.test.ts @@ -71,11 +71,11 @@ describe('clouds example', () => { @group(1) @binding(2) var sampler_1: sampler; fn noise3d(pos: vec3f) -> f32 { - var idx = floor(pos); - var frac = fract(pos); - var smooth_1 = ((frac * frac) * (3f - (2f * frac))); - var texCoord0 = fract((((idx.xy + frac.xy) + (vec2f(37, 239) * idx.z)) / 256f)); - var texCoord1 = fract((((idx.xy + frac.xy) + (vec2f(37, 239) * (idx.z + 1f))) / 256f)); + let idx = floor(pos); + let frac = fract(pos); + let smooth_1 = ((frac * frac) * (3f - (2f * frac))); + let texCoord0 = fract((((idx.xy + frac.xy) + (vec2f(37, 239) * idx.z)) / 256f)); + let texCoord1 = fract((((idx.xy + frac.xy) + (vec2f(37, 239) * (idx.z + 1f))) / 256f)); let val0 = textureSampleLevel(noiseTexture, sampler_1, texCoord0, 0).x; let val1 = textureSampleLevel(noiseTexture, sampler_1, texCoord1, 0).x; return ((mix(val0, val1, smooth_1.z) * 2f) - 1f); @@ -116,17 +116,17 @@ describe('clouds example', () => { let stepSize = (1f / f32(maxSteps)); var dist = (randFloat01() * stepSize); for (var i = 0; (i < maxSteps); i++) { - var samplePos = (rayOrigin + ((rayDir * dist) * maxDepth)); + let samplePos = (rayOrigin + ((rayDir * dist) * maxDepth)); let cloudDensity = sampleDensity(samplePos); if ((cloudDensity > 0f)) { - var shadowPos = (samplePos + sunDir); + let shadowPos = (samplePos + sunDir); let shadowDensity = sampleDensityCheap(shadowPos); let shadow = saturate((cloudDensity - shadowDensity)); let lightVal = mix(0.3f, 1f, shadow); - var light = (vec3f(0.6600000262260437, 0.4949999749660492, 0.824999988079071) + ((vec3f(1, 0.699999988079071, 0.30000001192092896) * lightVal) * 0.9f)); - var color = mix(vec3f(1), vec3f(0.20000000298023224), cloudDensity); - var lit = (color * light); - var contrib = ((vec4f(lit, 1f) * cloudDensity) * (0.88f - accum.a)); + let light = (vec3f(0.6600000262260437, 0.4949999749660492, 0.824999988079071) + ((vec3f(1, 0.699999988079071, 0.30000001192092896) * lightVal) * 0.9f)); + let color = mix(vec3f(1), vec3f(0.20000000298023224), cloudDensity); + let lit = (color * light); + let contrib = ((vec4f(lit, 1f) * cloudDensity) * (0.88f - accum.a)); accum += contrib; if ((accum.a >= 0.879f)) { break; @@ -147,16 +147,16 @@ describe('clouds example', () => { let aspect = ((*screenRes).x / (*screenRes).y); var screenPos = ((_arg_0.uv - 0.5f) * 2f); screenPos = vec2f((screenPos.x * max(aspect, 1f)), (screenPos.y * max((1f / aspect), 1f))); - var sunDir = vec3f(1, 0, 0); + let sunDir = vec3f(1, 0, 0); let time = params.time; - var rayOrigin = vec3f((sin((time * 0.6f)) * 0.5f), ((cos((time * 0.8f)) * 0.5f) - 1f), (time * 1f)); - var rayDir = normalize(vec3f(screenPos.x, screenPos.y, 1f)); + let rayOrigin = vec3f((sin((time * 0.6f)) * 0.5f), ((cos((time * 0.8f)) * 0.5f) - 1f), (time * 1f)); + let rayDir = normalize(vec3f(screenPos.x, screenPos.y, 1f)); let sunDot = saturate(dot(rayDir, sunDir)); let sunGlow = pow(sunDot, 1.371742112482853f); var skyCol = (vec3f(0.75, 0.6600000262260437, 0.8999999761581421) - ((vec3f(1, 0.699999988079071, 0.4300000071525574) * rayDir.y) * 0.35f)); skyCol += (vec3f(1, 0.3700000047683716, 0.17000000178813934) * sunGlow); - var cloudCol = raymarch(rayOrigin, rayDir, sunDir); - var finalCol = ((skyCol * (1.1f - cloudCol.a)) + cloudCol.rgb); + let cloudCol = raymarch(rayOrigin, rayDir, sunDir); + let finalCol = ((skyCol * (1.1f - cloudCol.a)) + cloudCol.rgb); return vec4f(finalCol, 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts index 921721055b..4dfdef4775 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts @@ -68,7 +68,7 @@ describe('confetti example', () => { @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { let width = (_arg_0.tilt / 350f); let height = (width / 2f); - var local = array(vec2f(), vec2f(width, 0f), vec2f(0f, height), vec2f(width, height)); + let local = array(vec2f(), vec2f(width, 0f), vec2f(0f, height), vec2f(width, height)); var pos = (rotate(local[_arg_0.vertexIndex], _arg_0.angle) + _arg_0.center); if ((aspectRatio < 1f)) { pos.x /= aspectRatio; diff --git a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts index fe28f8827d..4bfd0d95b1 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts @@ -42,8 +42,8 @@ describe('cubemap reflection example', () => { @group(0) @binding(2) var smoothFlag_1: u32; fn unpackVec2u(packed: vec2u) -> vec4f { - var xy = unpack2x16float(packed.x); - var zw = unpack2x16float(packed.y); + let xy = unpack2x16float(packed.x); + let zw = unpack2x16float(packed.y); return vec4f(xy, zw); } @@ -52,8 +52,8 @@ describe('cubemap reflection example', () => { } fn getAverageNormal(v1: vec4f, v2: vec4f, v3: vec4f) -> vec4f { - var edge1 = (v2.xyz - v1.xyz); - var edge2 = (v3.xyz - v1.xyz); + let edge1 = (v2.xyz - v1.xyz); + let edge2 = (v3.xyz - v1.xyz); return normalize(vec4f(cross(edge1, edge2), 0f)); } @@ -73,12 +73,12 @@ describe('cubemap reflection example', () => { return; } let baseIndexPrev = (triangleIndex * 3u); - var v1 = unpackVec2u((*prevVertices)[baseIndexPrev].position); - var v2 = unpackVec2u((*prevVertices)[(baseIndexPrev + 1u)].position); - var v3 = unpackVec2u((*prevVertices)[(baseIndexPrev + 2u)].position); - var v12 = vec4f(normalize(calculateMidpoint(v1, v2).xyz), 1f); - var v23 = vec4f(normalize(calculateMidpoint(v2, v3).xyz), 1f); - var v31 = vec4f(normalize(calculateMidpoint(v3, v1).xyz), 1f); + let v1 = unpackVec2u((*prevVertices)[baseIndexPrev].position); + let v2 = unpackVec2u((*prevVertices)[(baseIndexPrev + 1u)].position); + let v3 = unpackVec2u((*prevVertices)[(baseIndexPrev + 2u)].position); + let v12 = vec4f(normalize(calculateMidpoint(v1, v2).xyz), 1f); + let v23 = vec4f(normalize(calculateMidpoint(v2, v3).xyz), 1f); + let 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 @@ -253,7 +253,7 @@ describe('cubemap reflection example', () => { } @vertex fn cubeVertexFn(@location(0) _arg_position: vec3f) -> cubeVertexFn_Output { - var viewPos = (camera.view * vec4f(_arg_position.xyz, 0f)).xyz; + let viewPos = (camera.view * vec4f(_arg_position.xyz, 0f)).xyz; return cubeVertexFn_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } @@ -315,19 +315,19 @@ describe('cubemap reflection example', () => { } @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { - var normalizedNormal = normalize(_arg_0.normal.xyz); - var normalizedLightDir = normalize(light.direction); - var ambientLight = ((material.ambient * light.color) * light.intensity); + let normalizedNormal = normalize(_arg_0.normal.xyz); + let normalizedLightDir = normalize(light.direction); + let ambientLight = ((material.ambient * light.color) * light.intensity); let diffuseFactor = max(dot(normalizedNormal, normalizedLightDir), 0f); - var diffuseLight = (((material.diffuse * light.color) * light.intensity) * diffuseFactor); - var viewDirection = normalize((camera.position.xyz - _arg_0.worldPos.xyz)); - var reflectionDirection = reflect(-(normalizedLightDir), normalizedNormal); + let diffuseLight = (((material.diffuse * light.color) * light.intensity) * diffuseFactor); + let viewDirection = normalize((camera.position.xyz - _arg_0.worldPos.xyz)); + let reflectionDirection = reflect(-(normalizedLightDir), normalizedNormal); let specularFactor = pow(max(dot(viewDirection, reflectionDirection), 0f), material.shininess); - var specularLight = (((material.specular * light.color) * light.intensity) * specularFactor); - var reflectionVector = reflect(-(viewDirection), normalizedNormal); - var environmentColor = textureSample(cubemap, texSampler, reflectionVector); - var directLighting = (ambientLight + (diffuseLight + specularLight)); - var finalColor = mix(directLighting, environmentColor.rgb, material.reflectivity); + let specularLight = (((material.specular * light.color) * light.intensity) * specularFactor); + let reflectionVector = reflect(-(viewDirection), normalizedNormal); + let environmentColor = textureSample(cubemap, texSampler, reflectionVector); + let directLighting = (ambientLight + (diffuseLight + specularLight)); + let finalColor = mix(directLighting, environmentColor.rgb, material.reflectivity); return vec4f(finalColor, 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts index ba6d090535..a6a8f7f785 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts @@ -27,8 +27,8 @@ describe('disco example', () => { } @vertex fn mainVertex(@builtin(vertex_index) vertexIndex: u32) -> mainVertex_Output { - var pos = array(vec2f(-1, 1), vec2f(-1), vec2f(1, -1), vec2f(-1, 1), vec2f(1, -1), vec2f(1)); - var uv = array(vec2f(0, 1), vec2f(), vec2f(1, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1)); + let pos = array(vec2f(-1, 1), vec2f(-1), vec2f(1, -1), vec2f(-1, 1), vec2f(1, -1), vec2f(1)); + let uv = array(vec2f(0, 1), vec2f(), vec2f(1, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1)); return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } @@ -49,11 +49,11 @@ describe('disco example', () => { @group(0) @binding(1) var time: f32; fn palette(t: f32) -> vec3f { - var a = vec3f(0.5, 0.5899999737739563, 0.8500000238418579); - var b = vec3f(0.18000000715255737, 0.41999998688697815, 0.4000000059604645); - var c = vec3f(0.18000000715255737, 0.47999998927116394, 0.4099999964237213); - var e = vec3f(0.3499999940395355, 0.12999999523162842, 0.3199999928474426); - var expr = cos((6.28318f * ((c * t) + e))); + let a = vec3f(0.5, 0.5899999737739563, 0.8500000238418579); + let b = vec3f(0.18000000715255737, 0.41999998688697815, 0.4000000059604645); + let c = vec3f(0.18000000715255737, 0.47999998927116394, 0.4099999964237213); + let e = vec3f(0.3499999940395355, 0.12999999523162842, 0.3199999928474426); + let expr = cos((6.28318f * ((c * t) + e))); return (a + (b * expr)); } @@ -66,13 +66,13 @@ describe('disco example', () => { } @fragment fn mainFragment2(_arg_0: mainFragment2_Input) -> @location(0) vec4f { - var originalUv = aspectCorrected(_arg_0.uv); + let originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; var accumulatedColor = vec3f(); for (var iteration = 0; (iteration < 3i); iteration++) { aspectUv = (fract((aspectUv * -0.9f)) - 0.5f); var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * 0.5f))); - var paletteColor = palette((length(originalUv) + (time * 0.9f))); + let paletteColor = palette((length(originalUv) + (time * 0.9f))); radialLength = (sin(((radialLength * 8f) + time)) / 8f); radialLength = abs(radialLength); radialLength = smoothstep(0f, 0.1f, radialLength); @@ -87,7 +87,7 @@ describe('disco example', () => { } @fragment fn mainFragment3(_arg_0: mainFragment3_Input) -> @location(0) vec4f { - var originalUv = aspectCorrected(_arg_0.uv); + let originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; var accumulatedColor = vec3f(); let baseAngle = (time * 0.3f); @@ -101,7 +101,7 @@ describe('disco example', () => { aspectUv = (aspectUv * (1.15f + (iterationF32 * 0.05f))); aspectUv = (fract((aspectUv * (1.2f * sin(((time * 0.9f) + (iterationF32 * 0.3f)))))) - 0.5f); var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * 1.6f))); - var paletteColor = palette(((length(originalUv) + (time * 0.8f)) + (iterationF32 * 0.05f))); + let paletteColor = palette(((length(originalUv) + (time * 0.8f)) + (iterationF32 * 0.05f))); radialLength = (sin(((radialLength * 7f) + (time * 0.9f))) / 8f); radialLength = abs(radialLength); radialLength = smoothstep(0f, 0.11f, radialLength); @@ -117,9 +117,9 @@ describe('disco example', () => { @fragment fn mainFragment4(_arg_0: mainFragment4_Input) -> @location(0) vec4f { var aspectUv = aspectCorrected(_arg_0.uv); - var mirroredUv = ((vec2f(abs((fract((aspectUv.x * 1.2f)) - 0.5f)), abs((fract((aspectUv.y * 1.2f)) - 0.5f))) * 2f) - 1f); + let mirroredUv = ((vec2f(abs((fract((aspectUv.x * 1.2f)) - 0.5f)), abs((fract((aspectUv.y * 1.2f)) - 0.5f))) * 2f) - 1f); aspectUv = mirroredUv; - var originalUv = aspectUv; + let originalUv = aspectUv; var accumulatedColor = vec3f(); let time_1 = time; for (var iteration = 0; (iteration < 4i); iteration++) { @@ -136,7 +136,7 @@ describe('disco example', () => { radialLength = abs(radialLength); radialLength = smoothstep(0f, 0.105f, radialLength); radialLength = ((0.058f + (iterationF32 * 6e-3f)) / (radialLength + 1e-5f)); - var paletteColor = palette(((length(originalUv) + (time_1 * 0.65f)) + (iterationF32 * 0.045f))); + let paletteColor = palette(((length(originalUv) + (time_1 * 0.65f)) + (iterationF32 * 0.045f))); accumulatedColor = accumulate(accumulatedColor, paletteColor, radialLength); } return vec4f(accumulatedColor, 1f); @@ -147,7 +147,7 @@ describe('disco example', () => { } @fragment fn mainFragment5(_arg_0: mainFragment5_Input) -> @location(0) vec4f { - var originalUv = aspectCorrected(_arg_0.uv); + let originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; var accumulatedColor = vec3f(); for (var iteration = 0; (iteration < 3i); iteration++) { @@ -161,7 +161,7 @@ describe('disco example', () => { aspectUv = (vec2f(rotatedX, rotatedY) * (-0.85f - (iterationF32 * 0.07f))); aspectUv = (fract(aspectUv) - 0.5f); var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * (0.4f + (iterationF32 * 0.1f))))); - var paletteColor = palette(((length(originalUv) + (time * 0.9f)) + (iterationF32 * 0.08f))); + let paletteColor = palette(((length(originalUv) + (time * 0.9f)) + (iterationF32 * 0.08f))); radialLength = (sin(((radialLength * (6f + iterationF32)) + time)) / 8f); radialLength = abs(radialLength); radialLength = smoothstep(0f, 0.1f, radialLength); @@ -177,7 +177,7 @@ describe('disco example', () => { @fragment fn mainFragment6(_arg_0: mainFragment6_Input) -> @location(0) vec4f { var aspectUv = aspectCorrected(_arg_0.uv); - var originalUv = aspectUv; + let originalUv = aspectUv; var accumulatedColor = vec3f(); let time_1 = time; for (var iteration = 0; (iteration < 5i); iteration++) { @@ -188,13 +188,13 @@ describe('disco example', () => { let rotatedX = ((aspectUv.x * cosAngle) - (aspectUv.y * sinAngle)); let rotatedY = ((aspectUv.x * sinAngle) + (aspectUv.y * cosAngle)); aspectUv = (vec2f(rotatedX, rotatedY) * (1.08f + (iterationF32 * 0.04f))); - var warpedUv = (fract((aspectUv * (1.3f + (iterationF32 * 0.2f)))) - 0.5f); + let warpedUv = (fract((aspectUv * (1.3f + (iterationF32 * 0.2f)))) - 0.5f); var radialLength = (length(warpedUv) * exp((-(length(originalUv)) * (1.4f + (iterationF32 * 0.05f))))); radialLength = (sin(((radialLength * (7f + (iterationF32 * 0.7f))) + (time_1 * (0.9f + (iterationF32 * 0.15f))))) / 8f); radialLength = abs(radialLength); radialLength = smoothstep(0f, 0.1f, radialLength); radialLength = ((0.05f + (iterationF32 * 5e-3f)) / (radialLength + 1e-5f)); - var paletteColor = palette(((length(originalUv) + (time_1 * 0.7f)) + (iterationF32 * 0.04f))); + let paletteColor = palette(((length(originalUv) + (time_1 * 0.7f)) + (iterationF32 * 0.04f))); accumulatedColor = accumulate(accumulatedColor, paletteColor, radialLength); } return vec4f(accumulatedColor, 1f); @@ -207,7 +207,7 @@ describe('disco example', () => { @fragment fn mainFragment7(_arg_0: mainFragment7_Input) -> @location(0) vec4f { var aspectUv = aspectCorrected(_arg_0.uv); aspectUv = (vec2f(abs((fract((aspectUv.x * 1.5f)) - 0.5f)), abs((fract((aspectUv.y * 1.5f)) - 0.5f))) * 2f); - var originalUv = aspectUv; + let originalUv = aspectUv; var accumulatedColor = vec3f(); let time_1 = time; for (var iteration = 0; (iteration < 4i); iteration++) { @@ -226,7 +226,7 @@ describe('disco example', () => { radialLength = abs(radialLength); radialLength = smoothstep(0f, 0.11f, radialLength); radialLength = ((0.06f + (iterationF32 * 5e-3f)) / (radialLength + 1e-5f)); - var paletteColor = palette(((length(originalUv) + (time_1 * 0.75f)) + (iterationF32 * 0.05f))); + let paletteColor = palette(((length(originalUv) + (time_1 * 0.75f)) + (iterationF32 * 0.05f))); accumulatedColor = accumulate(accumulatedColor, paletteColor, radialLength); } return vec4f(accumulatedColor, 1f); @@ -238,8 +238,8 @@ describe('disco example', () => { } @vertex fn mainVertex(@builtin(vertex_index) vertexIndex: u32) -> mainVertex_Output { - var pos = array(vec2f(-1, 1), vec2f(-1), vec2f(1, -1), vec2f(-1, 1), vec2f(1, -1), vec2f(1)); - var uv = array(vec2f(0, 1), vec2f(), vec2f(1, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1)); + let pos = array(vec2f(-1, 1), vec2f(-1), vec2f(1, -1), vec2f(-1, 1), vec2f(1, -1), vec2f(1)); + let uv = array(vec2f(0, 1), vec2f(), vec2f(1, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1)); return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } @@ -260,11 +260,11 @@ describe('disco example', () => { @group(0) @binding(1) var time: f32; fn palette(t: f32) -> vec3f { - var a = vec3f(0.5, 0.5899999737739563, 0.8500000238418579); - var b = vec3f(0.18000000715255737, 0.41999998688697815, 0.4000000059604645); - var c = vec3f(0.18000000715255737, 0.47999998927116394, 0.4099999964237213); - var e = vec3f(0.3499999940395355, 0.12999999523162842, 0.3199999928474426); - var expr = cos((6.28318f * ((c * t) + e))); + let a = vec3f(0.5, 0.5899999737739563, 0.8500000238418579); + let b = vec3f(0.18000000715255737, 0.41999998688697815, 0.4000000059604645); + let c = vec3f(0.18000000715255737, 0.47999998927116394, 0.4099999964237213); + let e = vec3f(0.3499999940395355, 0.12999999523162842, 0.3199999928474426); + let expr = cos((6.28318f * ((c * t) + e))); return (a + (b * expr)); } @@ -277,7 +277,7 @@ describe('disco example', () => { } @fragment fn mainFragment1(_arg_0: mainFragment1_Input) -> @location(0) vec4f { - var originalUv = aspectCorrected(_arg_0.uv); + let originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; var accumulatedColor = vec3f(); for (var iteration = 0; (iteration < 5i); iteration++) { @@ -287,7 +287,7 @@ describe('disco example', () => { radialLength = abs(radialLength); radialLength = smoothstep(0f, 0.1f, radialLength); radialLength = (0.06f / radialLength); - var paletteColor = palette((length(originalUv) + (time * 0.9f))); + let paletteColor = palette((length(originalUv) + (time * 0.9f))); accumulatedColor = accumulate(accumulatedColor, paletteColor, radialLength); } return vec4f(accumulatedColor, 1f); diff --git a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts index df9620d6ed..8e21b7e6d3 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts @@ -172,14 +172,14 @@ describe('fluid double buffering example', () => { fn computeVelocity(x: i32, y: i32) -> vec2f { const gravityCost = 0.5; - var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); - var cell = getCell(x, y); + let neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); + let cell = getCell(x, y); var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; // unrolled iteration #0 { - var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); + let 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)) { @@ -197,7 +197,7 @@ describe('fluid double buffering example', () => { } // unrolled iteration #1 { - var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); + let 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)) { @@ -215,7 +215,7 @@ describe('fluid double buffering example', () => { } // unrolled iteration #2 { - var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); + let 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)) { @@ -233,7 +233,7 @@ describe('fluid double buffering example', () => { } // unrolled iteration #3 { - var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); + let 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)) { @@ -257,9 +257,9 @@ describe('fluid double buffering example', () => { if (!isValidCoord(x, y)) { return 0; } - var src = getCell(x, y); - var destPos = vec2i((x + i32(src.x)), (y + i32(src.y))); - var dest = getCell(destPos.x, destPos.y); + let src = getCell(x, y); + let destPos = vec2i((x + i32(src.x)), (y + i32(src.y))); + let dest = getCell(destPos.x, destPos.y); let diff = (src.z - dest.z); var outFlow = min(max(0.01f, (0.3f + (diff * 0.1f))), src.z); if ((length(src.xy) < 0.5f)) { @@ -285,7 +285,7 @@ describe('fluid double buffering example', () => { fn getMinimumInFlow(x: i32, y: i32) -> f32 { const gridSizeF = 256f; let sourceRadius = max(1f, (sourceParams.radius * gridSizeF)); - var sourcePos = vec2f((sourceParams.center.x * gridSizeF), (sourceParams.center.y * gridSizeF)); + let sourcePos = vec2f((sourceParams.center.x * gridSizeF), (sourceParams.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius)) { return sourceParams.intensity; } @@ -300,7 +300,7 @@ describe('fluid double buffering example', () => { let index = coordsToIndex(x, y); randSeed2(vec2f(f32(index), time)); var next = getCell(x, y); - var nextVelocity = computeVelocity(x, y); + let nextVelocity = computeVelocity(x, y); next.x = nextVelocity.x; next.y = nextVelocity.y; next.z = flowFromCell(x, y, x, y); @@ -399,14 +399,14 @@ describe('fluid double buffering example', () => { fn computeVelocity(x: i32, y: i32) -> vec2f { const gravityCost = 0.5; - var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); - var cell = getCell(x, y); + let neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); + let cell = getCell(x, y); var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; // unrolled iteration #0 { - var neighborDensity = getCell((x + neighborOffsets[0u].x), (y + neighborOffsets[0u].y)); + let 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)) { @@ -424,7 +424,7 @@ describe('fluid double buffering example', () => { } // unrolled iteration #1 { - var neighborDensity = getCell((x + neighborOffsets[1u].x), (y + neighborOffsets[1u].y)); + let 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)) { @@ -442,7 +442,7 @@ describe('fluid double buffering example', () => { } // unrolled iteration #2 { - var neighborDensity = getCell((x + neighborOffsets[2u].x), (y + neighborOffsets[2u].y)); + let 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)) { @@ -460,7 +460,7 @@ describe('fluid double buffering example', () => { } // unrolled iteration #3 { - var neighborDensity = getCell((x + neighborOffsets[3u].x), (y + neighborOffsets[3u].y)); + let 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)) { @@ -484,9 +484,9 @@ describe('fluid double buffering example', () => { if (!isValidCoord(x, y)) { return 0; } - var src = getCell(x, y); - var destPos = vec2i((x + i32(src.x)), (y + i32(src.y))); - var dest = getCell(destPos.x, destPos.y); + let src = getCell(x, y); + let destPos = vec2i((x + i32(src.x)), (y + i32(src.y))); + let dest = getCell(destPos.x, destPos.y); let diff = (src.z - dest.z); var outFlow = min(max(0.01f, (0.3f + (diff * 0.1f))), src.z); if ((length(src.xy) < 0.5f)) { @@ -512,7 +512,7 @@ describe('fluid double buffering example', () => { fn getMinimumInFlow(x: i32, y: i32) -> f32 { const gridSizeF = 256f; let sourceRadius = max(1f, (sourceParams.radius * gridSizeF)); - var sourcePos = vec2f((sourceParams.center.x * gridSizeF), (sourceParams.center.y * gridSizeF)); + let sourcePos = vec2f((sourceParams.center.x * gridSizeF), (sourceParams.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius)) { return sourceParams.intensity; } @@ -527,7 +527,7 @@ describe('fluid double buffering example', () => { let index = coordsToIndex(x, y); randSeed2(vec2f(f32(index), time)); var next = getCell(x, y); - var nextVelocity = computeVelocity(x, y); + let nextVelocity = computeVelocity(x, y); next.x = nextVelocity.x; next.y = nextVelocity.y; next.z = flowFromCell(x, y, x, y); @@ -553,8 +553,8 @@ describe('fluid double buffering example', () => { } @vertex fn vertexMain(@builtin(vertex_index) _arg_idx: u32) -> vertexMain_Output { - var pos = array(vec2f(1), vec2f(-1, 1), vec2f(1, -1), vec2f(-1)); - var uv = array(vec2f(1), vec2f(0, 1), vec2f(1, 0), vec2f()); + let pos = array(vec2f(1), vec2f(-1, 1), vec2f(1, -1), vec2f(-1)); + let uv = array(vec2f(1), vec2f(0, 1), vec2f(1, 0), vec2f()); return vertexMain_Output(vec4f(pos[_arg_idx].x, pos[_arg_idx].y, 0f, 1f), uv[_arg_idx]); } @@ -601,11 +601,11 @@ describe('fluid double buffering example', () => { let index = coordsToIndex(x, y); let cell = (&gridAlphaBuffer[index]); let density = max(0f, (*cell).z); - var obstacleColor = vec4f(0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 1); - var background = vec4f(0.8999999761581421, 0.8999999761581421, 0.8999999761581421, 1); - var firstColor = vec4f(0.20000000298023224, 0.6000000238418579, 1, 1); - var secondColor = vec4f(0.20000000298023224, 0.30000001192092896, 0.6000000238418579, 1); - var thirdColor = vec4f(0.10000000149011612, 0.20000000298023224, 0.4000000059604645, 1); + let obstacleColor = vec4f(0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 1); + let background = vec4f(0.8999999761581421, 0.8999999761581421, 0.8999999761581421, 1); + let firstColor = vec4f(0.20000000298023224, 0.6000000238418579, 1, 1); + let secondColor = vec4f(0.20000000298023224, 0.30000001192092896, 0.6000000238418579, 1); + let thirdColor = vec4f(0.10000000149011612, 0.20000000298023224, 0.4000000059604645, 1); const firstThreshold = 2f; const secondThreshold = 10f; const thirdThreshold = 20f; diff --git a/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts b/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts index 492f38c59d..eb44a19266 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts @@ -45,7 +45,7 @@ describe('function visualizer example', () => { let end = ((*properties).transformation * vec4f(1, 0, 0, 1)).x; let pointX = (start + (((end - start) / (f32((*properties).interpolationPoints) - 1f)) * f32(x))); let pointY = interpolatedFunction(pointX); - var result = ((*properties).inverseTransformation * vec4f(pointX, pointY, 0f, 1f)); + let result = ((*properties).inverseTransformation * vec4f(pointX, pointY, 0f, 1f)); lineVertices[x] = result.xy; } @@ -79,7 +79,7 @@ describe('function visualizer example', () => { let end = ((*properties).transformation * vec4f(1, 0, 0, 1)).x; let pointX = (start + (((end - start) / (f32((*properties).interpolationPoints) - 1f)) * f32(x))); let pointY = interpolatedFunction(pointX); - var result = ((*properties).inverseTransformation * vec4f(pointX, pointY, 0f, 1f)); + let result = ((*properties).inverseTransformation * vec4f(pointX, pointY, 0f, 1f)); lineVertices[x] = result.xy; } @@ -113,7 +113,7 @@ describe('function visualizer example', () => { let end = ((*properties).transformation * vec4f(1, 0, 0, 1)).x; let pointX = (start + (((end - start) / (f32((*properties).interpolationPoints) - 1f)) * f32(x))); let pointY = interpolatedFunction(pointX); - var result = ((*properties).inverseTransformation * vec4f(pointX, pointY, 0f, 1f)); + let result = ((*properties).inverseTransformation * vec4f(pointX, pointY, 0f, 1f)); lineVertices[x] = result.xy; } @@ -139,11 +139,11 @@ describe('function visualizer example', () => { @vertex fn backgroundVertex(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> backgroundVertex_Output { let properties = (&propertiesUniform); - var leftBot = ((*properties).transformation * vec4f(-1, -1, 0, 1)); - var rightTop = ((*properties).transformation * vec4f(1, 1, 0, 1)); + let leftBot = ((*properties).transformation * vec4f(-1, -1, 0, 1)); + let rightTop = ((*properties).transformation * vec4f(1, 1, 0, 1)); let aspectRatio = ((rightTop.x - leftBot.x) / (rightTop.y - leftBot.y)); - var transformedPoints = array(vec2f(leftBot.x, 0f), vec2f(rightTop.x, 0f), vec2f(0f, leftBot.y), vec2f(0f, rightTop.y)); - var currentPoint = ((*properties).inverseTransformation * vec4f(transformedPoints[((2u * iid) + u32((f32(vid) / 2f)))].xy, 0f, 1f)); + let transformedPoints = array(vec2f(leftBot.x, 0f), vec2f(rightTop.x, 0f), vec2f(0f, leftBot.y), vec2f(0f, rightTop.y)); + let currentPoint = ((*properties).inverseTransformation * vec4f(transformedPoints[((2u * iid) + u32((f32(vid) / 2f)))].xy, 0f, 1f)); return backgroundVertex_Output(vec4f((currentPoint.x + (((f32(iid) * select(-1f, 1f, ((vid % 2u) == 0u))) * 5e-3f) / aspectRatio)), (currentPoint.y + ((f32((1u - iid)) * select(-1f, 1f, ((vid % 2u) == 0u))) * 5e-3f)), currentPoint.zw)); } @@ -163,8 +163,8 @@ describe('function visualizer example', () => { @group(1) @binding(0) var lineVertices_1: array; fn orthonormalForLine(p1: vec2f, p2: vec2f) -> vec2f { - var line = (p2 - p1); - var ortho = vec2f(-(line.y), line.x); + let line = (p2 - p1); + let ortho = vec2f(-(line.y), line.x); return normalize(ortho); } @@ -176,9 +176,9 @@ describe('function visualizer example', () => { let previous = (&(*lineVertices)[u32((index - 1f))]); let current = (&(*lineVertices)[u32(index)]); let next = (&(*lineVertices)[u32((index + 1f))]); - var n1 = orthonormalForLine((*previous), (*current)); - var n2 = orthonormalForLine((*current), (*next)); - var avg = ((n1 + n2) / 2f); + let n1 = orthonormalForLine((*previous), (*current)); + let n2 = orthonormalForLine((*current), (*next)); + let avg = ((n1 + n2) / 2f); return normalize(avg); } @@ -190,12 +190,12 @@ describe('function visualizer example', () => { let properties = (&propertiesUniform); let lineVertices = (&lineVertices_1); let currentVertex = (f32(vid) / 2f); - var orthonormal = orthonormalForVertex(currentVertex); - var offset = ((orthonormal * (*properties).lineWidth) * select(-1f, 1f, ((vid % 2u) == 0u))); - var leftBot = ((*properties).transformation * vec4f(-1, -1, 0, 1)); - var rightTop = ((*properties).transformation * vec4f(1, 1, 0, 1)); + let orthonormal = orthonormalForVertex(currentVertex); + let offset = ((orthonormal * (*properties).lineWidth) * select(-1f, 1f, ((vid % 2u) == 0u))); + let leftBot = ((*properties).transformation * vec4f(-1, -1, 0, 1)); + let rightTop = ((*properties).transformation * vec4f(1, 1, 0, 1)); let canvasRatio = ((rightTop.x - leftBot.x) / (rightTop.y - leftBot.y)); - var adjustedOffset = vec2f((offset.x / canvasRatio), offset.y); + let adjustedOffset = vec2f((offset.x / canvasRatio), offset.y); return vertex_Output(vec4f(((*lineVertices)[u32(currentVertex)] + adjustedOffset), 0f, 1f)); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts index 516692dc41..f1b93c8c5b 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts @@ -76,7 +76,7 @@ describe('game of life example', () => { @compute @workgroup_size(16, 16) fn naiveCompute(@builtin(global_invocation_id) gid: vec3u) { let gs = gameSizeUniform; let vmax = (gs - 1u); - var p = gid.xy; + let p = gid.xy; var neighbors = 0u; for (var oy = -1; (oy <= 1i); oy++) { for (var ox = -1; (ox <= 1i); ox++) { @@ -124,8 +124,8 @@ describe('game of life example', () => { @compute @workgroup_size(16, 16) fn tiledCompute(@builtin(global_invocation_id) gid: vec3u, @builtin(local_invocation_id) lid: vec3u, @builtin(workgroup_id) wgid: vec3u) { let gs = f32(gameSizeUniform); - var texelSize = (vec2f(1) / gs); - var tileOrigin = ((vec2f(wgid.xy) * 16f) - 1f); + let texelSize = (vec2f(1) / gs); + let tileOrigin = ((vec2f(wgid.xy) * 16f) - 1f); let linearId = ((lid.y * 16u) + lid.x); const numGathers = 81u; if ((linearId < numGathers)) { @@ -133,8 +133,8 @@ describe('game of life example', () => { let gy = u32((f32(linearId) / 9f)); let sx = (gx * 2u); let sy = (gy * 2u); - var uv = ((tileOrigin + vec2f(f32((sx + 1u)), f32((sy + 1u)))) * texelSize); - var g = textureGather(0i, current, sampler_1, uv); + let uv = ((tileOrigin + vec2f(f32((sx + 1u)), f32((sy + 1u)))) * texelSize); + let g = textureGather(0i, current, sampler_1, uv); sharedTile[tileIdx(sx, sy)] = g.w; sharedTile[tileIdx((sx + 1u), sy)] = g.z; sharedTile[tileIdx(sx, (sy + 1u))] = g.x; @@ -177,8 +177,8 @@ describe('game of life example', () => { @compute @workgroup_size(16, 16) fn tiledCompute(@builtin(global_invocation_id) gid: vec3u, @builtin(local_invocation_id) lid: vec3u, @builtin(workgroup_id) wgid: vec3u) { let gs = f32(gameSizeUniform); - var texelSize = (vec2f(1) / gs); - var tileOrigin = ((vec2f(wgid.xy) * 16f) - 1f); + let texelSize = (vec2f(1) / gs); + let tileOrigin = ((vec2f(wgid.xy) * 16f) - 1f); let linearId = ((lid.y * 16u) + lid.x); const numGathers = 81u; if ((linearId < numGathers)) { @@ -186,8 +186,8 @@ describe('game of life example', () => { let gy = u32((f32(linearId) / 9f)); let sx = (gx * 2u); let sy = (gy * 2u); - var uv = ((tileOrigin + vec2f(f32((sx + 1u)), f32((sy + 1u)))) * texelSize); - var g = textureGather(0i, current, sampler_1, uv); + let uv = ((tileOrigin + vec2f(f32((sx + 1u)), f32((sy + 1u)))) * texelSize); + let g = textureGather(0i, current, sampler_1, uv); sharedTile[tileIdx(sx, sy)] = g.w; sharedTile[tileIdx((sx + 1u), sy)] = g.z; sharedTile[tileIdx(sx, (sy + 1u))] = g.x; @@ -226,7 +226,7 @@ describe('game of life example', () => { @group(0) @binding(1) var gameSizeUniform: u32; fn sdRoundedBox2d(point: vec2f, size: vec2f, cornerRadius: f32) -> f32 { - var d = ((abs(point) - size) + vec2f(cornerRadius)); + let d = ((abs(point) - size) + vec2f(cornerRadius)); return ((length(max(d, vec2f())) + min(max(d.x, d.y), 0f)) - cornerRadius); } @@ -246,13 +246,13 @@ describe('game of life example', () => { let zoom = (&zoomUniform); let gs = f32(gameSizeUniform); let halfView = (0.5f / (*zoom).level); - var clampedCenter = clamp(vec2f((*zoom).centerX, (*zoom).centerY), vec2f(halfView), vec2f((1f - halfView))); - var minimapMin = vec2f(0.7799999713897705); - var minimapMax = vec2f(0.9800000190734863); + let clampedCenter = clamp(vec2f((*zoom).centerX, (*zoom).centerY), vec2f(halfView), vec2f((1f - halfView))); + let minimapMin = vec2f(0.7799999713897705); + let minimapMax = vec2f(0.9800000190734863); const minimapSize = 0.2; let inMinimap = ((((((*zoom).enabled == 1u) && (_arg_0.uv.x >= minimapMin.x)) && (_arg_0.uv.x <= minimapMax.x)) && (_arg_0.uv.y >= minimapMin.y)) && (_arg_0.uv.y <= minimapMax.y)); if (inMinimap) { - var localUv = ((_arg_0.uv - minimapMin) / minimapSize); + let localUv = ((_arg_0.uv - minimapMin) / minimapSize); let edgeDist = sdRoundedBox2d((localUv - 0.5f), vec2f(0.5), 0.02f); if ((edgeDist > -0.02f)) { let alpha = (1f - smoothstep(0f, 0.02f, edgeDist)); @@ -262,12 +262,12 @@ describe('game of life example', () => { let dist = sdRoundedBox2d((localUv - clampedCenter), vec2f((viewSize / 2f)), 0.01f); const borderWidth = 0.015; if (((dist > -(borderWidth)) && (dist < borderWidth))) { - var borderColor = mix(vec4f(0.7689999938011169, 0.3919999897480011, 1, 1), vec4f(0.11400000005960464, 0.44699999690055847, 0.9409999847412109, 1), localUv.x); + let borderColor = mix(vec4f(0.7689999938011169, 0.3919999897480011, 1, 1), vec4f(0.11400000005960464, 0.44699999690055847, 0.9409999847412109, 1), localUv.x); let a = (1f - smoothstep(0f, borderWidth, abs(dist))); return vec4f(borderColor.x, borderColor.y, borderColor.z, a); } let value = sampleRegular(localUv, gs); - var alive = select(vec4f((localUv.x / 2.5f), (localUv.y / 2.5f), ((1f - localUv.x) / 2.5f), 0.8f), vec4f(0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.800000011920929), (viewModeUniform == 1u)); + let alive = select(vec4f((localUv.x / 2.5f), (localUv.y / 2.5f), ((1f - localUv.x) / 2.5f), 0.8f), vec4f(0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.800000011920929), (viewModeUniform == 1u)); return select(vec4f(0, 0, 0, 0.800000011920929), alive, (value == 1u)); } var sampleUv = _arg_0.uv; @@ -276,8 +276,8 @@ describe('game of life example', () => { } let value = sampleRegular(sampleUv, gs); let isClassic = (viewModeUniform == 1u); - var alive = select(normalize(vec4f((sampleUv.x / 1.5f), (sampleUv.y / 1.5f), (1f - (sampleUv.x / 1.5f)), 1f)), vec4f(1), isClassic); - var dead = select(vec4f(), vec4f(0, 0, 0, 1), isClassic); + let alive = select(normalize(vec4f((sampleUv.x / 1.5f), (sampleUv.y / 1.5f), (1f - (sampleUv.x / 1.5f)), 1f)), vec4f(1), isClassic); + let dead = select(vec4f(), vec4f(0, 0, 0, 1), isClassic); return select(dead, alive, (value == 1u)); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts b/apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts index 0853d247e9..ea5ca7f7ad 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/global-wind-map.test.ts @@ -45,9 +45,9 @@ describe('global wind map example', () => { let currentPosIndex = (frameCount % 20u); let prevPosIndex = (((20u + frameCount) - 1u) % 20u); let pos = (&(*particle).positions[prevPosIndex]); - var v0 = vectorField((*pos)); - var v1 = vectorField(((*pos) + (v0 * (0.5f * stepSize)))); - var newPos = ((*pos) + (v1 * stepSize)); + let v0 = vectorField((*pos)); + let v1 = vectorField(((*pos) + (v0 * (0.5f * stepSize)))); + let newPos = ((*pos) + (v1 * stepSize)); (*particle).positions[currentPosIndex] = newPos; particles[particleIndex] = (*particle); } @@ -99,13 +99,13 @@ describe('global wind map example', () => { let b = (distance_1.y * sinDivLen); let c = (distance_1.x * sinDivLen); let d = (distance_1.y * cosDivLen); - var nL = vec2f((a - b), (c + d)); - var nR = vec2f((a + b), (-(c) + d)); + let nL = vec2f((a - b), (c + d)); + let nR = vec2f((a + b), (-(c) + d)); return ExternalNormals(nL, nR); } fn miterPointNoCheck(a: vec2f, b: vec2f) -> vec2f { - var ab = (a + b); + let ab = (a + b); return (ab * (2f / dot(ab, ab))); } @@ -125,10 +125,10 @@ describe('global wind map example', () => { let tooCloseToJoinR = (dot(eAB.nR, eBC.nR) > 0.99f); let shouldJoinL = (isHairpin || (underLimitL && !tooCloseToJoinL)); let shouldJoinR = (isHairpin || (underLimitR && !tooCloseToJoinR)); - var dLMiter = miterPointNoCheck(eAB.nL, eBC.nL); - var dRMiter = miterPointNoCheck(eBC.nR, eAB.nR); - var dL = select(eBC.nL, dLMiter, (!isCap && !shouldJoinL)); - var dR = select(eBC.nR, dRMiter, (!isCap && !shouldJoinR)); + let dLMiter = miterPointNoCheck(eAB.nL, eBC.nL); + let dRMiter = miterPointNoCheck(eBC.nR, eAB.nR); + let dL = select(eBC.nL, dLMiter, (!isCap && !shouldJoinL)); + let dR = select(eBC.nR, dRMiter, (!isCap && !shouldJoinR)); return JoinResult(dL, dR, shouldJoinL, shouldJoinR, isHairpin); } @@ -143,10 +143,10 @@ describe('global wind map example', () => { } fn intersectLines(A1: vec2f, A2: vec2f, B1: vec2f, B2: vec2f) -> Intersection { - var a = (A2 - A1); - var b = (B2 - B1); + let a = (A2 - A1); + let b = (B2 - B1); let axb = cross2d(a, b); - var AB = (B1 - A1); + let AB = (B1 - A1); let t = (cross2d(AB, b) / axb); return Intersection((((axb != 0f) && (t >= 0f)) && (t <= 1f)), t, (A1 + (a * t))); } @@ -167,12 +167,12 @@ describe('global wind map example', () => { } fn arrow(join: JoinInput, joinVertexIndex: u32, _maxJoinCount: u32) -> vec2f { - var bw = -(normalize(join.fw)); - var vert = rot90ccw(bw); + let bw = -(normalize(join.fw)); + let vert = rot90ccw(bw); let sgn = sign(cross2d(bw, join.d)); - var svert = (vert * sgn); - var v0 = (svert + (bw * 7.5f)); - var v1 = (v0 + ((bw + svert) * 1.5f)); + let svert = (vert * sgn); + let v0 = (svert + (bw * 7.5f)); + let v1 = (v0 + ((bw + svert) * 1.5f)); if ((joinVertexIndex == 0u)) { return (join.C.position + (v0 * join.C.radius)); } @@ -183,10 +183,10 @@ describe('global wind map example', () => { } fn butt(join: JoinInput, _joinVertexIndex: u32, _maxJoinCount: u32) -> vec2f { - var fw = normalize(join.fw); - var vert = rot90ccw(fw); + let fw = normalize(join.fw); + let vert = rot90ccw(fw); let sgn = sign(cross2d(fw, join.d)); - var svert = (vert * sgn); + let svert = (vert * sgn); return (join.C.position + (svert * join.C.radius)); } @@ -197,9 +197,9 @@ describe('global wind map example', () => { fn bisectCcw(a: vec2f, b: vec2f) -> vec2f { let sin_1 = cross2d(a, b); let sinSign = select(-1f, 1f, (sin_1 >= 0f)); - var orthoA = rot90ccw(a); - var orthoB = rot90cw(b); - var dir = select(((a + b) * sinSign), (orthoA + orthoB), (dot(a, b) < 0f)); + let orthoA = rot90ccw(a); + let orthoB = rot90cw(b); + let dir = select(((a + b) * sinSign), (orthoA + orthoB), (dot(a, b) < 0f)); return normalize(dir); } @@ -208,7 +208,7 @@ describe('global wind map example', () => { } fn slerpApprox(a: vec2f, b: vec2f, t: f32) -> vec2f { - var mid = bisectNoCheck(a, b); + let mid = bisectNoCheck(a, b); var a_ = a; var b_ = mid; var t_ = (2f * t); @@ -224,15 +224,15 @@ describe('global wind map example', () => { if ((joinVertexIndex == 0u)) { return join.v; } - var dir = slerpApprox(join.d, bisectCcw(join.start, join.end), (f32(joinVertexIndex) / f32(maxJoinCount))); + let dir = slerpApprox(join.d, bisectCcw(join.start, join.end), (f32(joinVertexIndex) / f32(maxJoinCount))); return (join.C.position + (dir * join.C.radius)); } fn lineSegmentVariableWidth(vertexIndex: u32, A: LineControlPoint, B: LineControlPoint, C: LineControlPoint, D: LineControlPoint, maxJoinCount: u32) -> LineSegmentOutput { - var AB = (B.position - A.position); - var BC = (C.position - B.position); - var DC = (C.position - D.position); - var CB = -(BC); + let AB = (B.position - A.position); + let BC = (C.position - B.position); + let DC = (C.position - D.position); + let CB = -(BC); let radiusABDelta = (A.radius - B.radius); let radiusBCDelta = (B.radius - C.radius); let radiusCDDelta = (C.radius - D.radius); @@ -241,10 +241,10 @@ describe('global wind map example', () => { } let isCapB = (dot(AB, AB) <= (radiusABDelta * radiusABDelta)); let isCapC = (dot(DC, DC) <= (radiusCDDelta * radiusCDDelta)); - var eAB = externalNormals(AB, A.radius, B.radius); - var eBC = externalNormals(BC, B.radius, C.radius); - var eCB = ExternalNormals(eBC.nR, eBC.nL); - var eDC = externalNormals(DC, D.radius, C.radius); + let eAB = externalNormals(AB, A.radius, B.radius); + let eBC = externalNormals(BC, B.radius, C.radius); + let eCB = ExternalNormals(eBC.nR, eBC.nL); + let eDC = externalNormals(DC, D.radius, C.radius); let joinLimit = dot(eBC.nL, BC); var joinB = solveJoin(AB, BC, eAB, eBC, joinLimit, isCapB); var joinC = solveJoin(DC, CB, eDC, eCB, -(joinLimit), isCapC); @@ -252,16 +252,16 @@ describe('global wind map example', () => { let d3 = (&joinB.dR); let d4 = (&joinC.dL); let d5 = (&joinC.dR); - var v2orig = (B.position + ((*d2) * B.radius)); - var v3orig = (B.position + ((*d3) * B.radius)); - var v4orig = (C.position + ((*d4) * C.radius)); - var v5orig = (C.position + ((*d5) * C.radius)); - var limL = intersectLines(B.position, v2orig, C.position, v5orig); - var limR = intersectLines(B.position, v3orig, C.position, v4orig); - var v2 = select(v2orig, limL.point, limL.valid); - var v5 = select(v5orig, limL.point, limL.valid); - var v3 = select(v3orig, limR.point, limR.valid); - var v4 = select(v4orig, limR.point, limR.valid); + let v2orig = (B.position + ((*d2) * B.radius)); + let v3orig = (B.position + ((*d3) * B.radius)); + let v4orig = (C.position + ((*d4) * C.radius)); + let v5orig = (C.position + ((*d5) * C.radius)); + let limL = intersectLines(B.position, v2orig, C.position, v5orig); + let limR = intersectLines(B.position, v3orig, C.position, v4orig); + let v2 = select(v2orig, limL.point, limL.valid); + let v5 = select(v5orig, limL.point, limL.valid); + let v3 = select(v3orig, limR.point, limR.valid); + let v4 = select(v4orig, limR.point, limR.valid); if ((vertexIndex == 0u)) { return LineSegmentOutput(B.position, (1f / B.radius)); } @@ -319,11 +319,11 @@ describe('global wind map example', () => { let iB = trailIndex; let iC = (((20i + trailIndex) - 1i) % 20i); let iD = (((20i + trailIndex) - 2i) % 20i); - var A = LineControlPoint((*particle).positions[iA], lineWidth((f32(trailIndexOriginal) / 19f))); - var B = LineControlPoint((*particle).positions[iB], lineWidth((f32((trailIndexOriginal + 1u)) / 19f))); - var C = LineControlPoint((*particle).positions[iC], lineWidth((f32((trailIndexOriginal + 2u)) / 19f))); - var D = LineControlPoint((*particle).positions[iD], lineWidth((f32((trailIndexOriginal + 3u)) / 19f))); - var result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, 3u); + let A = LineControlPoint((*particle).positions[iA], lineWidth((f32(trailIndexOriginal) / 19f))); + let B = LineControlPoint((*particle).positions[iB], lineWidth((f32((trailIndexOriginal + 1u)) / 19f))); + let C = LineControlPoint((*particle).positions[iC], lineWidth((f32((trailIndexOriginal + 2u)) / 19f))); + let D = LineControlPoint((*particle).positions[iD], lineWidth((f32((trailIndexOriginal + 3u)) / 19f))); + let result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, 3u); return mainVertex_Output(vec4f(result.vertexPosition, 0f, 1f), result.vertexPosition, (f32(trailIndexOriginal) / 19f)); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts index 899b6cb76e..61311326c3 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts @@ -98,11 +98,11 @@ describe('gravity example', () => { } if (((current.collisionBehavior == 1u) && ((*other).collisionBehavior == 1u))) { if (isSmaller(currentId, otherId)) { - var dir = normalize((current.position - (*other).position)); + let dir = normalize((current.position - (*other).position)); current.position = ((*other).position + (dir * (radiusOf(current) + radiusOf((*other))))); } - var posDiff = (current.position - (*other).position); - var velDiff = (current.velocity - (*other).velocity); + let posDiff = (current.position - (*other).position); + let velDiff = (current.velocity - (*other).velocity); let posDiffFactor = ((((2f * (*other).mass) / (current.mass + (*other).mass)) * dot(velDiff, posDiff)) / dot(posDiff, posDiff)); current.velocity = ((current.velocity - (posDiff * posDiffFactor)) * 0.99f); } @@ -163,7 +163,7 @@ describe('gravity example', () => { } let dist = max((radiusOf(current) + radiusOf((*other))), distance(current.position, (*other).position)); let gravityForce = (((current.mass * (*other).mass) / dist) / dist); - var direction = normalize(((*other).position - current.position)); + let direction = normalize(((*other).position - current.position)); current.velocity += ((direction * (gravityForce / current.mass)) * dt); } current.position += (current.velocity * dt); @@ -188,7 +188,7 @@ describe('gravity example', () => { } @vertex fn skyBoxVertex(@location(0) _arg_position: vec3f) -> skyBoxVertex_Output { - var viewPos = (camera.view * vec4f(_arg_position, 0f)).xyz; + let viewPos = (camera.view * vec4f(_arg_position, 0f)).xyz; return skyBoxVertex_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } @@ -244,9 +244,9 @@ describe('gravity example', () => { @vertex fn mainVertex(@location(0) _arg_position: vec3f, @location(1) _arg_normal: vec3f, @location(2) _arg_uv: vec2f, @builtin(instance_index) _arg_instanceIndex: u32) -> mainVertex_Output { let currentBody = (&celestialBodies[_arg_instanceIndex]); - var worldPosition = ((*currentBody).position + (_arg_position.xyz * radiusOf((*currentBody)))); + let worldPosition = ((*currentBody).position + (_arg_position.xyz * radiusOf((*currentBody)))); let camera = (&camera_1); - var positionOnCanvas = (((*camera).projection * (*camera).view) * vec4f(worldPosition, 1f)); + let positionOnCanvas = (((*camera).projection * (*camera).view) * vec4f(worldPosition, 1f)); return mainVertex_Output(positionOnCanvas, _arg_uv, _arg_normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); } @@ -269,13 +269,13 @@ describe('gravity example', () => { if ((_arg_0.destroyed == 1u)) { discard;; } - var lightColor = vec3f(1, 0.8999999761581421, 0.8999999761581421); - var textureColor = textureSample(celestialBodyTextures, sampler_1, _arg_0.uv, _arg_0.sphereTextureIndex).rgb; - var ambient = ((textureColor * lightColor) * _arg_0.ambientLightFactor); + let lightColor = vec3f(1, 0.8999999761581421, 0.8999999761581421); + let textureColor = textureSample(celestialBodyTextures, sampler_1, _arg_0.uv, _arg_0.sphereTextureIndex).rgb; + let ambient = ((textureColor * lightColor) * _arg_0.ambientLightFactor); let normal = _arg_0.normals; - var lightDirection = normalize((lightSource - _arg_0.worldPosition)); + let lightDirection = normalize((lightSource - _arg_0.worldPosition)); let cosTheta = dot(normal, lightDirection); - var diffuse = ((textureColor * lightColor) * max(0f, cosTheta)); + let diffuse = ((textureColor * lightColor) * max(0f, cosTheta)); return vec4f((ambient + diffuse), 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts index 059828005e..f825d77895 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts @@ -69,15 +69,15 @@ describe('image tuning example', () => { } @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { - var color = textureSample(imageView, imageSampler, _arg_0.uv).rgb; + let color = textureSample(imageView, imageSampler, _arg_0.uv).rgb; let inputLuminance = dot(color, vec3f(0.29899999499320984, 0.5870000123977661, 0.11400000005960464)); - var normColor = saturate(((color - lut.min) / (lut.max - lut.min))); - var lutColor = select(color, textureSampleLevel(currentLUTTexture, lutSampler, normColor, 0).rgb, bool(lut.enabled)); - var lutColorNormalized = saturate(lutColor); + let normColor = saturate(((color - lut.min) / (lut.max - lut.min))); + let lutColor = select(color, textureSampleLevel(currentLUTTexture, lutSampler, normColor, 0).rgb, bool(lut.enabled)); + let lutColorNormalized = saturate(lutColor); let exposureBiased = (adjustments.exposure * 0.25f); - var exposureColor = clamp((lutColorNormalized * pow(2f, exposureBiased)), vec3f(), vec3f(2)); + let exposureColor = clamp((lutColorNormalized * pow(2f, exposureBiased)), vec3f(), vec3f(2)); let exposureLuminance = clamp((inputLuminance * pow(2f, exposureBiased)), 0f, 2f); - var contrastColor = (((exposureColor - 0.5f) * adjustments.contrast) + 0.5f); + let contrastColor = (((exposureColor - 0.5f) * adjustments.contrast) + 0.5f); let contrastLuminance = (((exposureLuminance - 0.5f) * adjustments.contrast) + 0.5f); let contrastColorLuminance = dot(contrastColor, vec3f(0.29899999499320984, 0.5870000123977661, 0.11400000005960464)); let highlightShift = (adjustments.highlights - 1f); @@ -86,15 +86,15 @@ describe('image tuning example', () => { let highlightWeight = smoothstep(0.5f, 1f, contrastColorLuminance); let highlightLuminanceAdjust = (contrastLuminance * highlightFactor); let highlightLuminance = mix(contrastLuminance, saturate(highlightLuminanceAdjust), highlightWeight); - var highlightColor = mix(contrastColor, saturate((contrastColor * highlightFactor)), highlightWeight); + let highlightColor = mix(contrastColor, saturate((contrastColor * highlightFactor)), highlightWeight); let shadowWeight = (1f - contrastColorLuminance); - var shadowAdjust = pow(highlightColor, vec3f((1f / adjustments.shadows))); + let shadowAdjust = pow(highlightColor, vec3f((1f / adjustments.shadows))); let shadowLuminanceAdjust = pow(highlightLuminance, (1f / adjustments.shadows)); - var toneColor = mix(highlightColor, shadowAdjust, shadowWeight); + let toneColor = mix(highlightColor, shadowAdjust, shadowWeight); let toneLuminance = mix(highlightLuminance, shadowLuminanceAdjust, shadowWeight); - var finalToneColor = saturate(toneColor); - var grayscaleColor = vec3f(toneLuminance); - var finalColor = mix(grayscaleColor, finalToneColor, adjustments.saturation); + let finalToneColor = saturate(toneColor); + let grayscaleColor = vec3f(toneLuminance); + let finalColor = mix(grayscaleColor, finalToneColor, adjustments.saturation); return vec4f(finalColor, 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts index 976bdbaaf2..fefdfc76f6 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts @@ -93,14 +93,14 @@ describe('jelly-slider example', () => { } fn getRay(ndc: vec2f) -> Ray { - var clipPos = vec4f(ndc.x, ndc.y, -1f, 1f); + let clipPos = vec4f(ndc.x, ndc.y, -1f, 1f); let invView = (&uniform_1.viewInv); let invProj = (&uniform_1.projInv); - var viewPos = ((*invProj) * clipPos); - var viewPosNormalized = vec4f((viewPos.xyz / viewPos.w), 1f); - var worldPos = ((*invView) * viewPosNormalized); - var rayOrigin = (*invView)[3i].xyz; - var rayDir = normalize((worldPos.xyz - rayOrigin)); + let viewPos = ((*invProj) * clipPos); + let viewPosNormalized = vec4f((viewPos.xyz / viewPos.w), 1f); + let worldPos = ((*invView) * viewPosNormalized); + let rayOrigin = (*invView)[3i].xyz; + let rayDir = normalize((worldPos.xyz - rayOrigin)); return Ray(rayOrigin, rayDir); } @@ -109,7 +109,7 @@ describe('jelly-slider example', () => { } fn sdRoundedBox2d(point: vec2f, size: vec2f, cornerRadius: f32) -> f32 { - var d = ((abs(point) - size) + vec2f(cornerRadius)); + let d = ((abs(point) - size) + vec2f(cornerRadius)); return ((length(max(d, vec2f())) + min(max(d.x, d.y), 0f)) - cornerRadius); } @@ -119,7 +119,7 @@ describe('jelly-slider example', () => { } fn opExtrudeY(point: vec3f, dd: f32, halfHeight: f32) -> f32 { - var w = vec2f(dd, (abs(point.y) - halfHeight)); + let w = vec2f(dd, (abs(point.y) - halfHeight)); return (min(max(w.x, w.y), 0f) + length(max(w, vec2f()))); } @@ -177,16 +177,16 @@ describe('jelly-slider example', () => { } fn getNormalFromSdf(position: vec3f, epsilon: f32) -> vec3f { - var k = vec3f(1, -1, 0); - var offset1 = (k.xyy * epsilon); - var offset2 = (k.yyx * epsilon); - var offset3 = (k.yxy * epsilon); - var offset4 = (k.xxx * epsilon); - var sample1 = (offset1 * getMainSceneDist((position + offset1))); - var sample2 = (offset2 * getMainSceneDist((position + offset2))); - var sample3 = (offset3 * getMainSceneDist((position + offset3))); - var sample4 = (offset4 * getMainSceneDist((position + offset4))); - var gradient = (((sample1 + sample2) + sample3) + sample4); + let k = vec3f(1, -1, 0); + let offset1 = (k.xyy * epsilon); + let offset2 = (k.yyx * epsilon); + let offset3 = (k.yxy * epsilon); + let offset4 = (k.xxx * epsilon); + let sample1 = (offset1 * getMainSceneDist((position + offset1))); + let sample2 = (offset2 * getMainSceneDist((position + offset2))); + let sample3 = (offset3 * getMainSceneDist((position + offset3))); + let sample4 = (offset4 * getMainSceneDist((position + offset4))); + let gradient = (((sample1 + sample2) + sample3) + sample4); return normalize(gradient); } @@ -215,10 +215,10 @@ describe('jelly-slider example', () => { return ((vec3f(1) * edgeDarkening) * (lightGradient * 0.5f)); } else { - var finalUV = vec2f((((position.x - ((position.z * lightDir.x) * sign(lightDir.z))) * 0.5f) + 0.5f), ((1f - ((-(position.z) / lightDir.z) * 0.5f)) - 0.2f)); - var data = textureSampleLevel(bezierTexture, filteringSampler, finalUV, 0); + let finalUV = vec2f((((position.x - ((position.z * lightDir.x) * sign(lightDir.z))) * 0.5f) + 0.5f), ((1f - ((-(position.z) / lightDir.z) * 0.5f)) - 0.2f)); + let data = textureSampleLevel(bezierTexture, filteringSampler, finalUV, 0); let jellySaturation = mix(0f, data.y, saturate(((position.x * 1.5f) + 1.1f))); - var shadowColor = mix(vec3f(), (*jellyColor).rgb, jellySaturation); + let shadowColor = mix(vec3f(), (*jellyColor).rgb, jellySaturation); let contrast = ((20f * saturate(finalUV.y)) * (0.8f + (endCapX * 0.2f))); const shadowOffset = -0.3; const featherSharpness = 10f; @@ -229,17 +229,17 @@ describe('jelly-slider example', () => { } fn calculateLighting(hitPosition: vec3f, normal: vec3f, rayOrigin: vec3f) -> vec3f { - var lightDir = -(lightUniform.direction); - var fakeShadow = getFakeShadow(hitPosition, lightDir); + let lightDir = -(lightUniform.direction); + let fakeShadow = getFakeShadow(hitPosition, lightDir); let diffuse = max(dot(normal, lightDir), 0f); - var viewDir = normalize((rayOrigin - hitPosition)); - var reflectDir = reflect(-(lightDir), normal); + let viewDir = normalize((rayOrigin - hitPosition)); + let reflectDir = reflect(-(lightDir), normal); let specularFactor = pow(max(dot(viewDir, reflectDir), 0f), 10f); - var specular = (lightUniform.color * (specularFactor * 0.6f)); - var baseColor = vec3f(0.8999999761581421); - var directionalLight = (((baseColor * lightUniform.color) * diffuse) * fakeShadow); - var ambientLight = ((baseColor * vec3f(0.6000000238418579)) * 0.6f); - var finalSpecular = (specular * fakeShadow); + let specular = (lightUniform.color * (specularFactor * 0.6f)); + let baseColor = vec3f(0.8999999761581421); + let directionalLight = (((baseColor * lightUniform.color) * diffuse) * fakeShadow); + let ambientLight = ((baseColor * vec3f(0.6000000238418579)) * 0.6f); + let finalSpecular = (specular * fakeShadow); return saturate(((directionalLight + ambientLight) + finalSpecular)); } @@ -261,28 +261,28 @@ describe('jelly-slider example', () => { } fn sdInflatedPolyline2D(p: vec2f) -> LineInfo { - var bbox = getSliderBbox(); - var uv = vec2f(((p.x - bbox.left) / (bbox.right - bbox.left)), ((bbox.top - p.y) / (bbox.top - bbox.bottom))); - var clampedUV = saturate(uv); - var sampledColor = textureSampleLevel(bezierTexture, filteringSampler, clampedUV, 0); + let bbox = getSliderBbox(); + let uv = vec2f(((p.x - bbox.left) / (bbox.right - bbox.left)), ((bbox.top - p.y) / (bbox.top - bbox.bottom))); + let clampedUV = saturate(uv); + let sampledColor = textureSampleLevel(bezierTexture, filteringSampler, clampedUV, 0); let segUnsigned = sampledColor.x; let progress = sampledColor.y; - var normal = sampledColor.zw; + let normal = sampledColor.zw; return LineInfo(progress, segUnsigned, normal); } fn opExtrudeZ(point: vec3f, dd: f32, halfHeight: f32) -> f32 { - var w = vec2f(dd, (abs(point.z) - halfHeight)); + let w = vec2f(dd, (abs(point.z) - halfHeight)); return (min(max(w.x, w.y), 0f) + length(max(w, vec2f()))); } fn sliderApproxDist(position: vec3f) -> f32 { - var bbox = getSliderBbox(); - var p = position.xy; + let bbox = getSliderBbox(); + let p = position.xy; if (((((p.x < bbox.left) || (p.x > bbox.right)) || (p.y < bbox.bottom)) || (p.y > bbox.top))) { return 1000000000; } - var poly2D = sdInflatedPolyline2D(p); + let poly2D = sdInflatedPolyline2D(p); let dist3D = (opExtrudeZ(position, poly2D.distance, 0.17f) - 0.024f); return dist3D; } @@ -299,7 +299,7 @@ describe('jelly-slider example', () => { const stepDistance = 0.03333333333333333; for (var i = 1; (i <= 3i); i++) { let sampleHeight = (stepDistance * f32(i)); - var samplePosition = (position + (normal * sampleHeight)); + let samplePosition = (position + (normal * sampleHeight)); let distanceToSurface = (getSceneDistForAO(samplePosition) - 5e-3f); let occlusionContribution = max(0f, (sampleHeight - distanceToSurface)); totalOcclusion += (occlusionContribution * sampleWeight); @@ -314,13 +314,13 @@ describe('jelly-slider example', () => { fn applyAO(litColor: vec3f, hitPosition: vec3f, normal: vec3f) -> vec4f { let ao = calculateAO(hitPosition, normal); - var finalColor = (litColor * ao); + let finalColor = (litColor * ao); return vec4f(finalColor, 1f); } fn renderBackground(rayOrigin: vec3f, rayDirection: vec3f, backgroundHitDist: f32, offset: f32) -> vec4f { - var hitPosition = (rayOrigin + (rayDirection * backgroundHitDist)); - var percentageSample = renderPercentageOnGround(hitPosition, vec3f(0.7200000286102295, 0, 0), u32(((endCapUniform.x + 0.43f) * 84f))); + let hitPosition = (rayOrigin + (rayDirection * backgroundHitDist)); + let percentageSample = renderPercentageOnGround(hitPosition, vec3f(0.7200000286102295, 0, 0), u32(((endCapUniform.x + 0.43f) * 84f))); var highlights = 0f; const highlightWidth = 1f; const highlightHeight = 0.2; @@ -335,8 +335,8 @@ describe('jelly-slider example', () => { if (((abs((hitPosition.x + offsetX)) < highlightWidth) && (abs((hitPosition.z + offsetZ)) < highlightHeight))) { let uvX_orig = ((((hitPosition.x + offsetX) + (highlightWidth * 2f)) / highlightWidth) * 0.5f); let uvZ_orig = ((((hitPosition.z + offsetZ) + (highlightHeight * 2f)) / highlightHeight) * 0.5f); - var centeredUV = vec2f((uvX_orig - 0.5f), (uvZ_orig - 0.5f)); - var finalUV = vec2f(centeredUV.x, (1f - (pow((abs((centeredUV.y - 0.5f)) * 2f), 2f) * 0.3f))); + let centeredUV = vec2f((uvX_orig - 0.5f), (uvZ_orig - 0.5f)); + let finalUV = vec2f(centeredUV.x, (1f - (pow((abs((centeredUV.y - 0.5f)) * 2f), 2f) * 0.3f))); let density = max(0f, ((textureSampleLevel(bezierTexture, filteringSampler, finalUV, 0).x - 0.25f) * 8f)); let fadeX = smoothstep(0f, -0.2f, (hitPosition.x - endCapX)); let fadeZ = (1f - pow((abs((centeredUV.y - 0.5f)) * 2f), 3f)); @@ -345,15 +345,15 @@ describe('jelly-slider example', () => { highlights = ((((pow(density, 3f) * edgeFade) * 3f) * (1f + (*lightDir).z)) / 1.5f); } let originYBound = saturate((rayOrigin.y + 0.01f)); - var posOffset = (hitPosition + (vec3f(0, 1, 0) * ((offset * (originYBound / (1f + originYBound))) * (1f + (randFloat01() / 2f))))); - var newNormal = getNormalMain(posOffset); + let posOffset = (hitPosition + (vec3f(0, 1, 0) * ((offset * (originYBound / (1f + originYBound))) * (1f + (randFloat01() / 2f))))); + let newNormal = getNormalMain(posOffset); let jellyColor = (&jellyColorUniform); let sqDist = sqLength((hitPosition - vec3f(endCapX, 0f, 0f))); - var bounceLight = ((*jellyColor).rgb * ((1f / ((sqDist * 15f) + 1f)) * 0.4f)); - var sideBounceLight = (((*jellyColor).rgb * ((1f / ((sqDist * 40f) + 1f)) * 0.3f)) * abs(newNormal.z)); - var litColor = calculateLighting(posOffset, newNormal, rayOrigin); - var backgroundColor = ((applyAO((vec3f(1) * litColor), posOffset, newNormal) + vec4f(bounceLight, 0f)) + vec4f(sideBounceLight, 0f)); - var textColor = saturate((backgroundColor.rgb * vec3f(0.5))); + let bounceLight = ((*jellyColor).rgb * ((1f / ((sqDist * 15f) + 1f)) * 0.4f)); + let sideBounceLight = (((*jellyColor).rgb * ((1f / ((sqDist * 40f) + 1f)) * 0.3f)) * abs(newNormal.z)); + let litColor = calculateLighting(posOffset, newNormal, rayOrigin); + let backgroundColor = ((applyAO((vec3f(1) * litColor), posOffset, newNormal) + vec4f(bounceLight, 0f)) + vec4f(sideBounceLight, 0f)); + let textColor = saturate((backgroundColor.rgb * vec3f(0.5))); return vec4f((mix(backgroundColor.rgb, textColor, percentageSample.x) * (1f + highlights)), 1f); } @@ -364,11 +364,11 @@ describe('jelly-slider example', () => { } fn intersectBox(rayOrigin: vec3f, rayDirection: vec3f, boxMin: vec3f, boxMax: vec3f) -> BoxIntersection { - var invDir = (vec3f(1) / rayDirection); - var t1 = ((boxMin - rayOrigin) * invDir); - var t2 = ((boxMax - rayOrigin) * invDir); - var tMinVec = min(t1, t2); - var tMaxVec = max(t1, t2); + let invDir = (vec3f(1) / rayDirection); + let t1 = ((boxMin - rayOrigin) * invDir); + let t2 = ((boxMax - rayOrigin) * invDir); + let tMinVec = min(t1, t2); + let tMaxVec = max(t1, t2); let tMin = max(max(tMinVec.x, tMinVec.y), tMinVec.z); let tMax = min(min(tMaxVec.x, tMaxVec.y), tMaxVec.z); var result = BoxIntersection(); @@ -388,10 +388,10 @@ describe('jelly-slider example', () => { fn cap3D(position: vec3f) -> f32 { let endCap = (&endCapUniform); - var secondLastPoint = vec2f((*endCap).x, (*endCap).y); - var lastPoint = vec2f((*endCap).z, (*endCap).w); + let secondLastPoint = vec2f((*endCap).x, (*endCap).y); + let lastPoint = vec2f((*endCap).z, (*endCap).w); let angle = atan2((lastPoint.y - secondLastPoint.y), (lastPoint.x - secondLastPoint.x)); - var rot = mat2x2f(cos(angle), -(sin(angle)), sin(angle), cos(angle)); + let rot = mat2x2f(cos(angle), -(sin(angle)), sin(angle), cos(angle)); var pieP = (position - vec3f(secondLastPoint, 0f)); pieP = vec3f((rot * pieP.xy), pieP.z); let hmm = sdPie(pieP.zx, vec2f(1, 0), 0.17f); @@ -400,7 +400,7 @@ describe('jelly-slider example', () => { } fn sliderSdf3D(position: vec3f) -> LineInfo { - var poly2D = sdInflatedPolyline2D(position.xy); + let poly2D = sdInflatedPolyline2D(position.xy); var finalDist = 0f; if ((poly2D.t > 0.94f)) { finalDist = cap3D(position); @@ -420,7 +420,7 @@ describe('jelly-slider example', () => { fn getSceneDist(position: vec3f) -> HitInfo { let mainScene = getMainSceneDist(position); - var poly3D = sliderSdf3D(position); + let poly3D = sliderSdf3D(position); var hitInfo = HitInfo(); if ((poly3D.distance < mainScene)) { hitInfo.distance = poly3D.distance; @@ -435,16 +435,16 @@ describe('jelly-slider example', () => { } fn getNormalFromSdf_1(position: vec3f, epsilon: f32) -> vec3f { - var k = vec3f(1, -1, 0); - var offset1 = (k.xyy * epsilon); - var offset2 = (k.yyx * epsilon); - var offset3 = (k.yxy * epsilon); - var offset4 = (k.xxx * epsilon); - var sample1 = (offset1 * cap3D((position + offset1))); - var sample2 = (offset2 * cap3D((position + offset2))); - var sample3 = (offset3 * cap3D((position + offset3))); - var sample4 = (offset4 * cap3D((position + offset4))); - var gradient = (((sample1 + sample2) + sample3) + sample4); + let k = vec3f(1, -1, 0); + let offset1 = (k.xyy * epsilon); + let offset2 = (k.yyx * epsilon); + let offset3 = (k.yxy * epsilon); + let offset4 = (k.xxx * epsilon); + let sample1 = (offset1 * cap3D((position + offset1))); + let sample2 = (offset2 * cap3D((position + offset2))); + let sample3 = (offset3 * cap3D((position + offset3))); + let sample4 = (offset4 * cap3D((position + offset4))); + let gradient = (((sample1 + sample2) + sample3) + sample4); return normalize(gradient); } @@ -462,15 +462,15 @@ describe('jelly-slider example', () => { const edgeContrib = 0.9; let zContrib = (1f - edgeContrib); let zDirection = sign(position.z); - var zAxisVector = vec3f(0f, 0f, zDirection); + let zAxisVector = vec3f(0f, 0f, zDirection); let edgeBlendDistance = ((edgeContrib * 0.024f) + (zContrib * 0.17f)); let blendFactor = smoothstep(edgeBlendDistance, 0f, ((zDistance * zContrib) + (edgeDistance * edgeContrib))); - var normal2D = vec3f((*gradient2D).xy, 0f); - var blendedNormal = mix(zAxisVector, normal2D, ((blendFactor * 0.5f) + 0.5f)); + let normal2D = vec3f((*gradient2D).xy, 0f); + let blendedNormal = mix(zAxisVector, normal2D, ((blendFactor * 0.5f) + 0.5f)); var normal = normalize(blendedNormal); if ((hitInfo.t > 0.94f)) { let ratio = ((hitInfo.t - 0.94f) / 0.02f); - var fullNormal = getNormalCap(position); + let fullNormal = getNormalCap(position); normal = normalize(mix(normal, fullNormal, ratio)); } return normal; @@ -494,7 +494,7 @@ describe('jelly-slider example', () => { var distanceFromOrigin = 0f; var hit = 0f; for (var i = 0; (i < 6i); i++) { - var p = (rayOrigin + (rayDirection * distanceFromOrigin)); + let p = (rayOrigin + (rayDirection * distanceFromOrigin)); hit = getMainSceneDist(p); distanceFromOrigin += hit; if (((distanceFromOrigin > 10f) || (hit < 0.01f))) { @@ -515,19 +515,19 @@ describe('jelly-slider example', () => { var totalSteps = 0u; var backgroundDist = 0f; for (var i = 0; (i < 64i); i++) { - var p = (rayOrigin + (rayDirection * backgroundDist)); + let p = (rayOrigin + (rayDirection * backgroundDist)); let hit = getMainSceneDist(p); backgroundDist += hit; if ((hit < 1e-3f)) { break; } } - var background = renderBackground(rayOrigin, rayDirection, backgroundDist, 0f); - var bbox = getSliderBbox(); + let background = renderBackground(rayOrigin, rayDirection, backgroundDist, 0f); + let bbox = getSliderBbox(); const zDepth = 0.25f; - var sliderMin = vec3f(bbox.left, bbox.bottom, -(zDepth)); - var sliderMax = vec3f(bbox.right, bbox.top, zDepth); - var intersection = intersectBox(rayOrigin, rayDirection, sliderMin, sliderMax); + let sliderMin = vec3f(bbox.left, bbox.bottom, -(zDepth)); + let sliderMax = vec3f(bbox.right, bbox.top, zDepth); + let intersection = intersectBox(rayOrigin, rayDirection, sliderMin, sliderMax); if (!intersection.hit) { return background; } @@ -536,40 +536,40 @@ describe('jelly-slider example', () => { if ((totalSteps >= 64u)) { break; } - var currentPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); - var hitInfo = getSceneDist(currentPosition); + let currentPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); + let hitInfo = getSceneDist(currentPosition); distanceFromOrigin += hitInfo.distance; totalSteps++; if ((hitInfo.distance < 1e-3f)) { - var hitPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); + let hitPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); if (!(hitInfo.objectType == 1i)) { break; } - var N = getNormal(hitPosition, hitInfo); + let N = getNormal(hitPosition, hitInfo); let I = rayDirection; let cosi = min(1f, max(0f, dot(-(I), N))); let F = fresnelSchlick(cosi, 1f, 1.4199999570846558f); - var reflection = saturate(vec3f((hitPosition.y + 0.2f))); + let reflection = saturate(vec3f((hitPosition.y + 0.2f))); const eta = 0.7042253521126761; let k = (1f - ((eta * eta) * (1f - (cosi * cosi)))); var refractedColor = vec3f(); if ((k > 0f)) { - var refrDir = normalize(((I * eta) + (N * ((eta * cosi) - sqrt(k))))); - var p = (hitPosition + (refrDir * 2e-3f)); - var exitPos = (p + (refrDir * 2e-3f)); - var env = rayMarchNoJelly(exitPos, refrDir); + let refrDir = normalize(((I * eta) + (N * ((eta * cosi) - sqrt(k))))); + let p = (hitPosition + (refrDir * 2e-3f)); + let exitPos = (p + (refrDir * 2e-3f)); + let env = rayMarchNoJelly(exitPos, refrDir); let progress = hitInfo.t; let jellyColor = (&jellyColorUniform); - var scatterTint = ((*jellyColor).rgb * 1.5f); + let scatterTint = ((*jellyColor).rgb * 1.5f); const density = 20f; - var absorb = ((vec3f(1) - (*jellyColor).rgb) * density); - var T = beerLambert((absorb * pow(progress, 2f)), 0.08f); - var lightDir = -(lightUniform.direction); + let absorb = ((vec3f(1) - (*jellyColor).rgb) * density); + let T = beerLambert((absorb * pow(progress, 2f)), 0.08f); + let lightDir = -(lightUniform.direction); let forward = max(0f, dot(lightDir, refrDir)); - var scatter = (scatterTint * ((3f * forward) * pow(progress, 3f))); + let scatter = (scatterTint * ((3f * forward) * pow(progress, 3f))); refractedColor = ((env * T) + scatter); } - var jelly = ((reflection * F) + (refractedColor * (1f - F))); + let jelly = ((reflection * F) + (refractedColor * (1f - F))); return vec4f(jelly, 1f); } if ((distanceFromOrigin > backgroundDist)) { @@ -585,9 +585,9 @@ describe('jelly-slider example', () => { @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); - var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); - var ray = getRay(ndc); - var color = rayMarch(ray.origin, ray.direction, _arg_0.uv); + let ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); + let ray = getRay(ndc); + let color = rayMarch(ray.origin, ray.direction, _arg_0.uv); return vec4f(tanh((color.rgb * 1.3f)), 1f); } @@ -598,34 +598,34 @@ describe('jelly-slider example', () => { @group(0) @binding(2) var outputTexture: texture_storage_2d; @compute @workgroup_size(16, 16) fn taaResolveFn(@builtin(global_invocation_id) gid: vec3u) { - var currentColor = textureLoad(currentTexture, gid.xy, 0); - var historyColor = textureLoad(historyTexture, gid.xy, 0); + let currentColor = textureLoad(currentTexture, gid.xy, 0); + let historyColor = textureLoad(historyTexture, gid.xy, 0); var minColor = vec3f(9999); var maxColor = vec3f(-9999); - var dimensions = textureDimensions(currentTexture); + let dimensions = textureDimensions(currentTexture); // unrolled iteration #0 { // unrolled iteration #0 { - var sampleCoord = (vec2i(gid.xy) + vec2i(-1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(-1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #1 { - var sampleCoord = (vec2i(gid.xy) + vec2i(-1, 0)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(-1, 0)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #2 { - var sampleCoord = (vec2i(gid.xy) + vec2i(-1, 1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(-1, 1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } @@ -634,25 +634,25 @@ describe('jelly-slider example', () => { { // unrolled iteration #0 { - var sampleCoord = (vec2i(gid.xy) + vec2i(0, -1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(0, -1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #1 { - var sampleCoord = (vec2i(gid.xy) + vec2i()); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i()); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #2 { - var sampleCoord = (vec2i(gid.xy) + vec2i(0, 1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(0, 1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } @@ -661,31 +661,31 @@ describe('jelly-slider example', () => { { // unrolled iteration #0 { - var sampleCoord = (vec2i(gid.xy) + vec2i(1, -1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(1, -1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #1 { - var sampleCoord = (vec2i(gid.xy) + vec2i(1, 0)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(1, 0)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #2 { - var sampleCoord = (vec2i(gid.xy) + vec2i(1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } } - var historyColorClamped = clamp(historyColor.rgb, minColor, maxColor); - var uv = (vec2f(gid.xy) / vec2f(dimensions.xy)); + let historyColorClamped = clamp(historyColor.rgb, minColor, maxColor); + let uv = (vec2f(gid.xy) / vec2f(dimensions.xy)); const textRegionMinX = 0.7099999785423279f; const textRegionMaxX = 0.8500000238418579f; const textRegionMinY = 0.4699999988079071f; @@ -697,7 +697,7 @@ describe('jelly-slider example', () => { let fadeOutY = (1f - smoothstep((textRegionMaxY - borderSize), (textRegionMaxY + borderSize), uv.y)); let inTextRegion = (((fadeInX * fadeOutX) * fadeInY) * fadeOutY); let blendFactor = mix(0.8999999761581421f, 0.699999988079071f, inTextRegion); - var resolvedColor = vec4f(mix(currentColor.rgb, historyColorClamped, blendFactor), 1f); + let resolvedColor = vec4f(mix(currentColor.rgb, historyColorClamped, blendFactor), 1f); textureStore(outputTexture, vec2u(gid.x, gid.y), resolvedColor); } @@ -738,10 +738,10 @@ describe('jelly-slider example', () => { } fn sdBezier(point: vec2f, A: vec2f, B: vec2f, C: vec2f) -> f32 { - var a = (B - A); - var b = ((A - (B * 2f)) + C); - var c = (a * 2f); - var d = (A - point); + let a = (B - A); + let b = ((A - (B * 2f)) + C); + let c = (a * 2f); + let d = (A - point); let dotB = max(dot(b, b), 1e-4f); let kk = (1f / dotB); let kx = (kk * dot(a, b)); @@ -754,8 +754,8 @@ describe('jelly-slider example', () => { var h = ((q * q) + (4f * p3)); if ((h >= 0f)) { h = sqrt(h); - var x = ((vec2f(h, -(h)) - q) * 0.5f); - var uv = (sign(x) * pow(abs(x), vec2f(0.3333333432674408))); + let x = ((vec2f(h, -(h)) - q) * 0.5f); + let uv = (sign(x) * pow(abs(x), vec2f(0.3333333432674408))); let t = saturate(((uv.x + uv.y) - kx)); res = dot2((d + ((c + (b * t)) * t))); } @@ -764,22 +764,22 @@ describe('jelly-slider example', () => { let v = (acos((q / ((p * z) * 2f))) / 3f); let m = cos(v); let n = (sin(v) * 1.732050808f); - var t = saturate(((vec3f((m + m), (-(n) - m), (n - m)) * z) - kx)); + let t = saturate(((vec3f((m + m), (-(n) - m), (n - m)) * z) - kx)); res = min(dot2((d + ((c + (b * t.x)) * t.x))), dot2((d + ((c + (b * t.y)) * t.y)))); } return sqrt(res); } fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var size = textureDimensions(bezierWriteView); - var pixelUV = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(size)); - var sliderPos = vec2f((-1.0189999997615815f + (pixelUV.x * 2.108999973535538f)), (0.65f - (pixelUV.y * 0.95f))); + let size = textureDimensions(bezierWriteView); + let pixelUV = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(size)); + let sliderPos = vec2f((-1.0189999997615815f + (pixelUV.x * 2.108999973535538f)), (0.65f - (pixelUV.y * 0.95f))); var minDist = 1e+10f; var closestSegment = 0i; var closestT = 0f; const epsilon = 0.029999999329447746f; - var xOffset = vec2f(epsilon, 0f); - var yOffset = vec2f(0f, epsilon); + let xOffset = vec2f(epsilon, 0f); + let yOffset = vec2f(0f, epsilon); var xPlusDist = 1e+10f; var xMinusDist = 1e+10f; var yPlusDist = 1e+10f; @@ -792,8 +792,8 @@ describe('jelly-slider example', () => { if ((dist < minDist)) { minDist = dist; closestSegment = i; - var AB = ((*B) - (*A)); - var AP = (sliderPos - (*A)); + let AB = ((*B) - (*A)); + let AP = (sliderPos - (*A)); let ABLength = length(AB); if ((ABLength > 0f)) { closestT = clamp((dot(AP, AB) / (ABLength * ABLength)), 0f, 1f); diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts index 6e507a2602..09ef507fb1 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts @@ -64,14 +64,14 @@ describe('jelly switch example', () => { } fn getRay(ndc: vec2f) -> Ray { - var clipPos = vec4f(ndc.x, ndc.y, -1f, 1f); + let clipPos = vec4f(ndc.x, ndc.y, -1f, 1f); let invView = (&uniform_1.viewInv); let invProj = (&uniform_1.projInv); - var viewPos = ((*invProj) * clipPos); - var viewPosNormalized = vec4f((viewPos.xyz / viewPos.w), 1f); - var worldPos = ((*invView) * viewPosNormalized); - var rayOrigin = (*invView)[3i].xyz; - var rayDir = normalize((worldPos.xyz - rayOrigin)); + let viewPos = ((*invProj) * clipPos); + let viewPosNormalized = vec4f((viewPos.xyz / viewPos.w), 1f); + let worldPos = ((*invView) * viewPosNormalized); + let rayOrigin = (*invView)[3i].xyz; + let rayDir = normalize((worldPos.xyz - rayOrigin)); return Ray(rayOrigin, rayDir); } @@ -80,7 +80,7 @@ describe('jelly switch example', () => { } fn sdRoundedBox2d(point: vec2f, size: vec2f, cornerRadius: f32) -> f32 { - var d = ((abs(point) - size) + vec2f(cornerRadius)); + let d = ((abs(point) - size) + vec2f(cornerRadius)); return ((length(max(d, vec2f())) + min(max(d.x, d.y), 0f)) - cornerRadius); } @@ -91,7 +91,7 @@ describe('jelly switch example', () => { } fn opExtrudeY(point: vec3f, dd: f32, halfHeight: f32) -> f32 { - var w = vec2f(dd, (abs(point.y) - halfHeight)); + let w = vec2f(dd, (abs(point.y) - halfHeight)); return (min(max(w.x, w.y), 0f) + length(max(w, vec2f()))); } @@ -121,20 +121,20 @@ describe('jelly switch example', () => { fn opCheapBend(p: vec3f, k: f32) -> vec3f { let c = cos((k * p.x)); let s = sin((k * p.x)); - var m = mat2x2f(c, -(s), s, c); + let m = mat2x2f(c, -(s), s, c); return vec3f((m * p.xy), p.z); } fn sdRoundedBox3d(point: vec3f, size: vec3f, cornerRadius: f32) -> f32 { - var d = ((abs(point) - size) + vec3f(cornerRadius)); + let d = ((abs(point) - size) + vec3f(cornerRadius)); return ((length(max(d, vec3f())) + min(max(max(d.x, d.y), d.z), 0f)) - cornerRadius); } fn getJellyDist(position: vec3f) -> f32 { let state = (&stateUniform); - var jellyOrigin = vec3f(((((*state).progress - 0.5f) * 0.4f) - (((*state).squashX * ((*state).progress - 0.5f)) * 0.2f)), 0.15000000596046448f, 0f); - var jellyInvScale = vec3f((1f - (*state).squashX), 1f, (1f - (*state).squashZ)); - var localPos = opRotateAxisAngle(((position - jellyOrigin) * jellyInvScale), vec3f(0, 0, 1), (*state).wiggleX); + let jellyOrigin = vec3f(((((*state).progress - 0.5f) * 0.4f) - (((*state).squashX * ((*state).progress - 0.5f)) * 0.2f)), 0.15000000596046448f, 0f); + let jellyInvScale = vec3f((1f - (*state).squashX), 1f, (1f - (*state).squashZ)); + let localPos = opRotateAxisAngle(((position - jellyOrigin) * jellyInvScale), vec3f(0, 0, 1), (*state).wiggleX); return sdRoundedBox3d(opCheapBend(localPos, 0.8f), vec3f(0.25, 0.20000001788139343, 0.20000001788139343), 0.1f); } @@ -160,7 +160,7 @@ describe('jelly switch example', () => { fn getApproxNormal(p: vec3f, e: f32) -> vec3f { let dist = getSceneDist(p).distance; - var n = vec3f((getSceneDist((p + vec3f(e, 0f, 0f))).distance - dist), (getSceneDist((p + vec3f(0f, e, 0f))).distance - dist), (getSceneDist((p + vec3f(0f, 0f, e))).distance - dist)); + let n = vec3f((getSceneDist((p + vec3f(e, 0f, 0f))).distance - dist), (getSceneDist((p + vec3f(0f, e, 0f))).distance - dist), (getSceneDist((p + vec3f(0f, 0f, e))).distance - dist)); return normalize(n); } @@ -197,17 +197,17 @@ describe('jelly switch example', () => { } fn calculateLighting(hitPosition: vec3f, normal: vec3f, rayOrigin: vec3f) -> vec3f { - var lightDir = -(lightUniform.direction); - var fakeShadow = getFakeShadow(hitPosition, lightDir); + let lightDir = -(lightUniform.direction); + let fakeShadow = getFakeShadow(hitPosition, lightDir); let diffuse = max(dot(normal, lightDir), 0f); - var viewDir = normalize((rayOrigin - hitPosition)); - var reflectDir = reflect(-(lightDir), normal); + let viewDir = normalize((rayOrigin - hitPosition)); + let reflectDir = reflect(-(lightDir), normal); let specularFactor = pow(max(dot(viewDir, reflectDir), 0f), 10f); - var specular = (lightUniform.color * (specularFactor * 0.6f)); - var baseColor = vec3f(0.8999999761581421); - var directionalLight = (((baseColor * lightUniform.color) * diffuse) * fakeShadow); - var ambientLight = ((baseColor * vec3f(0.6000000238418579)) * 0.6f); - var finalSpecular = (specular * fakeShadow); + let specular = (lightUniform.color * (specularFactor * 0.6f)); + let baseColor = vec3f(0.8999999761581421); + let directionalLight = (((baseColor * lightUniform.color) * diffuse) * fakeShadow); + let ambientLight = ((baseColor * vec3f(0.6000000238418579)) * 0.6f); + let finalSpecular = (specular * fakeShadow); return saturate(((directionalLight + ambientLight) + finalSpecular)); } @@ -225,7 +225,7 @@ describe('jelly switch example', () => { const stepDistance = 0.03333333333333333; for (var i = 1; (i <= 3i); i++) { let sampleHeight = (stepDistance * f32(i)); - var samplePosition = (position + (normal * sampleHeight)); + let samplePosition = (position + (normal * sampleHeight)); let distanceToSurface = (getSceneDistForAO(samplePosition) - 5e-3f); let occlusionContribution = max(0f, (sampleHeight - distanceToSurface)); totalOcclusion += (occlusionContribution * sampleWeight); @@ -240,22 +240,22 @@ describe('jelly switch example', () => { fn applyAO(litColor: vec3f, hitPosition: vec3f, normal: vec3f) -> vec4f { let ao = calculateAO(hitPosition, normal); - var finalColor = (litColor * ao); + let finalColor = (litColor * ao); return vec4f(finalColor, 1f); } fn renderBackground(rayOrigin: vec3f, rayDirection: vec3f, backgroundHitDist: f32) -> vec4f { let state = (&stateUniform); - var hitPosition = (rayOrigin + (rayDirection * backgroundHitDist)); - var newNormal = getNormal(hitPosition); + let hitPosition = (rayOrigin + (rayDirection * backgroundHitDist)); + let newNormal = getNormal(hitPosition); let switchX = (((*state).progress - 0.5f) * 0.4f); let jellyColor = (&jellyColorUniform); let sqDist = sqLength((hitPosition - vec3f(switchX, 0f, 0f))); - var bounceLight = ((*jellyColor).rgb * ((1f / ((sqDist * 15f) + 1f)) * 0.4f)); - var sideBounceLight = (((*jellyColor).rgb * ((1f / ((sqDist * 40f) + 1f)) * 0.3f)) * abs(newNormal.z)); + let bounceLight = ((*jellyColor).rgb * ((1f / ((sqDist * 15f) + 1f)) * 0.4f)); + let sideBounceLight = (((*jellyColor).rgb * ((1f / ((sqDist * 40f) + 1f)) * 0.3f)) * abs(newNormal.z)); let emission = ((smoothstep(0.7f, 1f, (*state).progress) * 2f) + 0.7f); - var litColor = calculateLighting(hitPosition, newNormal, rayOrigin); - var backgroundColor = ((applyAO((select(vec3f(1), vec3f(0.20000000298023224), (darkModeUniform == 1u)) * litColor), hitPosition, newNormal) + vec4f((bounceLight * emission), 0f)) + vec4f((sideBounceLight * emission), 0f)); + let litColor = calculateLighting(hitPosition, newNormal, rayOrigin); + let backgroundColor = ((applyAO((select(vec3f(1), vec3f(0.20000000298023224), (darkModeUniform == 1u)) * litColor), hitPosition, newNormal) + vec4f((bounceLight * emission), 0f)) + vec4f((sideBounceLight * emission), 0f)); return vec4f(backgroundColor.rgb, 1f); } @@ -275,11 +275,11 @@ describe('jelly switch example', () => { } fn intersectBox(rayOrigin: vec3f, rayDirection: vec3f, box: BoundingBox) -> BoxIntersection { - var invDir = (vec3f(1) / rayDirection); - var t1 = ((box.min - rayOrigin) * invDir); - var t2 = ((box.max - rayOrigin) * invDir); - var tMinVec = min(t1, t2); - var tMaxVec = max(t1, t2); + let invDir = (vec3f(1) / rayDirection); + let t1 = ((box.min - rayOrigin) * invDir); + let t2 = ((box.max - rayOrigin) * invDir); + let tMinVec = min(t1, t2); + let tMaxVec = max(t1, t2); let tMin = max(max(tMinVec.x, tMinVec.y), tMinVec.z); let tMax = min(min(tMaxVec.x, tMaxVec.y), tMaxVec.z); var result = BoxIntersection(); @@ -298,7 +298,7 @@ describe('jelly switch example', () => { var distanceFromOrigin = 0f; var hit = 0f; for (var i = 0; (i < 6i); i++) { - var p = (rayOrigin + (rayDirection * distanceFromOrigin)); + let p = (rayOrigin + (rayDirection * distanceFromOrigin)); hit = getMainSceneDist(p); distanceFromOrigin += hit; if (((distanceFromOrigin > 10f) || (hit < 0.01f))) { @@ -319,16 +319,16 @@ describe('jelly switch example', () => { var totalSteps = 0u; var backgroundDist = 0f; for (var i = 0; (i < 64i); i++) { - var p = (rayOrigin + (rayDirection * backgroundDist)); + let p = (rayOrigin + (rayDirection * backgroundDist)); let hit = getMainSceneDist(p); backgroundDist += hit; if ((hit < 1e-3f)) { break; } } - var background = renderBackground(rayOrigin, rayDirection, backgroundDist); - var bbox = getJellyBounds(); - var intersection = intersectBox(rayOrigin, rayDirection, bbox); + let background = renderBackground(rayOrigin, rayDirection, backgroundDist); + let bbox = getJellyBounds(); + let intersection = intersectBox(rayOrigin, rayDirection, bbox); if (!intersection.hit) { return background; } @@ -337,41 +337,41 @@ describe('jelly switch example', () => { if ((totalSteps >= 64u)) { break; } - var currentPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); - var hitInfo = getSceneDist(currentPosition); + let currentPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); + let hitInfo = getSceneDist(currentPosition); distanceFromOrigin += hitInfo.distance; totalSteps++; if ((hitInfo.distance < 1e-3f)) { - var hitPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); + let hitPosition = (rayOrigin + (rayDirection * distanceFromOrigin)); if (!(hitInfo.objectType == 1i)) { break; } - var N = getNormal(hitPosition); + let N = getNormal(hitPosition); let I = rayDirection; let cosi = min(1f, max(0f, dot(-(I), N))); let F = fresnelSchlick(cosi, 1f, 1.4199999570846558f); - var reflection = saturate(vec3f((hitPosition.y + 0.2f))); + let reflection = saturate(vec3f((hitPosition.y + 0.2f))); const eta = 0.7042253521126761; let k = (1f - ((eta * eta) * (1f - (cosi * cosi)))); var refractedColor = vec3f(); if ((k > 0f)) { - var refrDir = normalize(((I * eta) + (N * ((eta * cosi) - sqrt(k))))); - var p = (hitPosition + (refrDir * 2e-3f)); - var exitPos = (p + (refrDir * 2e-3f)); - var env = rayMarchNoJelly(exitPos, refrDir); + let refrDir = normalize(((I * eta) + (N * ((eta * cosi) - sqrt(k))))); + let p = (hitPosition + (refrDir * 2e-3f)); + let exitPos = (p + (refrDir * 2e-3f)); + let env = rayMarchNoJelly(exitPos, refrDir); let jellyColor = (&jellyColorUniform); - var scatterTint = ((*jellyColor).rgb * 1.5f); + let scatterTint = ((*jellyColor).rgb * 1.5f); const density = 20f; - var absorb = ((vec3f(1) - (*jellyColor).rgb) * density); + let absorb = ((vec3f(1) - (*jellyColor).rgb) * density); let state = (&stateUniform); let progress = (saturate(mix(1f, 0.6f, ((hitPosition.y * 1.6666666004392863f) + 0.25f))) * (*state).progress); - var T = beerLambert((absorb * pow(progress, 2f)), 0.08f); - var lightDir = -(lightUniform.direction); + let T = beerLambert((absorb * pow(progress, 2f)), 0.08f); + let lightDir = -(lightUniform.direction); let forward = max(0f, dot(lightDir, refrDir)); - var scatter = (scatterTint * ((3f * forward) * pow(progress, 3f))); + let scatter = (scatterTint * ((3f * forward) * pow(progress, 3f))); refractedColor = ((env * T) + scatter); } - var jelly = ((reflection * F) + (refractedColor * (1f - F))); + let jelly = ((reflection * F) + (refractedColor * (1f - F))); return vec4f(jelly, 1f); } if ((distanceFromOrigin > backgroundDist)) { @@ -387,9 +387,9 @@ describe('jelly switch example', () => { @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); - var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); - var ray = getRay(ndc); - var color = rayMarch(ray.origin, ray.direction, _arg_0.uv); + let ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); + let ray = getRay(ndc); + let color = rayMarch(ray.origin, ray.direction, _arg_0.uv); let exposure = select(1.5, 2., (darkModeUniform == 1u)); return vec4f(tanh((color.rgb * exposure)), 1f); } @@ -401,34 +401,34 @@ describe('jelly switch example', () => { @group(0) @binding(2) var outputTexture: texture_storage_2d; @compute @workgroup_size(16, 16) fn taaResolveFn(@builtin(global_invocation_id) gid: vec3u) { - var currentColor = textureLoad(currentTexture, gid.xy, 0); - var historyColor = textureLoad(historyTexture, gid.xy, 0); + let currentColor = textureLoad(currentTexture, gid.xy, 0); + let historyColor = textureLoad(historyTexture, gid.xy, 0); var minColor = vec3f(9999); var maxColor = vec3f(-9999); - var dimensions = textureDimensions(currentTexture); + let dimensions = textureDimensions(currentTexture); // unrolled iteration #0 { // unrolled iteration #0 { - var sampleCoord = (vec2i(gid.xy) + vec2i(-1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(-1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #1 { - var sampleCoord = (vec2i(gid.xy) + vec2i(-1, 0)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(-1, 0)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #2 { - var sampleCoord = (vec2i(gid.xy) + vec2i(-1, 1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(-1, 1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } @@ -437,25 +437,25 @@ describe('jelly switch example', () => { { // unrolled iteration #0 { - var sampleCoord = (vec2i(gid.xy) + vec2i(0, -1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(0, -1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #1 { - var sampleCoord = (vec2i(gid.xy) + vec2i()); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i()); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #2 { - var sampleCoord = (vec2i(gid.xy) + vec2i(0, 1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(0, 1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } @@ -464,32 +464,32 @@ describe('jelly switch example', () => { { // unrolled iteration #0 { - var sampleCoord = (vec2i(gid.xy) + vec2i(1, -1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(1, -1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #1 { - var sampleCoord = (vec2i(gid.xy) + vec2i(1, 0)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(1, 0)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } // unrolled iteration #2 { - var sampleCoord = (vec2i(gid.xy) + vec2i(1)); - var clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); - var neighborColor = textureLoad(currentTexture, clampedCoord, 0); + let sampleCoord = (vec2i(gid.xy) + vec2i(1)); + let clampedCoord = clamp(sampleCoord, vec2i(), (vec2i(dimensions.xy) - vec2i(1))); + let neighborColor = textureLoad(currentTexture, clampedCoord, 0); minColor = min(minColor, neighborColor.rgb); maxColor = max(maxColor, neighborColor.rgb); } } - var historyColorClamped = clamp(historyColor.rgb, minColor, maxColor); + let historyColorClamped = clamp(historyColor.rgb, minColor, maxColor); const blendFactor = 0.8999999761581421f; - var resolvedColor = vec4f(mix(currentColor.rgb, historyColorClamped, blendFactor), 1f); + let resolvedColor = vec4f(mix(currentColor.rgb, historyColorClamped, blendFactor), 1f); textureStore(outputTexture, vec2u(gid.x, gid.y), resolvedColor); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts index dcf73b0927..295b97b687 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts @@ -29,14 +29,14 @@ describe('jump flood (distance) example', () => { @group(1) @binding(0) var maskTexture: texture_storage_2d; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var size = textureDimensions(writeView); - var pos = vec2f(f32(x), f32(y)); - var uv = (pos / vec2f(size)); + let size = textureDimensions(writeView); + let pos = vec2f(f32(x), f32(y)); + let uv = (pos / vec2f(size)); let mask = textureLoad(maskTexture, vec2i(i32(x), i32(y))).x; let inside = (mask > 0u); - var invalid = vec2f(-1); - var insideCoord = select(invalid, uv, inside); - var outsideCoord = select(uv, invalid, inside); + let invalid = vec2f(-1); + let insideCoord = select(invalid, uv, inside); + let outsideCoord = select(uv, invalid, inside); textureStore(writeView, vec2i(i32(x), i32(y)), vec4f(insideCoord, outsideCoord)); } @@ -59,13 +59,13 @@ describe('jump flood (distance) example', () => { } fn sampleWithOffset(tex: texture_storage_2d, pos: vec2i, offset: vec2i) -> SampleResult { - var dims = textureDimensions(tex); - var samplePos = (pos + offset); + let dims = textureDimensions(tex); + let samplePos = (pos + offset); let outOfBounds = ((((samplePos.x < 0i) || (samplePos.y < 0i)) || (samplePos.x >= i32(dims.x))) || (samplePos.y >= i32(dims.y))); - var safePos = clamp(samplePos, vec2i(), vec2i((dims - 1u))); - var loaded = textureLoad(tex, safePos); - var inside = loaded.xy; - var outside = loaded.zw; + let safePos = clamp(samplePos, vec2i(), vec2i((dims - 1u))); + let loaded = textureLoad(tex, safePos); + let inside = loaded.xy; + let outside = loaded.zw; return SampleResult(select(inside, vec2f(-1), outOfBounds), select(outside, vec2f(-1), outOfBounds)); } @@ -73,8 +73,8 @@ describe('jump flood (distance) example', () => { fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { let offset = offsetUniform; - var size = textureDimensions(readView); - var pos = vec2f(f32(x), f32(y)); + let size = textureDimensions(readView); + let pos = vec2f(f32(x), f32(y)); var bestInsideCoord = vec2f(-1); var bestOutsideCoord = vec2f(-1); var bestInsideDist = 1e+20; @@ -83,7 +83,7 @@ describe('jump flood (distance) example', () => { { // unrolled iteration #0 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); + let 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)) { @@ -101,7 +101,7 @@ describe('jump flood (distance) example', () => { } // unrolled iteration #1 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); + let 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)) { @@ -119,7 +119,7 @@ describe('jump flood (distance) example', () => { } // unrolled iteration #2 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); + let 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)) { @@ -140,7 +140,7 @@ describe('jump flood (distance) example', () => { { // unrolled iteration #0 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); + let 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)) { @@ -158,7 +158,7 @@ describe('jump flood (distance) example', () => { } // unrolled iteration #1 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); + let 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)) { @@ -176,7 +176,7 @@ describe('jump flood (distance) example', () => { } // unrolled iteration #2 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); + let 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)) { @@ -197,7 +197,7 @@ describe('jump flood (distance) example', () => { { // unrolled iteration #0 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); + let 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)) { @@ -215,7 +215,7 @@ describe('jump flood (distance) example', () => { } // unrolled iteration #1 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); + let 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)) { @@ -233,7 +233,7 @@ describe('jump flood (distance) example', () => { } // unrolled iteration #2 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); + let 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)) { @@ -267,11 +267,11 @@ describe('jump flood (distance) example', () => { @group(2) @binding(0) var distTexture: texture_storage_2d; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var pos = vec2f(f32(x), f32(y)); - var size = textureDimensions(readView); - var texel = textureLoad(readView, vec2i(i32(x), i32(y))); - var insideCoord = texel.xy; - var outsideCoord = texel.zw; + let pos = vec2f(f32(x), f32(y)); + let size = textureDimensions(readView); + let texel = textureLoad(readView, vec2i(i32(x), i32(y))); + let insideCoord = texel.xy; + let outsideCoord = texel.zw; var insideDist = 1e+20; var outsideDist = 1e+20; if ((insideCoord.x >= 0f)) { @@ -323,7 +323,7 @@ describe('jump flood (distance) example', () => { } @fragment fn distanceFrag(_arg_0: distanceFrag_Input) -> @location(0) vec4f { - var size = textureDimensions(distTexture); + let size = textureDimensions(distTexture); var dist = textureSample(distTexture, sampler_1, _arg_0.uv).x; if (((paramsUniform.showInside == 0u) && (dist < 0f))) { dist = 0f; @@ -337,15 +337,15 @@ describe('jump flood (distance) example', () => { let gradientPos = (t * 4f); let idx = u32(gradientPos); let frac = fract(gradientPos); - var outsideBase = mix(outsideGradient[min(idx, 4u)], outsideGradient[min((idx + 1u), 4u)], frac); - var insideBase = mix(insideGradient[min(idx, 4u)], insideGradient[min((idx + 1u), 4u)], frac); + let outsideBase = mix(outsideGradient[min(idx, 4u)], outsideGradient[min((idx + 1u), 4u)], frac); + let insideBase = mix(insideGradient[min(idx, 4u)], insideGradient[min((idx + 1u), 4u)], frac); var baseColor = outsideBase; if ((dist < 0f)) { baseColor = insideBase; } let contourFreq = (maxDist / 12f); let contour = smoothstep(0f, 0.15f, abs((fract((unsigned / contourFreq)) - 0.5f))); - var color = (baseColor * (0.7f + (0.3f * contour))); + let color = (baseColor * (0.7f + (0.3f * contour))); return vec4f(color, 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-voronoi.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-voronoi.test.ts index 445bfdf43d..fa0e4cd1e5 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-voronoi.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-voronoi.test.ts @@ -55,14 +55,14 @@ describe('jump flood (voronoi) example', () => { const palette: array = array(vec3f(0.9215686321258545, 0.8117647171020508, 1), vec3f(0.7176470756530762, 0.545098066329956, 0.9803921580314636), vec3f(0.545098066329956, 0.3607843220233917, 0.9647058844566345), vec3f(0.4274509847164154, 0.2666666805744171, 0.9490196108818054)); fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var size = textureDimensions(writeView); + let size = textureDimensions(writeView); randSeed2(((vec2f(f32(x), f32(y)) / vec2f(size)) + timeUniform)); let randomVal = randFloat01(); let isSeed = (randomVal >= seedThresholdUniform); let paletteColor = palette[u32(floor((randFloat01() * 4f)))]; - var variation = (vec3f((randFloat01() - 0.5f), (randFloat01() - 0.5f), (randFloat01() - 0.5f)) * 0.15f); - var color = select(vec4f(), vec4f(saturate((paletteColor + variation)), 1f), isSeed); - var coord = select(vec2f(-1), (vec2f(f32(x), f32(y)) / vec2f(size)), isSeed); + let variation = (vec3f((randFloat01() - 0.5f), (randFloat01() - 0.5f), (randFloat01() - 0.5f)) * 0.15f); + let color = select(vec4f(), vec4f(saturate((paletteColor + variation)), 1f), isSeed); + let coord = select(vec2f(-1), (vec2f(f32(x), f32(y)) / vec2f(size)), isSeed); textureStore(writeView, vec2i(i32(x), i32(y)), 0, color); textureStore(writeView, vec2i(i32(x), i32(y)), 1, vec4f(coord, 0f, 0f)); } @@ -110,12 +110,12 @@ describe('jump flood (voronoi) example', () => { } fn sampleWithOffset(tex: texture_storage_2d_array, pos: vec2i, offset: vec2i) -> SampleResult { - var dims = textureDimensions(tex); - var samplePos = (pos + offset); + let dims = textureDimensions(tex); + let samplePos = (pos + offset); let outOfBounds = ((((samplePos.x < 0i) || (samplePos.y < 0i)) || (samplePos.x >= i32(dims.x))) || (samplePos.y >= i32(dims.y))); - var safePos = clamp(samplePos, vec2i(), vec2i((dims - 1u))); - var loadedColor = textureLoad(tex, safePos, 0); - var loadedCoord = textureLoad(tex, safePos, 1).xy; + let safePos = clamp(samplePos, vec2i(), vec2i((dims - 1u))); + let loadedColor = textureLoad(tex, safePos, 0); + let loadedCoord = textureLoad(tex, safePos, 1).xy; return SampleResult(select(loadedColor, vec4f(), outOfBounds), select(loadedCoord, vec2f(-1), outOfBounds)); } @@ -123,14 +123,14 @@ describe('jump flood (voronoi) example', () => { fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { let offset = offsetUniform; - var size = textureDimensions(readView); + let size = textureDimensions(readView); var minDist = 1e+20; var bestSample = SampleResult(vec4f(), vec2f(-1)); // unrolled iteration #0 { // unrolled iteration #0 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (-1i * offset))); + let 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)) { @@ -141,7 +141,7 @@ describe('jump flood (voronoi) example', () => { } // unrolled iteration #1 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (-1i * offset))); + let 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)) { @@ -152,7 +152,7 @@ describe('jump flood (voronoi) example', () => { } // unrolled iteration #2 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (-1i * offset))); + let 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)) { @@ -166,7 +166,7 @@ describe('jump flood (voronoi) example', () => { { // unrolled iteration #0 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (0i * offset))); + let 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)) { @@ -177,7 +177,7 @@ describe('jump flood (voronoi) example', () => { } // unrolled iteration #1 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (0i * offset))); + let 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)) { @@ -188,7 +188,7 @@ describe('jump flood (voronoi) example', () => { } // unrolled iteration #2 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (0i * offset))); + let 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)) { @@ -202,7 +202,7 @@ describe('jump flood (voronoi) example', () => { { // unrolled iteration #0 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((-1i * offset), (1i * offset))); + let 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)) { @@ -213,7 +213,7 @@ describe('jump flood (voronoi) example', () => { } // unrolled iteration #1 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((0i * offset), (1i * offset))); + let 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)) { @@ -224,7 +224,7 @@ describe('jump flood (voronoi) example', () => { } // unrolled iteration #2 { - var sample = sampleWithOffset(readView, vec2i(i32(x), i32(y)), vec2i((1i * offset), (1i * offset))); + let 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)) { diff --git a/apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts b/apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts index f17d94772f..ba2a90eec3 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/lines-combinations.test.ts @@ -39,7 +39,7 @@ describe('lines combinations example', () => { let s = sin(time); let c = cos(time); const r = 0.25; - var points = array(vec2f(((r * s) - 0.25f), (r * c)), vec2f(-0.25, 0), vec2f(0.25, 0), vec2f(((-(r) * s) + 0.25f), (r * c))); + let points = array(vec2f(((r * s) - 0.25f), (r * c)), vec2f(-0.25, 0), vec2f(0.25, 0), vec2f(((-(r) * s) + 0.25f), (r * c))); let i = clamp((i32(vertexIndex) - 1i), 0i, 3i); return LineControlPoint(points[i], 0.2f); } @@ -72,13 +72,13 @@ describe('lines combinations example', () => { let b = (distance_1.y * sinDivLen); let c = (distance_1.x * sinDivLen); let d = (distance_1.y * cosDivLen); - var nL = vec2f((a - b), (c + d)); - var nR = vec2f((a + b), (-(c) + d)); + let nL = vec2f((a - b), (c + d)); + let nR = vec2f((a + b), (-(c) + d)); return ExternalNormals(nL, nR); } fn miterPointNoCheck(a: vec2f, b: vec2f) -> vec2f { - var ab = (a + b); + let ab = (a + b); return (ab * (2f / dot(ab, ab))); } @@ -98,10 +98,10 @@ describe('lines combinations example', () => { let tooCloseToJoinR = (dot(eAB.nR, eBC.nR) > 0.99f); let shouldJoinL = (isHairpin || (underLimitL && !tooCloseToJoinL)); let shouldJoinR = (isHairpin || (underLimitR && !tooCloseToJoinR)); - var dLMiter = miterPointNoCheck(eAB.nL, eBC.nL); - var dRMiter = miterPointNoCheck(eBC.nR, eAB.nR); - var dL = select(eBC.nL, dLMiter, (!isCap && !shouldJoinL)); - var dR = select(eBC.nR, dRMiter, (!isCap && !shouldJoinR)); + let dLMiter = miterPointNoCheck(eAB.nL, eBC.nL); + let dRMiter = miterPointNoCheck(eBC.nR, eAB.nR); + let dL = select(eBC.nL, dLMiter, (!isCap && !shouldJoinL)); + let dR = select(eBC.nR, dRMiter, (!isCap && !shouldJoinR)); return JoinResult(dL, dR, shouldJoinL, shouldJoinR, isHairpin); } @@ -116,10 +116,10 @@ describe('lines combinations example', () => { } fn intersectLines(A1: vec2f, A2: vec2f, B1: vec2f, B2: vec2f) -> Intersection { - var a = (A2 - A1); - var b = (B2 - B1); + let a = (A2 - A1); + let b = (B2 - B1); let axb = cross2d(a, b); - var AB = (B1 - A1); + let AB = (B1 - A1); let t = (cross2d(AB, b) / axb); return Intersection((((axb != 0f) && (t >= 0f)) && (t <= 1f)), t, (A1 + (a * t))); } @@ -146,9 +146,9 @@ describe('lines combinations example', () => { fn bisectCcw(a: vec2f, b: vec2f) -> vec2f { let sin_1 = cross2d(a, b); let sinSign = select(-1f, 1f, (sin_1 >= 0f)); - var orthoA = rot90ccw(a); - var orthoB = rot90cw(b); - var dir = select(((a + b) * sinSign), (orthoA + orthoB), (dot(a, b) < 0f)); + let orthoA = rot90ccw(a); + let orthoB = rot90cw(b); + let dir = select(((a + b) * sinSign), (orthoA + orthoB), (dot(a, b) < 0f)); return normalize(dir); } @@ -157,7 +157,7 @@ describe('lines combinations example', () => { } fn slerpApprox(a: vec2f, b: vec2f, t: f32) -> vec2f { - var mid = bisectNoCheck(a, b); + let mid = bisectNoCheck(a, b); var a_ = a; var b_ = mid; var t_ = (2f * t); @@ -173,15 +173,15 @@ describe('lines combinations example', () => { if ((joinVertexIndex == 0u)) { return join.v; } - var dir = slerpApprox(join.d, bisectCcw(join.start, join.end), (f32(joinVertexIndex) / f32(maxJoinCount))); + let dir = slerpApprox(join.d, bisectCcw(join.start, join.end), (f32(joinVertexIndex) / f32(maxJoinCount))); return (join.C.position + (dir * join.C.radius)); } fn lineSegmentVariableWidth(vertexIndex: u32, A: LineControlPoint, B: LineControlPoint, C: LineControlPoint, D: LineControlPoint, maxJoinCount: u32) -> LineSegmentOutput { - var AB = (B.position - A.position); - var BC = (C.position - B.position); - var DC = (C.position - D.position); - var CB = -(BC); + let AB = (B.position - A.position); + let BC = (C.position - B.position); + let DC = (C.position - D.position); + let CB = -(BC); let radiusABDelta = (A.radius - B.radius); let radiusBCDelta = (B.radius - C.radius); let radiusCDDelta = (C.radius - D.radius); @@ -190,10 +190,10 @@ describe('lines combinations example', () => { } let isCapB = (dot(AB, AB) <= (radiusABDelta * radiusABDelta)); let isCapC = (dot(DC, DC) <= (radiusCDDelta * radiusCDDelta)); - var eAB = externalNormals(AB, A.radius, B.radius); - var eBC = externalNormals(BC, B.radius, C.radius); - var eCB = ExternalNormals(eBC.nR, eBC.nL); - var eDC = externalNormals(DC, D.radius, C.radius); + let eAB = externalNormals(AB, A.radius, B.radius); + let eBC = externalNormals(BC, B.radius, C.radius); + let eCB = ExternalNormals(eBC.nR, eBC.nL); + let eDC = externalNormals(DC, D.radius, C.radius); let joinLimit = dot(eBC.nL, BC); var joinB = solveJoin(AB, BC, eAB, eBC, joinLimit, isCapB); var joinC = solveJoin(DC, CB, eDC, eCB, -(joinLimit), isCapC); @@ -201,16 +201,16 @@ describe('lines combinations example', () => { let d3 = (&joinB.dR); let d4 = (&joinC.dL); let d5 = (&joinC.dR); - var v2orig = (B.position + ((*d2) * B.radius)); - var v3orig = (B.position + ((*d3) * B.radius)); - var v4orig = (C.position + ((*d4) * C.radius)); - var v5orig = (C.position + ((*d5) * C.radius)); - var limL = intersectLines(B.position, v2orig, C.position, v5orig); - var limR = intersectLines(B.position, v3orig, C.position, v4orig); - var v2 = select(v2orig, limL.point, limL.valid); - var v5 = select(v5orig, limL.point, limL.valid); - var v3 = select(v3orig, limR.point, limR.valid); - var v4 = select(v4orig, limR.point, limR.valid); + let v2orig = (B.position + ((*d2) * B.radius)); + let v3orig = (B.position + ((*d3) * B.radius)); + let v4orig = (C.position + ((*d4) * C.radius)); + let v5orig = (C.position + ((*d5) * C.radius)); + let limL = intersectLines(B.position, v2orig, C.position, v5orig); + let limR = intersectLines(B.position, v3orig, C.position, v4orig); + let v2 = select(v2orig, limL.point, limL.valid); + let v5 = select(v5orig, limL.point, limL.valid); + let v3 = select(v3orig, limR.point, limR.valid); + let v4 = select(v4orig, limR.point, limR.valid); if ((vertexIndex == 0u)) { return LineSegmentOutput(B.position, (1f / B.radius)); } @@ -256,14 +256,14 @@ describe('lines combinations example', () => { @vertex fn mainVertex(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> mainVertex_Output { let t = uniforms.time; - var A = item(instanceIndex, t); - var B = item((instanceIndex + 1u), t); - var C = item((instanceIndex + 2u), t); - var D = item((instanceIndex + 3u), t); + let A = item(instanceIndex, t); + let B = item((instanceIndex + 1u), t); + let C = item((instanceIndex + 2u), t); + let D = item((instanceIndex + 3u), t); if (((((A.radius < 0f) || (B.radius < 0f)) || (C.radius < 0f)) || (D.radius < 0f))) { return mainVertex_Output(); } - var result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, 6u); + let result = lineSegmentVariableWidth(vertexIndex, A, B, C, D, 6u); return mainVertex_Output(vec4f((result.vertexPosition * result.w), 0f, result.w), result.vertexPosition, vec2f(0f, select(0f, 1f, (vertexIndex > 1u))), instanceIndex, vertexIndex, 0u); } @@ -278,7 +278,7 @@ describe('lines combinations example', () => { @fragment fn mainFragment(_arg_0: mainFragment_Input, @builtin(front_facing) frontFacing: bool, @builtin(position) screenPosition: vec4f) -> @location(0) vec4f { let fillType = uniforms.fillType; var color = vec3f(); - var colors = array(vec3f(1, 0, 0), vec3f(0, 1, 0), vec3f(0, 0, 1), vec3f(1, 0, 1), vec3f(1, 1, 0), vec3f(0, 1, 1), vec3f(0.75, 0.25, 0.25), vec3f(0.25, 0.75, 0.25), vec3f(0.25, 0.25, 0.75)); + let colors = array(vec3f(1, 0, 0), vec3f(0, 1, 0), vec3f(0, 0, 1), vec3f(1, 0, 1), vec3f(1, 1, 0), vec3f(0, 1, 1), vec3f(0.75, 0.25, 0.25), vec3f(0.25, 0.75, 0.25), vec3f(0.25, 0.25, 0.75)); if ((fillType == 1u)) { color = mix(vec3f(0.7699999809265137, 0.38999998569488525, 1), vec3f(0.10999999940395355, 0.4399999976158142, 0.9399999976158142), ((_arg_0.position.x * 0.5f) + 0.5f)); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts index 56a5b4ecf5..c470fad45a 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts @@ -83,7 +83,7 @@ describe('liquid-glass example', () => { @group(0) @binding(1) var paramsUniform: Params; fn sdRoundedBox2d(point: vec2f, size: vec2f, cornerRadius: f32) -> f32 { - var d = ((abs(point) - size) + vec2f(cornerRadius)); + let d = ((abs(point) - size) + vec2f(cornerRadius)); return ((length(max(d, vec2f())) + min(max(d.x, d.y), 0f)) - cornerRadius); } @@ -108,17 +108,17 @@ describe('liquid-glass example', () => { var samples = array(); // unrolled iteration #0 { - var channelOffset = ((dir * -1f) * offset); + let channelOffset = ((dir * -1f) * offset); samples[0i] = textureSampleBias(tex, sampler_2, (uv - channelOffset), blur).rgb; } // unrolled iteration #1 { - var channelOffset = ((dir * 0f) * offset); + let channelOffset = ((dir * 0f) * offset); samples[1i] = textureSampleBias(tex, sampler_2, (uv - channelOffset), blur).rgb; } // unrolled iteration #2 { - var channelOffset = ((dir * 1f) * offset); + let channelOffset = ((dir * 1f) * offset); samples[2i] = textureSampleBias(tex, sampler_2, (uv - channelOffset), blur).rgb; } return vec3f(samples[0i].x, samples[1i].y, samples[2i].z); @@ -138,19 +138,19 @@ describe('liquid-glass example', () => { } @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { - var posInBoxSpace = (_arg_0.uv - mousePosUniform); + let posInBoxSpace = (_arg_0.uv - mousePosUniform); let sdfDist = sdRoundedBox2d(posInBoxSpace, paramsUniform.rectDims, paramsUniform.radius); - var dir = normalize((posInBoxSpace * paramsUniform.rectDims.yx)); + let dir = normalize((posInBoxSpace * paramsUniform.rectDims.yx)); let normalizedDist = ((sdfDist - paramsUniform.start) / (paramsUniform.end - paramsUniform.start)); - var texDim = textureDimensions(sampledView, 0); + let texDim = textureDimensions(sampledView, 0); let featherUV = (paramsUniform.edgeFeather / f32(max(texDim.x, texDim.y))); - var weights = calculateWeights(sdfDist, paramsUniform.start, paramsUniform.end, featherUV); - var blurSample = textureSampleBias(sampledView, sampler_1, _arg_0.uv, paramsUniform.blur); - var refractedSample = sampleWithChromaticAberration(sampledView, sampler_1, (_arg_0.uv + (dir * (paramsUniform.refractionStrength * normalizedDist))), (paramsUniform.chromaticStrength * normalizedDist), dir, (paramsUniform.blur * paramsUniform.edgeBlurMultiplier)); - var normalSample = textureSampleLevel(sampledView, sampler_1, _arg_0.uv, 0); - var tint = TintParams(paramsUniform.tintColor, paramsUniform.tintStrength); - var tintedBlur = applyTint(blurSample.rgb, tint); - var tintedRing = applyTint(refractedSample, tint); + let weights = calculateWeights(sdfDist, paramsUniform.start, paramsUniform.end, featherUV); + let blurSample = textureSampleBias(sampledView, sampler_1, _arg_0.uv, paramsUniform.blur); + let refractedSample = sampleWithChromaticAberration(sampledView, sampler_1, (_arg_0.uv + (dir * (paramsUniform.refractionStrength * normalizedDist))), (paramsUniform.chromaticStrength * normalizedDist), dir, (paramsUniform.blur * paramsUniform.edgeBlurMultiplier)); + let normalSample = textureSampleLevel(sampledView, sampler_1, _arg_0.uv, 0); + let tint = TintParams(paramsUniform.tintColor, paramsUniform.tintStrength); + let tintedBlur = applyTint(blurSample.rgb, tint); + let tintedRing = applyTint(refractedSample, tint); return (((tintedBlur * weights.inside) + (tintedRing * weights.ring)) + (normalSample * weights.outside)); }" `); 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 3ee6a9035b..df7d438e69 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 @@ -976,13 +976,13 @@ describe('console log example', () => { } fn wrappedCallback(_arg_0: u32, _arg_1: u32, _arg_2: u32) { - var simpleStruct = SimpleStruct(vec3u(1, 2, 3), 4u); + let simpleStruct = SimpleStruct(vec3u(1, 2, 3), 4u); log1(simpleStruct); - var complexStruct = ComplexStruct(simpleStruct, true); + let complexStruct = ComplexStruct(simpleStruct, true); log2_1(complexStruct); - var simpleArray = array(1u, 2u); + let simpleArray = array(1u, 2u); log3(simpleArray); - var complexArray = array, 3>(array(3u, 4u), array(5u, 6u), array(7u, 8u)); + let complexArray = array, 3>(array(3u, 4u), array(5u, 6u), array(7u, 8u)); log4(complexArray); } @@ -1449,7 +1449,7 @@ describe('console log example', () => { } @vertex fn mainVertex(@builtin(vertex_index) _arg_vertexIndex: u32) -> mainVertex_Output { - var positions = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); + let positions = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); return mainVertex_Output(vec4f(positions[_arg_vertexIndex], 0f, 1f)); } @@ -1502,7 +1502,7 @@ describe('console log example', () => { } @vertex fn mainVertex(@builtin(vertex_index) _arg_vertexIndex: u32) -> mainVertex_Output { - var positions = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); + let positions = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); return mainVertex_Output(vec4f(positions[_arg_vertexIndex], 0f, 1f)); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts index 23abf35258..396869d6d7 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts @@ -130,7 +130,7 @@ describe('oklab example', () => { fn findCusp(a: f32, b: f32) -> LC { let S_cusp = computeMaxSaturation(a, b); - var rgb_at_max = oklabToLinearRgb(vec3f(1f, (S_cusp * a), (S_cusp * b))); + let rgb_at_max = oklabToLinearRgb(vec3f(1f, (S_cusp * a), (S_cusp * b))); let L_cusp = cbrt((1f / max(max(rgb_at_max.x, rgb_at_max.y), rgb_at_max.z))); let C_cusp = (L_cusp * S_cusp); return LC(L_cusp, C_cusp); @@ -203,7 +203,7 @@ describe('oklab example', () => { let Ld = (L - 0.5f); let e1 = ((0.5f + abs(Ld)) + (alpha * C)); let L0 = (0.5f * (1f + (sign(Ld) * (e1 - sqrt(max(0f, ((e1 * e1) - (2f * abs(Ld))))))))); - var cusp = findCusp(a_, b_); + let cusp = findCusp(a_, b_); let t = clamp(findGamutIntersection(a_, b_, L, C, L0, cusp), 0f, 1f); let L_clipped = mix(L0, L, t); let C_clipped = (t * C); @@ -227,15 +227,15 @@ describe('oklab example', () => { } @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { - var uv = ((_arg_0.uv - 0.5f) * vec2f(2, -2)); + let uv = ((_arg_0.uv - 0.5f) * vec2f(2, -2)); let hue = uniforms.hue; - var pos = scaleView(uv); - var yzDir = vec2f(cos(hue), sin(hue)); - var lab = vec3f(pos.y, (yzDir * pos.x)); - var rgb = oklabToLinearRgb(lab); + let pos = scaleView(uv); + let yzDir = vec2f(cos(hue), sin(hue)); + let lab = vec3f(pos.y, (yzDir * pos.x)); + let rgb = oklabToLinearRgb(lab); let outOfGamut = (any((rgb < vec3f())) || any((rgb > vec3f(1)))); - var clipLab = gamutClipAdaptiveL05(lab); - var color = oklabToRgb(lab); + let clipLab = gamutClipAdaptiveL05(lab); + let color = oklabToRgb(lab); let patternScaled = ((item_1(uv, clipLab) * 0.1f) + 0.9f); return vec4f(select(color, (color * patternScaled), outOfGamut), 1f); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/perlin-noise.test.ts b/apps/typegpu-docs/tests/individual-example-tests/perlin-noise.test.ts index 56c00a8203..8351e9c7d4 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/perlin-noise.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/perlin-noise.test.ts @@ -92,7 +92,7 @@ describe('perlin noise example', () => { @group(1) @binding(1) var perlin3dCache__memory: array; fn getJunctionGradient(pos: vec3i) -> vec3f { - var size = vec3i(perlin3dCache__size.xyz); + let size = vec3i(perlin3dCache__size.xyz); let x = (((pos.x % size.x) + size.x) % size.x); let y = (((pos.y % size.y) + size.y) % size.y); let z = (((pos.z % size.z) + size.z) % size.z); @@ -100,8 +100,8 @@ describe('perlin noise example', () => { } fn dotProdGrid(pos: vec3f, junction: vec3f) -> f32 { - var relative = (pos - junction); - var gridVector = getJunctionGradient(vec3i(junction)); + let relative = (pos - junction); + let gridVector = getJunctionGradient(vec3i(junction)); return dot(relative, gridVector); } @@ -110,7 +110,7 @@ describe('perlin noise example', () => { } fn sample(pos: vec3f) -> f32 { - var minJunction = floor(pos); + let minJunction = floor(pos); let xyz = dotProdGrid(pos, minJunction); let xyZ = dotProdGrid(pos, (minJunction + vec3f(0, 0, 1))); let xYz = dotProdGrid(pos, (minJunction + vec3f(0, 1, 0))); @@ -119,8 +119,8 @@ describe('perlin noise example', () => { let XyZ = dotProdGrid(pos, (minJunction + vec3f(1, 0, 1))); let XYz = dotProdGrid(pos, (minJunction + vec3f(1, 1, 0))); let XYZ = dotProdGrid(pos, (minJunction + vec3f(1))); - var partial = (pos - minJunction); - var smoothPartial = quinticInterpolation(partial); + let partial = (pos - minJunction); + let smoothPartial = quinticInterpolation(partial); let xy = mix(xyz, xyZ, smoothPartial.z); let xY = mix(xYz, xYZ, smoothPartial.z); let Xy = mix(Xyz, XyZ, smoothPartial.z); @@ -141,12 +141,12 @@ describe('perlin noise example', () => { } @fragment fn fragment(_arg_0: FragmentIn) -> @location(0) vec4f { - var suv = (gridSize * _arg_0.uv); + let suv = (gridSize * _arg_0.uv); let n = sample(vec3f(suv, time)); let sharp = exponentialSharpen(n, sharpness); let n01 = ((sharp * 0.5f) + 0.5f); - var dark = vec3f(0, 0.20000000298023224, 1); - var light = vec3f(1, 0.30000001192092896, 0.5); + let dark = vec3f(0, 0.20000000298023224, 1); + let light = vec3f(1, 0.30000001192092896, 0.5); return vec4f(mix(dark, light, n01), 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts index 0240d9ae44..12ec94a289 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts @@ -48,9 +48,9 @@ describe('phong reflection example', () => { } @vertex fn vertexShader(@location(0) _arg_modelPosition: vec3f, @location(1) _arg_modelNormal: vec3f) -> vertexShader_Output { - var worldPosition = vec4f(_arg_modelPosition, 1f); + let worldPosition = vec4f(_arg_modelPosition, 1f); let camera = (&cameraUniform); - var canvasPosition = (((*camera).projection * (*camera).view) * worldPosition); + let canvasPosition = (((*camera).projection * (*camera).view) * worldPosition); return vertexShader_Output(_arg_modelPosition, _arg_modelNormal, canvasPosition); } @@ -70,18 +70,18 @@ describe('phong reflection example', () => { } @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { - var lightColor = normalize(exampleControlsUniform.lightColor); - var lightDirection = normalize(exampleControlsUniform.lightDirection); + let lightColor = normalize(exampleControlsUniform.lightColor); + let lightDirection = normalize(exampleControlsUniform.lightDirection); let ambientColor = (&exampleControlsUniform.ambientColor); let ambientStrength = exampleControlsUniform.ambientStrength; let specularStrength = exampleControlsUniform.specularExponent; - var ambient = ((*ambientColor) * ambientStrength); + let ambient = ((*ambientColor) * ambientStrength); let cosTheta = dot(_arg_0.worldNormal, lightDirection); - var diffuse = (lightColor * max(0f, cosTheta)); - var reflectionDirection = reflect((lightDirection * -1f), _arg_0.worldNormal); - var viewDirection = normalize((cameraUniform.position.xyz - _arg_0.worldPosition)); - var specular = (lightColor * pow(max(0f, dot(reflectionDirection, viewDirection)), specularStrength)); - var color = ((ambient + diffuse) + specular); + let diffuse = (lightColor * max(0f, cosTheta)); + let reflectionDirection = reflect((lightDirection * -1f), _arg_0.worldNormal); + let viewDirection = normalize((cameraUniform.position.xyz - _arg_0.worldPosition)); + let specular = (lightColor * pow(max(0f, dot(reflectionDirection, viewDirection)), specularStrength)); + let color = ((ambient + diffuse) + specular); return vec4f(color, 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts index 2ce441c6b2..351db35ed2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts @@ -35,9 +35,9 @@ describe('point light shadow example', () => { } @vertex fn vertexDepth(@location(0) position: vec3f, @location(3) column1: vec4f, @location(4) column2: vec4f, @location(5) column3: vec4f, @location(6) column4: vec4f) -> vertexDepth_Output { - var modelMatrix = mat4x4f(column1, column2, column3, column4); - var worldPos = (modelMatrix * vec4f(position, 1f)).xyz; - var pos = (camera.viewProjectionMatrix * vec4f(worldPos, 1f)); + let modelMatrix = mat4x4f(column1, column2, column3, column4); + let worldPos = (modelMatrix * vec4f(position, 1f)).xyz; + let pos = (camera.viewProjectionMatrix * vec4f(worldPos, 1f)); return vertexDepth_Output(pos, worldPos); } @@ -67,10 +67,10 @@ describe('point light shadow example', () => { } @vertex fn vertexMain(@location(0) position: vec3f, @location(2) uv: vec2f, @location(1) normal: vec3f, @location(3) column1: vec4f, @location(4) column2: vec4f, @location(5) column3: vec4f, @location(6) column4: vec4f) -> vertexMain_Output { - var modelMatrix = mat4x4f(column1, column2, column3, column4); - var worldPos = (modelMatrix * vec4f(position, 1f)).xyz; - var pos = (camera.viewProjectionMatrix * vec4f(worldPos, 1f)); - var worldNormal = normalize((modelMatrix * vec4f(normal, 0f)).xyz); + let modelMatrix = mat4x4f(column1, column2, column3, column4); + let worldPos = (modelMatrix * vec4f(position, 1f)).xyz; + let pos = (camera.viewProjectionMatrix * vec4f(worldPos, 1f)); + let worldNormal = normalize((modelMatrix * vec4f(normal, 0f)).xyz); return vertexMain_Output(pos, worldPos, uv, worldNormal); } @@ -99,31 +99,31 @@ describe('point light shadow example', () => { @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let lightPos = (&lightPosition); - var toLight = ((*lightPos) - _arg_0.worldPos); + let toLight = ((*lightPos) - _arg_0.worldPos); let dist = length(toLight); - var lightDir = (toLight / dist); + let lightDir = (toLight / dist); let ndotl = max(dot(_arg_0.normal, lightDir), 0f); let normalBiasWorld = (shadowParams.normalBiasBase + (shadowParams.normalBiasSlope * (1f - ndotl))); - var biasedPos = (_arg_0.worldPos + (_arg_0.normal * normalBiasWorld)); - var toLightBiased = (biasedPos - (*lightPos)); + let biasedPos = (_arg_0.worldPos + (_arg_0.normal * normalBiasWorld)); + let toLightBiased = (biasedPos - (*lightPos)); let distBiased = length(toLightBiased); - var dir = ((toLightBiased / distBiased) * vec3f(-1, 1, 1)); + let dir = ((toLightBiased / distBiased) * vec3f(-1, 1, 1)); let depthRef = (distBiased / 100f); - var up = select(vec3f(1, 0, 0), vec3f(0, 1, 0), (abs(dir.y) < 0.9998999834060669f)); - var right = normalize(cross(up, dir)); - var realUp = cross(dir, right); + let up = select(vec3f(1, 0, 0), vec3f(0, 1, 0), (abs(dir.y) < 0.9998999834060669f)); + let right = normalize(cross(up, dir)); + let realUp = cross(dir, right); let PCF_SAMPLES = shadowParams.pcfSamples; let diskRadius = shadowParams.diskRadius; var visibilityAcc = 0f; for (var i = 0u; (i < PCF_SAMPLES); i++) { - var o = (samplesUniform[i].xy * diskRadius); - var sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); + let o = (samplesUniform[i].xy * diskRadius); + let sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); visibilityAcc += textureSampleCompare(shadowDepthCube, shadowSampler, sampleDir, depthRef); } let rawNdotl = dot(_arg_0.normal, lightDir); let visibility = select((visibilityAcc / f32(PCF_SAMPLES)), 0f, (rawNdotl < 0f)); - var baseColor = vec3f(1, 0.5, 0.3100000023841858); - var color = (baseColor * ((ndotl * visibility) + 0.1f)); + let baseColor = vec3f(1, 0.5, 0.3100000023841858); + let color = (baseColor * ((ndotl * visibility) + 0.1f)); return vec4f(color, 1f); } @@ -141,8 +141,8 @@ describe('point light shadow example', () => { } @vertex fn vertexLightIndicator(@location(0) position: vec3f) -> vertexLightIndicator_Output { - var worldPos = ((position * 0.15f) + lightPosition); - var pos = (camera.viewProjectionMatrix * vec4f(worldPos, 1f)); + let worldPos = ((position * 0.15f) + lightPosition); + let pos = (camera.viewProjectionMatrix * vec4f(worldPos, 1f)); return vertexLightIndicator_Output(pos); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/probability.test.ts b/apps/typegpu-docs/tests/individual-example-tests/probability.test.ts index a4ae7762a4..5e6c7c9c61 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/probability.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/probability.test.ts @@ -55,8 +55,8 @@ describe('probability distribution plot example', () => { fn randInUnitSphere() -> vec3f { let u = sample(); - var v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); - var vNorm = normalize(v); + let v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); + let vNorm = normalize(v); return (vNorm * pow(u, 0.33f)); } @@ -300,13 +300,13 @@ describe('probability distribution plot example', () => { fn randInUnitSphere() -> vec3f { let u = sample(); - var v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); - var vNorm = normalize(v); + let v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); + let vNorm = normalize(v); return (vNorm * pow(u, 0.33f)); } fn randInUnitHemisphere(normal: vec3f) -> vec3f { - var value = randInUnitSphere(); + let value = randInUnitSphere(); let alignment = dot(normal, value); return (sign(alignment) * value); } @@ -356,7 +356,7 @@ describe('probability distribution plot example', () => { } fn randOnUnitHemisphere(normal: vec3f) -> vec3f { - var value = randOnUnitSphere(); + let value = randOnUnitSphere(); let alignment = dot(normal, value); return (sign(alignment) * value); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts index bdfa96686f..49f9a60c4d 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts @@ -26,8 +26,8 @@ describe('ray-marching example', () => { } @vertex fn vertexMain(@builtin(vertex_index) idx: u32) -> vertexMain_Output { - var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); - var uv = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); + let pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); + let uv = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } @@ -43,8 +43,8 @@ describe('ray-marching example', () => { } fn sdBoxFrame3d(point: vec3f, size: vec3f, thickness: f32) -> f32 { - var p1 = (abs(point) - size); - var q = (abs((p1 + thickness)) - vec3f(thickness)); + let p1 = (abs(point) - size); + let q = (abs((p1 + thickness)) - vec3f(thickness)); let d1 = (length(max(vec3f(p1.x, q.y, q.z), vec3f())) + min(max(p1.x, max(q.y, q.z)), 0f)); let d2 = (length(max(vec3f(q.x, p1.y, q.z), vec3f())) + min(max(q.x, max(p1.y, q.z)), 0f)); let d3 = (length(max(vec3f(q.x, q.y, p1.z), vec3f())) + min(max(q.x, max(q.y, p1.z)), 0f)); @@ -56,30 +56,30 @@ describe('ray-marching example', () => { let m = (h * h); let dist = (min(a.dist, b.dist) - ((m * k) * 0.25f)); let weight = (m + select(0f, (1f - m), (a.dist > b.dist))); - var color = mix(a.color, b.color, weight); + let color = mix(a.color, b.color, weight); return Shape(color, dist); } fn getMorphingShape(p: vec3f, t: f32) -> Shape { - var center = vec3f(0, 2, 6); - var localP = (p - center); - var rotMatZ = mat4x4f(cos(-(t)), sin(-(t)), 0, 0, -sin(-(t)), cos(-(t)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-(t) * 0.6f)), sin((-(t) * 0.6f)), 0, 0, -sin((-(t) * 0.6f)), cos((-(t) * 0.6f)), 0, 0, 0, 0, 1); - var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1f))).xyz; - var boxSize = vec3f(0.699999988079071); - var sphere1Offset = vec3f((cos((t * 2f)) * 0.8f), (sin((t * 3f)) * 0.3f), (sin((t * 2f)) * 0.8f)); - var sphere2Offset = vec3f((cos(((t * 2f) + 3.14f)) * 0.8f), (sin(((t * 3f) + 1.57f)) * 0.3f), (sin(((t * 2f) + 3.14f)) * 0.8f)); - var sphere1 = Shape(vec3f(0.4000000059604645, 0.5, 1), sdSphere((localP - sphere1Offset), 0.5f)); - var sphere2 = Shape(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere((localP - sphere2Offset), 0.3f)); - var box = Shape(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d(rotatedP, boxSize, 0.1f)); - var spheres = smoothShapeUnion(sphere1, sphere2, 0.1f); + let center = vec3f(0, 2, 6); + let localP = (p - center); + let rotMatZ = mat4x4f(cos(-(t)), sin(-(t)), 0, 0, -sin(-(t)), cos(-(t)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + let rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-(t) * 0.6f)), sin((-(t) * 0.6f)), 0, 0, -sin((-(t) * 0.6f)), cos((-(t) * 0.6f)), 0, 0, 0, 0, 1); + let rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1f))).xyz; + let boxSize = vec3f(0.699999988079071); + let sphere1Offset = vec3f((cos((t * 2f)) * 0.8f), (sin((t * 3f)) * 0.3f), (sin((t * 2f)) * 0.8f)); + let sphere2Offset = vec3f((cos(((t * 2f) + 3.14f)) * 0.8f), (sin(((t * 3f) + 1.57f)) * 0.3f), (sin(((t * 2f) + 3.14f)) * 0.8f)); + let sphere1 = Shape(vec3f(0.4000000059604645, 0.5, 1), sdSphere((localP - sphere1Offset), 0.5f)); + let sphere2 = Shape(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere((localP - sphere2Offset), 0.3f)); + let box = Shape(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d(rotatedP, boxSize, 0.1f)); + let spheres = smoothShapeUnion(sphere1, sphere2, 0.1f); return smoothShapeUnion(spheres, box, 0.2f); } @group(0) @binding(1) var time: f32; fn checkerBoard(uv: vec2f) -> f32 { - var fuv = floor(uv); + let fuv = floor(uv); return (abs((fuv.x + fuv.y)) % 2f); } @@ -92,8 +92,8 @@ describe('ray-marching example', () => { } fn getSceneDist(p: vec3f) -> Shape { - var shape = getMorphingShape(p, time); - var floor_1 = Shape(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard((p.xz * 2f))), sdPlane(p, vec3f(0, 1, 0), 0f)); + let shape = getMorphingShape(p, time); + let floor_1 = Shape(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard((p.xz * 2f))), sdPlane(p, vec3f(0, 1, 0), 0f)); return shapeUnion(shape, floor_1); } @@ -101,8 +101,8 @@ describe('ray-marching example', () => { var dO = 0f; var result = Shape(vec3f(), 30f); for (var i = 0; (i < 1000i); i++) { - var p = (ro + (rd * dO)); - var scene = getSceneDist(p); + let p = (ro + (rd * dO)); + let scene = getSceneDist(p); dO += scene.dist; if (((dO > 30f) || (scene.dist < 1e-3f))) { result.dist = dO; @@ -116,7 +116,7 @@ describe('ray-marching example', () => { fn getNormal(p: vec3f) -> vec3f { let dist = getSceneDist(p).dist; const e = 0.01; - var n = vec3f((getSceneDist((p + vec3f(e, 0f, 0f))).dist - dist), (getSceneDist((p + vec3f(0f, e, 0f))).dist - dist), (getSceneDist((p + vec3f(0f, 0f, e))).dist - dist)); + let n = vec3f((getSceneDist((p + vec3f(e, 0f, 0f))).dist - dist), (getSceneDist((p + vec3f(0f, e, 0f))).dist - dist), (getSceneDist((p + vec3f(0f, 0f, e))).dist - dist)); return normalize(n); } @@ -151,21 +151,21 @@ describe('ray-marching example', () => { @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolution.x / resolution.y); - var ro = vec3f(0, 2, 3); - var rd = normalize(vec3f(uv.x, uv.y, 1f)); - var march = rayMarch(ro, rd); + let ro = vec3f(0, 2, 3); + let rd = normalize(vec3f(uv.x, uv.y, 1f)); + let march = rayMarch(ro, rd); let fog = pow(min((march.dist / 30f), 1f), 0.7f); var p = (ro + (rd * march.dist)); - var n = getNormal(p); - var lightPos = getOrbitingLightPos(time); + let n = getNormal(p); + let lightPos = getOrbitingLightPos(time); var l = normalize((lightPos - p)); let diff = max(dot(n, l), 0f); let shadowRo = (&p); let shadowRd = (&l); let shadowDist = length((lightPos - p)); let shadow = softShadow((*shadowRo), (*shadowRd), 0.1f, shadowDist, 16f); - var litColor = (march.color * diff); - var finalColor = mix((litColor * 0.5f), litColor, shadow); + let litColor = (march.color * diff); + let finalColor = mix((litColor * 0.5f), litColor, shadow); return mix(vec4f(finalColor, 1f), vec4f(0.699999988079071, 0.800000011920929, 0.8999999761581421, 1), fog); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts index 24dcc7ee1c..f49bb897bf 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts @@ -131,7 +131,7 @@ describe('ripple-cube example', () => { @group(0) @binding(2) var memoryBuffer: array; fn getJunctionGradient(pos: vec3i) -> vec3f { - var size_i = vec3i(128); + let size_i = vec3i(128); let x = (((pos.x % size_i.x) + size_i.x) % size_i.x); let y = (((pos.y % size_i.y) + size_i.y) % size_i.y); let z = (((pos.z % size_i.z) + size_i.z) % size_i.z); @@ -139,8 +139,8 @@ describe('ripple-cube example', () => { } fn dotProdGrid(pos: vec3f, junction: vec3f) -> f32 { - var relative = (pos - junction); - var gridVector = getJunctionGradient(vec3i(junction)); + let relative = (pos - junction); + let gridVector = getJunctionGradient(vec3i(junction)); return dot(relative, gridVector); } @@ -149,7 +149,7 @@ describe('ripple-cube example', () => { } fn sample(pos: vec3f) -> f32 { - var minJunction = floor(pos); + let minJunction = floor(pos); let xyz = dotProdGrid(pos, minJunction); let xyZ = dotProdGrid(pos, (minJunction + vec3f(0, 0, 1))); let xYz = dotProdGrid(pos, (minJunction + vec3f(0, 1, 0))); @@ -158,8 +158,8 @@ describe('ripple-cube example', () => { let XyZ = dotProdGrid(pos, (minJunction + vec3f(1, 0, 1))); let XYz = dotProdGrid(pos, (minJunction + vec3f(1, 1, 0))); let XYZ = dotProdGrid(pos, (minJunction + vec3f(1))); - var partial = (pos - minJunction); - var smoothPartial = quinticInterpolation(partial); + let partial = (pos - minJunction); + let smoothPartial = quinticInterpolation(partial); let xy = mix(xyz, xyZ, smoothPartial.z); let xY = mix(xYz, xYZ, smoothPartial.z); let Xy = mix(Xyz, XyZ, smoothPartial.z); @@ -175,11 +175,11 @@ describe('ripple-cube example', () => { let n3 = ((sample(((rd * 5f) + 150f)) * 0.5f) + 0.5f); let n4 = ((sample(((rd * 0.8f) + 300f)) * 0.5f) + 0.5f); let colorShift = ((sample(((rd * 0.5f) + 500f)) * 0.5f) + 0.5f); - var purple = (vec3f(0.6000000238418579, 0.10000000149011612, 0.800000011920929) * (pow(n1, 1.8f) * 0.5f)); - var blue = (vec3f(0.10000000149011612, 0.30000001192092896, 0.699999988079071) * (pow(n2, 2f) * 0.4f)); - var cyan = (vec3f(0.10000000149011612, 0.6000000238418579, 0.6000000238418579) * (pow((n3 * n4), 1.5f) * 0.3f)); - var pink = (vec3f(0.699999988079071, 0.20000000298023224, 0.4000000059604645) * ((pow((1f - n1), 3f) * n2) * 0.4f)); - var gold = (vec3f(0.800000011920929, 0.5, 0.10000000149011612) * (pow((n2 * n3), 3f) * 0.6f)); + let purple = (vec3f(0.6000000238418579, 0.10000000149011612, 0.800000011920929) * (pow(n1, 1.8f) * 0.5f)); + let blue = (vec3f(0.10000000149011612, 0.30000001192092896, 0.699999988079071) * (pow(n2, 2f) * 0.4f)); + let cyan = (vec3f(0.10000000149011612, 0.6000000238418579, 0.6000000238418579) * (pow((n3 * n4), 1.5f) * 0.3f)); + let pink = (vec3f(0.699999988079071, 0.20000000298023224, 0.4000000059604645) * ((pow((1f - n1), 3f) * n2) * 0.4f)); + let gold = (vec3f(0.800000011920929, 0.5, 0.10000000149011612) * (pow((n2 * n3), 3f) * 0.6f)); var color = (((vec3f(0.009999999776482582, 0.009999999776482582, 0.029999999329447746) + mix(purple, blue, colorShift)) + mix(pink, cyan, n4)) + gold); color = (color + (mix(vec3f(0.800000011920929, 0.4000000059604645, 1), vec3f(0.4000000059604645, 0.800000011920929, 1), colorShift) * (pow(((n1 * n2) * n3), 4f) * 2f))); return color; @@ -209,15 +209,15 @@ describe('ripple-cube example', () => { fn spaceBackground(rd: vec3f) -> vec3f { var color = spaceNebula(rd); - var starPos = (rd * 50f); + let starPos = (rd * 50f); randSeed3(floor(starPos)); - var starCenter = ((vec3f(randFloat01(), randFloat01(), randFloat01()) * 0.6f) + 0.2f); + let starCenter = ((vec3f(randFloat01(), randFloat01(), randFloat01()) * 0.6f) + 0.2f); let starDist = length((fract(starPos) - starCenter)); let starHash = randFloat01(); color = (color + (mix(vec3f(1, 0.8999999761581421, 0.800000011920929), vec3f(0.800000011920929, 0.8999999761581421, 1), starHash) * ((pow(max((1f - (starDist * 4f)), 0f), 4f) * step(0.85f, starHash)) * 3f))); - var bigStarPos = (rd * 20f); + let bigStarPos = (rd * 20f); randSeed3(floor(bigStarPos)); - var bigStarCenter = ((vec3f(randFloat01(), randFloat01(), randFloat01()) * 0.5f) + 0.25f); + let bigStarCenter = ((vec3f(randFloat01(), randFloat01(), randFloat01()) * 0.5f) + 0.25f); let bigStarDist = length((fract(bigStarPos) - bigStarCenter)); color = (color + (vec3f(1, 0.949999988079071, 0.8999999761581421) * ((pow(max((1f - (bigStarDist * 3f)), 0f), 3f) * step(0.95f, randFloat01())) * 8f))); return color; @@ -229,8 +229,8 @@ describe('ripple-cube example', () => { let u = ((((f32(x) + 0.5f) / 1024f) * 2f) - 1f); let v = ((((f32(y) + 0.5f) / 1024f) * 2f) - 1f); let basis = (&faceBasisUniform); - var direction = normalize((((*basis).forward + ((*basis).right * u)) + ((*basis).up * v))); - var color = spaceBackground(direction); + let direction = normalize((((*basis).forward + ((*basis).right * u)) + ((*basis).up * v))); + let color = spaceBackground(direction); textureStore(outputTexture, vec2u(x, y), vec4f(color, 1f)); } @@ -260,7 +260,7 @@ describe('ripple-cube example', () => { fn wrappedCallback(x: u32, y: u32, z: u32) { const cellSize = 0.0047169811320754715; - var p = ((vec3f(f32(x), f32(y), f32(z)) + 0.5f) * cellSize); + let p = ((vec3f(f32(x), f32(y), f32(z)) + 0.5f) * cellSize); let r = (timeUniform * 0.15f); let iterCount = select(5, 11, (extendedRippleUniform == 1u)); var shellD = 1e+10f; @@ -273,7 +273,7 @@ describe('ripple-cube example', () => { let qx = select((ox + p.x), (ox - p.x), ((ix % 2i) == 0i)); let qy = select((oy + p.y), (oy - p.y), ((iy % 2i) == 0i)); let qz = select((oz + p.z), (oz - p.z), ((iz % 2i) == 0i)); - var q = vec3f(qx, qy, qz); + let q = vec3f(qx, qy, qz); shellD = opSmoothUnion(shellD, (abs((length(q) - r)) - 5e-3f), blendFactorUniform); } } @@ -324,11 +324,11 @@ describe('ripple-cube example', () => { fn getRayForUV(uv: vec2f) -> Ray { let camera = (&cameraUniform); - var jitteredUV = (uv + jitterUniform); - var ndc = (((jitteredUV * 2f) - 1f) * vec2f(1, -1)); - var farView = ((*camera).projectionInverse * vec4f(ndc.xy, 1f, 1f)); - var farWorld = ((*camera).viewInverse * vec4f((farView.xyz / farView.w), 1f)); - var direction = normalize((farWorld.xyz - (*camera).position.xyz)); + let jitteredUV = (uv + jitterUniform); + let ndc = (((jitteredUV * 2f) - 1f) * vec2f(1, -1)); + let farView = ((*camera).projectionInverse * vec4f(ndc.xy, 1f, 1f)); + let farWorld = ((*camera).viewInverse * vec4f((farView.xyz / farView.w), 1f)); + let direction = normalize((farWorld.xyz - (*camera).position.xyz)); return Ray((*camera).position, vec4f(direction, 0f)); } @@ -337,13 +337,13 @@ describe('ripple-cube example', () => { @group(1) @binding(1) var sdfSampler: sampler; fn sdBox3d(point: vec3f, size: vec3f) -> f32 { - var d = (abs(point) - size); + let d = (abs(point) - size); return (length(max(d, vec3f())) + min(max(max(d.x, d.y), d.z), 0f)); } fn sdBoxFrame3d(point: vec3f, size: vec3f, thickness: f32) -> f32 { - var p1 = (abs(point) - size); - var q = (abs((p1 + thickness)) - vec3f(thickness)); + let p1 = (abs(point) - size); + let q = (abs((p1 + thickness)) - vec3f(thickness)); let d1 = (length(max(vec3f(p1.x, q.y, q.z), vec3f())) + min(max(p1.x, max(q.y, q.z)), 0f)); let d2 = (length(max(vec3f(q.x, p1.y, q.z), vec3f())) + min(max(q.x, max(p1.y, q.z)), 0f)); let d3 = (length(max(vec3f(q.x, q.y, p1.z), vec3f())) + min(max(q.x, max(q.y, p1.z)), 0f)); @@ -351,7 +351,7 @@ describe('ripple-cube example', () => { } fn sceneSDF(p: vec3f) -> f32 { - var uv = (abs(p) * 2f); + let uv = (abs(p) * 2f); let sdfValue = textureSampleLevel(sdfTexture, sdfSampler, uv, 0).x; let interior = max(sdBox3d(p, vec3f(0.5)), sdfValue); return min(sdBoxFrame3d(p, vec3f(0.5), 5e-3f), interior); @@ -404,26 +404,26 @@ describe('ripple-cube example', () => { } fn evaluateLight(p: vec3f, n: vec3f, v: vec3f, light: Light, material: Material, f0: vec3f) -> vec3f { - var toLight = (light.position - p); + let toLight = (light.position - p); let dist = length(toLight); - var l = normalize(toLight); - var h = normalize((v + l)); - var radiance = (light.color / pow(dist, 2f)); + let l = normalize(toLight); + let h = normalize((v + l)); + let radiance = (light.color / pow(dist, 2f)); let ndotl = max(dot(n, l), 0f); let ndoth = max(dot(n, h), 0f); let ndotv = max(dot(n, v), 1e-3f); let ndf = distributionGGX(ndoth, material.roughness); let g = geometrySmith(ndotv, ndotl, material.roughness); - var fresnel = fresnelSchlick(ndoth, f0); - var specular = ((fresnel * (ndf * g)) / (((4f * ndotv) * ndotl) + 1e-3f)); - var kd = ((1f - fresnel) * (1f - material.metallic)); + let fresnel = fresnelSchlick(ndoth, f0); + let specular = ((fresnel * (ndf * g)) / (((4f * ndotv) * ndotl) + 1e-3f)); + let kd = ((1f - fresnel) * (1f - material.metallic)); return (((((kd * material.albedo) / 3.141592653589793f) + specular) * radiance) * ndotl); } @group(0) @binding(7) var memoryBuffer: array; fn getJunctionGradient(pos: vec3i) -> vec3f { - var size_i = vec3i(32); + let size_i = vec3i(32); let x = (((pos.x % size_i.x) + size_i.x) % size_i.x); let y = (((pos.y % size_i.y) + size_i.y) % size_i.y); let z = (((pos.z % size_i.z) + size_i.z) % size_i.z); @@ -431,8 +431,8 @@ describe('ripple-cube example', () => { } fn dotProdGrid(pos: vec3f, junction: vec3f) -> f32 { - var relative = (pos - junction); - var gridVector = getJunctionGradient(vec3i(junction)); + let relative = (pos - junction); + let gridVector = getJunctionGradient(vec3i(junction)); return dot(relative, gridVector); } @@ -441,7 +441,7 @@ describe('ripple-cube example', () => { } fn sample(pos: vec3f) -> f32 { - var minJunction = floor(pos); + let minJunction = floor(pos); let xyz = dotProdGrid(pos, minJunction); let xyZ = dotProdGrid(pos, (minJunction + vec3f(0, 0, 1))); let xYz = dotProdGrid(pos, (minJunction + vec3f(0, 1, 0))); @@ -450,8 +450,8 @@ describe('ripple-cube example', () => { let XyZ = dotProdGrid(pos, (minJunction + vec3f(1, 0, 1))); let XYz = dotProdGrid(pos, (minJunction + vec3f(1, 1, 0))); let XYZ = dotProdGrid(pos, (minJunction + vec3f(1))); - var partial = (pos - minJunction); - var smoothPartial = quinticInterpolation(partial); + let partial = (pos - minJunction); + let smoothPartial = quinticInterpolation(partial); let xy = mix(xyz, xyZ, smoothPartial.z); let xY = mix(xYz, xYZ, smoothPartial.z); let Xy = mix(Xyz, XyZ, smoothPartial.z); @@ -463,7 +463,7 @@ describe('ripple-cube example', () => { fn shade(p: vec3f, n: vec3f, v: vec3f) -> vec3f { let material = (&materialUniform); - var f0 = mix(vec3f(0.03999999910593033), (*material).albedo, (*material).metallic); + let f0 = mix(vec3f(0.03999999910593033), (*material).albedo, (*material).metallic); var lo = vec3f(); // unrolled iteration #0 { @@ -473,33 +473,33 @@ describe('ripple-cube example', () => { { lo += evaluateLight(p, n, v, lightsUniform[1i], (*material), f0); } - var reflectDir = reflect(v, n); - var pScaled = (p * 50f); - var roughOffset = ((vec3f(sample(pScaled), sample((pScaled + 100f)), sample((pScaled + 200f))) * (*material).roughness) * 0.3f); - var blurredReflectDir = normalize((reflectDir + roughOffset)); - var envColor = textureSampleLevel(envMap, envSampler, blurredReflectDir, ((*material).roughness * 4f)); + let reflectDir = reflect(v, n); + let pScaled = (p * 50f); + let roughOffset = ((vec3f(sample(pScaled), sample((pScaled + 100f)), sample((pScaled + 200f))) * (*material).roughness) * 0.3f); + let blurredReflectDir = normalize((reflectDir + roughOffset)); + let envColor = textureSampleLevel(envMap, envSampler, blurredReflectDir, ((*material).roughness * 4f)); let ndotv = max(dot(n, v), 0f); - var fresnel = fresnelSchlick(ndotv, f0); - var reflectionTint = mix(vec3f(1), (*material).albedo, (*material).metallic); + let fresnel = fresnelSchlick(ndotv, f0); + let reflectionTint = mix(vec3f(1), (*material).albedo, (*material).metallic); let reflectionStrength = (1f - ((*material).roughness * 0.85f)); - var envContribution = (((envColor.rgb * fresnel) * reflectionTint) * reflectionStrength); - var ambient = ((*material).albedo * ((*material).ao * 0.05f)); - var color = ((ambient + lo) + envContribution); + let envContribution = (((envColor.rgb * fresnel) * reflectionTint) * reflectionStrength); + let ambient = ((*material).albedo * ((*material).ao * 0.05f)); + let color = ((ambient + lo) + envContribution); return pow((color / (color + 1f)), vec3f(0.4545454680919647)); } fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { randSeed2((vec2f(f32(x), f32(y)) + timeUniform)); - var textureSize = textureDimensions(writeView); - var uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(textureSize)); - var ray = getRayForUV(uv); - var ro = ray.origin.xyz; - var rd = ray.direction.xyz; + let textureSize = textureDimensions(writeView); + let uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(textureSize)); + let ray = getRayForUV(uv); + let ro = ray.origin.xyz; + let rd = ray.direction.xyz; var totalDist = 0f; var lastDist = 5f; var hit = false; for (var i = 0; (i < 48i); i++) { - var p = (ro + (rd * totalDist)); + let p = (ro + (rd * totalDist)); lastDist = sceneSDF(p); if ((lastDist < 1e-3f)) { hit = true; @@ -515,12 +515,12 @@ describe('ripple-cube example', () => { } var finalColor = textureSampleLevel(envMap, envSampler, rd, 0).rgb; if (hit) { - var p = (ro + (rd * totalDist)); - var n = getNormal(p); - var v = normalize((ro - p)); - var sceneColor = shade(p, n, v); + let p = (ro + (rd * totalDist)); + let n = getNormal(p); + let v = normalize((ro - p)); + let sceneColor = shade(p, n, v); let fog = exp((-(totalDist) * 0.05f)); - var fogColor = vec3f(0.019999999552965164, 0.019999999552965164, 0.03999999910593033); + let fogColor = vec3f(0.019999999552965164, 0.019999999552965164, 0.03999999910593033); finalColor = mix(fogColor, sceneColor, fog); } textureStore(writeView, vec2u(x, y), vec4f(finalColor, 1f)); @@ -542,34 +542,34 @@ describe('ripple-cube example', () => { @group(1) @binding(2) var outputTexture: texture_storage_2d; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var coord = vec2i(i32(x), i32(y)); - var current = textureLoad(currentTexture, coord, 0); - var historyColor = textureLoad(historyTexture, coord, 0); + let coord = vec2i(i32(x), i32(y)); + let current = textureLoad(currentTexture, coord, 0); + let historyColor = textureLoad(historyTexture, coord, 0); var minColor = vec3f(9999); var maxColor = vec3f(-9999); // unrolled iteration #0 { // unrolled iteration #0 { - var sampleCoord = (coord + vec2i(-1)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(-1)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } // unrolled iteration #1 { - var sampleCoord = (coord + vec2i(-1, 0)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(-1, 0)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } // unrolled iteration #2 { - var sampleCoord = (coord + vec2i(-1, 1)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(-1, 1)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } @@ -578,25 +578,25 @@ describe('ripple-cube example', () => { { // unrolled iteration #0 { - var sampleCoord = (coord + vec2i(0, -1)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(0, -1)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } // unrolled iteration #1 { - var sampleCoord = (coord + vec2i()); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i()); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } // unrolled iteration #2 { - var sampleCoord = (coord + vec2i(0, 1)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(0, 1)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } @@ -605,31 +605,31 @@ describe('ripple-cube example', () => { { // unrolled iteration #0 { - var sampleCoord = (coord + vec2i(1, -1)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(1, -1)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } // unrolled iteration #1 { - var sampleCoord = (coord + vec2i(1, 0)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(1, 0)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } // unrolled iteration #2 { - var sampleCoord = (coord + vec2i(1)); - var clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); - var neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; + let sampleCoord = (coord + vec2i(1)); + let clampedCoord = clamp(sampleCoord, vec2i(), vec2i(181)); + let neighbor = textureLoad(currentTexture, clampedCoord, 0).rgb; minColor = min(minColor, neighbor); maxColor = max(maxColor, neighbor); } } - var clampedHistory = clamp(historyColor.rgb, minColor, maxColor); - var blended = mix(current.rgb, clampedHistory, 0.85f); + let clampedHistory = clamp(historyColor.rgb, minColor, maxColor); + let blended = mix(current.rgb, clampedHistory, 0.85f); textureStore(outputTexture, vec2u(x, y), vec4f(blended, 1f)); } @@ -647,7 +647,7 @@ describe('ripple-cube example', () => { @group(1) @binding(1) var outputTexture: texture_storage_2d; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var color = textureLoad(inputTexture, vec2i(i32(x), i32(y)), 0); + let color = textureLoad(inputTexture, vec2i(i32(x), i32(y)), 0); textureStore(outputTexture, vec2u(x, y), color); } @@ -674,13 +674,13 @@ describe('ripple-cube example', () => { @group(0) @binding(1) var bloomUniform: BloomParams; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var dimensions = textureDimensions(outputTexture); - var uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(dimensions)); - var color = textureSampleLevel(inputTexture, sampler_1, uv, 0); + let dimensions = textureDimensions(outputTexture); + let uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(dimensions)); + let color = textureSampleLevel(inputTexture, sampler_1, uv, 0); let brightness = dot(color.rgb, vec3f(0.2125999927520752, 0.7152000069618225, 0.0722000002861023)); let threshold = bloomUniform.threshold; let bright = (max((brightness - threshold), 0f) / max(brightness, 1e-4f)); - var bloomColor = (color.rgb * bright); + let bloomColor = (color.rgb * bright); textureStore(outputTexture, vec2u(x, y), vec4f(bloomColor, 1f)); } @@ -700,14 +700,14 @@ describe('ripple-cube example', () => { @group(1) @binding(1) var outputTexture: texture_storage_2d; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var dimensions = textureDimensions(inputTexture); - var texelSize = (1f / vec2f(dimensions)); - var uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(dimensions)); - var offsetDir = vec2f(1, 0); + let dimensions = textureDimensions(inputTexture); + let texelSize = (1f / vec2f(dimensions)); + let uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(dimensions)); + let offsetDir = vec2f(1, 0); var result = vec3f(); var totalWeight = 0f; for (var i = -8; (i <= 8i); i++) { - var offset = ((offsetDir * f32(i)) * texelSize); + let offset = ((offsetDir * f32(i)) * texelSize); let weight = exp((-(f32((i * i))) / 16f)); result += (textureSampleLevel(inputTexture, sampler_1, (uv + offset), 0).rgb * weight); totalWeight += weight; @@ -731,14 +731,14 @@ describe('ripple-cube example', () => { @group(1) @binding(1) var outputTexture: texture_storage_2d; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var dimensions = textureDimensions(inputTexture); - var texelSize = (1f / vec2f(dimensions)); - var uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(dimensions)); - var offsetDir = vec2f(0, 1); + let dimensions = textureDimensions(inputTexture); + let texelSize = (1f / vec2f(dimensions)); + let uv = ((vec2f(f32(x), f32(y)) + 0.5f) / vec2f(dimensions)); + let offsetDir = vec2f(0, 1); var result = vec3f(); var totalWeight = 0f; for (var i = -8; (i <= 8i); i++) { - var offset = ((offsetDir * f32(i)) * texelSize); + let offset = ((offsetDir * f32(i)) * texelSize); let weight = exp((-(f32((i * i))) / 16f)); result += (textureSampleLevel(inputTexture, sampler_1, (uv + offset), 0).rgb * weight); totalWeight += weight; @@ -783,10 +783,10 @@ describe('ripple-cube example', () => { } @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { - var color = textureSample(colorTexture, sampler_1, _arg_0.uv); - var bloomColor = textureSample(bloomTexture, sampler_1, _arg_0.uv); + let color = textureSample(colorTexture, sampler_1, _arg_0.uv); + let bloomColor = textureSample(bloomTexture, sampler_1, _arg_0.uv); var final_1 = (color.rgb + (bloomColor.rgb * bloomUniform.intensity)); - var centeredUV = ((_arg_0.uv - 0.5f) * 2f); + let centeredUV = ((_arg_0.uv - 0.5f) * 2f); let vignette = (1f - (dot(centeredUV, centeredUV) * 0.15f)); final_1 *= vignette; return vec4f(final_1, 1f); diff --git a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts index e6d2de8222..0eb1086769 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts @@ -47,8 +47,8 @@ describe('simple shadow example', () => { } @vertex fn shadowVert(@location(0) position: vec4f) -> shadowVert_Output { - var world = (instanceInfo.modelMatrix * position); - var clip = (lightSpaceUniform.viewProj * world); + let world = (instanceInfo.modelMatrix * position); + let clip = (lightSpaceUniform.viewProj * world); return shadowVert_Output(clip); } @@ -82,10 +82,10 @@ describe('simple shadow example', () => { @vertex fn mainVert(@location(0) position: vec4f, @location(1) normal: vec4f) -> mainVert_Output { let modelMatrixUniform = (&instanceInfo.modelMatrix); - var worldPos = ((*modelMatrixUniform) * position); - var viewPos = (cameraUniform.view * worldPos); - var clipPos = (cameraUniform.projection * viewPos); - var transformedNormal = ((*modelMatrixUniform) * normal); + let worldPos = ((*modelMatrixUniform) * position); + let viewPos = (cameraUniform.view * worldPos); + let clipPos = (cameraUniform.projection * viewPos); + let transformedNormal = ((*modelMatrixUniform) * normal); return mainVert_Output(clipPos, transformedNormal, worldPos.xyz); } @@ -120,12 +120,12 @@ describe('simple shadow example', () => { @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { let instanceInfo_1 = (&instanceInfo); - var N = normalize(_arg_0.normal.xyz); - var L = normalize(-(light.direction)); - var V = normalize((cameraUniform.position - _arg_0.worldPos)); - var R = reflect(-(L), N); - var lp4 = (lightSpaceUniform.viewProj * vec4f(_arg_0.worldPos, 1f)); - var ndc = (lp4.xyz / lp4.w); + let N = normalize(_arg_0.normal.xyz); + let L = normalize(-(light.direction)); + let V = normalize((cameraUniform.position - _arg_0.worldPos)); + let R = reflect(-(L), N); + let lp4 = (lightSpaceUniform.viewProj * vec4f(_arg_0.worldPos, 1f)); + let ndc = (lp4.xyz / lp4.w); var uv = ((ndc.xy * 0.5f) + 0.5f); uv = vec2f(uv.x, (1f - uv.y)); let currentDepth = ndc.z; @@ -134,13 +134,13 @@ describe('simple shadow example', () => { if (!inBounds) { shadowFactor = 1f; } - var ambient = ((*instanceInfo_1).material.ambient * light.color); + let ambient = ((*instanceInfo_1).material.ambient * light.color); let diff = max(0f, dot(N, L)); - var diffuse = (((*instanceInfo_1).material.diffuse * light.color) * diff); + let diffuse = (((*instanceInfo_1).material.diffuse * light.color) * diff); let spec = pow(max(0f, dot(V, R)), (*instanceInfo_1).material.shininess); - var specular = (((*instanceInfo_1).material.specular * light.color) * spec); - var lit = ((diffuse + specular) * shadowFactor); - var finalColor = (ambient + lit); + let specular = (((*instanceInfo_1).material.specular * light.color) * spec); + let lit = ((diffuse + specular) * shadowFactor); + let finalColor = (ambient + lit); if ((paramsUniform.shadowOnly == 1f)) { return vec4f(vec3f(shadowFactor), 1f); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts index daab5433e6..0acb522042 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts @@ -52,8 +52,8 @@ describe('slime mold 3d example', () => { fn randInUnitSphere() -> vec3f { let u = sample(); - var v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); - var vNorm = normalize(v); + let v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); + let vNorm = normalize(v); return (vNorm * pow(u, 0.33f)); } @@ -68,9 +68,9 @@ describe('slime mold 3d example', () => { fn wrappedCallback(x: u32, _arg_1: u32, _arg_2: u32) { randSeed((f32(x) / 8e+5f)); - var pos = ((randInUnitSphere() * 64f) + vec3f(128)); - var center = vec3f(128); - var dir = normalize((center - pos)); + let pos = ((randInUnitSphere() * 64f) + vec3f(128)); + let center = vec3f(128); + let dir = normalize((center - pos)); item[x] = Agent(pos, dir); item_1[x] = Agent(pos, dir); } @@ -104,11 +104,11 @@ describe('slime mold 3d example', () => { @group(1) @binding(1) var newState: texture_storage_3d; @compute @workgroup_size(4, 4, 4) fn blur(@builtin(global_invocation_id) gid: vec3u) { - var dims = textureDimensions(oldState); + let dims = textureDimensions(oldState); if ((((gid.x >= dims.x) || (gid.y >= dims.y)) || (gid.z >= dims.z))) { return; } - var uv = ((vec3f(gid) + 0.5f) / vec3f(dims)); + let uv = ((vec3f(gid) + 0.5f) / vec3f(dims)); var sum = 0f; sum += getSummand(uv, (vec3f(-1, 0, 0) / vec3f(dims))); sum += getSummand(uv, (vec3f(1, 0, 0) / vec3f(dims))); @@ -173,19 +173,19 @@ describe('slime mold 3d example', () => { } fn sense3D(pos: vec3f, direction: vec3f) -> SenseResult { - var dims = textureDimensions(oldState); - var dimsf = vec3f(dims); + let dims = textureDimensions(oldState); + let dimsf = vec3f(dims); var weightedDir = vec3f(); var totalWeight = 0f; - var perp1 = getPerpendicular(direction); - var perp2 = cross(direction, perp1); + let perp1 = getPerpendicular(direction); + let perp2 = cross(direction, perp1); // unrolled iteration #0 { 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 - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -193,10 +193,10 @@ describe('slime mold 3d example', () => { // unrolled iteration #1 { 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 - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -204,10 +204,10 @@ describe('slime mold 3d example', () => { // unrolled iteration #2 { 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 - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -215,10 +215,10 @@ describe('slime mold 3d example', () => { // unrolled iteration #3 { 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 - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -226,10 +226,10 @@ describe('slime mold 3d example', () => { // unrolled iteration #4 { 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 - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -237,10 +237,10 @@ describe('slime mold 3d example', () => { // unrolled iteration #5 { 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 - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -248,10 +248,10 @@ describe('slime mold 3d example', () => { // unrolled iteration #6 { 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 - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -259,10 +259,10 @@ describe('slime mold 3d example', () => { // unrolled iteration #7 { 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)); - var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); + let coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); + let sensorDir = normalize((direction + (coneOffset * sin(params.sensorAngle)))); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - 1f))); let weight = textureLoad(oldState, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); @@ -288,7 +288,7 @@ describe('slime mold 3d example', () => { } fn randOnUnitHemisphere(normal: vec3f) -> vec3f { - var value = randOnUnitSphere(); + let value = randOnUnitSphere(); let alignment = dot(normal, value); return (sign(alignment) * value); } @@ -305,13 +305,13 @@ describe('slime mold 3d example', () => { fn randInUnitSphere() -> vec3f { let u = sample(); - var v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); - var vNorm = normalize(v); + let v = vec3f(randNormal(0f, 1f), randNormal(0f, 1f), randNormal(0f, 1f)); + let vNorm = normalize(v); return (vNorm * pow(u, 0.33f)); } fn randInUnitHemisphere(normal: vec3f) -> vec3f { - var value = randInUnitSphere(); + let value = randInUnitSphere(); let alignment = dot(normal, value); return (sign(alignment) * value); } @@ -325,23 +325,23 @@ describe('slime mold 3d example', () => { return; } randSeed(((f32(gid.x) / 8e+5f) + 0.1f)); - var dims = textureDimensions(oldState); - var dimsf = vec3f(dims); + let dims = textureDimensions(oldState); + let dimsf = vec3f(dims); let agent = (&oldAgents[gid.x]); var direction = normalize((*agent).direction); - var senseResult = sense3D((*agent).position, direction); - var targetDirection = select(randOnUnitHemisphere(direction), normalize(senseResult.weightedDir), (senseResult.totalWeight > 0.01f)); + let senseResult = sense3D((*agent).position, direction); + let targetDirection = select(randOnUnitHemisphere(direction), normalize(senseResult.weightedDir), (senseResult.totalWeight > 0.01f)); direction = normalize((direction + ((targetDirection * params.turnSpeed) * params.deltaTime))); var newPos = ((*agent).position + ((direction * params.moveSpeed) * params.deltaTime)); - var center = (dimsf / 2f); + let center = (dimsf / 2f); if (((newPos.x < 0f) || (newPos.x >= dimsf.x))) { newPos.x = clamp(newPos.x, 0f, (dimsf.x - 1f)); var normal = vec3f(1, 0, 0); if ((newPos.x > 1f)) { normal = vec3f(-1, 0, 0); } - var randomDir = randInUnitHemisphere(normal); - var toCenter = normalize((center - newPos)); + let randomDir = randInUnitHemisphere(normal); + let toCenter = normalize((center - newPos)); direction = normalize(((randomDir * 0.3f) + (toCenter * 0.7f))); } if (((newPos.y < 0f) || (newPos.y >= dimsf.y))) { @@ -350,8 +350,8 @@ describe('slime mold 3d example', () => { if ((newPos.y > 1f)) { normal = vec3f(0, -1, 0); } - var randomDir = randInUnitHemisphere(normal); - var toCenter = normalize((center - newPos)); + let randomDir = randInUnitHemisphere(normal); + let toCenter = normalize((center - newPos)); direction = normalize(((randomDir * 0.3f) + (toCenter * 0.7f))); } if (((newPos.z < 0f) || (newPos.z >= dimsf.z))) { @@ -360,8 +360,8 @@ describe('slime mold 3d example', () => { if ((newPos.z > 1f)) { normal = vec3f(0, 0, -1); } - var randomDir = randInUnitHemisphere(normal); - var toCenter = normalize((center - newPos)); + let randomDir = randInUnitHemisphere(normal); + let toCenter = normalize((center - newPos)); direction = normalize(((randomDir * 0.3f) + (toCenter * 0.7f))); } newAgents[gid.x] = Agent(newPos, direction); @@ -407,11 +407,11 @@ describe('slime mold 3d example', () => { } fn rayBoxIntersection(rayOrigin: vec3f, rayDir: vec3f, boxMin: vec3f, boxMax: vec3f) -> RayBoxResult { - var invDir = (1f / rayDir); - var t0 = ((boxMin - rayOrigin) * invDir); - var t1 = ((boxMax - rayOrigin) * invDir); - var tmin = min(t0, t1); - var tmax = max(t0, t1); + let invDir = (1f / rayDir); + let t0 = ((boxMin - rayOrigin) * invDir); + let t1 = ((boxMax - rayOrigin) * invDir); + let tmin = min(t0, t1); + let tmax = max(t0, t1); let tNear = max(max(tmin.x, tmin.y), tmin.z); let tFar = min(min(tmax.x, tmax.y), tmax.z); let hit = ((tFar >= tNear) && (tFar >= 0f)); @@ -440,17 +440,17 @@ describe('slime mold 3d example', () => { @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { randSeed2(_arg_0.uv); - var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), (1f - (_arg_0.uv.y * 2f))); - var ndcNear = vec4f(ndc, -1f, 1f); - var ndcFar = vec4f(ndc, 1f, 1f); - var worldNear = (cameraData.invViewProj * ndcNear); - var worldFar = (cameraData.invViewProj * ndcFar); - var rayOrigin = (worldNear.xyz / worldNear.w); - var rayEnd = (worldFar.xyz / worldFar.w); - var rayDir = normalize((rayEnd - rayOrigin)); - var boxMin = vec3f(); - var boxMax = vec3f(256); - var isect = rayBoxIntersection(rayOrigin, rayDir, boxMin, boxMax); + let ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), (1f - (_arg_0.uv.y * 2f))); + let ndcNear = vec4f(ndc, -1f, 1f); + let ndcFar = vec4f(ndc, 1f, 1f); + let worldNear = (cameraData.invViewProj * ndcNear); + let worldFar = (cameraData.invViewProj * ndcFar); + let rayOrigin = (worldNear.xyz / worldNear.w); + let rayEnd = (worldFar.xyz / worldFar.w); + let rayDir = normalize((rayEnd - rayOrigin)); + let boxMin = vec3f(); + let boxMax = vec3f(256); + let isect = rayBoxIntersection(rayOrigin, rayDir, boxMin, boxMax); if (!isect.hit) { return vec4f(); } @@ -468,20 +468,20 @@ describe('slime mold 3d example', () => { const thresholdHi = 0.25f; const gamma = 1.399999976158142f; const sigmaT = 0.10000000149011612f; - var albedo = vec3f(0.5699999928474426, 0.4399999976158142, 0.9599999785423279); + let albedo = vec3f(0.5699999928474426, 0.4399999976158142, 0.9599999785423279); var transmittance = 1f; var accum = vec3f(); const TMin = 0.0010000000474974513f; var i = 0i; while (((i < numSteps) && (transmittance > TMin))) { let t = (tStart + ((f32(i) + 0.5f) * stepSize)); - var pos = (rayOrigin + (rayDir * t)); - var texCoord = (pos / vec3f(256)); + let pos = (rayOrigin + (rayDir * t)); + let texCoord = (pos / vec3f(256)); let sampleValue = textureSampleLevel(state, sampler_1, texCoord, 0).x; let d0 = smoothstep(thresholdLo, thresholdHi, sampleValue); let density = pow(d0, gamma); let alphaSrc = (1f - exp(((-(sigmaT) * density) * stepSize))); - var contrib = (albedo * alphaSrc); + let contrib = (albedo * alphaSrc); accum += (contrib * transmittance); transmittance = (transmittance * (1f - alphaSrc)); i += 1i; diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts index 4b77dc41f4..bd3763d8bd 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts @@ -55,7 +55,7 @@ describe('slime mold example', () => { fn wrappedCallback(x: u32, _arg_1: u32, _arg_2: u32) { randSeed(((f32(x) / 2e+5f) + 0.1f)); - var pos = ((randInUnitCircle() * 118f) + vec2f(128)); + let pos = ((randInUnitCircle() * 118f) + vec2f(128)); let angle = atan2((128f - pos.y), (128f - pos.x)); agentsData[x] = Agent(pos, angle); } @@ -82,7 +82,7 @@ describe('slime mold example', () => { @group(1) @binding(1) var newState: texture_storage_2d; @compute @workgroup_size(16, 16) fn blur(@builtin(global_invocation_id) gid: vec3u) { - var dims = textureDimensions(oldState); + let dims = textureDimensions(oldState); if (((gid.x >= dims.x) || (gid.y >= dims.y))) { return; } @@ -92,30 +92,30 @@ describe('slime mold example', () => { { // unrolled iteration #0 { - var samplePos = (vec2i(gid.xy) + vec2i(-1)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(-1)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } } // unrolled iteration #1 { - var samplePos = (vec2i(gid.xy) + vec2i(0, -1)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(0, -1)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } } // unrolled iteration #2 { - var samplePos = (vec2i(gid.xy) + vec2i(1, -1)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(1, -1)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } @@ -125,30 +125,30 @@ describe('slime mold example', () => { { // unrolled iteration #0 { - var samplePos = (vec2i(gid.xy) + vec2i(-1, 0)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(-1, 0)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } } // unrolled iteration #1 { - var samplePos = (vec2i(gid.xy) + vec2i()); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i()); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } } // unrolled iteration #2 { - var samplePos = (vec2i(gid.xy) + vec2i(1, 0)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(1, 0)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } @@ -158,37 +158,37 @@ describe('slime mold example', () => { { // unrolled iteration #0 { - var samplePos = (vec2i(gid.xy) + vec2i(-1, 1)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(-1, 1)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } } // unrolled iteration #1 { - var samplePos = (vec2i(gid.xy) + vec2i(0, 1)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(0, 1)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } } // unrolled iteration #2 { - var samplePos = (vec2i(gid.xy) + vec2i(1)); - var dimsi = vec2i(dims); + let samplePos = (vec2i(gid.xy) + vec2i(1)); + let 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; + let color = textureLoad(oldState, vec2u(samplePos)).rgb; sum += color; count += 1f; } } } - var blurred = (sum / count); - var newColor = saturate((blurred - params.evaporationRate)); + let blurred = (sum / count); + let newColor = saturate((blurred - params.evaporationRate)); textureStore(newState, gid.xy, vec4f(newColor, 1f)); } @@ -235,12 +235,12 @@ describe('slime mold example', () => { fn sense(pos: vec2f, angle: f32, sensorAngleOffset: f32) -> f32 { let sensorAngle = (angle + sensorAngleOffset); - var sensorDir = vec2f(cos(sensorAngle), sin(sensorAngle)); - var sensorPos = (pos + (sensorDir * params.sensorDistance)); - var dims = textureDimensions(oldState); - var dimsf = vec2f(dims); - var sensorPosInt = vec2u(clamp(sensorPos, vec2f(), (dimsf - 1f))); - var color = textureLoad(oldState, sensorPosInt).rgb; + let sensorDir = vec2f(cos(sensorAngle), sin(sensorAngle)); + let sensorPos = (pos + (sensorDir * params.sensorDistance)); + let dims = textureDimensions(oldState); + let dimsf = vec2f(dims); + let sensorPosInt = vec2u(clamp(sensorPos, vec2f(), (dimsf - 1f))); + let color = textureLoad(oldState, sensorPosInt).rgb; return ((color.x + color.y) + color.z); } @@ -253,7 +253,7 @@ describe('slime mold example', () => { return; } randSeed(((f32(gid.x) / 2e+5f) + 0.1f)); - var dims = textureDimensions(oldState); + let dims = textureDimensions(oldState); let agent = (&agentsData[gid.x]); let random = randFloat01(); let weightForward = sense((*agent).position, (*agent).angle, 0f); @@ -278,9 +278,9 @@ describe('slime mold example', () => { } } } - var dir = vec2f(cos(angle), sin(angle)); + let dir = vec2f(cos(angle), sin(angle)); var newPos = ((*agent).position + ((dir * params.moveSpeed) * deltaTime)); - var dimsf = vec2f(dims); + let dimsf = vec2f(dims); if (((((newPos.x < 0f) || (newPos.x > dimsf.x)) || (newPos.y < 0f)) || (newPos.y > dimsf.y))) { newPos = clamp(newPos, vec2f(), (dimsf - 1f)); if (((newPos.x <= 0f) || (newPos.x >= (dimsf.x - 1f)))) { @@ -292,8 +292,8 @@ describe('slime mold example', () => { angle += ((random - 0.5f) * 0.1f); } agentsData[gid.x] = Agent(newPos, angle); - var oldState_1 = textureLoad(oldState, vec2u(newPos)).rgb; - var newState = (oldState_1 + 1f); + let oldState_1 = textureLoad(oldState, vec2u(newPos)).rgb; + let newState = (oldState_1 + 1f); textureStore(newState_1, vec2u(newPos), vec4f(newState, 1f)); } @@ -303,8 +303,8 @@ describe('slime mold example', () => { } @vertex fn fullScreenTriangle(@builtin(vertex_index) _arg_vertexIndex: u32) -> fullScreenTriangle_Output { - var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); - var uv = array(vec2f(0, 1), vec2f(2, 1), vec2f(0, -1)); + let pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); + let uv = array(vec2f(0, 1), vec2f(2, 1), vec2f(0, -1)); return fullScreenTriangle_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), uv[_arg_vertexIndex]); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/smoky-triangle.test.ts b/apps/typegpu-docs/tests/individual-example-tests/smoky-triangle.test.ts index ab5a14973c..62d467d90a 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/smoky-triangle.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/smoky-triangle.test.ts @@ -101,7 +101,7 @@ describe('smoky triangle', () => { @group(0) @binding(1) var memoryBuffer: array; fn getJunctionGradient(pos: vec3i) -> vec3f { - var size_i = vec3i(32); + let size_i = vec3i(32); let x = (((pos.x % size_i.x) + size_i.x) % size_i.x); let y = (((pos.y % size_i.y) + size_i.y) % size_i.y); let z = (((pos.z % size_i.z) + size_i.z) % size_i.z); @@ -109,8 +109,8 @@ describe('smoky triangle', () => { } fn dotProdGrid(pos: vec3f, junction: vec3f) -> f32 { - var relative = (pos - junction); - var gridVector = getJunctionGradient(vec3i(junction)); + let relative = (pos - junction); + let gridVector = getJunctionGradient(vec3i(junction)); return dot(relative, gridVector); } @@ -119,7 +119,7 @@ describe('smoky triangle', () => { } fn sample(pos: vec3f) -> f32 { - var minJunction = floor(pos); + let minJunction = floor(pos); let xyz = dotProdGrid(pos, minJunction); let xyZ = dotProdGrid(pos, (minJunction + vec3f(0, 0, 1))); let xYz = dotProdGrid(pos, (minJunction + vec3f(0, 1, 0))); @@ -128,8 +128,8 @@ describe('smoky triangle', () => { let XyZ = dotProdGrid(pos, (minJunction + vec3f(1, 0, 1))); let XYz = dotProdGrid(pos, (minJunction + vec3f(1, 1, 0))); let XYZ = dotProdGrid(pos, (minJunction + vec3f(1))); - var partial = (pos - minJunction); - var smoothPartial = quinticInterpolation(partial); + let partial = (pos - minJunction); + let smoothPartial = quinticInterpolation(partial); let xy = mix(xyz, xyZ, smoothPartial.z); let xY = mix(xYz, xYZ, smoothPartial.z); let Xy = mix(Xyz, XyZ, smoothPartial.z); @@ -164,10 +164,10 @@ describe('smoky triangle', () => { @fragment fn fragment(_arg_0: FragmentIn) -> @location(0) vec4f { let params = (¶msUniform); let t = ((*params).time * 0.1f); - var ouv = ((_arg_0.uv * 5f) + vec2f(0f, -(t))); + let ouv = ((_arg_0.uv * 5f) + vec2f(0f, -(t))); var off = (vec2f(sample(vec3f(ouv, t)), (sample(vec3f((ouv * 2f), (t + 10f))) * 0.5f)) + -0.1f); off = tanhVec((off * (*params).sharpness)); - var p = (_arg_0.uv + (off * (*params).distortion)); + let p = (_arg_0.uv + (off * (*params).distortion)); var factor = 0f; if (((*params).polarCoords == 1u)) { factor = length(((p - vec2f(0.5, 0.30000001192092896)) * 2f)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/square.test.ts b/apps/typegpu-docs/tests/individual-example-tests/square.test.ts index c1e9ec6fc5..b3d2254fc3 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/square.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/square.test.ts @@ -26,7 +26,7 @@ describe('square example', () => { } @vertex fn vertex(@builtin(vertex_index) idx: u32, @location(0) color: vec4f) -> vertex_Output { - var vertices = array(vec2f(-1), vec2f(1, -1), vec2f(1), vec2f(-1, 1)); + let vertices = array(vec2f(-1), vec2f(1, -1), vec2f(1), vec2f(-1, 1)); return vertex_Output(color, vec4f(vertices[idx], 0f, 1f)); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts index b94b01c01f..0972af3eac 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts @@ -39,18 +39,18 @@ describe('stable-fluid example', () => { @group(0) @binding(3) var linSampler: sampler; @compute @workgroup_size(16, 16) fn advectFn(@builtin(global_invocation_id) _arg_gid: vec3u) { - var texSize = textureDimensions(src); - var pixelPos = _arg_gid.xy; + let texSize = textureDimensions(src); + let pixelPos = _arg_gid.xy; if (((((pixelPos.x >= (texSize.x - 1u)) || (pixelPos.y >= (texSize.y - 1u))) || (pixelPos.x <= 0u)) || (pixelPos.y <= 0u))) { textureStore(dst, pixelPos, vec4f(0, 0, 0, 1)); return; } - var velocity = textureLoad(src, pixelPos, 0); + let velocity = textureLoad(src, pixelPos, 0); let timeStep = simParams.dt; - var prevPos = (vec2f(pixelPos) - (timeStep * velocity.xy)); - var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - 0.5f)); - var normalizedPos = ((clampedPos + 0.5f) / vec2f(texSize.xy)); - var prevVelocity = textureSampleLevel(src, linSampler, normalizedPos, 0); + let prevPos = (vec2f(pixelPos) - (timeStep * velocity.xy)); + let clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - 0.5f)); + let normalizedPos = ((clampedPos + 0.5f) / vec2f(texSize.xy)); + let prevVelocity = textureSampleLevel(src, linSampler, normalizedPos, 0); textureStore(dst, pixelPos, prevVelocity); } @@ -87,19 +87,19 @@ describe('stable-fluid example', () => { @group(0) @binding(1) var out: texture_storage_2d; @compute @workgroup_size(16, 16) fn diffusionFn(@builtin(global_invocation_id) _arg_gid: vec3u) { - var pixelPos = vec2i(_arg_gid.xy); - var texSize = vec2i(textureDimensions(in)); - var centerVal = textureLoad(in, pixelPos, 0); - var neighbors = getNeighbors(pixelPos, texSize); - var leftVal = textureLoad(in, neighbors[0i], 0); - var upVal = textureLoad(in, neighbors[1i], 0); - var rightVal = textureLoad(in, neighbors[2i], 0); - var downVal = textureLoad(in, neighbors[3i], 0); + let pixelPos = vec2i(_arg_gid.xy); + let texSize = vec2i(textureDimensions(in)); + let centerVal = textureLoad(in, pixelPos, 0); + let neighbors = getNeighbors(pixelPos, texSize); + let leftVal = textureLoad(in, neighbors[0i], 0); + let upVal = textureLoad(in, neighbors[1i], 0); + let rightVal = textureLoad(in, neighbors[2i], 0); + let downVal = textureLoad(in, neighbors[3i], 0); let timeStep = simParams.dt; let viscosity = simParams.viscosity; let diffuseRate = (viscosity * timeStep); let blendFactor = (1f / (4f + diffuseRate)); - var diffusedVal = (vec4f(blendFactor) * ((((leftVal + rightVal) + upVal) + downVal) + (centerVal * diffuseRate))); + let diffusedVal = (vec4f(blendFactor) * ((((leftVal + rightVal) + upVal) + downVal) + (centerVal * diffuseRate))); textureStore(out, pixelPos, diffusedVal); } @@ -129,13 +129,13 @@ describe('stable-fluid example', () => { @group(0) @binding(1) var div: texture_storage_2d; @compute @workgroup_size(16, 16) fn divergenceFn(@builtin(global_invocation_id) _arg_gid: vec3u) { - var pixelPos = vec2i(_arg_gid.xy); - var texSize = vec2i(textureDimensions(vel)); - var neighbors = getNeighbors(pixelPos, texSize); - var leftVel = textureLoad(vel, neighbors[0i], 0); - var upVel = textureLoad(vel, neighbors[1i], 0); - var rightVel = textureLoad(vel, neighbors[2i], 0); - var downVel = textureLoad(vel, neighbors[3i], 0); + let pixelPos = vec2i(_arg_gid.xy); + let texSize = vec2i(textureDimensions(vel)); + let neighbors = getNeighbors(pixelPos, texSize); + let leftVel = textureLoad(vel, neighbors[0i], 0); + let upVel = textureLoad(vel, neighbors[1i], 0); + let rightVel = textureLoad(vel, neighbors[2i], 0); + let downVel = textureLoad(vel, neighbors[3i], 0); let divergence = (0.5f * ((rightVel.x - leftVel.x) + (downVel.y - upVel.y))); textureStore(div, pixelPos, vec4f(divergence, 0f, 0f, 1f)); } @@ -168,13 +168,13 @@ describe('stable-fluid example', () => { @group(0) @binding(2) var out: texture_storage_2d; @compute @workgroup_size(16, 16) fn pressureFn(@builtin(global_invocation_id) _arg_gid: vec3u) { - var pixelPos = vec2i(_arg_gid.xy); - var texSize = vec2i(textureDimensions(x)); - var neighbors = getNeighbors(pixelPos, texSize); - var leftPressure = textureLoad(x, neighbors[0i], 0); - var upPressure = textureLoad(x, neighbors[1i], 0); - var rightPressure = textureLoad(x, neighbors[2i], 0); - var downPressure = textureLoad(x, neighbors[3i], 0); + let pixelPos = vec2i(_arg_gid.xy); + let texSize = vec2i(textureDimensions(x)); + let neighbors = getNeighbors(pixelPos, texSize); + let leftPressure = textureLoad(x, neighbors[0i], 0); + let upPressure = textureLoad(x, neighbors[1i], 0); + let rightPressure = textureLoad(x, neighbors[2i], 0); + let downPressure = textureLoad(x, neighbors[3i], 0); let divergence = textureLoad(b, pixelPos, 0).x; let newPressure = (0.25f * ((((leftPressure.x + rightPressure.x) + upPressure.x) + downPressure.x) - divergence)); textureStore(out, pixelPos, vec4f(newPressure, 0f, 0f, 1f)); @@ -208,16 +208,16 @@ describe('stable-fluid example', () => { @group(0) @binding(2) var out: texture_storage_2d; @compute @workgroup_size(16, 16) fn projectFn(@builtin(global_invocation_id) _arg_gid: vec3u) { - var pixelPos = vec2i(_arg_gid.xy); - var texSize = vec2i(textureDimensions(vel)); - var velocity = textureLoad(vel, pixelPos, 0); - var neighbors = getNeighbors(pixelPos, texSize); - var leftPressure = textureLoad(p, neighbors[0i], 0); - var upPressure = textureLoad(p, neighbors[1i], 0); - var rightPressure = textureLoad(p, neighbors[2i], 0); - var downPressure = textureLoad(p, neighbors[3i], 0); - var pressureGrad = vec2f((0.5f * (rightPressure.x - leftPressure.x)), (0.5f * (downPressure.x - upPressure.x))); - var projectedVel = (velocity.xy - pressureGrad); + let pixelPos = vec2i(_arg_gid.xy); + let texSize = vec2i(textureDimensions(vel)); + let velocity = textureLoad(vel, pixelPos, 0); + let neighbors = getNeighbors(pixelPos, texSize); + let leftPressure = textureLoad(p, neighbors[0i], 0); + let upPressure = textureLoad(p, neighbors[1i], 0); + let rightPressure = textureLoad(p, neighbors[2i], 0); + let downPressure = textureLoad(p, neighbors[3i], 0); + let pressureGrad = vec2f((0.5f * (rightPressure.x - leftPressure.x)), (0.5f * (downPressure.x - upPressure.x))); + let projectedVel = (velocity.xy - pressureGrad); textureStore(out, pixelPos, vec4f(projectedVel, 0f, 1f)); } @@ -237,14 +237,14 @@ describe('stable-fluid example', () => { @group(0) @binding(2) var dst: texture_storage_2d; @compute @workgroup_size(16, 16) fn advectInkFn(@builtin(global_invocation_id) _arg_gid: vec3u) { - var texSize = textureDimensions(src); - var pixelPos = _arg_gid.xy; - var velocity = textureLoad(vel, pixelPos, 0).xy; + let texSize = textureDimensions(src); + let pixelPos = _arg_gid.xy; + let velocity = textureLoad(vel, pixelPos, 0).xy; let timeStep = simParams.dt; - var prevPos = (vec2f(pixelPos) - (timeStep * velocity)); - var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - vec2f(0.5))); - var normalizedPos = ((clampedPos + 0.5f) / vec2f(texSize.xy)); - var inkVal = textureSampleLevel(src, linSampler, normalizedPos, 0); + let prevPos = (vec2f(pixelPos) - (timeStep * velocity)); + let clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - vec2f(0.5))); + let normalizedPos = ((clampedPos + 0.5f) / vec2f(texSize.xy)); + let inkVal = textureSampleLevel(src, linSampler, normalizedPos, 0); textureStore(dst, pixelPos, inkVal); } @@ -254,8 +254,8 @@ describe('stable-fluid example', () => { } @vertex fn renderFn(@builtin(vertex_index) _arg_idx: u32) -> renderFn_Output { - var vertices = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); - var texCoords = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); + let vertices = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); + let texCoords = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); return renderFn_Output(vec4f(vertices[_arg_idx], 0f, 1f), texCoords[_arg_idx]); } @@ -278,9 +278,9 @@ describe('stable-fluid example', () => { let gradientX = (rightSample - leftSample); let gradientY = (upSample - downSample); const distortStrength = 0.8; - var distortVector = vec2f(gradientX, gradientY); - var distortedUV = (_arg_0.uv + (distortVector * vec2f(distortStrength, -(distortStrength)))); - var outputColor = textureSample(background, linSampler, vec2f(distortedUV.x, (1f - distortedUV.y))); + let distortVector = vec2f(gradientX, gradientY); + let distortedUV = (_arg_0.uv + (distortVector * vec2f(distortStrength, -(distortStrength)))); + let outputColor = textureSample(background, linSampler, vec2f(distortedUV.x, (1f - distortedUV.y))); return vec4f(outputColor.rgb, 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/stencil.test.ts b/apps/typegpu-docs/tests/individual-example-tests/stencil.test.ts index 5d18a11d52..48bb4050f8 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/stencil.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/stencil.test.ts @@ -36,7 +36,7 @@ describe('stencil example', () => { @vertex fn vertexFn(@builtin(vertex_index) vid: u32) -> vertexFn_Output { let pos = vertices[vid]; let uv = uvs[vid]; - var rotatedPos = (rotationUniform * pos); + let rotatedPos = (rotationUniform * pos); return vertexFn_Output(vec4f(rotatedPos, 0f, 1f), uv); } @@ -54,7 +54,7 @@ describe('stencil example', () => { @vertex fn vertexFn(@builtin(vertex_index) vid: u32) -> vertexFn_Output { let pos = vertices[vid]; let uv = uvs[vid]; - var rotatedPos = (rotationUniform * pos); + let rotatedPos = (rotationUniform * pos); return vertexFn_Output(vec4f(rotatedPos, 0f, 1f), uv); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/tgsl-parsing-test.test.ts b/apps/typegpu-docs/tests/individual-example-tests/tgsl-parsing-test.test.ts index 00d7e99d0e..996fd3e99e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/tgsl-parsing-test.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/tgsl-parsing-test.test.ts @@ -33,7 +33,7 @@ describe('tgsl parsing test example', () => { } fn negateStruct(input: Schema) -> Schema { - var result = Schema(!(input.vec2b), !(input.vec4b), !(input.vec3b), !input.bool); + let result = Schema(!(input.vec2b), !(input.vec4b), !(input.vec3b), !input.bool); return result; } @@ -63,10 +63,10 @@ describe('tgsl parsing test example', () => { s = (s && true); s = (s && true); s = (s && true); - var vec = vec3(true, false, true); + let vec = vec3(true, false, true); s = (s && all(!(vec) == negate(vec))); - var inputStruct = Schema(vec2(false, true), vec4(false, true, false, true), vec3(true, true, false), true); - var resultStruct = negateStruct(inputStruct); + let inputStruct = Schema(vec2(false, true), vec4(false, true, false, true), vec3(true, true, false), true); + let resultStruct = negateStruct(inputStruct); s = (s && all(!(inputStruct.vec2b) == resultStruct.vec2b)); s = (s && all(!(inputStruct.vec4b) == resultStruct.vec4b)); s = (s && all(!(inputStruct.vec3b) == resultStruct.vec3b)); @@ -126,40 +126,40 @@ describe('tgsl parsing test example', () => { fn arrayAndStructConstructorsTest() -> bool { var s = true; - var defaultComplexStruct = ComplexStruct(); + let defaultComplexStruct = ComplexStruct(); s = (s && (2 == 2i)); s = (s && (defaultComplexStruct.arr[0i] == 0i)); s = (s && (defaultComplexStruct.arr[1i] == 0i)); - var defaultComplexArray = array(); + let defaultComplexArray = array(); s = (s && (3 == 3i)); s = (s && all(defaultComplexArray[0i].vec == vec2f())); s = (s && all(defaultComplexArray[1i].vec == vec2f())); s = (s && all(defaultComplexArray[2i].vec == vec2f())); var simpleStruct = SimpleStruct(vec2f(1, 2)); - var clonedSimpleStruct = simpleStruct; + let clonedSimpleStruct = simpleStruct; s = (s && all(simpleStruct.vec == clonedSimpleStruct.vec)); simpleStruct.vec[1i] += 1f; s = (s && !all(simpleStruct.vec == clonedSimpleStruct.vec)); var simpleArray = array(3i, 4i); - var clonedSimpleArray = simpleArray; + let clonedSimpleArray = simpleArray; s = (s && (simpleArray[0i] == clonedSimpleArray[0i])); s = (s && (simpleArray[1i] == clonedSimpleArray[1i])); simpleArray[1i] += 1i; s = (s && !(simpleArray[1i] == clonedSimpleArray[1i])); var complexStruct = ComplexStruct(array(5i, 6i)); - var clonedComplexStruct = complexStruct; + let clonedComplexStruct = complexStruct; s = (s && (complexStruct.arr[0i] == clonedComplexStruct.arr[0i])); s = (s && (complexStruct.arr[1i] == clonedComplexStruct.arr[1i])); complexStruct.arr[1i] += 1i; s = (s && !(complexStruct.arr[1i] == clonedComplexStruct.arr[1i])); var complexArray = array(SimpleStruct(vec2f(7, 8)), SimpleStruct(vec2f(9, 10)), SimpleStruct(vec2f(11, 12))); - var clonedComplexArray = complexArray; + let clonedComplexArray = complexArray; s = (s && all(complexArray[2i].vec == clonedComplexArray[2i].vec)); complexArray[2i].vec[1i] += 1f; s = (s && !all(complexArray[2i].vec == clonedComplexArray[2i].vec)); - var indirectClonedStruct = complexArray[0i]; + let indirectClonedStruct = complexArray[0i]; s = (s && all(indirectClonedStruct.vec == complexArray[0i].vec)); - var indirectlyClonedArray = complexStruct.arr; + let indirectlyClonedArray = complexStruct.arr; s = (s && (indirectlyClonedArray[0i] == complexStruct.arr[0i])); s = (s && (indirectlyClonedArray[1i] == complexStruct.arr[1i])); return s; diff --git a/apps/typegpu-docs/tests/individual-example-tests/two-boxes.test.ts b/apps/typegpu-docs/tests/individual-example-tests/two-boxes.test.ts index 03e50a2c57..2f9ec7a8a8 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/two-boxes.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/two-boxes.test.ts @@ -41,7 +41,7 @@ describe('two boxes example', () => { } @vertex fn vertex(@location(0) _arg_position: vec4f, @location(1) _arg_color: vec4f) -> vertex_Output { - var pos = (camera.projection * (camera.view * (transform.model * _arg_position))); + let pos = (camera.projection * (camera.view * (transform.model * _arg_position))); return vertex_Output(pos, _arg_color); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts index 11a61fe114..445bfbfd9b 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts @@ -66,8 +66,8 @@ describe('uniformity test example', () => { } @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { - var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); - var gridedUV = floor((uv * gridSizeUniform)); + let uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); + let gridedUV = floor((uv * gridSizeUniform)); randSeed2(gridedUV); return vec4f(vec3f(randFloat01()), 1f); } @@ -103,8 +103,8 @@ describe('uniformity test example', () => { } @fragment fn fragmentShader_1(_arg_0: fragmentShader_Input_1) -> @location(0) vec4f { - var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); - var gridedUV = floor((uv * gridSizeUniform)); + let uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); + let gridedUV = floor((uv * gridSizeUniform)); randSeed2_1(gridedUV); return vec4f(vec3f(randFloat01_1()), 1f); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts index dafd303f91..a9a5623a9a 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts @@ -74,8 +74,8 @@ describe('vaporrave example', () => { } @vertex fn vertexMain(@builtin(vertex_index) idx: u32) -> vertexMain_Output { - var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); - var uv = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); + let pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); + let uv = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } @@ -91,9 +91,9 @@ describe('vaporrave example', () => { } fn circles(uv: vec2f, angle: f32) -> vec3f { - var uvRotated = (rotateXY(angle) * vec2f(uv.x, (uv.y - 12f))); - var uvNormalized = fract((vec2f(uvRotated.x, uvRotated.y) / 1.2f)); - var diff2 = pow((vec2f(0.5) - uvNormalized), vec2f(2)); + let uvRotated = (rotateXY(angle) * vec2f(uv.x, (uv.y - 12f))); + let uvNormalized = fract((vec2f(uvRotated.x, uvRotated.y) / 1.2f)); + let diff2 = pow((vec2f(0.5) - uvNormalized), vec2f(2)); let distO = pow((diff2.x + diff2.y), 0.5f); return mix(vec3f(), vec3f(0.9200000166893005, 0.20999999344348907, 0.9599999785423279), exp((-5f * distO))); } @@ -119,7 +119,7 @@ describe('vaporrave example', () => { @group(0) @binding(2) var memoryBuffer: array; fn getJunctionGradient(pos: vec3i) -> vec3f { - var size_i = vec3i(7); + let size_i = vec3i(7); let x = (((pos.x % size_i.x) + size_i.x) % size_i.x); let y = (((pos.y % size_i.y) + size_i.y) % size_i.y); let z = (((pos.z % size_i.z) + size_i.z) % size_i.z); @@ -127,8 +127,8 @@ describe('vaporrave example', () => { } fn dotProdGrid(pos: vec3f, junction: vec3f) -> f32 { - var relative = (pos - junction); - var gridVector = getJunctionGradient(vec3i(junction)); + let relative = (pos - junction); + let gridVector = getJunctionGradient(vec3i(junction)); return dot(relative, gridVector); } @@ -137,7 +137,7 @@ describe('vaporrave example', () => { } fn sample(pos: vec3f) -> f32 { - var minJunction = floor(pos); + let minJunction = floor(pos); let xyz = dotProdGrid(pos, minJunction); let xyZ = dotProdGrid(pos, (minJunction + vec3f(0, 0, 1))); let xYz = dotProdGrid(pos, (minJunction + vec3f(0, 1, 0))); @@ -146,8 +146,8 @@ describe('vaporrave example', () => { let XyZ = dotProdGrid(pos, (minJunction + vec3f(1, 0, 1))); let XYz = dotProdGrid(pos, (minJunction + vec3f(1, 1, 0))); let XYZ = dotProdGrid(pos, (minJunction + vec3f(1))); - var partial = (pos - minJunction); - var smoothPartial = quinticInterpolation(partial); + let partial = (pos - minJunction); + let smoothPartial = quinticInterpolation(partial); let xy = mix(xyz, xyZ, smoothPartial.z); let xY = mix(xYz, xYZ, smoothPartial.z); let Xy = mix(Xyz, XyZ, smoothPartial.z); @@ -158,10 +158,10 @@ describe('vaporrave example', () => { } fn getSphere(p: vec3f, sphereColor: vec3f, sphereCenter: vec3f, angle: f32) -> Ray { - var localP = (p - sphereCenter); - var rotMatZ = rotateAroundZ((-(angle) * 0.3f)); - var rotMatX = rotateAroundX((-(angle) * 0.7f)); - var rotatedP = ((localP * rotMatZ) * rotMatX); + let localP = (p - sphereCenter); + let rotMatZ = rotateAroundZ((-(angle) * 0.3f)); + let rotMatX = rotateAroundX((-(angle) * 0.7f)); + let rotatedP = ((localP * rotMatZ) * rotMatX); let radius = (3f + sin(angle)); let rawDist = sdSphere(rotatedP, radius); var noise = 0f; @@ -180,8 +180,8 @@ describe('vaporrave example', () => { } fn getSceneRay(p: vec3f) -> Ray { - var floor_1 = Ray(circles(p.xz, floorAngleUniform), sdPlane(p, vec3f(0, 1, 0), 1f)); - var sphere = getSphere(p, sphereColorUniform, vec3f(0, 6, 12), sphereAngleUniform); + let floor_1 = Ray(circles(p.xz, floorAngleUniform), sdPlane(p, vec3f(0, 1, 0), 1f)); + let sphere = getSphere(p, sphereColorUniform, vec3f(0, 6, 12), sphereAngleUniform); return rayUnion(floor_1, sphere); } @@ -195,9 +195,9 @@ describe('vaporrave example', () => { var result = Ray(vec3f(), 19f); var glow = vec3f(); for (var i = 0; (i < 1000i); i++) { - var p = ((rd * distOrigin) + ro); - var scene = getSceneRay(p); - var sphereDist = getSphere(p, sphereColorUniform, vec3f(0, 6, 12), sphereAngleUniform); + let p = ((rd * distOrigin) + ro); + let scene = getSceneRay(p); + let sphereDist = getSphere(p, sphereColorUniform, vec3f(0, 6, 12), sphereAngleUniform); glow += (sphereColorUniform * exp(-(sphereDist.dist))); distOrigin += scene.dist; if ((distOrigin > 19f)) { @@ -222,11 +222,11 @@ describe('vaporrave example', () => { @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolutionUniform.x / resolutionUniform.y); - var ro = vec3f(0, 2, -1); - var rd = normalize(vec3f(uv.x, uv.y, 1f)); - var march = rayMarch(ro, rd); + let ro = vec3f(0, 2, -1); + let rd = normalize(vec3f(uv.x, uv.y, 1f)); + let march = rayMarch(ro, rd); let y = (((rd.y * march.ray.dist) + ro.y) - 2f); - var sky = mix(vec4f(0.10000000149011612, 0, 0.20000000298023224, 1), vec4f(0.2800000011920929, 0, 0.5400000214576721, 1), (y / 19f)); + let sky = mix(vec4f(0.10000000149011612, 0, 0.20000000298023224, 1), vec4f(0.2800000011920929, 0, 0.5400000214576721, 1), (y / 19f)); let fog = min((march.ray.dist / 19f), 1f); return mix(mix(vec4f(march.ray.color, 1f), sky, fog), vec4f(march.glow, 1f), glowIntensityUniform); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/wgsl-resolution.test.ts b/apps/typegpu-docs/tests/individual-example-tests/wgsl-resolution.test.ts index 315d8404b9..a7456be9e5 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/wgsl-resolution.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/wgsl-resolution.test.ts @@ -40,10 +40,10 @@ describe('wgsl resolution example', () => { @vertex fn vertex_shader(@location(0) _arg_v: vec2f, @location(1) _arg_center: vec2f, @location(2) _arg_velocity: vec2f) -> vertex_shader_Output { let angle = get_rotation_from_velocity_util(_arg_velocity); - var rotated = rotate_util(_arg_v, angle); - var pos = vec4f((rotated.x + _arg_center.x), (rotated.y + _arg_center.y), 0f, 1f); + let rotated = rotate_util(_arg_v, angle); + let pos = vec4f((rotated.x + _arg_center.x), (rotated.y + _arg_center.y), 0f, 1f); let colorPalette = (&colorPalette_1); - var color = vec4f(((sin((angle + (*colorPalette).x)) * 0.45f) + 0.45f), ((sin((angle + (*colorPalette).y)) * 0.45f) + 0.45f), ((sin((angle + (*colorPalette).z)) * 0.45f) + 0.45f), 1f); + let color = vec4f(((sin((angle + (*colorPalette).x)) * 0.45f) + 0.45f), ((sin((angle + (*colorPalette).y)) * 0.45f) + 0.45f), ((sin((angle + (*colorPalette).z)) * 0.45f) + 0.45f), 1f); return vertex_shader_Output(pos, color); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts index 894d6e4d67..8438e76d59 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts @@ -26,7 +26,7 @@ describe('xor dev centrifuge example', () => { } @vertex fn vertexMain(@builtin(vertex_index) _arg_vertexIndex: u32) -> vertexMain_Output { - var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); + let pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } @@ -53,16 +53,16 @@ describe('xor dev centrifuge example', () => { @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let params = (¶msUniform); - var ratio = vec2f((*params).aspectRatio, 1f); - var dir = normalize(vec3f((_arg_0.uv * ratio), -1f)); + let ratio = vec2f((*params).aspectRatio, 1f); + let dir = normalize(vec3f((_arg_0.uv * ratio), -1f)); var z = 0f; var acc = vec3f(); for (var i = 0; (i < (*params).tunnelDepth); i++) { var p = (dir * z); p.x += (*params).cameraPos.x; p.y += (*params).cameraPos.y; - var coords = vec3f(((atan2(p.y, p.x) * (*params).bigStrips) + (*params).time), ((p.z * (*params).dollyZoom) - (5f * (*params).time)), (length(p.xy) - 11f)); - var coords2 = (cos((coords + cos((coords * (*params).smallStrips)))) - 1f); + let coords = vec3f(((atan2(p.y, p.x) * (*params).bigStrips) + (*params).time), ((p.z * (*params).dollyZoom) - (5f * (*params).time)), (length(p.xy) - 11f)); + let coords2 = (cos((coords + cos((coords * (*params).smallStrips)))) - 1f); let dd = ((length(vec4f(coords.z, coords2)) * 0.5f) - 0.1f); acc += ((1.2f - cos(((*params).color * p.z))) / dd); z += dd; diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts index ec9b1c27a0..7544091949 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts @@ -30,7 +30,7 @@ describe('xor dev runner example', () => { } @vertex fn vertexMain(@builtin(vertex_index) _arg_vertexIndex: u32) -> vertexMain_Output { - var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); + let pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } @@ -54,9 +54,9 @@ describe('xor dev runner example', () => { fn getRayForUV(uv: vec2f) -> Ray { let camera = (&cameraUniform); - var farView = ((*camera).projectionInverse * vec4f(uv, 1f, 1f)); - var farWorld = ((*camera).viewInverse * vec4f((farView.xyz / farView.w), 1f)); - var direction = normalize((farWorld.xyz - (*camera).pos.xyz)); + let farView = ((*camera).projectionInverse * vec4f(uv, 1f, 1f)); + let farWorld = ((*camera).viewInverse * vec4f((farView.xyz / farView.w), 1f)); + let direction = normalize((farWorld.xyz - (*camera).pos.xyz)); return Ray((*camera).pos, vec4f(direction, 0f)); } @@ -83,12 +83,12 @@ describe('xor dev runner example', () => { } @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { - var icolor = (colorUniform * 4f); - var ray = getRayForUV(_arg_0.uv); + let icolor = (colorUniform * 4f); + let ray = getRayForUV(_arg_0.uv); var acc = vec3f(); var z = 0f; for (var l = 0; (l < 30i); l++) { - var p = ((((vec3f(3, 0, 3) + controlsOffsetUniform) + autoMoveOffsetUniform) + ray.origin.xyz) + (ray.direction.xyz * z)); + let p = ((((vec3f(3, 0, 3) + controlsOffsetUniform) + autoMoveOffsetUniform) + ray.origin.xyz) + (ray.direction.xyz * z)); var q = p; var prox = p.y; for (var i = 40.1; (i > 0.01f); i *= 0.2f) { From baa830ba76a2d3f654308de7e4644a53c08cbc8b Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:06:38 +0200 Subject: [PATCH 22/34] Fix the weird unroll case (thx @cieplypolar) --- packages/typegpu/src/tgsl/conversion.ts | 4 ++ .../typegpu/tests/mutabilityTracking.test.ts | 48 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index a11954784a..1bee6c1b5a 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -227,6 +227,10 @@ function applyActionToSnippet( targetType: BaseData, ): Snippet { if (action.action === 'none') { + if (targetType === snippet.dataType) { + return snippet; + } + return snip( snippet.value, targetType, diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 49ae6ec26b..31a3cf46a0 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -133,6 +133,9 @@ describe('mutability tracking', () => { var a = arg.x; if ((a < 1u)) { a = 1u; + } + else { + } return a; }" @@ -289,7 +292,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn item(arg: vec4u) -> u32 { - for (var i = 0i; i < 3i; i += 1i) { + for (var i = 0u; i < 3u; i += 1u) { } for (var j = 0; (j < 3i); j++) { @@ -506,6 +509,49 @@ describe('mutability tracking', () => { expect(resolved).toContain('var struct_1 = Struct()'); }); + it('resolves an array with its element referenced', () => { + const fn = fnShell((arg) => { + 'use gpu'; + const t = [d.vec2u()]; + const e = t[0]!; + return e.x; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var t = array(vec2u()); + let e = (&t[0i]); + return (*e).x; + }" + `); + expect(resolved).toContain('var t = '); + }); + + it('resolves the weird unroll case', () => { + const fn = fnShell((arg) => { + 'use gpu'; + const a = d.vec2u(); + for (const e of tgpu.unroll([a])) { + a.x += 1; + } + return 0; + }); + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn item(arg: vec4u) -> u32 { + var a = vec2u(); + // unrolled iteration #0 + { + a.x += 1u; + } + return 0u; + }" + `); + expect(resolved).toContain('var a = '); + }); + it('resolves a struct when its prop is only read', () => { const Struct = d.struct({ prop: d.vec4f }); const fn = () => { From d0affe8c73c34272f7af2171b9d0b50a9a890e4e Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:28:03 +0200 Subject: [PATCH 23/34] Update some tests --- packages/typegpu/tests/accessor.test.ts | 14 ++--- packages/typegpu/tests/array.test.ts | 34 +++++----- .../typegpu/tests/computePipeline.test.ts | 2 +- packages/typegpu/tests/indent.test.ts | 24 +++---- packages/typegpu/tests/lazy.test.ts | 6 +- .../typegpu/tests/pipeline-resolution.test.ts | 6 +- packages/typegpu/tests/renderPipeline.test.ts | 4 +- packages/typegpu/tests/slot.test.ts | 6 +- .../typegpu/tests/std/boolean/not.test.ts | 6 +- .../typegpu/tests/std/matrix/rotate.test.ts | 6 +- .../typegpu/tests/std/matrix/scale.test.ts | 2 +- .../tests/std/matrix/translate.test.ts | 2 +- .../tests/std/numeric/bitShift.test.ts | 28 ++++----- packages/typegpu/tests/std/range.test.ts | 4 +- .../tests/std/texture/textureGather.test.ts | 16 ++--- .../tests/std/texture/textureLoad.test.ts | 38 ++++++------ packages/typegpu/tests/struct.test.ts | 12 ++-- packages/typegpu/tests/tgsl/codeGen.test.ts | 2 +- .../typegpu/tests/tgsl/consoleLog.test.ts | 2 +- .../typegpu/tests/tgsl/infixOperators.test.ts | 62 +++++++++---------- .../tests/tgsl/operatorOverloads.test.ts | 10 +-- .../typegpu/tests/tgsl/typeInference.test.ts | 34 +++++----- packages/typegpu/tests/tgslFn.test.ts | 26 ++++---- packages/typegpu/tests/vector.test.ts | 42 ++++++------- 24 files changed, 194 insertions(+), 194 deletions(-) diff --git a/packages/typegpu/tests/accessor.test.ts b/packages/typegpu/tests/accessor.test.ts index 851263eb88..12c83520da 100644 --- a/packages/typegpu/tests/accessor.test.ts +++ b/packages/typegpu/tests/accessor.test.ts @@ -162,9 +162,9 @@ describe('tgpu.accessor', () => { } fn main() { - var color = vec3f(1, 0, 0); + let color = vec3f(1, 0, 0); let color2 = (&redUniform); - var color3 = getColor(); + let color3 = getColor(); const colorX = 1f; let color2X = redUniform.x; let color3X = getColor().x; @@ -234,7 +234,7 @@ describe('tgpu.accessor', () => { } fn main() { - var pixel = getPixel(0i, 0i); + let pixel = getPixel(0i, 0i); }" `); }); @@ -276,8 +276,8 @@ describe('tgpu.accessor', () => { } fn main() { - var foo = getColor(); - var bar = getColor_1(); + let foo = getColor(); + let bar = getColor_1(); }" `); }); @@ -309,7 +309,7 @@ describe('tgpu.accessor', () => { } fn main() { - var foo = getColor(); + let foo = getColor(); }" `); }); @@ -358,7 +358,7 @@ describe('tgpu.accessor', () => { } fn main() { - var pixel = getPixel(0i, 0i); + let pixel = getPixel(0i, 0i); }" `); }); diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index 0eb30bbd72..fccd3ba623 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -184,7 +184,7 @@ describe('array', () => { expect(tgpu.resolve([testFunction])).toMatchInlineSnapshot(` "fn testFunction() { - var defaultValue = array, 2>(); + let defaultValue = array, 2>(); }" `); }); @@ -207,12 +207,12 @@ describe('array', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn f(arr: array) { - var clone = arr; + let clone = arr; } fn testFn() { - var myArray = array(10u); - var myClone = myArray; + let myArray = array(10u); + let myClone = myArray; f(myArray); return; }" @@ -230,8 +230,8 @@ describe('array', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() { - var myArrays = array, 1>(array(10i)); - var myClone = myArrays[0i]; + let myArrays = array, 1>(array(10i)); + let myClone = myArrays[0i]; return; }" `); @@ -246,7 +246,7 @@ describe('array', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var arr = array(6f, 7f); + let arr = array(6f, 7f); return; }" `); @@ -270,11 +270,11 @@ describe('array', () => { "fn f(v: vec4f) { var v2 = vec4f(3); let v3 = (&v2); - var arr = array(v, v2, (*v3)); + let arr = array(v, v2, (*v3)); } fn main() { - var v1 = vec4f(7); + let v1 = vec4f(7); f(v1); return; }" @@ -290,7 +290,7 @@ describe('array', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var arr = array(5f, 6.7f, 8f); + let arr = array(5f, 6.7f, 8f); return; }" `); @@ -303,7 +303,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() { - var result = array(); + let result = array(); }" `); }); @@ -315,7 +315,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() { - var result = array(); + let result = array(); }" `); }); @@ -350,7 +350,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() { - var result = array(1f, 2f, 3f, 4f); + let result = array(1f, 2f, 3f, 4f); }" `); }); @@ -362,7 +362,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() { - var result = array(4f, 3f, 2f, 1f); + let result = array(4f, 3f, 2f, 1f); }" `); }); @@ -376,7 +376,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() { - var result = array(4f, 3f, 2f, 1f); + let result = array(4f, 3f, 2f, 1f); }" `); }); @@ -392,7 +392,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() { - var result = array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f); + let result = array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f); }" `); }); @@ -439,7 +439,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo(n: u32) { const m = 1u; - var result = array(1u, n, m); + let result = array(1u, n, m); }" `); }); diff --git a/packages/typegpu/tests/computePipeline.test.ts b/packages/typegpu/tests/computePipeline.test.ts index 6fadc72872..3cbea14d08 100644 --- a/packages/typegpu/tests/computePipeline.test.ts +++ b/packages/typegpu/tests/computePipeline.test.ts @@ -453,7 +453,7 @@ describe('TgpuComputePipeline', () => { enable subgroups; @compute @workgroup_size(1) fn fn_1() { - var a = array(); + let a = array(); }" `); }); diff --git a/packages/typegpu/tests/indent.test.ts b/packages/typegpu/tests/indent.test.ts index 2952762fcf..650caa62a3 100644 --- a/packages/typegpu/tests/indent.test.ts +++ b/packages/typegpu/tests/indent.test.ts @@ -28,8 +28,8 @@ describe('indents', () => { } fn updateParicle(particle: Particle, gravity: vec3f, deltaTime: f32) -> Particle { - var newVelocity = ((particle.velocity * gravity) * deltaTime); - var newPosition = (particle.position + (newVelocity * deltaTime)); + let newVelocity = ((particle.velocity * gravity) * deltaTime); + let newPosition = (particle.position + (newVelocity * deltaTime)); return Particle(newPosition, newVelocity); }" `); @@ -90,8 +90,8 @@ describe('indents', () => { @group(0) @binding(0) var systemData: SystemData; fn updateParicle(particle: Particle, gravity: vec3f, deltaTime: f32) -> Particle { - var newVelocity = ((particle.velocity * gravity) * deltaTime); - var newPosition = (particle.position + (newVelocity * deltaTime)); + let newVelocity = ((particle.velocity * gravity) * deltaTime); + let newPosition = (particle.position + (newVelocity * deltaTime)); return Particle(newPosition, newVelocity); } @@ -207,9 +207,9 @@ describe('indents', () => { fn updateParticle(particle: Particle, gravity: vec3f, deltaTime: f32) -> Particle { let density = getDensityAt(particle.physics.position); - var force = (gravity * density); - var newVelocity = (particle.physics.velocity + (force * deltaTime)); - var newPosition = (particle.physics.position + (newVelocity * deltaTime)); + let force = (gravity * density); + let newVelocity = (particle.physics.velocity + (force * deltaTime)); + let newPosition = (particle.physics.position + (newVelocity * deltaTime)); return Particle(particle.id, PhysicsData(particle.physics.weight, newVelocity, newPosition)); } @@ -375,16 +375,16 @@ describe('indents', () => { @vertex fn someVertex(@location(0) _arg_position: vec4f, @location(1) _arg_something: vec4f) -> someVertex_Output { let uniBoid = (&boids); for (var i = 0u; (i < 1u); i++) { - var sampled_1 = textureSample(sampled, sampler_1, vec2f(0.5), i); - var someVal = textureLoad(smoothRender, vec2i(), 0); + let sampled_1 = textureSample(sampled, sampler_1, vec2f(0.5), i); + let someVal = textureLoad(smoothRender, vec2i(), 0); if (((someVal.x + sampled_1.x) > 0.5f)) { - var newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); + let newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); } else { while (true) { - var newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); + let newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); if ((newPos.x > 0f)) { - var evenNewer = (newPos + _arg_position); + let evenNewer = (newPos + _arg_position); } } } diff --git a/packages/typegpu/tests/lazy.test.ts b/packages/typegpu/tests/lazy.test.ts index 5058c537e2..5f06e809d3 100644 --- a/packages/typegpu/tests/lazy.test.ts +++ b/packages/typegpu/tests/lazy.test.ts @@ -152,7 +152,7 @@ describe('TgpuLazy', () => { @group(0) @binding(0) var boid: Boid; fn func() { - var pos = vec3f(2, 4, 6); + let pos = vec3f(2, 4, 6); const posX = 2f; let vel = (&boid.vel); let velX = boid.vel.x; @@ -237,11 +237,11 @@ describe('TgpuLazy', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn foo() { - var array_1 = array(); + let array_1 = array(); } fn foo_1() { - var array_1 = array(); + let array_1 = array(); } fn main() { diff --git a/packages/typegpu/tests/pipeline-resolution.test.ts b/packages/typegpu/tests/pipeline-resolution.test.ts index 3d5272ae8f..5bbf9618ca 100644 --- a/packages/typegpu/tests/pipeline-resolution.test.ts +++ b/packages/typegpu/tests/pipeline-resolution.test.ts @@ -50,7 +50,7 @@ describe('resolve', () => { } @vertex fn vertexFn() -> vertexFn_Output { - var myBoid = Boid(); + let myBoid = Boid(); return vertexFn_Output(vec4f(myBoid.position, 0f, 1f), myBoid.color); } @@ -74,7 +74,7 @@ describe('resolve', () => { } @compute @workgroup_size(1, 1, 1) fn computeFn() { - var myBoid = Boid(vec2f(), vec4f(1, 0, 0, 1)); + let myBoid = Boid(vec2f(), vec4f(1, 0, 0, 1)); }" `); }); @@ -97,7 +97,7 @@ describe('resolve', () => { } fn wrappedCallback(x: u32, y: u32, z: u32) { - var myBoid = Boid(vec2f(), vec4f(f32(x), f32(y), f32(z), 1f)); + let myBoid = Boid(vec2f(), vec4f(f32(x), f32(y), f32(z), 1f)); } @compute @workgroup_size(8, 8, 4) fn mainCompute(@builtin(global_invocation_id) id: vec3u) { diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index be0c2888e4..aa406650fe 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -1456,7 +1456,7 @@ describe('root.createRenderPipeline', () => { } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { - var pos = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); + let pos = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); return VertexOut(vec4f(pos[_arg_0.vertexIndex], 0f, 1f), (pos[_arg_0.vertexIndex] + vec2f(0.5))); } @@ -1566,7 +1566,7 @@ describe('root.createRenderPipeline', () => { } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { - var uv = array(vec2f(0.5, 1), vec2f(), vec2f(1, 0)); + let uv = array(vec2f(0.5, 1), vec2f(), vec2f(1, 0)); return VertexOut(vec4f(_arg_0.localPos, 1f), uv[_arg_0.vertexIndex]); } diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts index 8f1fd76175..8e6d38fb76 100644 --- a/packages/typegpu/tests/slot.test.ts +++ b/packages/typegpu/tests/slot.test.ts @@ -286,13 +286,13 @@ describe('tgpu.slot', () => { } fn func() { - var pos = vec3f(1, 2, 3); + let pos = vec3f(1, 2, 3); const posX = 1f; let vel = (&boid.vel); let velX = boid.vel.x; let vel_ = (&boid.vel); let velX_ = boid.vel.x; - var color = getColor(); + let color = getColor(); }" `); }); @@ -319,7 +319,7 @@ describe('tgpu.slot', () => { // Gamma Correction: OFF expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main(uv: vec2f) -> vec3f { - var color = vec3f(1, 0, 1); + let color = vec3f(1, 0, 1); return color; }" `); diff --git a/packages/typegpu/tests/std/boolean/not.test.ts b/packages/typegpu/tests/std/boolean/not.test.ts index 930c405ccd..8d4a3f2eb3 100644 --- a/packages/typegpu/tests/std/boolean/not.test.ts +++ b/packages/typegpu/tests/std/boolean/not.test.ts @@ -102,7 +102,7 @@ describe('not', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var v = vec4(false, false, true, false); + let v = vec4(false, false, true, false); }" `); }); @@ -142,7 +142,7 @@ describe('not', () => { "fn testFn(v: vec3f, a: atomic, p: ptr) { const _b0 = false; let _b1 = false; - var _b2 = !(vec3(v)); + let _b2 = !(vec3(v)); let _b3 = false; let _b4 = !bool(atomicLoad(&a)); let _b5 = false; @@ -233,7 +233,7 @@ describe('not', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var v = vec4(false, false, true, false); + let v = vec4(false, false, true, false); }" `); }); diff --git a/packages/typegpu/tests/std/matrix/rotate.test.ts b/packages/typegpu/tests/std/matrix/rotate.test.ts index 191aa4d3ab..2b6642afc4 100644 --- a/packages/typegpu/tests/std/matrix/rotate.test.ts +++ b/packages/typegpu/tests/std/matrix/rotate.test.ts @@ -16,7 +16,7 @@ describe('rotate', () => { expect(tgpu.resolve([rotateFn])).toMatchInlineSnapshot(` "fn rotateFn() { const angle = 4; - var resultExpression = (mat4x4f(1, 0, 0, 0, 0, cos(f32(angle)), sin(f32(angle)), 0, 0, -sin(f32(angle)), cos(f32(angle)), 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); + let resultExpression = (mat4x4f(1, 0, 0, 0, 0, cos(f32(angle)), sin(f32(angle)), 0, 0, -sin(f32(angle)), cos(f32(angle)), 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); }" `); }); @@ -32,7 +32,7 @@ describe('rotate', () => { expect(tgpu.resolve([rotateFn])).toMatchInlineSnapshot(` "fn rotateFn() { const angle = 4; - var resultExpression = (mat4x4f(cos(f32(angle)), 0, -sin(f32(angle)), 0, 0, 1, 0, 0, sin(f32(angle)), 0, cos(f32(angle)), 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); + let resultExpression = (mat4x4f(cos(f32(angle)), 0, -sin(f32(angle)), 0, 0, 1, 0, 0, sin(f32(angle)), 0, cos(f32(angle)), 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); }" `); }); @@ -48,7 +48,7 @@ describe('rotate', () => { expect(tgpu.resolve([rotateFn])).toMatchInlineSnapshot(` "fn rotateFn() { const angle = 4; - var resultExpression = (mat4x4f(cos(f32(angle)), sin(f32(angle)), 0, 0, -sin(f32(angle)), cos(f32(angle)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); + let resultExpression = (mat4x4f(cos(f32(angle)), sin(f32(angle)), 0, 0, -sin(f32(angle)), cos(f32(angle)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); }" `); }); diff --git a/packages/typegpu/tests/std/matrix/scale.test.ts b/packages/typegpu/tests/std/matrix/scale.test.ts index 89907e7ec8..706085a81f 100644 --- a/packages/typegpu/tests/std/matrix/scale.test.ts +++ b/packages/typegpu/tests/std/matrix/scale.test.ts @@ -21,7 +21,7 @@ describe('scale', () => { expect(tgpu.resolve([scaleFn])).toMatchInlineSnapshot(` "fn scaleFn() { - var resultExpression = mat4x4f(2, 0, 0, 1, 0, 2, 0, 0, 2, 0, 4, 0, 0, 2, 0, 1); + let resultExpression = mat4x4f(2, 0, 0, 1, 0, 2, 0, 0, 2, 0, 4, 0, 0, 2, 0, 1); }" `); }); diff --git a/packages/typegpu/tests/std/matrix/translate.test.ts b/packages/typegpu/tests/std/matrix/translate.test.ts index 20b3998551..bf46e6565a 100644 --- a/packages/typegpu/tests/std/matrix/translate.test.ts +++ b/packages/typegpu/tests/std/matrix/translate.test.ts @@ -21,7 +21,7 @@ describe('translate', () => { expect(tgpu.resolve([translateFn])).toMatchInlineSnapshot(` "fn translateFn() { - var resultExpression = mat4x4f(3, 2, 4, 1, 0, 1, 0, 0, 1, 0, 1, 0, 2, 3, 4, 1); + let resultExpression = mat4x4f(3, 2, 4, 1, 0, 1, 0, 0, 1, 0, 1, 0, 2, 3, 4, 1); }" `); }); diff --git a/packages/typegpu/tests/std/numeric/bitShift.test.ts b/packages/typegpu/tests/std/numeric/bitShift.test.ts index ce87d830c6..aecfb3cecf 100644 --- a/packages/typegpu/tests/std/numeric/bitShift.test.ts +++ b/packages/typegpu/tests/std/numeric/bitShift.test.ts @@ -64,10 +64,10 @@ describe('bit shift', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var shift = vec3u(4); - var x = vec3i(256); - var y = (x << shift); - var z = (x >> shift); + let shift = vec3u(4); + let x = vec3i(256); + let y = (x << shift); + let z = (x >> shift); }" `); }); @@ -83,10 +83,10 @@ describe('bit shift', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var shift = vec3u(4); - var x = vec3i(256); - var y = (x << shift); - var z = (x >> shift); + let shift = vec3u(4); + let x = vec3i(256); + let y = (x << shift); + let z = (x >> shift); }" `); }); @@ -103,9 +103,9 @@ describe('bit shift', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { const shift = 4u; - var x = vec3i(256); - var y = (x << vec3u(shift)); - var z = (x >> vec3u(shift)); + let x = vec3i(256); + let y = (x << vec3u(shift)); + let z = (x >> vec3u(shift)); }" `); }); @@ -138,8 +138,8 @@ describe('bit shift', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> vec3i { - var shift = vec3u(4); - var x = vec3i(256); + let shift = vec3u(4); + let x = vec3i(256); return (x << shift); }" `); @@ -156,7 +156,7 @@ describe('bit shift', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var shift = vec3u(4); + let shift = vec3u(4); var x = vec3i(256); x >>= shift; }" diff --git a/packages/typegpu/tests/std/range.test.ts b/packages/typegpu/tests/std/range.test.ts index d51e5971ed..8e0c8a95f1 100644 --- a/packages/typegpu/tests/std/range.test.ts +++ b/packages/typegpu/tests/std/range.test.ts @@ -70,7 +70,7 @@ describe('on the GPU', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> array { - var result = array(0f, 2f, 4f, 6f); + let result = array(0f, 2f, 4f, 6f); return result; }" `); @@ -85,7 +85,7 @@ describe('on the GPU', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> array { - var result = array(0f, 2f, 4f, 6f); + let result = array(0f, 2f, 4f, 6f); return result; }" `); diff --git a/packages/typegpu/tests/std/texture/textureGather.test.ts b/packages/typegpu/tests/std/texture/textureGather.test.ts index 902cc8ae4e..d341c1f105 100644 --- a/packages/typegpu/tests/std/texture/textureGather.test.ts +++ b/packages/typegpu/tests/std/texture/textureGather.test.ts @@ -82,16 +82,16 @@ describe('textureGather', () => { @group(0) @binding(5) var texdepth2d_array: texture_depth_2d_array; fn testFn() { - var uv2d = vec2f(0.5); - var uv3d = vec3f(0.5, 0.5, 0); + let uv2d = vec2f(0.5); + let uv3d = vec3f(0.5, 0.5, 0); const idx = 1.2000000476837158f; const component = 0i; - var gather2d = textureGather(component, tex2d, sampler_1, uv2d); - var gather2d_u32 = textureGather(component, tex2d_u32, sampler_1, uv2d); - var gather2d_array = textureGather(component, tex2d_array, sampler_1, uv2d, u32(idx)); - var gathercube_array = textureGather(component, texcube_array, sampler_1, uv3d, u32(idx)); - var gatherdepth2d = textureGather(texdepth2d, sampler_1, uv2d); - var gatherdepth2d_array = textureGather(texdepth2d_array, sampler_1, uv2d, u32(idx)); + let gather2d = textureGather(component, tex2d, sampler_1, uv2d); + let gather2d_u32 = textureGather(component, tex2d_u32, sampler_1, uv2d); + let gather2d_array = textureGather(component, tex2d_array, sampler_1, uv2d, u32(idx)); + let gathercube_array = textureGather(component, texcube_array, sampler_1, uv3d, u32(idx)); + let gatherdepth2d = textureGather(texdepth2d, sampler_1, uv2d); + let gatherdepth2d_array = textureGather(texdepth2d_array, sampler_1, uv2d, u32(idx)); }" `); }); diff --git a/packages/typegpu/tests/std/texture/textureLoad.test.ts b/packages/typegpu/tests/std/texture/textureLoad.test.ts index ff498b7e05..af44f4b706 100644 --- a/packages/typegpu/tests/std/texture/textureLoad.test.ts +++ b/packages/typegpu/tests/std/texture/textureLoad.test.ts @@ -85,18 +85,18 @@ describe('textureLoad', () => { fn testFn() { const coord1d = 0i; - var coord2d = vec2i(); - var coord3d = vec3i(); + let coord2d = vec2i(); + let coord3d = vec3i(); const level = 0i; const arrayIndex = 0i; const sampleIndex = 0i; - var load1d = textureLoad(tex1d, coord1d, level); - var load2d = textureLoad(tex2d, coord2d, level); - var load2d_u32 = textureLoad(tex2d_u32, coord2d, level); - var load2d_i32 = textureLoad(tex2d_i32, coord2d, level); - var load2d_array = textureLoad(tex2d_array, coord2d, arrayIndex, level); - var load3d = textureLoad(tex3d, coord3d, level); - var loadms2d = textureLoad(texms2d, coord2d, sampleIndex); + let load1d = textureLoad(tex1d, coord1d, level); + let load2d = textureLoad(tex2d, coord2d, level); + let load2d_u32 = textureLoad(tex2d_u32, coord2d, level); + let load2d_i32 = textureLoad(tex2d_i32, coord2d, level); + let load2d_array = textureLoad(tex2d_array, coord2d, arrayIndex, level); + let load3d = textureLoad(tex3d, coord3d, level); + let loadms2d = textureLoad(texms2d, coord2d, sampleIndex); let loaddepth2d = textureLoad(texdepth2d, coord2d, level); let loaddepth2d_array = textureLoad(texdepth2d_array, coord2d, arrayIndex, level); let loaddepthms2d = textureLoad(texdepthms2d, coord2d, sampleIndex); @@ -152,15 +152,15 @@ describe('textureLoad', () => { fn testFn() { const coord1d = 0i; - var coord2d = vec2i(); - var coord3d = vec3i(); + let coord2d = vec2i(); + let coord3d = vec3i(); const arrayIndex = 0i; - var loadStore1d = textureLoad(store1d, coord1d); - var loadStore2d = textureLoad(store2d, coord2d); - var loadStore2d_uint = textureLoad(store2d_uint, coord2d); - var loadStore2d_sint = textureLoad(store2d_sint, coord2d); - var loadStore2d_array = textureLoad(store2d_array, coord2d, arrayIndex); - var loadStore3d = textureLoad(store3d, coord3d); + let loadStore1d = textureLoad(store1d, coord1d); + let loadStore2d = textureLoad(store2d, coord2d); + let loadStore2d_uint = textureLoad(store2d_uint, coord2d); + let loadStore2d_sint = textureLoad(store2d_sint, coord2d); + let loadStore2d_array = textureLoad(store2d_array, coord2d, arrayIndex); + let loadStore3d = textureLoad(store3d, coord3d); }" `); }); @@ -184,8 +184,8 @@ describe('textureLoad', () => { "@group(0) @binding(0) var texExternal: texture_external; fn testFn() { - var coord2d = vec2i(); - var loadExternal = textureLoad(texExternal, coord2d); + let coord2d = vec2i(); + let loadExternal = textureLoad(texExternal, coord2d); }" `); }); diff --git a/packages/typegpu/tests/struct.test.ts b/packages/typegpu/tests/struct.test.ts index 14dc0984aa..1e3341b59b 100644 --- a/packages/typegpu/tests/struct.test.ts +++ b/packages/typegpu/tests/struct.test.ts @@ -315,7 +315,7 @@ describe('struct', () => { } fn testFunction() { - var defaultValue = Outer(); + let defaultValue = Outer(); }" `); }); @@ -339,8 +339,8 @@ describe('struct', () => { } fn testFn() { - var myStruct = TestStruct(1u, 2f); - var myClone = myStruct; + let myStruct = TestStruct(1u, 2f); + let myClone = myStruct; return; }" `); @@ -365,8 +365,8 @@ describe('struct', () => { } fn testFn() { - var myStructs = array(TestStruct(1u, 2f)); - var myClone = myStructs[0i]; + let myStructs = array(TestStruct(1u, 2f)); + let myClone = myStructs[0i]; return; }" `); @@ -467,7 +467,7 @@ describe('abstruct', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn(x: f32) -> f32 { - var result = frexp(x); + let result = frexp(x); return f32(result.exp); }" `); diff --git a/packages/typegpu/tests/tgsl/codeGen.test.ts b/packages/typegpu/tests/tgsl/codeGen.test.ts index 38f48adc6e..b5db914b79 100644 --- a/packages/typegpu/tests/tgsl/codeGen.test.ts +++ b/packages/typegpu/tests/tgsl/codeGen.test.ts @@ -32,7 +32,7 @@ describe('codeGen', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> f32 { - var size = vec3f(1, 2, 3); + let size = vec3f(1, 2, 3); return ((size.x * size.y) * size.z); }" `); diff --git a/packages/typegpu/tests/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index c3fe5c7189..b2ae1f8fb3 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -618,7 +618,7 @@ describe('wgslGenerator with console.log', () => { } @compute @workgroup_size(1) fn fn_1() { - var complexStruct = ComplexStruct(vec3f(1, 2, 3), array(SimpleStruct(0u, array(9u, 8u, 7u, 6u)), SimpleStruct(1u, array(8u, 7u, 6u, 5u)), SimpleStruct(2u, array(7u, 6u, 5u, 4u)))); + let complexStruct = ComplexStruct(vec3f(1, 2, 3), array(SimpleStruct(0u, array(9u, 8u, 7u, 6u)), SimpleStruct(1u, array(8u, 7u, 6u, 5u)), SimpleStruct(2u, array(7u, 6u, 5u, 4u)))); log1(complexStruct); }" `); diff --git a/packages/typegpu/tests/tgsl/infixOperators.test.ts b/packages/typegpu/tests/tgsl/infixOperators.test.ts index 2e18ed3331..73a4ec5223 100644 --- a/packages/typegpu/tests/tgsl/infixOperators.test.ts +++ b/packages/typegpu/tests/tgsl/infixOperators.test.ts @@ -14,11 +14,11 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() { - var v1 = vec4f(1); - var v2 = vec3f(3, 4, 5); - var v3 = vec2f(6); - var m1 = mat2x2f(0, 0, 0, 0); - var m2 = mat3x3f(0, 0, 0, 0, 0, 0, 0, 0, 0); + let v1 = vec4f(1); + let v2 = vec3f(3, 4, 5); + let v3 = vec2f(6); + let m1 = mat2x2f(0, 0, 0, 0); + let m2 = mat3x3f(0, 0, 0, 0, 0, 0, 0, 0, 0); }" `); }); @@ -34,11 +34,11 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() { - var v1 = vec4f(-1); - var v2 = vec3f(-1, -2, -3); - var v3 = vec2f(); - var m1 = mat2x2f(0, 0, 0, 0); - var m2 = mat3x3f(0, 0, 0, 0, 0, 0, 0, 0, 0); + let v1 = vec4f(-1); + let v2 = vec3f(-1, -2, -3); + let v3 = vec2f(); + let m1 = mat2x2f(0, 0, 0, 0); + let m2 = mat3x3f(0, 0, 0, 0, 0, 0, 0, 0, 0); }" `); }); @@ -58,14 +58,14 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() { - var v1 = vec2f(6); - var v2 = vec3f(4, 6, 8); - var v3 = vec4f(); - var v4 = vec3f(); - var m1 = mat2x2f(0, 0, 0, 0); - var m2 = vec3f(); - var m3 = mat4x4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - var m4 = mat2x2f(0, 0, 0, 0); + let v1 = vec2f(6); + let v2 = vec3f(4, 6, 8); + let v3 = vec4f(); + let v4 = vec3f(); + let m1 = mat2x2f(0, 0, 0, 0); + let m2 = vec3f(); + let m3 = mat4x4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + let m4 = mat2x2f(0, 0, 0, 0); }" `); }); @@ -90,7 +90,7 @@ describe('wgslGenerator', () => { } fn testFn() { - var v1 = (getVec() * getVec()); + let v1 = (getVec() * getVec()); }" `); }); @@ -110,8 +110,8 @@ describe('wgslGenerator', () => { } fn testFn() { - var s = Struct(vec3f()); - var v1 = (s.vec * s.vec); + let s = Struct(vec3f()); + let v1 = (s.vec * s.vec); }" `); }); @@ -125,9 +125,9 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() { - var v1 = vec4f(0.5); - var v2 = vec3f(6, 3, 2); - var v3 = vec2f(0.25); + let v1 = vec4f(0.5); + let v2 = vec3f(6, 3, 2); + let v3 = vec2f(0.25); }" `); }); @@ -140,8 +140,8 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() { - var v1 = vec4f(1); - var v2 = vec3f(0.5, 1.5, 3.5); + let v1 = vec4f(1); + let v2 = vec3f(0.5, 1.5, 3.5); }" `); }); @@ -162,9 +162,9 @@ describe('wgslGenerator', () => { @group(0) @binding(1) var barUniform: vec3f; fn testFn() { - var v1 = (fooUniform + 2f); - var v2 = (vec3f(1, 2, 3) + barUniform); - var v3 = (fooUniform + barUniform); + let v1 = (fooUniform + 2f); + let v2 = (vec3f(1, 2, 3) + barUniform); + let v3 = (fooUniform + barUniform); }" `); }); @@ -177,8 +177,8 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() { - var v1 = vec3f(6, 7, 8); - var v2 = vec3f(4); + let v1 = vec3f(6, 7, 8); + let v2 = vec3f(4); }" `); }); diff --git a/packages/typegpu/tests/tgsl/operatorOverloads.test.ts b/packages/typegpu/tests/tgsl/operatorOverloads.test.ts index a351fefcb4..3e39e99f4a 100644 --- a/packages/typegpu/tests/tgsl/operatorOverloads.test.ts +++ b/packages/typegpu/tests/tgsl/operatorOverloads.test.ts @@ -129,7 +129,7 @@ test('+= refOfVec3f', () => { "const constant: vec3f = vec3f(-10); fn foo(arg: vec3f) -> vec3f { - var local = vec3f(100, 10, 1); + let local = vec3f(100, 10, 1); var result = vec3f(); result += local; result += arg; @@ -154,7 +154,7 @@ describe('num op', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> vec3f { - var result = vec3f(2, 3, 4); + let result = vec3f(2, 3, 4); return result; }" `); @@ -171,7 +171,7 @@ describe('num op', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> vec3f { - var result = vec3f(0, -1, -2); + let result = vec3f(0, -1, -2); return result; }" `); @@ -188,7 +188,7 @@ describe('num op', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> vec3f { - var result = vec3f(2, 4, 6); + let result = vec3f(2, 4, 6); return result; }" `); @@ -205,7 +205,7 @@ describe('num op', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> vec3f { - var result = vec3f(3, 2, 1); + let result = vec3f(3, 2, 1); return result; }" `); diff --git a/packages/typegpu/tests/tgsl/typeInference.test.ts b/packages/typegpu/tests/tgsl/typeInference.test.ts index 10c6c81726..3e9ca4eb9e 100644 --- a/packages/typegpu/tests/tgsl/typeInference.test.ts +++ b/packages/typegpu/tests/tgsl/typeInference.test.ts @@ -34,7 +34,7 @@ describe('wgsl generator type inference', () => { } fn myFn() { - var myStruct = Outer(Inner(vec2f())); + let myStruct = Outer(Inner(vec2f())); }" `); }); @@ -101,10 +101,10 @@ describe('wgsl generator type inference', () => { expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` "fn myFn() { - var myArrayF32 = array(1f, 2f); - var myArrayF16 = array(3h, 4h); - var myArrayI32 = array(5i, 6i); - var myArrayU32 = array(7u, 8u); + let myArrayF32 = array(1f, 2f); + let myArrayF16 = array(3h, 4h); + let myArrayI32 = array(5i, 6i); + let myArrayU32 = array(7u, 8u); }" `); }); @@ -143,7 +143,7 @@ describe('wgsl generator type inference', () => { } fn myFn() { - var myStructArray = array(Struct(vec2f(1, 2)), Struct(vec2f(3, 4))); + let myStructArray = array(Struct(vec2f(1, 2)), Struct(vec2f(3, 4))); }" `); }); @@ -167,7 +167,7 @@ describe('wgsl generator type inference', () => { } fn myFn() { - var myBoid = id(Boid(vec2f(1), vec2f())); + let myBoid = id(Boid(vec2f(1), vec2f())); }" `); }); @@ -359,7 +359,7 @@ describe('wgsl generator js type inference', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() { - var result = array(1f, 2f, 3f); + let result = array(1f, 2f, 3f); }" `); }); @@ -411,7 +411,7 @@ describe('wgsl generator js type inference', () => { } fn myFn() { - var myStruct = Outer(Inner(vec2f())); + let myStruct = Outer(Inner(vec2f())); }" `); }); @@ -485,10 +485,10 @@ describe('wgsl generator js type inference', () => { expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` "fn myFn() { - var myArrayF32 = array(1f, 2f); - var myArrayF16 = array(3h, 4h); - var myArrayI32 = array(5i, 6i); - var myArrayU32 = array(7u, 8u); + let myArrayF32 = array(1f, 2f); + let myArrayF16 = array(3h, 4h); + let myArrayI32 = array(5i, 6i); + let myArrayU32 = array(7u, 8u); }" `); }); @@ -524,7 +524,7 @@ describe('wgsl generator js type inference', () => { } fn myFn() { - var myStructArray = array(Struct(vec2f(1, 2)), Struct(vec2f(3, 4))); + let myStructArray = array(Struct(vec2f(1, 2)), Struct(vec2f(3, 4))); }" `); }); @@ -549,7 +549,7 @@ describe('wgsl generator js type inference', () => { } fn myFn() { - var myBoid = id(Boid(vec2f(1), vec2f())); + let myBoid = id(Boid(vec2f(1), vec2f())); }" `); }); @@ -658,8 +658,8 @@ describe('wgsl generator js type inference', () => { } fn main() { - var foo = interpolate(0.1f, array(0., 0.5, 1.), array(100, 200, 100)); - var bar = interpolate_1(0.6f, array(0., 0.5), array(100., 40.5)); + let foo = interpolate(0.1f, array(0., 0.5, 1.), array(100, 200, 100)); + let bar = interpolate_1(0.6f, array(0., 0.5), array(100., 40.5)); }" `); }); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 9278a82a00..265634e9db 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -61,18 +61,18 @@ describe('TGSL tgpu.fn function', () => { expect(tgpu.resolve([getY])).toMatchInlineSnapshot(` "fn getColor() -> vec3f { - var color = vec3f(); - var color2 = vec3f(1, 2, 3); + let color = vec3f(); + let color2 = vec3f(1, 2, 3); return color; } fn getX() -> f32 { - var color = getColor(); + let color = getColor(); return 3f; } fn getY() -> f32 { - var c = getColor(); + let c = getColor(); return getX(); }" `); @@ -273,7 +273,7 @@ describe('TGSL tgpu.fn function', () => { } @vertex fn vertex_fn(@builtin(vertex_index) _arg_vi: u32, @builtin(instance_index) _arg_ii: u32, @location(0) _arg_color: vec4f) -> vertex_fn_Output { - var myOutput = vertex_fn_Output(vec4f(_arg_color.w, f32(_arg_ii), f32(_arg_vi), 1f), vec2f(_arg_color.w, f32(_arg_vi))); + let myOutput = vertex_fn_Output(vec4f(_arg_color.w, f32(_arg_ii), f32(_arg_vi), 1f), vec2f(_arg_color.w, f32(_arg_vi))); return myOutput; }" `); @@ -297,7 +297,7 @@ describe('TGSL tgpu.fn function', () => { let index = _arg_gid.x; const iterationF = 0f; const sign_1 = 0; - var change = vec4f(); + let change = vec4f(); }" `); }); @@ -320,7 +320,7 @@ describe('TGSL tgpu.fn function', () => { let index = gid.x; const iterationF = 0f; const sign_1 = 0; - var change = vec4f(); + let change = vec4f(); }" `); }); @@ -437,7 +437,7 @@ describe('TGSL tgpu.fn function', () => { expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` "@fragment fn fragmentFn() -> @location(0) vec4f { - var hmm = vec4f(1.25); + let hmm = vec4f(1.25); return hmm; }" `); @@ -567,7 +567,7 @@ describe('TGSL tgpu.fn function', () => { } @compute @workgroup_size(24) fn compute_fn() { - var testStruct = getTestStruct(); + let testStruct = getTestStruct(); }" `); }); @@ -646,7 +646,7 @@ describe('TGSL tgpu.fn function', () => { } fn fun(_arg_0: Input) { - var vector = vec2u(u32(_arg_0.value)); + let vector = vec2u(u32(_arg_0.value)); }" `); }); @@ -666,7 +666,7 @@ describe('TGSL tgpu.fn function', () => { } fn fun(input: Input) { - var vector = vec2u(u32(input.value)); + let vector = vec2u(u32(input.value)); }" `); }); @@ -686,7 +686,7 @@ describe('TGSL tgpu.fn function', () => { } fn fun(_arg_0: Input) { - var vector = vec2u(u32(_arg_0.value)); + let vector = vec2u(u32(_arg_0.value)); }" `); }); @@ -706,7 +706,7 @@ describe('TGSL tgpu.fn function', () => { } fn fun(_arg_0: Input, x: i32, _arg_2: Input) { - var vector = vec3u(u32(_arg_0.value), u32(x), u32(_arg_2.value)); + let vector = vec3u(u32(_arg_0.value), u32(x), u32(_arg_2.value)); }" `); }); diff --git a/packages/typegpu/tests/vector.test.ts b/packages/typegpu/tests/vector.test.ts index 87149c62a0..c8700e4f55 100644 --- a/packages/typegpu/tests/vector.test.ts +++ b/packages/typegpu/tests/vector.test.ts @@ -828,10 +828,10 @@ describe('v3f', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var planarPosLocal = vec2f(1, 2); - var one = vec3f(1, 2, 12); - var two = vec3f(planarPosLocal, 12f); - var three = vec3f(1, 2, 12); + let planarPosLocal = vec2f(1, 2); + let one = vec3f(1, 2, 12); + let two = vec3f(planarPosLocal, 12f); + let three = vec3f(1, 2, 12); }" `); }); @@ -875,10 +875,10 @@ describe('v4f', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var green = vec3f(0, 1, 0); - var one = vec4f(0.125, 0.25, 0.375, 1); - var two = vec4f(green, 1f); - var three = vec4f(0, 0, 1, 1); + let green = vec3f(0, 1, 0); + let one = vec4f(0.125, 0.25, 0.375, 1); + let two = vec4f(green, 1f); + let three = vec4f(0, 0, 1, 1); }" `); }); @@ -904,10 +904,10 @@ describe('v4f', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var fooLocal = vec3f(0.25, 0.5, 0.75); - var one = vec4f(0.25, 0.25, 0.5, 0.75); - var two = vec4f(0.1f, fooLocal); - var three = vec4f(0.125, 0.25, 0.5, 0.75); + let fooLocal = vec3f(0.25, 0.5, 0.75); + let one = vec4f(0.25, 0.25, 0.5, 0.75); + let two = vec4f(0.1f, fooLocal); + let three = vec4f(0.125, 0.25, 0.5, 0.75); }" `); }); @@ -959,10 +959,10 @@ describe('v4b', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var vecLocal = vec3(true); - var one = vec4(true, false, true, true); - var two = vec4(vecLocal, false); - var three = vec4(false, false, true, true); + let vecLocal = vec3(true); + let one = vec4(true, false, true, true); + let two = vec4(vecLocal, false); + let three = vec4(false, false, true, true); }" `); }); @@ -1000,8 +1000,8 @@ describe('type predicates', () => { } fn main() { - var foo = ceil_1(vec3f(1, 2, 3)); - var bar = ceil_2(vec3i(1, 2, 3)); + let foo = ceil_1(vec3f(1, 2, 3)); + let bar = ceil_2(vec3i(1, 2, 3)); }" `); }); @@ -1203,7 +1203,7 @@ describe('RGBA swizzles', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> vec3f { - var color = vec4f(1, 0.5, 0.25, 1); + let color = vec4f(1, 0.5, 0.25, 1); return color.rgb; }" `); @@ -1220,7 +1220,7 @@ describe('RGBA swizzles', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> vec4f { - var color = vec4f(1, 0.5, 0.25, 1); + let color = vec4f(1, 0.5, 0.25, 1); return color.bgra; }" `); @@ -1252,7 +1252,7 @@ describe('RGBA swizzles', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() -> f32 { - var color = vec4f(1, 0.5, 0.25, 0.75); + let color = vec4f(1, 0.5, 0.25, 0.75); return color.a; }" `); From 3aaf4556ffe9b4fd7f520131b0963537fa9b4dd0 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:41:40 +0200 Subject: [PATCH 24/34] Add new tests --- .../typegpu/tests/mutabilityTracking.test.ts | 43 ++++++++++++++++ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 50 +++++++++---------- packages/typegpu/tests/unroll.test.ts | 32 ++++++------ 3 files changed, 84 insertions(+), 41 deletions(-) diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 31a3cf46a0..766b6ed419 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -573,5 +573,48 @@ describe('mutability tracking', () => { `); expect(resolved).toContain('let struct_1 = Struct()'); }); + + it('resolves for..of on a referential array', () => { + const fn = () => { + 'use gpu'; + const t = [d.vec2f()]; + let result = d.vec2f(); + for (const v of t) { + result += v; + } + }; + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn fn_1() { + let t = array(vec2f()); + var result = vec2f(); + for (var i = 0u; i < 1u; i += 1u) { + let v = (&t[i]); + { + result += (*v); + } + } + }" + `); + expect(resolved).toContain('var t = '); + }); + + it('resolves tgpu.unroll() on a referential element', () => { + const fn = () => { + 'use gpu'; + const v = d.vec2f(); + const u = tgpu.unroll(v); + }; + + const resolved = tgpu.resolve([fn]); + expect(resolved).toMatchInlineSnapshot(` + "fn fn_1() { + let v = vec2f(); + let u = (&v); + }" + `); + expect(resolved).toContain('var v = '); + }); }); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index eb377fbcab..c69b6326d3 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -232,7 +232,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var arr = array(1f, 2f, 3f); + let arr = array(1f, 2f, 3f); var res = 0f; for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; @@ -258,7 +258,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var arr = array(1f, 2f, 3f); + let arr = array(1f, 2f, 3f); var res = 0f; for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; @@ -289,7 +289,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var arr = array(1f, 2f, 3f); + let arr = array(1f, 2f, 3f); var res = 0f; for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; @@ -318,7 +318,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var arr = array(vec2f(1), vec2f(2), vec2f(3)); + let arr = array(vec2f(1), vec2f(2), vec2f(3)); var res = 0; for (var i = 0u; i < 3u; i += 1u) { let foo = (&arr[i]); @@ -376,14 +376,14 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - var v1 = vec4u(44, 88, 132, 176); + let v1 = vec4u(44, 88, 132, 176); for (var i = 0u; i < 4u; i += 1u) { let foo = v1[i]; { continue; } } - var v2 = vec2f(1, 2); + let v2 = vec2f(1, 2); for (var i = 0u; i < 2u; i += 1u) { let foo = v2[i]; { @@ -450,9 +450,9 @@ describe('wgslGenerator', () => { 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); + let v1 = vec4f(1, 2, 3, 4); + let v2 = vec3u(5, 6, 7); + let v3 = vec2(true, false); var res1 = 0f; var res2 = 0u; var res3 = false; @@ -497,7 +497,7 @@ describe('wgslGenerator', () => { } fn main() { - var testStruct = TestStruct(array(1f, 8f, 8f, 2f)); + let testStruct = TestStruct(array(1f, 8f, 8f, 2f)); for (var i = 0u; i < 4u; i += 1u) { let foo = testStruct.arr[i]; { @@ -595,7 +595,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f1])).toMatchInlineSnapshot(` "fn f1() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; { @@ -617,7 +617,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f2])).toMatchInlineSnapshot(` "fn f2() { const i = 7; - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); for (var i_1 = 0u; i_1 < 3u; i_1 += 1u) { let foo = arr[i_1]; { @@ -645,7 +645,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i: u32; fn f() { - var arr = array(1u, 2u, 3u, i); + let arr = array(1u, 2u, 3u, i); for (var i_1 = 0u; i_1 < 4u; i_1 += 1u) { let foo = arr[i_1]; { @@ -672,7 +672,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i_1: u32; fn f() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; { @@ -702,7 +702,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i_1: u32; fn f() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; { @@ -725,7 +725,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); var res = 0; for (var i = 0u; i < 3u; i += 1u) { let i_1 = arr[i]; @@ -753,7 +753,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i: u32; fn f() { - var arr = array(1u, 2u, 3u, i); + let arr = array(1u, 2u, 3u, i); var res = 0; for (var i_1 = 0u; i_1 < 4u; i_1 += 1u) { let i_2 = arr[i_1]; @@ -836,7 +836,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() -> u32 { - var arr = array(1u, 2u, 3u); + let arr = array(1u, 2u, 3u); return arr[1i]; }" `); @@ -857,7 +857,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() -> u32 { - var arr = array(vec2u(1, 2), vec2u(3, 4), vec2u(5, 6)); + let arr = array(vec2u(1, 2), vec2u(3, 4), vec2u(5, 6)); return arr[1i].x; }" `); @@ -906,7 +906,7 @@ describe('wgslGenerator', () => { } fn testFn() -> f32 { - var arr = array(TestStruct(1u, 2f), TestStruct(3u, 4f)); + let arr = array(TestStruct(1u, 2f), TestStruct(3u, 4f)); return arr[1i].y; }" `); @@ -933,7 +933,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() -> f32 { - var arr = array(vec2f(44, 88), vec2f(88, 176)); + let arr = array(vec2f(44, 88), vec2f(88, 176)); return arr[1i].y; }" `); @@ -1380,7 +1380,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { - var list = array(1); + let list = array(1); // unrolled iteration #0 { const y = 100; @@ -1425,7 +1425,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { var result = 0i; - var list = array(1); + let list = array(1); // unrolled iteration #0 { { @@ -1836,7 +1836,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { - var res = -1; + let res = -1; return res; }" `); @@ -1881,7 +1881,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { - var res = -1; + let res = -1; return res; }" `); diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index c35689684e..e484e7c838 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -30,8 +30,8 @@ describe('tgpu.unroll', () => { "@group(0) @binding(0) var arr_1: array; fn f() -> f32 { - var a = array(1, 2, 3); - var v1 = vec2f(7); + let a = array(1, 2, 3); + let v1 = vec2f(7); let v2 = (&v1); let arr = (&arr_1); return (*arr)[0i]; @@ -80,7 +80,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var foo = vec3f(6); + let foo = vec3f(6); // unrolled iteration #0 { const boo = 1; @@ -385,7 +385,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { - var v = vec3f(7); + let v = vec3f(7); var res = 0; // unrolled iteration #0 { @@ -503,7 +503,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> f32 { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); var res = 0f; // unrolled iteration #0 { @@ -542,10 +542,10 @@ describe('tgpu.unroll', () => { 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); + let v1 = vec2f(1); + let v2 = vec2f(8); + let v3 = vec2f(2); + let arr = array(v1, v2, v2, v3); var res = vec2f(); // unrolled iteration #0 { @@ -600,9 +600,9 @@ describe('tgpu.unroll', () => { } fn f() -> vec2f { - var b1 = Boid(vec2i(1), vec2f(1)); - var b2 = Boid(vec2i(2), vec2f(2)); - var arr = array(b1, b2); + let b1 = Boid(vec2i(1), vec2f(1)); + let b2 = Boid(vec2i(2), vec2f(2)); + let arr = array(b1, b2); var res = vec2f(); // unrolled iteration #0 { @@ -685,7 +685,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); var r = 0f; // unrolled iteration #0 { @@ -703,7 +703,7 @@ describe('tgpu.unroll', () => { `); expect(tgpu.resolve([tgpu.fn(f).with(unroll, false)])).toMatchInlineSnapshot(` "fn f() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); var r = 0f; for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; @@ -791,7 +791,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); // unrolled iteration #0 { for (var i = 0; (i < 2i); i++) { @@ -868,7 +868,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { - var a = 0; + let a = 0; return a; }" `); From b032f6f8ccbefd78fb9c4da5f675a9a80cc58129 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:51:50 +0200 Subject: [PATCH 25/34] Handle two more edge cases --- .../fluid-double-buffering.test.ts | 4 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 6 ++- .../typegpu/tests/mutabilityTracking.test.ts | 4 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 38 +++++++++---------- packages/typegpu/tests/unroll.test.ts | 12 +++--- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts index 8e21b7e6d3..38038d6f9a 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts @@ -172,7 +172,7 @@ describe('fluid double buffering example', () => { fn computeVelocity(x: i32, y: i32) -> vec2f { const gravityCost = 0.5; - let neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); + var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); let cell = getCell(x, y); var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); @@ -399,7 +399,7 @@ describe('fluid double buffering example', () => { fn computeVelocity(x: i32, y: i32) -> vec2f { const gravityCost = 0.5; - let neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); + var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); let cell = getCell(x, y); var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 07d2c0724d..91b41d2ece 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -43,7 +43,7 @@ import { resolveData } from '../core/resolve/resolveData.ts'; import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { _ref, RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; -import { UnrollableIterable } from '../core/unroll/tgpuUnroll.ts'; +import { unroll, 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'; @@ -691,7 +691,7 @@ ${this.ctx.pre}}`; ); } - if (callee.value === _ref && argNodes[0]) { + if ((callee.value === _ref || callee.value === unroll) && argNodes[0]) { this.tryMarkModified(argNodes[0]); } @@ -1262,6 +1262,8 @@ ${this.ctx.pre}else ${alternate}`; throw new WgslTypeError('Only `for (const ... of ... )` loops are supported'); } + this.tryMarkModified(iterable); // overly-defensive, but let's not tempt fate + let ctxIndent = false; const prevUnrollingFlag = this.#unrolling; diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 766b6ed419..8a74ba4ec2 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -587,7 +587,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn fn_1() { - let t = array(vec2f()); + var t = array(vec2f()); var result = vec2f(); for (var i = 0u; i < 1u; i += 1u) { let v = (&t[i]); @@ -610,7 +610,7 @@ describe('mutability tracking', () => { const resolved = tgpu.resolve([fn]); expect(resolved).toMatchInlineSnapshot(` "fn fn_1() { - let v = vec2f(); + var v = vec2f(); let u = (&v); }" `); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index c69b6326d3..1a94065b09 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -232,7 +232,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - let arr = array(1f, 2f, 3f); + var arr = array(1f, 2f, 3f); var res = 0f; for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; @@ -258,7 +258,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - let arr = array(1f, 2f, 3f); + var arr = array(1f, 2f, 3f); var res = 0f; for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; @@ -289,7 +289,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - let arr = array(1f, 2f, 3f); + var arr = array(1f, 2f, 3f); var res = 0f; for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; @@ -318,7 +318,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - let arr = array(vec2f(1), vec2f(2), vec2f(3)); + var arr = array(vec2f(1), vec2f(2), vec2f(3)); var res = 0; for (var i = 0u; i < 3u; i += 1u) { let foo = (&arr[i]); @@ -376,14 +376,14 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - let v1 = vec4u(44, 88, 132, 176); + var v1 = vec4u(44, 88, 132, 176); for (var i = 0u; i < 4u; i += 1u) { let foo = v1[i]; { continue; } } - let v2 = vec2f(1, 2); + var v2 = vec2f(1, 2); for (var i = 0u; i < 2u; i += 1u) { let foo = v2[i]; { @@ -450,9 +450,9 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([main])).toMatchInlineSnapshot(` "fn main() { - let v1 = vec4f(1, 2, 3, 4); - let v2 = vec3u(5, 6, 7); - let v3 = vec2(true, false); + 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; @@ -497,7 +497,7 @@ describe('wgslGenerator', () => { } fn main() { - let testStruct = TestStruct(array(1f, 8f, 8f, 2f)); + var testStruct = TestStruct(array(1f, 8f, 8f, 2f)); for (var i = 0u; i < 4u; i += 1u) { let foo = testStruct.arr[i]; { @@ -595,7 +595,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f1])).toMatchInlineSnapshot(` "fn f1() { - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; { @@ -617,7 +617,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f2])).toMatchInlineSnapshot(` "fn f2() { const i = 7; - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); for (var i_1 = 0u; i_1 < 3u; i_1 += 1u) { let foo = arr[i_1]; { @@ -645,7 +645,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i: u32; fn f() { - let arr = array(1u, 2u, 3u, i); + var arr = array(1u, 2u, 3u, i); for (var i_1 = 0u; i_1 < 4u; i_1 += 1u) { let foo = arr[i_1]; { @@ -672,7 +672,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i_1: u32; fn f() { - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; { @@ -702,7 +702,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i_1: u32; fn f() { - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); for (var i = 0u; i < 3u; i += 1u) { let foo = arr[i]; { @@ -725,7 +725,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); var res = 0; for (var i = 0u; i < 3u; i += 1u) { let i_1 = arr[i]; @@ -753,7 +753,7 @@ describe('wgslGenerator', () => { "@group(0) @binding(0) var i: u32; fn f() { - let arr = array(1u, 2u, 3u, i); + var arr = array(1u, 2u, 3u, i); var res = 0; for (var i_1 = 0u; i_1 < 4u; i_1 += 1u) { let i_2 = arr[i_1]; @@ -1380,7 +1380,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> i32 { - let list = array(1); + var list = array(1); // unrolled iteration #0 { const y = 100; @@ -1425,7 +1425,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { var result = 0i; - let list = array(1); + var list = array(1); // unrolled iteration #0 { { diff --git a/packages/typegpu/tests/unroll.test.ts b/packages/typegpu/tests/unroll.test.ts index e484e7c838..adfef7b0ef 100644 --- a/packages/typegpu/tests/unroll.test.ts +++ b/packages/typegpu/tests/unroll.test.ts @@ -31,7 +31,7 @@ describe('tgpu.unroll', () => { fn f() -> f32 { let a = array(1, 2, 3); - let v1 = vec2f(7); + var v1 = vec2f(7); let v2 = (&v1); let arr = (&arr_1); return (*arr)[0i]; @@ -503,7 +503,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() -> f32 { - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); var res = 0f; // unrolled iteration #0 { @@ -545,7 +545,7 @@ describe('tgpu.unroll', () => { let v1 = vec2f(1); let v2 = vec2f(8); let v3 = vec2f(2); - let arr = array(v1, v2, v2, v3); + var arr = array(v1, v2, v2, v3); var res = vec2f(); // unrolled iteration #0 { @@ -602,7 +602,7 @@ describe('tgpu.unroll', () => { fn f() -> vec2f { let b1 = Boid(vec2i(1), vec2f(1)); let b2 = Boid(vec2i(2), vec2f(2)); - let arr = array(b1, b2); + var arr = array(b1, b2); var res = vec2f(); // unrolled iteration #0 { @@ -685,7 +685,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); var r = 0f; // unrolled iteration #0 { @@ -791,7 +791,7 @@ describe('tgpu.unroll', () => { expect(tgpu.resolve([f])).toMatchInlineSnapshot(` "fn f() { - let arr = array(1, 2, 3); + var arr = array(1, 2, 3); // unrolled iteration #0 { for (var i = 0; (i < 2i); i++) { From acdca4f2bfe85248a7a45df322a5c23629e23b89 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:44:22 +0200 Subject: [PATCH 26/34] Optimize string replacement --- packages/typegpu/src/tgsl/wgslGenerator.ts | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 91b41d2ece..346ea6cf0a 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -914,16 +914,21 @@ ${this.ctx.pre}}`; let body = this._block(options.body); const scope = this.ctx.topFunctionScope; invariant(scope, 'Expected function scope to be present'); - // TODO: optimize this to one pass - body = scope.modifiedVariables.values().reduce((body: string, variable: Snippet) => { - const placeholder = scope.placeholderForVariable.get(variable); - invariant( - placeholder, - `Expected placeholder (like #VAR_3#) to be present for ${variable.value}`, + const replacements = Object.fromEntries( + scope.placeholderForVariable + .entries() + .map(([variable, placeholder]) => [ + placeholder, + scope.modifiedVariables.has(variable) ? 'var' : 'let', + ]), + ); + if (Object.keys(replacements).length > 0) { + const regex = new RegExp(Object.keys(replacements).join('|'), 'gi'); + body = body.replace( + regex, + (match) => replacements[match as keyof typeof replacements] ?? '#ERR', ); - return body.replaceAll(placeholder, 'var'); - }, body); - body = body.replaceAll(/#VAR_[0-9]+#/g, 'let'); + } // Function header const returnType = options.determineReturnType(); From ec85ed17194457769f4f624f65f61ea718771b77 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:07:45 +0200 Subject: [PATCH 27/34] Review fixes --- packages/typegpu/src/tgsl/wgslGenerator.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 346ea6cf0a..2bb93d04de 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -1199,9 +1199,7 @@ ${this.ctx.pre}else ${alternate}`; let emittedVarType: string | undefined = varType; if (emittedVarType === undefined) { const scope = this.ctx.topFunctionScope; - const snippet = this.ctx.getById(rawId); invariant(scope, `Expected function scope to be present for ${rawId}`); - invariant(snippet, `Expected snippet to be present for ${rawId}`); emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`; scope.placeholderForVariable.set(snippet, emittedVarType); } @@ -1410,7 +1408,7 @@ ${this.ctx.pre}else ${alternate}`; const snippet = this.ctx.getById(maybeObject); const scope = this.ctx.topFunctionScope; if (snippet && scope && scope.placeholderForVariable.has(snippet)) { - this.ctx.topFunctionScope?.modifiedVariables.add(snippet); + scope.modifiedVariables.add(snippet); } } } From 62a097df0513b399cbc426daa3fd4ab11e28ac10 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 10 Apr 2026 12:30:13 +0200 Subject: [PATCH 28/34] fix: Improvement to argument usage tracking --- .../individual-example-tests/3d-fish.test.ts | 18 +- .../bitonic-sort.test.ts | 4 +- .../individual-example-tests/blur.test.ts | 8 +- .../camera-thresholding.test.ts | 8 +- .../individual-example-tests/caustics.test.ts | 8 +- .../chroma-keying.test.ts | 8 +- .../individual-example-tests/confetti.test.ts | 6 +- .../cubemap-reflection.test.ts | 18 +- .../individual-example-tests/disco.test.ts | 16 +- .../fluid-double-buffering.test.ts | 8 +- .../game-of-life.test.ts | 8 +- .../gradient-tiles.test.ts | 4 +- .../individual-example-tests/gravity.test.ts | 20 +- .../image-tuning.test.ts | 8 +- .../jelly-slider.test.ts | 16 +- .../jelly-switch.test.ts | 16 +- .../jump-flood-distance.test.ts | 8 +- .../liquid-glass.test.ts | 8 +- .../individual-example-tests/oklab.test.ts | 8 +- .../phong-reflection.test.ts | 10 +- .../point-light-shadow.test.ts | 16 +- .../ray-marching.test.ts | 8 +- .../ripple-cube.test.ts | 8 +- .../simple-shadow.test.ts | 10 +- .../slime-mold-3d.test.ts | 8 +- .../slime-mold.test.ts | 8 +- .../stable-fluid.test.ts | 8 +- .../uniformity.test.ts | 16 +- .../vaporrave.test.ts | 8 +- .../xor-dev-centrifuge-2.test.ts | 8 +- .../xor-dev-runner.test.ts | 8 +- packages/typegpu/src/core/function/autoIO.ts | 4 +- .../src/core/function/entryInputRouter.ts | 39 +- packages/typegpu/src/core/function/fnCore.ts | 42 +- .../src/core/function/shelllessImpl.ts | 2 +- .../src/core/function/tgpuComputeFn.ts | 5 +- packages/typegpu/src/core/function/tgpuFn.ts | 2 +- .../src/core/function/tgpuFragmentFn.ts | 2 +- .../typegpu/src/core/function/tgpuVertexFn.ts | 2 +- packages/typegpu/src/data/snippet.ts | 7 +- packages/typegpu/src/resolutionCtx.ts | 257 +++---- packages/typegpu/src/tgsl/accessProp.ts | 11 +- packages/typegpu/src/tgsl/conversion.ts | 2 +- packages/typegpu/src/tgsl/shaderGenerator.ts | 4 +- .../src/tgsl/shaderGenerator_members.ts | 14 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 25 +- packages/typegpu/src/types.ts | 18 +- packages/typegpu/tests/renderPipeline.test.ts | 33 +- .../tests/tgsl/extensionEnabled.test.ts | 14 +- .../typegpu/tests/tgsl/multiplication.test.ts | 8 +- .../typegpu/tests/tgsl/nameClashes.test.ts | 16 +- .../tests/tgsl/ternaryOperator.test.ts | 68 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 674 +++--------------- packages/typegpu/tests/tgslFn.test.ts | 24 +- packages/typegpu/tests/utils/parseResolved.ts | 4 +- 55 files changed, 575 insertions(+), 1016 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts index dca6c22f4e..6430284a2e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/3d-fish.test.ts @@ -305,15 +305,6 @@ describe('3d fish example', () => { return vertexShader_Output(worldPosition.xyz, worldNormal, canvasPosition, (*currentModelData).variant, _arg_textureUV, (*currentModelData).applySeaFog, (*currentModelData).applySeaDesaturation); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - @location(2) variant: f32, - @location(3) textureUV: vec2f, - @location(4) @interpolate(flat) applySeaFog: u32, - @location(5) @interpolate(flat) applySeaDesaturation: u32, - } - @group(0) @binding(1) var modelTexture: texture_2d; @group(0) @binding(3) var sampler_1: sampler; @@ -418,6 +409,15 @@ describe('3d fish example', () => { return vec3f(r, g, b); } + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + @location(2) variant: f32, + @location(3) textureUV: vec2f, + @location(4) @interpolate(flat) applySeaFog: u32, + @location(5) @interpolate(flat) applySeaDesaturation: u32, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var textureColorWithAlpha = textureSample(modelTexture, sampler_1, _arg_0.textureUV); var textureColor = textureColorWithAlpha.rgb; diff --git a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts index 06b1c586cb..6368fd715f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/bitonic-sort.test.ts @@ -118,12 +118,12 @@ describe('bitonic sort example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var data_1: array; + struct fragmentFn_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var data_1: array; - @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { let data = (&data_1); let arrayLength_1 = arrayLength(&(*data)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts index 00df4923ed..7f81d96515 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts @@ -470,14 +470,14 @@ describe('blur example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct renderFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var renderView: texture_2d; @group(0) @binding(1) var sampler_1: sampler; + struct renderFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn renderFragment(_arg_0: renderFragment_Input) -> @location(0) vec4f { return textureSample(renderView, sampler_1, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts index 43e10c97b2..783bb59558 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/camera-thresholding.test.ts @@ -32,10 +32,6 @@ describe('camera thresholding example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFrag_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransformUniform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('camera thresholding example', () => { @group(0) @binding(3) var thresholdBuffer: f32; + struct mainFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { var uv2 = ((uvTransformUniform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts index a1938d5c34..9d011f7a74 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/caustics.test.ts @@ -31,10 +31,6 @@ describe('caustics example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var tileDensity: f32; fn tilePattern(uv: vec2f) -> f32 { @@ -120,6 +116,10 @@ describe('caustics example', () => { return mat2x2f(vec2f(cos(angle), sin(angle)), vec2f(-(sin(angle)), cos(angle))); } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f((-1.9866933079506122f + (_arg_0.uv.x * 3f)), 4.900332889206208f)); var skewedUv = (skewMat * _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts index 9b25129771..60175ac939 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/chroma-keying.test.ts @@ -32,10 +32,6 @@ describe('chroma keying example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var uvTransform: mat2x2f; @group(1) @binding(0) var inputTexture: texture_external; @@ -48,6 +44,10 @@ describe('chroma keying example', () => { @group(0) @binding(3) var threshold: f32; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var uv2 = ((uvTransform * (_arg_0.uv - 0.5f)) + 0.5f); var col = textureSampleBaseClampToEdge(inputTexture, sampler_1, uv2); diff --git a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts index 3e39636f7a..921721055b 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/confetti.test.ts @@ -59,10 +59,10 @@ describe('confetti example', () => { struct VertexIn { @location(0) tilt: f32, - @location(1) angle: f32, - @location(2) color: vec4f, - @location(3) center: vec2f, @builtin(vertex_index) vertexIndex: u32, + @location(1) angle: f32, + @location(2) center: vec2f, + @location(3) color: vec4f, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { diff --git a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts index c77faffba2..fe28f8827d 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/cubemap-reflection.test.ts @@ -257,14 +257,14 @@ describe('cubemap reflection example', () => { return cubeVertexFn_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct cubeFragmentFn_Input { - @location(0) texCoord: vec3f, - } - @group(1) @binding(0) var cubemap: texture_cube; @group(1) @binding(1) var texSampler: sampler; + struct cubeFragmentFn_Input { + @location(0) texCoord: vec3f, + } + @fragment fn cubeFragmentFn(_arg_0: cubeFragmentFn_Input) -> @location(0) vec4f { return textureSample(cubemap, texSampler, normalize(_arg_0.texCoord)); } @@ -287,11 +287,6 @@ describe('cubemap reflection example', () => { return vertexFn_Output((camera.projection * (camera.view * _arg_position)), _arg_normal, _arg_position); } - struct fragmentFn_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec4f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -314,6 +309,11 @@ describe('cubemap reflection example', () => { @group(1) @binding(1) var texSampler: sampler; + struct fragmentFn_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec4f, + } + @fragment fn fragmentFn(_arg_0: fragmentFn_Input) -> @location(0) vec4f { var normalizedNormal = normalize(_arg_0.normal.xyz); var normalizedLightDir = normalize(light.direction); diff --git a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts index 8ea4e8d7f7..ba6d090535 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/disco.test.ts @@ -32,10 +32,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment2_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -65,6 +61,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment2_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment2(_arg_0: mainFragment2_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; @@ -243,10 +243,6 @@ describe('disco example', () => { return mainVertex_Output(vec4f(pos[vertexIndex], 0f, 1f), uv[vertexIndex]); } - struct mainFragment1_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; fn aspectCorrected(uv: vec2f) -> vec2f { @@ -276,6 +272,10 @@ describe('disco example', () => { return (acc + (col * weight)); } + struct mainFragment1_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment1(_arg_0: mainFragment1_Input) -> @location(0) vec4f { var originalUv = aspectCorrected(_arg_0.uv); var aspectUv = originalUv; diff --git a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts index ef73e74189..df9620d6ed 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/fluid-double-buffering.test.ts @@ -558,10 +558,6 @@ describe('fluid double buffering example', () => { return vertexMain_Output(vec4f(pos[_arg_idx].x, pos[_arg_idx].y, 0f, 1f), uv[_arg_idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - fn coordsToIndex(x: i32, y: i32) -> i32 { return (x + (y * 256i)); } @@ -595,6 +591,10 @@ describe('fluid double buffering example', () => { return false; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let x = i32((_arg_0.uv.x * 256f)); let y = i32((_arg_0.uv.y * 256f)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts index 3028c19bf5..516692dc41 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/game-of-life.test.ts @@ -214,10 +214,6 @@ describe('game of life example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct displayFragment_Input { - @location(0) uv: vec2f, - } - struct ZoomParams { enabled: u32, level: f32, @@ -242,6 +238,10 @@ describe('game of life example', () => { @group(0) @binding(2) var viewModeUniform: u32; + struct displayFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn displayFragment(_arg_0: displayFragment_Input) -> @location(0) vec4f { let zoom = (&zoomUniform); let gs = f32(gameSizeUniform); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts index f0d3016198..d3c209eb18 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gradient-tiles.test.ts @@ -32,12 +32,12 @@ describe('gradient tiles example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } + @group(0) @binding(0) var spanUniform: vec2f; + struct fragment_Input { @location(0) uv: vec2f, } - @group(0) @binding(0) var spanUniform: vec2f; - @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { let red = (floor((_arg_0.uv.x * spanUniform.x)) / spanUniform.x); let green = (floor((_arg_0.uv.y * spanUniform.y)) / spanUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts index 55ed0d2fb8..899b6cb76e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/gravity.test.ts @@ -192,14 +192,14 @@ describe('gravity example', () => { return skyBoxVertex_Output((camera.projection * vec4f(viewPos, 1f)), _arg_position.xyz); } - struct skyBoxFragment_Input { - @location(0) texCoord: vec3f, - } - @group(0) @binding(1) var skyBox: texture_cube; @group(0) @binding(2) var sampler_1: sampler; + struct skyBoxFragment_Input { + @location(0) texCoord: vec3f, + } + @fragment fn skyBoxFragment(_arg_0: skyBoxFragment_Input) -> @location(0) vec4f { return textureSample(skyBox, sampler_1, normalize(_arg_0.texCoord)); } @@ -250,6 +250,12 @@ describe('gravity example', () => { return mainVertex_Output(positionOnCanvas, _arg_uv, _arg_normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); } + @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; + + @group(0) @binding(1) var sampler_1: sampler; + + @group(0) @binding(2) var lightSource: vec3f; + struct mainFragment_Input { @location(0) uv: vec2f, @location(1) normals: vec3f, @@ -259,12 +265,6 @@ describe('gravity example', () => { @location(5) ambientLightFactor: f32, } - @group(1) @binding(0) var celestialBodyTextures: texture_2d_array; - - @group(0) @binding(1) var sampler_1: sampler; - - @group(0) @binding(2) var lightSource: vec3f; - @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { if ((_arg_0.destroyed == 1u)) { discard;; diff --git a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts index a9fa1865c8..059828005e 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/image-tuning.test.ts @@ -37,10 +37,6 @@ describe('image tuning example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragment_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var imageView: texture_2d; @group(0) @binding(1) var imageSampler: sampler; @@ -68,6 +64,10 @@ describe('image tuning example', () => { @group(0) @binding(4) var adjustments: Adjustments; + struct fragment_Input { + @location(0) uv: vec2f, + } + @fragment fn fragment(_arg_0: fragment_Input) -> @location(0) vec4f { var color = textureSample(imageView, imageSampler, _arg_0.uv).rgb; let inputLuminance = dot(color, vec3f(0.29899999499320984, 0.5870000123977661, 0.11400000005960464)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts index 941ee300b4..976bdbaaf2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts @@ -66,10 +66,6 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -583,6 +579,10 @@ describe('jelly-slider example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -713,14 +713,14 @@ describe('jelly-slider example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); } diff --git a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts index 7a192b5f54..6e507a2602 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jelly-switch.test.ts @@ -37,10 +37,6 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct raymarchFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var randomUniform: vec2f; var seed: vec2f; @@ -385,6 +381,10 @@ describe('jelly switch example', () => { return background; } + struct raymarchFn_Input { + @location(0) uv: vec2f, + } + @fragment fn raymarchFn(_arg_0: raymarchFn_Input) -> @location(0) vec4f { randSeed2((randomUniform * _arg_0.uv)); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); @@ -505,14 +505,14 @@ describe('jelly switch example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var currentTexture: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { return textureSample(currentTexture, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts index 3fb0e7d685..dcf73b0927 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts @@ -303,10 +303,6 @@ describe('jump flood (distance) example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct distanceFrag_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var distTexture: texture_2d; @group(1) @binding(1) var sampler_1: sampler; @@ -322,6 +318,10 @@ describe('jump flood (distance) example', () => { const insideGradient: array = array(vec3f(0.05000000074505806, 0.05000000074505806, 0.15000000596046448), vec3f(0.10000000149011612, 0.20000000298023224, 0.30000001192092896), vec3f(0.20000000298023224, 0.44999998807907104, 0.550000011920929), vec3f(0.4000000059604645, 0.75, 0.699999988079071), vec3f(0.8999999761581421, 1, 0.949999988079071)); + struct distanceFrag_Input { + @location(0) uv: vec2f, + } + @fragment fn distanceFrag(_arg_0: distanceFrag_Input) -> @location(0) vec4f { var size = textureDimensions(distTexture); var dist = textureSample(distTexture, sampler_1, _arg_0.uv).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts index e247d26a22..56a5b4ecf5 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts @@ -64,10 +64,6 @@ describe('liquid-glass example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var mousePosUniform: vec2f; struct Params { @@ -137,6 +133,10 @@ describe('liquid-glass example', () => { return mix(vec4f(color, 1f), vec4f(tint.color, 1f), tint.strength); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var posInBoxSpace = (_arg_0.uv - mousePosUniform); let sdfDist = sdRoundedBox2d(posInBoxSpace, paramsUniform.rectDims, paramsUniform.radius); diff --git a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts index b8e64d3654..23abf35258 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/oklab.test.ts @@ -32,10 +32,6 @@ describe('oklab example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct mainFragment_Input { - @location(0) uv: vec2f, - } - struct item { hue: f32, alpha: f32, @@ -226,6 +222,10 @@ describe('oklab example', () => { return 1f; } + struct mainFragment_Input { + @location(0) uv: vec2f, + } + @fragment fn mainFragment(_arg_0: mainFragment_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv - 0.5f) * vec2f(2, -2)); let hue = uniforms.hue; diff --git a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts index 1cfbf878ea..0240d9ae44 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/phong-reflection.test.ts @@ -54,11 +54,6 @@ describe('phong reflection example', () => { return vertexShader_Output(_arg_modelPosition, _arg_modelNormal, canvasPosition); } - struct fragmentShader_Input { - @location(0) worldPosition: vec3f, - @location(1) worldNormal: vec3f, - } - struct ExampleControls { lightColor: vec3f, lightDirection: vec3f, @@ -69,6 +64,11 @@ describe('phong reflection example', () => { @group(0) @binding(1) var exampleControlsUniform: ExampleControls; + struct fragmentShader_Input { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var lightColor = normalize(exampleControlsUniform.lightColor); var lightDirection = normalize(exampleControlsUniform.lightDirection); diff --git a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts index 283d8d652b..2ce441c6b2 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/point-light-shadow.test.ts @@ -41,12 +41,12 @@ describe('point light shadow example', () => { return vertexDepth_Output(pos, worldPos); } + @group(0) @binding(1) var lightPosition: vec3f; + struct fragmentDepth_Input { @location(0) worldPos: vec3f, } - @group(0) @binding(1) var lightPosition: vec3f; - @fragment fn fragmentDepth(_arg_0: fragmentDepth_Input) -> @builtin(frag_depth) f32 { let dist = length((_arg_0.worldPos - lightPosition)); return (dist / 100f); @@ -74,12 +74,6 @@ describe('point light shadow example', () => { return vertexMain_Output(pos, worldPos, uv, worldNormal); } - struct fragmentMain_Input { - @location(0) worldPos: vec3f, - @location(1) uv: vec2f, - @location(2) normal: vec3f, - } - @group(1) @binding(3) var lightPosition: vec3f; struct item { @@ -97,6 +91,12 @@ describe('point light shadow example', () => { @group(1) @binding(2) var shadowSampler: sampler_comparison; + struct fragmentMain_Input { + @location(0) worldPos: vec3f, + @location(1) uv: vec2f, + @location(2) normal: vec3f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let lightPos = (&lightPosition); var toLight = ((*lightPos) - _arg_0.worldPos); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts index 1d56a2d307..bdfa96686f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ray-marching.test.ts @@ -31,10 +31,6 @@ describe('ray-marching example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolution: vec2f; struct Shape { @@ -148,6 +144,10 @@ describe('ray-marching example', () => { return res; } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolution.x / resolution.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts index e73568d898..24dcc7ee1c 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/ripple-cube.test.ts @@ -765,10 +765,6 @@ describe('ripple-cube example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var colorTexture: texture_2d; @group(1) @binding(2) var sampler_1: sampler; @@ -782,6 +778,10 @@ describe('ripple-cube example', () => { @group(0) @binding(0) var bloomUniform: BloomParams; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var color = textureSample(colorTexture, sampler_1, _arg_0.uv); var bloomColor = textureSample(bloomTexture, sampler_1, _arg_0.uv); diff --git a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts index f1f6775cc0..e6d2de8222 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/simple-shadow.test.ts @@ -89,11 +89,6 @@ describe('simple shadow example', () => { return mainVert_Output(clipPos, transformedNormal, worldPos.xyz); } - struct mainFrag_Input { - @location(0) normal: vec4f, - @location(1) worldPos: vec3f, - } - struct DirectionalLight { direction: vec3f, color: vec3f, @@ -118,6 +113,11 @@ describe('simple shadow example', () => { @group(0) @binding(3) var paramsUniform: VisParams; + struct mainFrag_Input { + @location(0) normal: vec4f, + @location(1) worldPos: vec3f, + } + @fragment fn mainFrag(_arg_0: mainFrag_Input) -> @location(0) vec4f { let instanceInfo_1 = (&instanceInfo); var N = normalize(_arg_0.normal.xyz); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts index 16ec9b7e12..daab5433e6 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold-3d.test.ts @@ -382,10 +382,6 @@ describe('slime mold 3d example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - var seed: vec2f; fn seed2(value: vec2f) { @@ -438,6 +434,10 @@ describe('slime mold 3d example', () => { @group(0) @binding(1) var sampler_1: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { randSeed2(_arg_0.uv); var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), (1f - (_arg_0.uv.y * 2f))); diff --git a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts index 44d050241e..4b77dc41f4 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/slime-mold.test.ts @@ -308,14 +308,14 @@ describe('slime mold example', () => { return fullScreenTriangle_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), uv[_arg_vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(1) @binding(0) var state: texture_2d; @group(0) @binding(0) var filteringSampler: sampler; + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { return textureSample(state, filteringSampler, _arg_0.uv); }" diff --git a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts index 0e97b0d9e9..b94b01c01f 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/stable-fluid.test.ts @@ -259,16 +259,16 @@ describe('stable-fluid example', () => { return renderFn_Output(vec4f(vertices[_arg_idx], 0f, 1f), texCoords[_arg_idx]); } - struct fragmentImageFn_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var result: texture_2d; @group(0) @binding(2) var linSampler: sampler; @group(0) @binding(1) var background: texture_2d; + struct fragmentImageFn_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentImageFn(_arg_0: fragmentImageFn_Input) -> @location(0) vec4f { const pixelStep = 0.001953125f; let leftSample = textureSample(result, linSampler, vec2f((_arg_0.uv.x - pixelStep), _arg_0.uv.y)).x; diff --git a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts index 21ccd861f6..11a61fe114 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/uniformity.test.ts @@ -35,10 +35,6 @@ describe('uniformity test example', () => { return fullScreenTriangle_Output(vec4f(pos[vertexIndex], 0, 1), uv[vertexIndex]); } - struct fragmentShader_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var canvasRatioUniform: f32; @group(0) @binding(1) var gridSizeUniform: f32; @@ -65,6 +61,10 @@ describe('uniformity test example', () => { return sample(); } + struct fragmentShader_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader(_arg_0: fragmentShader_Input) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); @@ -72,10 +72,6 @@ describe('uniformity test example', () => { return vec4f(vec3f(randFloat01()), 1f); } - struct fragmentShader_Input_1 { - @location(0) uv: vec2f, - } - var seed_1: u32; fn seed2_1(value: vec2f) { @@ -102,6 +98,10 @@ describe('uniformity test example', () => { return sample_1(); } + struct fragmentShader_Input_1 { + @location(0) uv: vec2f, + } + @fragment fn fragmentShader_1(_arg_0: fragmentShader_Input_1) -> @location(0) vec4f { var uv = (((_arg_0.uv + 1f) / 2f) * vec2f(canvasRatioUniform, 1f)); var gridedUV = floor((uv * gridSizeUniform)); diff --git a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts index d7834afa66..dafd303f91 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/vaporrave.test.ts @@ -79,10 +79,6 @@ describe('vaporrave example', () => { return vertexMain_Output(vec4f(pos[idx], 0f, 1f), uv[idx]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var resolutionUniform: vec2f; struct Ray { @@ -219,6 +215,10 @@ describe('vaporrave example', () => { @group(0) @binding(5) var glowIntensityUniform: f32; + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var uv = ((_arg_0.uv * 2f) - 1f); uv.x *= (resolutionUniform.x / resolutionUniform.y); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts index 9abffaf6b8..894d6e4d67 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-centrifuge-2.test.ts @@ -30,10 +30,6 @@ describe('xor dev centrifuge example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - struct Params { time: f32, aspectRatio: f32, @@ -51,6 +47,10 @@ describe('xor dev centrifuge example', () => { return select(tanh(v), sign(v), (abs(v) > vec3f(10))); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { let params = (¶msUniform); var ratio = vec2f((*params).aspectRatio, 1f); diff --git a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts index 419c95807c..ec9b1c27a0 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/xor-dev-runner.test.ts @@ -34,10 +34,6 @@ describe('xor dev runner example', () => { return vertexMain_Output(vec4f(pos[_arg_vertexIndex], 0f, 1f), pos[_arg_vertexIndex]); } - struct fragmentMain_Input { - @location(0) uv: vec2f, - } - @group(0) @binding(0) var colorUniform: vec3f; struct Camera { @@ -82,6 +78,10 @@ describe('xor dev runner example', () => { return select(tanh(v), sign(v), (abs(v) > 10f)); } + struct fragmentMain_Input { + @location(0) uv: vec2f, + } + @fragment fn fragmentMain(_arg_0: fragmentMain_Input) -> @location(0) vec4f { var icolor = (colorUniform * 4f); var ray = getRayForUV(_arg_0.uv); diff --git a/packages/typegpu/src/core/function/autoIO.ts b/packages/typegpu/src/core/function/autoIO.ts index 6feece51d5..b35a092b1a 100644 --- a/packages/typegpu/src/core/function/autoIO.ts +++ b/packages/typegpu/src/core/function/autoIO.ts @@ -85,7 +85,7 @@ export class AutoFragmentFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'fragmentFn'); } - this.#core = createFnCore(impl, '@fragment '); + this.#core = createFnCore(impl, 'fragment'); this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations); setName(this.autoIn, 'FragmentIn'); this.autoOut = new AutoStruct(builtinFragmentOut, vec4f); @@ -131,7 +131,7 @@ export class AutoVertexFn implements SelfResolvable { if (!getName(impl)) { setName(impl, 'vertexFn'); } - this.#core = createFnCore(impl, '@vertex '); + this.#core = createFnCore(impl, 'vertex'); this.autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations); setName(this.autoIn, 'VertexIn'); this.autoOut = new AutoStruct(builtinVertexOut, undefined); diff --git a/packages/typegpu/src/core/function/entryInputRouter.ts b/packages/typegpu/src/core/function/entryInputRouter.ts index 8ba6669372..b756366c4b 100644 --- a/packages/typegpu/src/core/function/entryInputRouter.ts +++ b/packages/typegpu/src/core/function/entryInputRouter.ts @@ -1,12 +1,7 @@ -import { undecorate } from '../../data/dataTypes.ts'; -import { snip, type Snippet } from '../../data/snippet.ts'; +import { type Snippet } from '../../data/snippet.ts'; import { $internal, $repr } from '../../shared/symbols.ts'; import { type BaseData, isWgslStruct } from '../../data/wgslTypes.ts'; - -interface PositionalArgEntry { - argName: string; - type: BaseData; -} +import type { FunctionArgumentAccess } from '../../types.ts'; /** * Routes `(input) => { input.x }` style property access to the correct WGSL @@ -18,38 +13,32 @@ export class EntryInputRouter implements BaseData { readonly type = 'entry-input-router' as const; // Type-token only, not present at runtime: declare readonly [$repr]: never; - readonly structArgName: string; - readonly dataSchema: BaseData | undefined; + + readonly structArg: FunctionArgumentAccess | undefined; /** Maps schemaKey → { WGSL arg name, type } */ - readonly positionalArgsMap: Map; + readonly positionalArgsMap: Map; constructor( - structArgName: string, - dataSchema: BaseData | undefined, - positionalArgs: { schemaKey: string; argName: string; type: BaseData }[], + structArg: FunctionArgumentAccess | undefined, + positionalArgs: { schemaKey: string; arg: FunctionArgumentAccess }[], ) { - this.structArgName = structArgName; - this.dataSchema = dataSchema; - this.positionalArgsMap = new Map( - positionalArgs.map((a) => [a.schemaKey, { argName: a.argName, type: a.type }]), - ); + this.structArg = structArg; + this.positionalArgsMap = new Map(positionalArgs.map((a) => [a.schemaKey, a.arg])); } toString(): string { return 'entry-input-router'; } - accessProp(propName: string): Snippet | undefined { + accessProp(propName: string): Snippet | { target: Snippet; prop: string } | undefined { const positionalEntry = this.positionalArgsMap.get(propName); if (positionalEntry) { - return snip(positionalEntry.argName, positionalEntry.type, 'argument'); + return positionalEntry(); } - if (this.dataSchema && isWgslStruct(this.dataSchema)) { - const propType = this.dataSchema.propTypes[propName]; - if (propType) { - return snip(`${this.structArgName}.${propName}`, undecorate(propType), 'argument'); - } + const structSnippet = this.structArg?.(); + if (structSnippet && isWgslStruct(structSnippet.dataType)) { + return { target: structSnippet, prop: propName }; } return undefined; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 66429067e5..77d676c0d5 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -5,7 +5,7 @@ import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTy import { MissingLinksError } from '../../errors.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; -import type { ResolutionCtx } from '../../types.ts'; +import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; import { applyExternals, type ExternalMap, replaceExternalsInWgsl } from '../resolve/externals.ts'; import { extractArgs } from './extractArgs.ts'; import type { Implementation, SeparatedEntryArgs } from './fnTypes.ts'; @@ -32,7 +32,11 @@ export interface FnCore { ): ResolvedSnippet; } -export function createFnCore(implementation: Implementation, fnAttribute = ''): FnCore { +export function createFnCore( + implementation: Implementation, + functionType: 'normal' | TgpuShaderStage, + workgroupSize?: number[], +): FnCore { /** * External application has to be deferred until resolution because * some externals can reference the owner function which has not been @@ -57,6 +61,15 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): ): ResolvedSnippet { const externalMap: ExternalMap = {}; + let attributes = ''; + if (functionType === 'compute') { + attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `; + } else if (functionType === 'vertex') { + attributes = `@vertex `; + } else if (functionType === 'fragment') { + attributes = `@fragment `; + } + for (const externals of externalsToApply) { applyExternals(externalMap, externals); } @@ -83,7 +96,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): let header = ''; let body = ''; - if (fnAttribute !== '' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput && validArgNames) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { @@ -140,7 +153,8 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): body = replacedImpl.slice(providedArgs.range.end); } - ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); + ctx.addDeclaration(`${attributes}fn ${id}${header}${body}`); + return snip(id, returnType, /* origin */ 'runtime'); } @@ -178,7 +192,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // If an entrypoint implementation has a second argument, it represents the output schema. // We look at the identifier chosen by the user and add it to externals. const maybeSecondArg = ast.params[1]; - if (maybeSecondArg && maybeSecondArg.type === 'i' && fnAttribute !== '') { + if (maybeSecondArg && maybeSecondArg.type === 'i' && functionType !== 'normal') { applyExternals(externalMap, { // oxlint-disable-next-line typescript/no-non-null-assertion -- entry functions cannot be shellless [maybeSecondArg.name]: undecorate(returnType!), @@ -187,18 +201,8 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): // generate wgsl string - const { - head, - body, - returnType: actualReturnType, - } = ctx.fnToWgsl({ - functionType: fnAttribute.includes('@compute') - ? 'compute' - : fnAttribute.includes('@vertex') - ? 'vertex' - : fnAttribute.includes('@fragment') - ? 'fragment' - : 'normal', + const { code, returnType: actualReturnType } = ctx.fnToWgsl({ + functionType, argTypes, entryInput, params: ast.params, @@ -207,9 +211,7 @@ export function createFnCore(implementation: Implementation, fnAttribute = ''): externalMap, }); - ctx.addDeclaration( - `${fnAttribute}fn ${id}${ctx.resolve(head).value}${ctx.resolve(body).value}`, - ); + ctx.addDeclaration(`${attributes}fn ${id}${code}`); return snip(id, actualReturnType, /* origin */ 'runtime'); }, diff --git a/packages/typegpu/src/core/function/shelllessImpl.ts b/packages/typegpu/src/core/function/shelllessImpl.ts index 23b60bdde1..9263303a31 100644 --- a/packages/typegpu/src/core/function/shelllessImpl.ts +++ b/packages/typegpu/src/core/function/shelllessImpl.ts @@ -31,7 +31,7 @@ export function createShelllessImpl( argTypes: BaseData[], implementation: (...args: never[]) => unknown, ): ShelllessImpl { - const core = createFnCore(implementation, ''); + const core = createFnCore(implementation, 'normal'); return { [$internal]: true, diff --git a/packages/typegpu/src/core/function/tgpuComputeFn.ts b/packages/typegpu/src/core/function/tgpuComputeFn.ts index 6f48cbe643..9cef2d49cc 100644 --- a/packages/typegpu/src/core/function/tgpuComputeFn.ts +++ b/packages/typegpu/src/core/function/tgpuComputeFn.ts @@ -114,10 +114,7 @@ function createComputeFn>( [$getNameForward]: FnCore; }; - const core = createFnCore( - implementation, - `@compute @workgroup_size(${workgroupSize.join(', ')}) `, - ); + const core = createFnCore(implementation, 'compute', workgroupSize); const result: This = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index 65ea52de65..40f6548922 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -173,7 +173,7 @@ function createFn( implementation = _implementation; } - const core = createFnCore(implementation as Implementation, ''); + const core = createFnCore(implementation as Implementation, 'normal'); const fnBase = { shell, diff --git a/packages/typegpu/src/core/function/tgpuFragmentFn.ts b/packages/typegpu/src/core/function/tgpuFragmentFn.ts index 631617bcdc..cb3b4c990f 100644 --- a/packages/typegpu/src/core/function/tgpuFragmentFn.ts +++ b/packages/typegpu/src/core/function/tgpuFragmentFn.ts @@ -183,7 +183,7 @@ function createFragmentFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@fragment '); + const core = createFnCore(implementation, 'fragment'); const outputType = shell.returnType; if (typeof implementation === 'string') { addReturnTypeToExternals(implementation, outputType, (externals) => diff --git a/packages/typegpu/src/core/function/tgpuVertexFn.ts b/packages/typegpu/src/core/function/tgpuVertexFn.ts index a261634b3d..8c5b230891 100644 --- a/packages/typegpu/src/core/function/tgpuVertexFn.ts +++ b/packages/typegpu/src/core/function/tgpuVertexFn.ts @@ -158,7 +158,7 @@ function createVertexFn( [$getNameForward]: FnCore; }; - const core = createFnCore(implementation, '@vertex '); + const core = createFnCore(implementation, 'vertex'); const entryInput: SeparatedEntryArgs = separateAllAsPositional(shell.in ?? {}); const result: This = { diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index fe658ef99d..9c2e954f0d 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -74,14 +74,9 @@ export interface Snippet { readonly origin: Origin; } -export interface ResolvedSnippet { +export interface ResolvedSnippet extends Snippet { readonly value: string; - /** - * The type that `value` is assignable to (not necessary exactly inferred as). - * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float - */ readonly dataType: BaseData; - readonly origin: Origin; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5b3daa0067..5eedec298d 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -12,10 +12,9 @@ import { type TgpuLazy, type TgpuSlot, } from './core/slot/slotTypes.ts'; -import { getAttributesString } from './data/attributes.ts'; -import { isData, undecorate, UnknownData } from './data/dataTypes.ts'; +import { isData, UnknownData } from './data/dataTypes.ts'; import { bool } from './data/numeric.ts'; -import { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; +import { type Origin, type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; import { type BaseData, isPtr, isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts'; import { invariant, MissingSlotValueError, ResolutionError, WgslTypeError } from './errors.ts'; import { provideCtx, topLevelState } from './execMode.ts'; @@ -41,6 +40,7 @@ import type { ExecMode, ExecState, FnToWgslOptions, + FunctionArgumentAccess, FunctionScopeLayer, ItemLayer, ItemStateStack, @@ -58,6 +58,7 @@ import { createIoSchema } from './core/function/ioSchema.ts'; import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; +import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; /** * Inserted into bind group entry definitions that belong @@ -115,16 +116,14 @@ class ItemStateStackImpl implements ItemStateStack { pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, returnType: BaseData | undefined, externalMap: Record, ): FunctionScopeLayer { const scope: FunctionScopeLayer = { type: 'functionScope', functionType, - args, - argAliases, + argAccess, returnType, externalMap, reportedReturnTypes: new Set(), @@ -184,13 +183,9 @@ class ItemStateStackImpl implements ItemStateStack { const layer = this._stack[i]; if (layer?.type === 'functionScope') { - const arg = layer.args.find((a) => a.value === id); - if (arg !== undefined) { - return arg; - } - - if (layer.argAliases[id]) { - return layer.argAliases[id]; + const access = layer.argAccess[id]; + if (access) { + return access(); } const external = layer.externalMap[id]; @@ -311,6 +306,39 @@ interface FixedBindingConfig { resource: object; } +function createArgument( + name: string, + type: BaseData, + origin: Origin = 'argument', +): FunctionArgument { + let used = false; + + return { + name, + access: () => { + used = true; + return snip(name, type, origin); + }, + decoratedType: type, + get used() { + return used; + }, + }; +} + +function createArgumentPropAccess( + argAccess: FunctionArgumentAccess, + prop: string, +): FunctionArgumentAccess { + return () => { + const argSnippet = argAccess(); + if (!argSnippet) { + return undefined; + } + return accessProp(argSnippet, prop); + }; +} + export class ResolutionCtxImpl implements ResolutionCtx { readonly #namespaceInternal: NamespaceInternal; @@ -438,28 +466,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { return this.#logGenerator.logResources; } - fnToWgsl(options: FnToWgslOptions): { head: Wgsl; body: Wgsl; returnType: BaseData } { + fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { let fnScopePushed = false; try { this.#namespaceInternal.nameRegistry.pushFunctionScope(); - const args: Snippet[] = []; - const argAliases: [string, Snippet][] = []; - // For entry functions: collect pending header entries to be filtered after body generation. - const pendingHeaderEntries: { argName: string; header: string }[] = []; + const args: FunctionArgument[] = []; + const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; - const structArgName = this.makeNameValid('_arg_0'); - const structArg = dataSchema ? snip(structArgName, dataSchema, 'argument') : undefined; + const structArg = dataSchema + ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + : undefined; + if (structArg) { args.push(structArg); - pendingHeaderEntries.push({ - argName: structArgName, - header: `${structArgName}: ${this.resolve(dataSchema).value}`, - }); } if (firstParam?.type === FuncParameterType.destructuredObject) { @@ -467,45 +491,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const argName = this.makeNameValid(alias); - const argSnippet = snip(argName, argInfo.type, 'argument'); - args.push(argSnippet); - argAliases.push([alias, argSnippet]); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(argInfo.type)}${argName}: ${this.resolve(undecorate(argInfo.type)).value}`, - }); + const arg = createArgument(this.makeNameValid(alias), argInfo.type); + args.push(arg); + argAccess[alias] = arg.access; } else if (structArg) { - const propSnippet = accessProp(structArg, name); - if (propSnippet) { - argAliases.push([alias, propSnippet]); - } + argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. - const proxyEntries: Array<{ schemaKey: string; argName: string; type: BaseData }> = []; + const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - const s = snip(argName, a.type, 'argument'); - args.push(s); - proxyEntries.push({ schemaKey: a.schemaKey, argName, type: a.type }); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } - const router = new EntryInputRouter(structArgName, dataSchema, proxyEntries); - argAliases.push([firstParam.name, snip(firstParam.name, router, 'argument')]); + const router = new EntryInputRouter(structArg?.access, proxyEntries); + argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { const argName = this.makeNameValid(`_arg_${a.schemaKey}`); - args.push(snip(argName, a.type, 'argument')); - pendingHeaderEntries.push({ - argName, - header: `${getAttributesString(a.type)}${argName}: ${this.resolve(undecorate(a.type)).value}`, - }); + const arg = createArgument(argName, a.type); + args.push(arg); + argAccess[argName] = arg.access; } } } else { @@ -528,22 +538,17 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const rawName = astParam.name; - const snippet = snip(this.makeNameValid(rawName), argType, origin); - args.push(snippet); - if (snippet.value !== rawName) { - argAliases.push([rawName, snippet]); - } + const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + args.push(arg); + argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objSnippet = snip(`_arg_${i}`, argType, origin); - args.push(objSnippet); - argAliases.push( - ...astParam.props.map( - ({ name, alias }) => [alias, accessProp(objSnippet, name)] as [string, Snippet], - ), - ); + const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + args.push(objArg); + for (const { name, alias } of astParam.props) { + argAccess[alias] = createArgumentPropAccess(objArg.access, name); + } break; } case undefined: { @@ -551,7 +556,16 @@ export class ResolutionCtxImpl implements ResolutionCtx { // If we're not using an auto-struct, it's not going to // have any properties anyway. if (!(argType instanceof AutoStruct)) { - args.push(snip(`_arg_${i}`, argType, origin)); + args.push({ + name: this.makeNameValid(`_arg_${i}`), + access: () => { + throw new Error( + `Unreachable: Accessing an argument that wasn't named in the function signature`, + ); + }, + decoratedType: argType, + used: false, + }); } } } @@ -560,68 +574,71 @@ export class ResolutionCtxImpl implements ResolutionCtx { const scope = this._itemStateStack.pushFunctionScope( options.functionType, - args, - Object.fromEntries(argAliases), + argAccess, options.returnType, options.externalMap, ); fnScopePushed = true; - const body = this.gen.functionDefinition(options.body); + let returnType: BaseData | undefined; - let returnType = options.returnType; - if (returnType instanceof AutoStruct) { - // We're expecting an "auto" return type, so if there were structs returned, - // we accept the struct, otherwise we let the rest of the code unify on a - // primitive type. - if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { - returnType = returnType.completeStruct; - } else { - returnType = undefined; - } - } + const code = this.gen.functionDefinition({ + functionType: options.functionType, + args, + body: options.body, + determineReturnType: () => { + if (returnType) { + // Already determined + return returnType; + } - if (!returnType) { - const returnTypes = [...scope.reportedReturnTypes]; - if (returnTypes.length === 0) { - returnType = Void; - } else { - const conversion = getBestConversion(returnTypes); - if (conversion && !conversion.hasImplicitConversions) { - returnType = conversion.targetType; + returnType = options.returnType; + if (returnType instanceof AutoStruct) { + // We're expecting an "auto" return type, so if there were structs returned, + // we accept the struct, otherwise we let the rest of the code unify on a + // primitive type. + if (isWgslStruct(scope.reportedReturnTypes.values().next().value)) { + returnType = returnType.completeStruct; + } else { + returnType = undefined; + } } - } - if (!returnType) { - throw new Error( - `Expected function to have a single return type, got [${returnTypes.join( - ', ', - )}]. Cast explicitly to the desired type.`, - ); - } + if (!returnType) { + const returnTypes = [...scope.reportedReturnTypes]; + if (returnTypes.length === 0) { + returnType = Void; + } else { + const conversion = getBestConversion(returnTypes); + if (conversion && !conversion.hasImplicitConversions) { + returnType = conversion.targetType; + } + } - returnType = concretize(returnType); + if (!returnType) { + throw new Error( + `Expected function to have a single return type, got [${returnTypes.join( + ', ', + )}]. Cast explicitly to the desired type.`, + ); + } - if (options.functionType === 'vertex' || options.functionType === 'fragment') { - returnType = createIoSchema(returnType as IOData); - } - } + returnType = concretize(returnType); - if (options.entryInput) { - const headerParts = pendingHeaderEntries - .filter(({ argName }) => isArgUsedInBody(argName, body)) - .map(({ header }) => header); - const argList = headerParts.join(', '); - const returnStr = - returnType.type !== 'void' - ? `-> ${getAttributesString(returnType)}${this.resolve(returnType).value} ` - : ''; - return { head: `(${argList}) ${returnStr}`, body, returnType }; + if (options.functionType === 'vertex' || options.functionType === 'fragment') { + returnType = createIoSchema(returnType as IOData); + } + } + return returnType; + }, + }); + + if (!returnType) { + throw new Error(`Failed to determine return type`); } return { - head: resolveFunctionHeader(this, args, returnType), - body, + code, returnType, }; } finally { @@ -1079,17 +1096,3 @@ export function resolve(item: Wgsl, options: ResolutionCtxImplOptions): Resoluti logResources: ctx.logResources, }; } - -function isArgUsedInBody(argName: string, body: string): boolean { - return new RegExp(`\\b${argName}\\b`).test(body); -} - -function resolveFunctionHeader(ctx: ResolutionCtx, args: Snippet[], returnType: BaseData) { - const argList = args - .map((arg) => `${arg.value}: ${ctx.resolve(arg.dataType as BaseData).value}`) - .join(', '); - - return returnType.type !== 'void' - ? `(${argList}) -> ${getAttributesString(returnType)}${ctx.resolve(returnType).value} ` - : `(${argList}) `; -} diff --git a/packages/typegpu/src/tgsl/accessProp.ts b/packages/typegpu/src/tgsl/accessProp.ts index b264a154ba..3410d3e383 100644 --- a/packages/typegpu/src/tgsl/accessProp.ts +++ b/packages/typegpu/src/tgsl/accessProp.ts @@ -10,7 +10,7 @@ import { } from '../data/dataTypes.ts'; import { abstractInt, bool, f16, f32, i32, u32 } from '../data/numeric.ts'; import { derefSnippet } from '../data/ref.ts'; -import { isEphemeralSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { isEphemeralSnippet, isSnippet, snip, type Snippet } from '../data/snippet.ts'; import { vec2b, vec2f, @@ -160,7 +160,14 @@ export function accessProp(target: Snippet, propName: string): Snippet | undefin } if (target.dataType instanceof EntryInputRouter) { - return target.dataType.accessProp(propName); + const result = target.dataType.accessProp(propName); + if (isSnippet(result)) { + return result; + } + if (result) { + return accessProp(result.target, result.prop); + } + return undefined; } if (isPtr(target.dataType)) { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index f410e53e6d..b9525ce87e 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -308,7 +308,7 @@ export function convertToCommonType( if ((TEST || DEV) && verbose && conversion.hasImplicitConversions) { console.warn( `Implicit conversions from [\n${values - .map((v) => ` ${v.value}: ${safeStringify(v.dataType)}`) + .map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`) .join(',\n')}\n] to ${conversion.targetType.type} are supported, but not recommended. Consider using explicit conversions instead.`, ); diff --git a/packages/typegpu/src/tgsl/shaderGenerator.ts b/packages/typegpu/src/tgsl/shaderGenerator.ts index e482b03cc8..adde48bf00 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator.ts @@ -1,7 +1,7 @@ -import type { Block } from 'tinyest'; import type { BaseData } from '../data/wgslTypes.ts'; import type { GenerationCtx } from './generationHelpers.ts'; import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; /** * **NOTE: This is an unstable API and may change in the future.** @@ -12,7 +12,7 @@ import type { ResolvedSnippet, Snippet } from '../data/snippet.ts'; export interface ShaderGenerator { initGenerator(ctx: GenerationCtx): void; - functionDefinition(body: Block): string; + functionDefinition(options: FunctionDefinitionOptions): string; typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet; typeAnnotation(schema: BaseData): string; } diff --git a/packages/typegpu/src/tgsl/shaderGenerator_members.ts b/packages/typegpu/src/tgsl/shaderGenerator_members.ts index 1eb11ae0c9..38ad393c2d 100644 --- a/packages/typegpu/src/tgsl/shaderGenerator_members.ts +++ b/packages/typegpu/src/tgsl/shaderGenerator_members.ts @@ -1,6 +1,18 @@ +import type { Block } from 'tinyest'; +import type { BaseData } from '../data/wgslTypes.ts'; +import type { FunctionArgument, TgpuShaderStage } from '../types.ts'; + export { UnknownData } from '../data/dataTypes.ts'; // types -export type { ResolutionCtx } from '../types.ts'; +export type { ResolutionCtx, FunctionArgument } from '../types.ts'; export type { Snippet } from '../data/snippet.ts'; export type { Origin } from '../data/snippet.ts'; + +export interface FunctionDefinitionOptions { + readonly functionType: 'normal' | TgpuShaderStage; + readonly args: readonly FunctionArgument[]; + readonly body: Block; + + determineReturnType(): BaseData; +} diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 38d2a2ee44..c35bf9128f 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -51,6 +51,8 @@ import { mathToStd } from './math.ts'; import type { ExternalMap } from '../core/resolve/externals.ts'; import * as forOfUtils from './forOfUtils.ts'; import { isTgpuRange } from '../std/range.ts'; +import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts'; +import { getAttributesString } from '../data/attributes.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -887,8 +889,27 @@ ${this.ctx.pre}}`; assertExhaustive(expression); } - public functionDefinition(body: tinyest.Block): string { - return this._block(body); + public functionDefinition(options: FunctionDefinitionOptions): string { + // Function body + const body = this._block(options.body); + + // Function header + const returnType = options.determineReturnType(); + + const argList = options.args + // Stripping out unused arguments in entry functions + .filter((arg) => arg.used || options.functionType === 'normal') + .map((arg) => { + return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`; + }) + .join(', '); + + const head = + returnType.type !== 'void' + ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` + : `(${argList}) `; + + return `${head}${body}`; } /** diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 1c92f8a43f..75f9e71697 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -99,11 +99,19 @@ export type ItemLayer = { usedSlots: Set>; }; +export type FunctionArgumentAccess = () => Snippet | undefined; + +export interface FunctionArgument { + name: string; + access: FunctionArgumentAccess; + decoratedType: BaseData; + used: boolean; +} + export type FunctionScopeLayer = { type: 'functionScope'; functionType: 'normal' | 'compute' | 'vertex' | 'fragment'; - args: Snippet[]; - argAliases: Record; + argAccess: Record; externalMap: Record; /** * The return type of the function. If undefined, the type should be inferred @@ -138,8 +146,7 @@ export interface ItemStateStack { pushSlotBindings(pairs: SlotValuePair[]): void; pushFunctionScope( functionType: 'normal' | TgpuShaderStage, - args: Snippet[], - argAliases: Record, + argAccess: Record, /** * The return type of the function. If undefined, the type should be inferred * from the implementation (relevant for shellless functions). @@ -305,8 +312,7 @@ export interface ResolutionCtx { resolveSnippet(snippet: Snippet): ResolvedSnippet; fnToWgsl(options: FnToWgslOptions): { - head: Wgsl; - body: Wgsl; + code: string; returnType: BaseData; }; diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 316f2d0e02..be0c2888e4 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -277,13 +277,6 @@ describe('root.withVertex(...).withFragment(...)', () => { return vertexMain_Output(vec3f(), vec3f(), vec3f(), 0f, 0u, vec4f()); } - struct fragmentMain_Input { - @location(3) baz3: u32, - @location(1) bar: vec3f, - @location(2) foo: vec3f, - @location(5) baz2: f32, - } - @fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(); }" @@ -1568,8 +1561,8 @@ describe('root.createRenderPipeline', () => { } struct VertexIn { - @builtin(vertex_index) vertexIndex: u32, @location(0) localPos: vec3f, + @builtin(vertex_index) vertexIndex: u32, } @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { @@ -1712,18 +1705,13 @@ describe('root.createRenderPipeline', () => { { "arrayStride": 16, "attributes": [ - { - "format": "float32", - "offset": 12, - "shaderLocation": 0, - }, { "format": "float32x3", "offset": 0, - "shaderLocation": 1, + "shaderLocation": 0, }, ], - "stepMode": "instance", + "stepMode": "vertex", }, { "arrayStride": 16, @@ -1731,10 +1719,15 @@ describe('root.createRenderPipeline', () => { { "format": "float32x3", "offset": 0, + "shaderLocation": 1, + }, + { + "format": "float32", + "offset": 12, "shaderLocation": 2, }, ], - "stepMode": "vertex", + "stepMode": "instance", }, ], "module": "mockShaderModule", @@ -1751,10 +1744,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "instanceBuffer", + "label": "vertexBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 16, + "size": 48, "unmap": [MockFunction], "usage": 44, }, @@ -1766,10 +1759,10 @@ describe('root.createRenderPipeline', () => { { "destroy": [MockFunction], "getMappedRange": [MockFunction], - "label": "vertexBuffer", + "label": "instanceBuffer", "mapAsync": [MockFunction], "mapState": "unmapped", - "size": 48, + "size": 16, "unmap": [MockFunction], "usage": 44, }, diff --git a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts index 727e9fed70..58150fbee5 100644 --- a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts +++ b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts @@ -17,14 +17,14 @@ describe('extension based pruning', () => { }); expect(tgpu.resolve([someFn], { enableExtensions: ['f16'] })).toMatchInlineSnapshot(` - "enable f16; + "enable f16; - fn someFn() -> f32 { - { - return 6.599609375f; - } - }" - `); + fn someFn() -> f32 { + { + return 6.599609375f; + } + }" + `); expect(tgpu.resolve([someFn])).toMatchInlineSnapshot(` "fn someFn() -> f32 { diff --git a/packages/typegpu/tests/tgsl/multiplication.test.ts b/packages/typegpu/tests/tgsl/multiplication.test.ts index 30b3b589c8..1a1e335463 100644 --- a/packages/typegpu/tests/tgsl/multiplication.test.ts +++ b/packages/typegpu/tests/tgsl/multiplication.test.ts @@ -31,7 +31,7 @@ test('multiplying i32 with a float literal should implicitly convert to an f32', [ [ "Implicit conversions from [ - 1: i32 + 1i: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], @@ -43,7 +43,7 @@ test('multiplying i32 with a float literal should implicitly convert to an f32', ], [ "Implicit conversions from [ - 1: i32 + 1i: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], @@ -78,7 +78,7 @@ test('multiplying u32 with a float literal should implicitly convert to an f32', [ [ "Implicit conversions from [ - 10: u32 + 10u: u32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], @@ -90,7 +90,7 @@ test('multiplying u32 with a float literal should implicitly convert to an f32', ], [ "Implicit conversions from [ - 1: u32 + 1u: u32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], diff --git a/packages/typegpu/tests/tgsl/nameClashes.test.ts b/packages/typegpu/tests/tgsl/nameClashes.test.ts index 86167245ed..586e8e3bf1 100644 --- a/packages/typegpu/tests/tgsl/nameClashes.test.ts +++ b/packages/typegpu/tests/tgsl/nameClashes.test.ts @@ -223,14 +223,14 @@ test('should allow duplicate name after block end', () => { }; expect(tgpu.resolve([main])).toMatchInlineSnapshot(` - "fn main() -> u32 { - for (var i = 0; (i < 3i); i++) { - let foo = (i + 1i); - } - const foo = 7u; - return foo; - }" - `); + "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', () => { diff --git a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts index fd37903d12..c41cc6b27c 100644 --- a/packages/typegpu/tests/tgsl/ternaryOperator.test.ts +++ b/packages/typegpu/tests/tgsl/ternaryOperator.test.ts @@ -18,14 +18,14 @@ describe('ternary operator', () => { myFn.with(mySlot, false).$name('falseFn'), ]), ).toMatchInlineSnapshot(` - "fn trueFn() -> u32 { - return 10u; - } - - fn falseFn() -> u32 { - return 20u; - }" - `); + "fn trueFn() -> u32 { + return 10u; + } + + fn falseFn() -> u32 { + return 20u; + }" + `); }); it('should work for different comptime known expressions', () => { @@ -72,22 +72,22 @@ describe('ternary operator', () => { myFn.with(mySlot, 3).$name('threeFn'), ]), ).toMatchInlineSnapshot(` - "fn myFn() -> u32 { - return -1u; - } - - fn oneFn() -> u32 { - return 10u; - } - - fn twoFn() -> u32 { - return 20u; - } - - fn threeFn() -> u32 { - return 30u; - }" - `); + "fn myFn() -> u32 { + return -1u; + } + + fn oneFn() -> u32 { + return 10u; + } + + fn twoFn() -> u32 { + return 20u; + } + + fn threeFn() -> u32 { + return 30u; + }" + `); }); it('should not include unused dependencies', ({ root }) => { @@ -103,20 +103,20 @@ describe('ternary operator', () => { }); expect(tgpu.resolve([myFn.with(mySlot, true).$name('trueFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myUniform: u32; + "@group(0) @binding(0) var myUniform: u32; - fn trueFn() -> u32 { - return myUniform; - }" - `); + fn trueFn() -> u32 { + return myUniform; + }" + `); expect(tgpu.resolve([myFn.with(mySlot, false).$name('falseFn')])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var myReadonly: u32; + "@group(0) @binding(0) var myReadonly: u32; - fn falseFn() -> u32 { - return myReadonly; - }" - `); + fn falseFn() -> u32 { + return myReadonly; + }" + `); }); it('should handle undefined', ({ root }) => { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index e1129649ac..43f7b6ce77 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -3,19 +3,17 @@ import { beforeEach, describe, expect, vi } from 'vitest'; import { namespace } from '../../src/core/resolve/namespace.ts'; import * as d from '../../src/data/index.ts'; import { abstractFloat, abstractInt } from '../../src/data/numeric.ts'; -import { snip } from '../../src/data/snippet.ts'; -import { Void, type WgslArray } from '../../src/data/wgslTypes.ts'; +import { type WgslArray } from '../../src/data/wgslTypes.ts'; import { provideCtx } from '../../src/execMode.ts'; import tgpu from '../../src/index.js'; import { ResolutionCtxImpl } from '../../src/resolutionCtx.ts'; import { getMetaData } from '../../src/shared/meta.ts'; -import { $internal } from '../../src/shared/symbols.ts'; import * as std from '../../src/std/index.ts'; import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { CodegenState } from '../../src/types.ts'; import { it } from 'typegpu-testing-utility'; import { ArrayExpression } from '../../src/tgsl/generationHelpers.ts'; -import { extractSnippetFromFn } from '../utils/parseResolved.ts'; +import { expectDataTypeOf, extractSnippetFromFn } from '../utils/parseResolved.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -40,19 +38,11 @@ describe('wgslGenerator', () => { return true; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot(`"[0,[[10,true]]]"`); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.bool, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - return true; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> bool { + return true; + }" + `); }); it('creates a function body', () => { @@ -63,23 +53,13 @@ describe('wgslGenerator', () => { return a; }; - const parsedBody = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsedBody)).toMatchInlineSnapshot( - `"[0,[[12,"a",[5,"12"]],[2,"a","+=",[5,"21"]],[10,"a"]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope('normal', [], {}, d.i32, {}); - const gen = wgslGenerator.functionDefinition(parsedBody); - expect(gen).toMatchInlineSnapshot(` - "{ - var a = 12; - a += 21i; - return a; - }" - `); - }); + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() -> i32 { + var a = 12; + a += 21i; + return a; + }" + `); }); it('creates correct resources for numeric literals', () => { @@ -134,57 +114,22 @@ describe('wgslGenerator', () => { }); const testBuffer = root.createBuffer(TestStruct).$usage('storage'); - const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$.a + testUsage.$.b.x; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - if (!astInfo) { - throw new Error('Expected prebuilt AST to be present'); - } + expectDataTypeOf(() => { + 'use gpu'; + return testUsage.$.a; + }).toStrictEqual(d.u32); - expect(JSON.stringify(astInfo.ast?.body)).toMatchInlineSnapshot( - `"[0,[[10,[1,[7,[7,"testUsage","$"],"a"],"+",[7,[7,[7,"testUsage","$"],"b"],"x"]]]]]"`, - ); - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); + expectDataTypeOf(() => { + 'use gpu'; + return testUsage.$.b.x; + }).toStrictEqual(d.u32); - provideCtx(ctx, () => { - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res1 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[1], - ); - - expect(res1.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const res2 = wgslGenerator._expression( - ((astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.BinaryExpression)[3], - ); - expect(res2.dataType).toStrictEqual(d.u32); - - // Check for: return testUsage.$.a + testUsage.$.b.x; - // ^ this should be a u32 - const sum = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - expect(sum.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + return testUsage.$.a + testUsage.$.b.x; + }).toStrictEqual(d.u32); }); it('generates correct resources for external resource array index access', ({ root }) => { @@ -192,42 +137,10 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('uniform'); - const testFn = tgpu.fn( - [], - d.u32, - )(() => { - return testUsage.$[3] as number; - }); - - 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,[[10,[8,[7,"testUsage","$"],[5,"3"]]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.u32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return testUsage.$[3]; - // ^ this should be a u32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.u32); - }); + expectDataTypeOf(() => { + 'use gpu'; + return testUsage.$[3]; + }).toStrictEqual(d.u32); }); it('generates correct resources for nested struct with atomics in a complex expression', ({ @@ -253,156 +166,29 @@ describe('wgslGenerator', () => { const testUsage = testBuffer.as('mutable'); - const testFn = tgpu.fn( - [d.u32], - d.vec4f, - )((idx) => { - const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - const vec = std.mix(d.vec4f(), testUsage.$.a, value); - std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - return vec; - }); - - const astInfo = getMetaData( - testFn[$internal].implementation as (...args: unknown[]) => unknown, - ); - - if (!astInfo?.ast) { - throw new Error('Expected prebuilt AST to be present'); - } - - expect(JSON.stringify(astInfo.ast.body)).toMatchInlineSnapshot( - `"[0,[[13,"value",[6,[7,"std","atomicLoad"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"y"]]]],[13,"vec",[6,[7,"std","mix"],[[6,[7,"d","vec4f"],[]],[7,[7,"testUsage","$"],"a"],"value"]]],[6,[7,"std","atomicStore"],[[7,[8,[7,[7,[7,"testUsage","$"],"b"],"aa"],"idx"],"x"],[7,"vec","y"]]],[10,"vec"]]]"`, - ); - - if (astInfo.ast.params.filter((arg) => arg.type !== 'i').length > 0) { - throw new Error('Expected arguments as identifier names in ast'); - } - - const args = astInfo.ast.params.map((arg) => - snip((arg as { type: 'i'; name: string }).name, d.u32, /* origin */ 'runtime'), - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - args, - {}, - d.vec4f, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); - // ^ this part should be a i32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.i32); - - // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); - // ^ this part should be a vec4f - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'value', d.i32, 'runtime'); - const res2 = wgslGenerator._expression( - (astInfo.ast?.body[1][1] as tinyest.Const)[2] as tinyest.Expression, - ); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res2.dataType).toStrictEqual(d.vec4f); - - // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); - // ^ this part should be an atomic u32 - // ^ this part should be void - ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'function'); - const res3 = wgslGenerator._expression( - (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, - ); - const res4 = wgslGenerator._expression(astInfo.ast?.body[1][2] as tinyest.Expression); - ctx[$internal].itemStateStack.pop('blockScope'); - - expect(res3.dataType).toStrictEqual(d.atomic(d.u32)); - expect(res4.dataType).toStrictEqual(Void); - }); - }); - - it('creates correct code for for statements', () => { - const main = () => { + // Check for: const value = std.atomicLoad(testUsage.$.b.aa[idx]!.y); + // ^ this part should be a i32 + expectDataTypeOf(() => { 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[14,[12,"i",[5,"0"]],[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); + const idx = d.u32(0); + return std.atomicLoad(testUsage.$.b.aa[idx]!.y); + }).toStrictEqual(d.i32); - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); - - it('creates correct code for for statements with outside init', () => { - const main = () => { + // Check for: const vec = std.mix(d.vec4f(), testUsage.$.a, value); + // ^ this part should be a vec4f + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - for (; i < 10; i += 1) { - continue; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; + const value = std.atomicLoad(testUsage.$.b.aa[0]!.y); + return std.mix(d.vec4f(), testUsage.$.a, value); + }).toStrictEqual(d.vec4f); - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[14,null,[1,"i","<",[5,"10"]],[2,"i","+=",[5,"1"]],[0,[[16]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - for (; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); - - it('creates correct code for while statements', () => { - const main = () => { + // Check for: std.atomicStore(testUsage.$.b.aa[idx]!.x, vec.y); + // ^ this part should be an atomic u32 + expectDataTypeOf(() => { 'use gpu'; - let i = 0; - while (i < 10) { - i += 1; - } - }; - - const parsed = getMetaData(main)?.ast?.body as tinyest.Block; - expect(JSON.stringify(parsed)).toMatchInlineSnapshot( - `"[0,[[12,"i",[5,"0"]],[15,[1,"i","<",[5,"10"]],[0,[[2,"i","+=",[5,"1"]]]]]]]"`, - ); - - const gen = provideCtx(ctx, () => wgslGenerator.functionDefinition(parsed)); - - expect(gen).toMatchInlineSnapshot(` - "{ - var i = 0; - while ((i < 10i)) { - i += 1i; - } - }" - `); + const idx = d.u32(0); + return testUsage.$.b.aa[idx]!.x; + }).toStrictEqual(d.atomic(d.u32)); }); it('parses correctly "for ... of ..." statements', () => { @@ -1003,6 +789,11 @@ describe('wgslGenerator', () => { }); it('creates correct resources for lazy values and slots', () => { + expectDataTypeOf(() => { + 'use gpu'; + return lazyV4u.$; + }).toStrictEqual(d.vec4u); + const testFn = tgpu.fn([], d.vec4u)(() => lazyV4u.$); expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` @@ -1010,76 +801,14 @@ describe('wgslGenerator', () => { return vec4u(44, 88, 132, 176); }" `); - - 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,[[10,[7,"lazyV4u","$"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.vec4u, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return lazyV4u.$; - // ^ this should be a vec4u - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.vec4u); - }); }); it('creates correct resources for indexing into a lazy value', () => { - const testFn = tgpu.fn( - [d.u32], - d.f32, - )((idx) => { - return lazyV2f.$[idx] as number; - }); - - 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,[[10,[8,[7,"lazyV2f","$"],"idx"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [snip('idx', d.u32, /* origin */ 'runtime')], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: return lazyV2f.$[idx]; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + const idx = d.u32(0); + return lazyV2f.$[idx]; + }).toStrictEqual(d.f32); }); it('creates intermediate representation for array expression', () => { @@ -1105,44 +834,11 @@ describe('wgslGenerator', () => { }); 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( - (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); - }); + "fn testFn() -> u32 { + var arr = array(1u, 2u, 3u); + return arr[1i]; + }" + `); }); it('generates correct code for complex array expressions', () => { @@ -1164,39 +860,6 @@ 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( - (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); - }); }); it('does not autocast lhs of an assignment', () => { @@ -1247,38 +910,15 @@ describe('wgslGenerator', () => { }" `); - 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,"TestStruct",[[104,{"x":[5,"1"],"y":[5,"2"]}]]],[6,"TestStruct",[[104,{"x":[5,"3"],"y":[5,"4"]}]]]]]],[10,[7,[8,"arr",[5,"1"]],"y"]]]]"`, - ); - - const res = provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - // Check for: const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; - // ^ this should be an array - wgslGenerator.initGenerator(ctx); - return wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Const)[2] as tinyest.Expression, - ); + const arraySnippet = extractSnippetFromFn(() => { + 'use gpu'; + const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; + return arr; }); - 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); + expect(d.isWgslArray(arraySnippet.dataType)).toBe(true); + expect((arraySnippet.dataType as unknown as WgslArray).elementCount).toBe(2); + expect((arraySnippet.dataType as unknown as WgslArray).elementType).toBe(TestStruct); }); it('generates correct code for array expressions with lazy elements', () => { @@ -1296,37 +936,6 @@ describe('wgslGenerator', () => { return arr[1i].y; }" `); - - 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,[[7,"lazyV2f","$"],[6,[7,"std","mul"],[[7,"lazyV2f","$"],[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); }); it('allows for member access on values returned from function calls', () => { @@ -1364,34 +973,10 @@ describe('wgslGenerator', () => { }" `); - const astInfo = getMetaData(fnTwo[$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,[[10,[7,[7,[6,"fnOne",[]],"y"],"x"]]]]"`, - ); - - provideCtx(ctx, () => { - ctx[$internal].itemStateStack.pushFunctionScope( - 'normal', - [], - {}, - d.f32, - (astInfo.externals as () => Record)() ?? {}, - ); - - wgslGenerator.initGenerator(ctx); - // Check for: return fnOne().y.x; - // ^ this should be a f32 - const res = wgslGenerator._expression( - (astInfo.ast?.body[1][0] as tinyest.Return)[1] as tinyest.Expression, - ); - - expect(res.dataType).toStrictEqual(d.f32); - }); + expectDataTypeOf(() => { + 'use gpu'; + return fnOne().y.x; + }).toStrictEqual(d.f32); }); it('generates correct code for conditional with single statement', () => { @@ -1464,27 +1049,6 @@ describe('wgslGenerator', () => { `); }); - it('generates correct code for for loops with single statements', () => { - const main = () => { - 'use gpu'; - for (let i = 0; i < 10; i += 1) { - continue; - } - }; - - const gen = provideCtx(ctx, () => - wgslGenerator.functionDefinition(getMetaData(main)?.ast?.body as tinyest.Block), - ); - - expect(gen).toMatchInlineSnapshot(` - "{ - for (var i = 0; (i < 10i); i += 1i) { - continue; - } - }" - `); - }); - it('generates correct code for while loops with single statements', () => { const main = tgpu.fn([])(() => { let i = 0; @@ -1820,59 +1384,51 @@ describe('wgslGenerator', () => { it('block externals do not override identifiers', () => { const f = () => { 'use gpu'; - const y = 100; - const x = y; - return x; + const list = [1]; + for (const x of tgpu.unroll(list)) { + 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); - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() -> i32 { + var list = array(1); + // unrolled iteration #0 + { + const y = 100; + const x = y; + return x; + } + }" + `); }); it('block externals are injected correctly', () => { const f = () => { 'use gpu'; - for (const x of []) { + for (const x of tgpu.unroll([1])) { 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; - }" - `); - }); + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + // unrolled iteration #0 + { + const y = 1; + } + }" + `); }); 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) { + const list = [1]; + for (const elem of tgpu.unroll(list)) { { // We use the `elem` in a nested block result += elem; @@ -1880,24 +1436,18 @@ describe('wgslGenerator', () => { } }; - 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(` - "{ + expect(tgpu.resolve([f])).toMatchInlineSnapshot(` + "fn f() { + var result = 0i; + var list = array(1); + // unrolled iteration #0 + { { - result += 7i; + result += list[0u]; } - }" - `); - }); + } + }" + `); }); it('prunes comptime if/else', () => { diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 529e9f0cfa..9278a82a00 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -363,11 +363,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -410,11 +406,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, @@ -444,11 +436,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - @fragment fn fragmentFn() -> @location(0) vec4f { + "@fragment fn fragmentFn() -> @location(0) vec4f { var hmm = vec4f(1.25); return hmm; }" @@ -481,11 +469,7 @@ describe('TGSL tgpu.fn function', () => { }); expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` - "struct fragmentFn_Input { - @location(0) uv: vec2f, - } - - struct fragmentFn_Output { + "struct fragmentFn_Output { @builtin(sample_mask) sampleMask: u32, @builtin(frag_depth) fragDepth: f32, @location(0) out: vec4f, diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index 7576fedc31..f6dffc8bdd 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -17,10 +17,10 @@ class ExtractingGenerator extends WgslGenerator { this.#fnDepth = 0; } - public functionDefinition(body: tinyest.Block): string { + public functionDefinition(options: ShaderGenerator.FunctionDefinitionOptions): string { this.#fnDepth++; try { - return super.functionDefinition(body); + return super.functionDefinition(options); } finally { this.#fnDepth--; } From 8517cba5270b5b427eeb4e0362d840c2c5d0a67b Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 11 Apr 2026 13:31:41 +0200 Subject: [PATCH 29/34] refactor: Naming cleanup --- .../typegpu/src/core/buffer/bufferUsage.ts | 4 +- .../typegpu/src/core/constant/tgpuConstant.ts | 2 +- packages/typegpu/src/core/function/fnCore.ts | 27 +-- .../typegpu/src/core/resolve/namespace.ts | 55 +----- .../typegpu/src/core/resolve/resolveData.ts | 5 +- packages/typegpu/src/core/sampler/sampler.ts | 4 +- .../src/core/texture/externalTexture.ts | 2 +- packages/typegpu/src/core/texture/texture.ts | 4 +- .../typegpu/src/core/variable/tgpuVariable.ts | 2 +- packages/typegpu/src/data/autoStruct.ts | 2 +- packages/typegpu/src/data/struct.ts | 2 +- .../src/{nameRegistry.ts => nameUtils.ts} | 166 +----------------- packages/typegpu/src/resolutionCtx.ts | 135 ++++++++++---- packages/typegpu/src/tgsl/wgslGenerator.ts | 31 ++-- packages/typegpu/src/types.ts | 25 ++- packages/typegpu/tests/namespace.test.ts | 30 +--- packages/typegpu/tests/resolve.test.ts | 4 +- 17 files changed, 187 insertions(+), 313 deletions(-) rename packages/typegpu/src/{nameRegistry.ts => nameUtils.ts} (58%) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 24f28dcbca..d8a1028e78 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -118,7 +118,7 @@ class TgpuFixedBufferImpl implements TgpuConst, } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const resolvedDataType = ctx.resolve(this.dataType).value; const resolvedValue = ctx.resolve(this.#value, this.dataType).value; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 77d676c0d5..513000f199 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -3,6 +3,7 @@ import { undecorate } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTypes.ts'; import { MissingLinksError } from '../../errors.ts'; +import { isValidIdentifier } from '../../nameUtils.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; @@ -74,21 +75,25 @@ export function createFnCore( applyExternals(externalMap, externals); } - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); if (typeof implementation === 'string') { if (!returnType) { throw new Error('Explicit return type is required for string implementation'); } - const validArgNames = entryInput - ? Object.fromEntries( - entryInput.positionalArgs.map((a) => [a.schemaKey, ctx.makeNameValid(a.schemaKey)]), - ) - : undefined; + if (entryInput) { + for (const arg of entryInput.positionalArgs) { + if (!isValidIdentifier(arg.schemaKey)) { + throw new Error(`Invalid argument name: ${arg.schemaKey}`); + } + } - if (validArgNames && Object.keys(validArgNames).length > 0) { - applyExternals(externalMap, { in: validArgNames }); + applyExternals(externalMap, { + in: Object.fromEntries( + entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey]), + ), + }); } const replacedImpl = replaceExternalsInWgsl(ctx, externalMap, implementation); @@ -96,15 +101,15 @@ export function createFnCore( let header = ''; let body = ''; - if (functionType !== 'normal' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { parts.push(`in: ${ctx.resolve(dataSchema).value}`); } for (const a of positionalArgs) { - const argName = validArgNames[a.schemaKey] ?? ''; - if (argName !== '' && isArgUsedInBody(argName, replacedImpl)) { + const argName = a.schemaKey; + if (isArgUsedInBody(argName, replacedImpl)) { parts.push(`${getAttributesString(a.type)}${argName}: ${ctx.resolve(a.type).value}`); } } diff --git a/packages/typegpu/src/core/resolve/namespace.ts b/packages/typegpu/src/core/resolve/namespace.ts index f112cd4fb0..90fcba08f7 100644 --- a/packages/typegpu/src/core/resolve/namespace.ts +++ b/packages/typegpu/src/core/resolve/namespace.ts @@ -1,6 +1,5 @@ import type { ResolvedSnippet } from '../../data/snippet.ts'; -import { type NameRegistry, RandomNameRegistry, StrictNameRegistry } from '../../nameRegistry.ts'; -import { getName } from '../../shared/meta.ts'; +import { bannedTokens, builtins } from '../../nameUtils.ts'; import { $internal } from '../../shared/symbols.ts'; import { ShelllessRepository } from '../../tgsl/shellless.ts'; import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; @@ -8,8 +7,9 @@ import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; type SlotToValueMap = Map, unknown>; export interface NamespaceInternal { - readonly nameRegistry: NameRegistry; + readonly takenGlobalIdentifiers: Set; readonly shelllessRepo: ShelllessRepository; + readonly strategy: 'random' | 'strict'; memoizedResolves: WeakMap< // WeakMap because if the item does not exist anymore, @@ -24,73 +24,32 @@ export interface NamespaceInternal { TgpuLazy, { slotToValueMap: SlotToValueMap; result: unknown }[] >; - - listeners: { - [K in keyof NamespaceEventMap]: Set<(event: NamespaceEventMap[K]) => void>; - }; } -type NamespaceEventMap = { - name: { target: object; name: string }; -}; - -type DetachListener = () => void; - export interface Namespace { readonly [$internal]: NamespaceInternal; - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener; } class NamespaceImpl implements Namespace { readonly [$internal]: NamespaceInternal; - constructor(nameRegistry: NameRegistry) { + constructor(strategy: 'random' | 'strict') { this[$internal] = { - nameRegistry, + strategy, + takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins]), shelllessRepo: new ShelllessRepository(), memoizedResolves: new WeakMap(), memoizedLazy: new WeakMap(), - listeners: { - name: new Set(), - }, }; } - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener { - if (event === 'name') { - const listeners = this[$internal].listeners.name; - listeners.add(listener); - - return () => listeners.delete(listener); - } - - throw new Error(`Unsupported event: ${event}`); - } } export interface NamespaceOptions { names?: 'random' | 'strict' | undefined; } -export function getUniqueName(namespace: NamespaceInternal, resource: object): string { - const name = namespace.nameRegistry.makeUnique(getName(resource), true); - for (const listener of namespace.listeners.name) { - listener({ target: resource, name }); - } - return name; -} - export function namespace(options?: NamespaceOptions): Namespace { const { names = 'strict' } = options ?? {}; - return new NamespaceImpl( - names === 'strict' ? new StrictNameRegistry() : new RandomNameRegistry(), - ); + return new NamespaceImpl(names); } diff --git a/packages/typegpu/src/core/resolve/resolveData.ts b/packages/typegpu/src/core/resolve/resolveData.ts index 19e5da347d..bc5320c80d 100644 --- a/packages/typegpu/src/core/resolve/resolveData.ts +++ b/packages/typegpu/src/core/resolve/resolveData.ts @@ -38,6 +38,7 @@ import type { WgslArray, WgslStruct, } from '../../data/wgslTypes.ts'; +import { getName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { assertExhaustive } from '../../shared/utilityTypes.ts'; import type { ResolutionCtx } from '../../types.ts'; @@ -127,7 +128,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) { if (struct[$internal].isAbstruct) { throw new Error('Cannot resolve abstract struct types to WGSL.'); } - const id = ctx.getUniqueName(struct); + const id = ctx.makeUniqueIdentifier(getName(struct), 'global'); ctx.addDeclaration(`\ struct ${id} { @@ -155,7 +156,7 @@ ${Object.entries(struct.propTypes) * ``` */ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) { - const id = ctx.getUniqueName(unstruct); + const id = ctx.makeUniqueIdentifier(getName(unstruct), 'global'); ctx.addDeclaration(`\ struct ${id} { diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index 4daaf1c917..adf10ee8ce 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -99,7 +99,7 @@ export class TgpuLaidOutSamplerImpl< } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( @@ -186,7 +186,7 @@ class TgpuFixedSamplerImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( this.schema.type === 'sampler_comparison' diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index c9d457c48b..78ae4cd582 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl implements TgpuExternalTexture, SelfResolva } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 27923995d6..fbe7295d83 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -600,7 +600,7 @@ class TgpuFixedTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( isWgslStorageTexture(this.schema) ? { @@ -642,7 +642,7 @@ export class TgpuLaidOutTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const pre = `var<${this.#scope}> ${id}: ${ctx.resolve(this.#dataType).value}`; if (this.#initialValue) { diff --git a/packages/typegpu/src/data/autoStruct.ts b/packages/typegpu/src/data/autoStruct.ts index 647d13b79c..c227b565fe 100644 --- a/packages/typegpu/src/data/autoStruct.ts +++ b/packages/typegpu/src/data/autoStruct.ts @@ -1,5 +1,5 @@ import { createIoSchema } from '../core/function/ioSchema.ts'; -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal, $repr, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; diff --git a/packages/typegpu/src/data/struct.ts b/packages/typegpu/src/data/struct.ts index c044da5f8f..c2d024acc3 100644 --- a/packages/typegpu/src/data/struct.ts +++ b/packages/typegpu/src/data/struct.ts @@ -1,4 +1,4 @@ -import { isValidProp } from '../nameRegistry.ts'; +import { isValidProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { schemaCallWrapper } from './schemaCallWrapper.ts'; diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameUtils.ts similarity index 58% rename from packages/typegpu/src/nameRegistry.ts rename to packages/typegpu/src/nameUtils.ts index aa2cd1ada0..f66bfbb79a 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameUtils.ts @@ -1,6 +1,4 @@ -import { invariant } from './errors.ts'; - -const bannedTokens = new Set([ +export const bannedTokens = new Set([ // keywords 'alias', 'break', @@ -181,7 +179,7 @@ const bannedTokens = new Set([ 'storage', ]); -const builtins = new Set([ +export const builtins = new Set([ // constructors 'array', 'bool', @@ -361,37 +359,8 @@ const builtins = new Set([ 'quadSwapY', ]); -export interface NameRegistry { - /** - * Creates a valid WGSL identifier, each guaranteed to be unique - * in the lifetime of a single resolution process - * (excluding non-global identifiers from popped scopes). - * Should append "_" to primer, followed by some id. - * @param primer Used in the generation process, makes the identifier more recognizable. - * @param global Whether the name should be registered in the global scope (true), or in the current function scope (false) - */ - makeUnique(primer: string | undefined, global: boolean): string; - - /** - * Creates a valid WGSL identifier. - * Renames identifiers that are WGSL reserved words. - * @param primer Used in the generation process. - * - * @example - * makeValid("notAKeyword"); // "notAKeyword" - * makeValid("struct"); // makeUnique("struct") - * makeValid("struct_1"); // makeUnique("struct_1") (to avoid potential name collisions) - * makeValid("_"); // ERROR (too difficult to make valid to care) - */ - makeValid(primer: string): string; - - pushFunctionScope(): void; - popFunctionScope(): void; - pushBlockScope(): void; - popBlockScope(): void; -} - -function sanitizePrimer(primer: string | undefined) { +/*#__NO_SIDE_EFFECTS__*/ +export function sanitizePrimer(primer: string | undefined) { if (primer) { // sanitizing return primer @@ -411,7 +380,8 @@ function sanitizePrimer(primer: string | undefined) { * isValidIdentifier("_"); // ERROR * isValidIdentifier("my variable"); // ERROR */ -function isValidIdentifier(ident: string): boolean { +/*#__NO_SIDE_EFFECTS__*/ +export function isValidIdentifier(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( `Invalid identifier '${ident}'. Choose an identifier without whitespaces or leading underscores.`, @@ -424,6 +394,7 @@ function isValidIdentifier(ident: string): boolean { /** * Same as `isValidIdentifier`, except does not check for builtin clashes. */ +/*#__NO_SIDE_EFFECTS__*/ export function isValidProp(ident: string): boolean { if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { throw new Error( @@ -433,126 +404,3 @@ 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; - - readonly #usedNames: Set; - readonly #scopeStack: ScopeLayer[]; - - constructor() { - this.#usedNames = new Set([...bannedTokens, ...builtins]); - this.#scopeStack = []; - } - - get #usedBlockScopeNames(): Set | undefined { - return (this.#scopeStack[this.#scopeStack.length - 1] as BlockScopeLayer | undefined) - ?.usedBlockScopeNames; - } - - makeUnique(primer: string | undefined, global: boolean): string { - const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer); - - if (global) { - this.#usedNames.add(name); - } else { - 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.#isUsedInBlocksBefore(primer) - ) { - this.#usedBlockScopeNames?.add(primer); - return primer; - } - return this.makeUnique(primer, false); - } - - isUsed(name: string): boolean { - return this.#usedNames.has(name) || this.#isUsedInBlocksBefore(name); - } - - pushFunctionScope(): void { - this.#scopeStack.push({ type: 'functionScope' }); - this.#scopeStack.push({ - type: 'blockScope', - usedBlockScopeNames: new Set(), - }); - } - - popFunctionScope(): void { - const functionScopeIndex = this.#scopeStack.findLastIndex( - (scope) => scope.type === 'functionScope', - ); - - if (functionScopeIndex === -1) { - throw new Error('Tried to pop function scope when no scope was present.'); - } - - 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 { - let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name)) { - name = `${base}_${this.#lastUniqueId++}`; - } - return name; - } -} - -export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string): string { - let index = 0; - let name = base; - while (this.isUsed(name)) { - index++; - name = `${base}_${index}`; - } - return name; - } -} diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5eedec298d..2ed8dde06a 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -1,5 +1,5 @@ import { isTgpuFn } from './core/function/tgpuFn.ts'; -import { getUniqueName, type Namespace, type NamespaceInternal } from './core/resolve/namespace.ts'; +import type { Namespace, NamespaceInternal } from './core/resolve/namespace.ts'; import { stitch } from './core/resolve/stitch.ts'; import { ConfigurableImpl } from './core/root/configurableImpl.ts'; import type { Configurable, ExperimentalTgpuRoot } from './core/root/rootTypes.ts'; @@ -37,6 +37,7 @@ import { coerceToSnippet, concretize, numericLiteralToSnippet } from './tgsl/gen import type { ShaderGenerator } from './tgsl/shaderGenerator.ts'; import wgslGenerator from './tgsl/wgslGenerator.ts'; import type { + BlockScopeLayer, ExecMode, ExecState, FnToWgslOptions, @@ -59,6 +60,7 @@ import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; +import { isValidIdentifier, sanitizePrimer } from './nameUtils.ts'; /** * Inserted into bind group entry definitions that belong @@ -99,6 +101,10 @@ class ItemStateStackImpl implements ItemStateStack { return this._stack.findLast((e) => e.type === 'functionScope'); } + get topBlockScope(): BlockScopeLayer | undefined { + return this._stack.findLast((e) => e.type === 'blockScope'); + } + pushItem() { this._itemDepth++; this._stack.push({ @@ -136,6 +142,7 @@ class ItemStateStackImpl implements ItemStateStack { pushBlockScope() { this._stack.push({ type: 'blockScope', + takenLocalIdentifiers: new Set(), declarations: new Map(), externals: new Map(), }); @@ -213,6 +220,26 @@ class ItemStateStackImpl implements ItemStateStack { return undefined; } + isIdentifierTakenLocally(id: string): boolean { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + + if (layer?.type === 'functionScope') { + // Since functions cannot access resources from the calling scope, we + // return early here. + return false; + } + + if (layer?.type === 'blockScope') { + if (layer.takenLocalIdentifiers.has(id)) { + return true; + } + } + } + + return false; + } + defineBlockVariable(id: string, snippet: Snippet): void { if (snippet.dataType === UnknownData) { throw Error(`Tried to define variable '${id}' of unknown type`); @@ -375,6 +402,11 @@ export class ResolutionCtxImpl implements ResolutionCtx { public readonly enableExtensions: WgslExtension[] | undefined; public expectedType: BaseData | undefined; + /** + * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy. + */ + #lastUniqueId = 0; + constructor(opts: ResolutionCtxImplOptions) { this.enableExtensions = opts.enableExtensions; this.gen = opts.shaderGenerator ?? wgslGenerator; @@ -382,12 +414,42 @@ export class ResolutionCtxImpl implements ResolutionCtx { this.#namespaceInternal = opts.namespace[$internal]; } - getUniqueName(resource: object): string { - return getUniqueName(this.#namespaceInternal, resource); + isIdentifierTaken(name: string): boolean { + return ( + this.#namespaceInternal.takenGlobalIdentifiers.has(name) || + this._itemStateStack.isIdentifierTakenLocally(name) + ); } - makeNameValid(name: string): string { - return this.#namespaceInternal.nameRegistry.makeValid(name); + makeUniqueIdentifier(primer: string = 'item', scope: 'global' | 'block'): string { + if (scope === 'block' && isValidIdentifier(primer) && !this.isIdentifierTaken(primer)) { + // Preserving local definitions as they are, provided they are valid and not already taken. + this.reserveIdentifier(primer, 'block'); + return primer; + } + + const base = sanitizePrimer(primer); + let index = 0; + const random = this.#namespaceInternal.strategy === 'random'; + let name = random ? `${base}_${this.#lastUniqueId++}` : base; + while (this.isIdentifierTaken(name)) { + name = random ? `${base}_${this.#lastUniqueId++}` : `${base}_${++index}`; + } + + this.reserveIdentifier(name, scope); + return name; + } + + reserveIdentifier(name: string, scope: 'global' | 'block'): void { + if (scope === 'block') { + const blockScope = this._itemStateStack.topBlockScope; + if (blockScope) { + blockScope.takenLocalIdentifiers.add(name); + return; + } + // Fall through if no block scope is present, treating as global. + } + this.#namespaceInternal.takenGlobalIdentifiers.add(name); } get pre(): string { @@ -441,12 +503,10 @@ export class ResolutionCtxImpl implements ResolutionCtx { } pushBlockScope() { - this.#namespaceInternal.nameRegistry.pushBlockScope(); this._itemStateStack.pushBlockScope(); } popBlockScope() { - this.#namespaceInternal.nameRegistry.popBlockScope(); this._itemStateStack.pop('blockScope'); } @@ -467,19 +527,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { } fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { - let fnScopePushed = false; - try { - this.#namespaceInternal.nameRegistry.pushFunctionScope(); + const scope = this._itemStateStack.pushFunctionScope( + options.functionType, + {}, + options.returnType, + options.externalMap, + ); + // Pushing a block scope as well, so that any identifiers declared at this point will be scoped to the function body. + this._itemStateStack.pushBlockScope(); + const args: FunctionArgument[] = []; - const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; const structArg = dataSchema - ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + ? createArgument(this.makeUniqueIdentifier('_arg_0', 'block'), dataSchema) : undefined; if (structArg) { @@ -491,31 +556,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const arg = createArgument(this.makeNameValid(alias), argInfo.type); + const arg = createArgument(this.makeUniqueIdentifier(alias, 'block'), argInfo.type); args.push(arg); - argAccess[alias] = arg.access; + scope.argAccess[alias] = arg.access; } else if (structArg) { - argAccess[alias] = createArgumentPropAccess(structArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } const router = new EntryInputRouter(structArg?.access, proxyEntries); - argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); + scope.argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); - argAccess[argName] = arg.access; + scope.argAccess[argName] = arg.access; } } } else { @@ -538,16 +603,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + const arg = createArgument( + this.makeUniqueIdentifier(astParam.name, 'block'), + argType, + origin, + ); args.push(arg); - argAccess[astParam.name] = arg.access; + scope.argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + const objArg = createArgument( + this.makeUniqueIdentifier(`_arg_${i}`, 'block'), + argType, + origin, + ); args.push(objArg); for (const { name, alias } of astParam.props) { - argAccess[alias] = createArgumentPropAccess(objArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(objArg.access, name); } break; } @@ -557,7 +630,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { // have any properties anyway. if (!(argType instanceof AutoStruct)) { args.push({ - name: this.makeNameValid(`_arg_${i}`), + name: this.makeUniqueIdentifier(`_arg_${i}`, 'block'), access: () => { throw new Error( `Unreachable: Accessing an argument that wasn't named in the function signature`, @@ -572,14 +645,6 @@ export class ResolutionCtxImpl implements ResolutionCtx { } } - const scope = this._itemStateStack.pushFunctionScope( - options.functionType, - argAccess, - options.returnType, - options.externalMap, - ); - fnScopePushed = true; - let returnType: BaseData | undefined; const code = this.gen.functionDefinition({ @@ -642,10 +707,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { returnType, }; } finally { - if (fnScopePushed) { - this._itemStateStack.pop('functionScope'); - } - this.#namespaceInternal.nameRegistry.popFunctionScope(); + this._itemStateStack.pop('blockScope'); + this._itemStateStack.pop('functionScope'); } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index c35bf9128f..50f2305a6d 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -241,8 +241,12 @@ ${this.ctx.pre}}`; } } + public _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string { + return `${this.ctx.pre}${this._block(block, externalMap)}`; + } + public refVariable(id: string, dataType: wgsl.StorableData): string { - const varName = this.ctx.makeNameValid(id); + const varName = this.ctx.makeUniqueIdentifier(id, 'block'); const ptrType = ptrFn(dataType); const snippet = snip( new RefOperator(snip(varName, dataType, 'function'), ptrType), @@ -280,7 +284,11 @@ ${this.ctx.pre}}`; varOrigin = 'runtime'; } - const snippet = snip(this.ctx.makeNameValid(id), dataType, /* origin */ varOrigin); + const snippet = snip( + this.ctx.makeUniqueIdentifier(id, 'block'), + dataType, + /* origin */ varOrigin, + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -1038,7 +1046,7 @@ Try 'return ${typeStr}(${str});' instead. return this._statement(node); } // simplify 'if (true) {A} else {B}' to '{A}' - return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`; + return this._blockStatement(blockifySingleStatement(node)); } const consequent = this._block(blockifySingleStatement(consNode)); @@ -1171,7 +1179,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.block) { - return `${this.ctx.pre}${this._block(statement)}`; + return this._blockStatement(statement); } if (statement[0] === NODE.for) { @@ -1267,12 +1275,9 @@ ${this.ctx.pre}else ${alternate}`; const blocks = elements.map( (e, i) => - `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block( - blockified, - { - [originalLoopVarName]: e, - }, - )}`, + `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { + [originalLoopVarName]: e, + })}`, ); return blocks.join('\n'); @@ -1280,7 +1285,7 @@ ${this.ctx.pre}else ${alternate}`; this.#unrolling = false; - const index = this.ctx.makeNameValid('i'); + const index = this.ctx.makeUniqueIdentifier('i', 'block'); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`; @@ -1293,7 +1298,7 @@ ${this.ctx.pre}else ${alternate}`; } else { this.ctx.indent(); ctxIndent = true; - const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, snip(index, u32, 'runtime'), @@ -1307,7 +1312,7 @@ ${this.ctx.pre}else ${alternate}`; false, )};`; - bodyStr = `{\n${loopVarDeclStr}\n${this.ctx.pre}${this._block(blockified, { + bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin), })}\n`; this.ctx.dedent(); diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 75f9e71697..d2b7892faf 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -131,6 +131,7 @@ export type SlotBindingLayer = { export type BlockScopeLayer = { type: 'blockScope'; + takenLocalIdentifiers: Set; declarations: Map; externals: Map; }; @@ -140,6 +141,7 @@ export type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | Blo export interface ItemStateStack { readonly itemDepth: number; readonly topItem: ItemLayer; + readonly topBlockScope: BlockScopeLayer | undefined; readonly topFunctionScope: FunctionScopeLayer | undefined; pushItem(): void; @@ -330,8 +332,27 @@ export interface ResolutionCtx { */ withRenamed(item: object, name: string | undefined, callback: () => T): T; - getUniqueName(resource: object): string; - makeNameValid(name: string): string; + /** + * @param primer The basis for the unique identifier. Depending on the strategy, or + * the names already taken, this may be modified to ensure uniqueness. + * @param scope The scope in which to generate the identifier. 'global' means + * the identifier is meant to be unique across the entire program, while + * 'block' means it cannot shadow any existing identifiers visible from + * within the current block. After the block is popped, any identifiers + * defined within it are no longer visible. + * @returns an identifier that is unique within the given scope + */ + makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string; + + isIdentifierTaken(name: string): boolean; + + /** + * Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier} + * within the given scope. + * @param name The name to reserve + * @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter. + */ + reserveIdentifier(name: string, scope: 'global' | 'block'): void; } /** diff --git a/packages/typegpu/tests/namespace.test.ts b/packages/typegpu/tests/namespace.test.ts index ee6fe33099..7ac6407b43 100644 --- a/packages/typegpu/tests/namespace.test.ts +++ b/packages/typegpu/tests/namespace.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, vi } from 'vitest'; +import { describe, expect } from 'vitest'; import tgpu, { d } from '../src/index.js'; import { it } from 'typegpu-testing-utility'; @@ -71,34 +71,6 @@ describe('tgpu.namespace', () => { `); }); - it('fires "name" event', () => { - const Boid = d.struct({ - pos: d.vec3f, - }); - - const names = tgpu['~unstable'].namespace(); - - const listener = vi.fn((event) => {}); - names.on('name', listener); - - const code = tgpu.resolve([Boid], { names }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledWith({ name: 'Boid', target: Boid }); - - expect(code).toMatchInlineSnapshot(` - "struct Boid { - pos: vec3f, - }" - `); - - const code2 = tgpu.resolve([Boid], { names }); - - // No more events - expect(listener).toHaveBeenCalledTimes(1); - expect(code2).toMatchInlineSnapshot(`""`); - }); - it('handles name collision', () => { let code1: string, code2: string; const names = tgpu['~unstable'].namespace(); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 254730def2..83e8e1e787 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from 'vitest'; import tgpu, { d } from '../src/index.js'; -import { setName } from '../src/shared/meta.ts'; +import { getName, setName } from '../src/shared/meta.ts'; import { $gpuValueOf, $internal, $ownSnippet, $resolve } from '../src/shared/symbols.ts'; import type { ResolutionCtx } from '../src/types.ts'; import { it } from 'typegpu-testing-utility'; @@ -55,7 +55,7 @@ describe('tgpu resolve', () => { } as unknown as number, [$resolve](ctx: ResolutionCtx) { - const name = ctx.getUniqueName(this); + const name = ctx.makeUniqueIdentifier(getName(this), 'global'); ctx.addDeclaration(`@group(0) @binding(0) var ${name}: f32;`); return snip(name, d.f32, /* origin */ 'runtime'); }, From 8beed4f9c9da55047ab36ce8f9079d7ee7297666 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 11 Apr 2026 13:31:41 +0200 Subject: [PATCH 30/34] refactor: Naming cleanup --- .../typegpu/src/core/buffer/bufferUsage.ts | 4 +- .../typegpu/src/core/constant/tgpuConstant.ts | 2 +- packages/typegpu/src/core/function/fnCore.ts | 30 ++- .../typegpu/src/core/resolve/namespace.ts | 55 +--- .../typegpu/src/core/resolve/resolveData.ts | 5 +- packages/typegpu/src/core/sampler/sampler.ts | 4 +- .../src/core/texture/externalTexture.ts | 2 +- packages/typegpu/src/core/texture/texture.ts | 4 +- .../typegpu/src/core/variable/tgpuVariable.ts | 2 +- packages/typegpu/src/data/autoStruct.ts | 7 +- packages/typegpu/src/data/struct.ts | 7 +- .../src/{nameRegistry.ts => nameUtils.ts} | 254 ++++++------------ packages/typegpu/src/resolutionCtx.ts | 139 +++++++--- packages/typegpu/src/tgsl/wgslGenerator.ts | 31 ++- packages/typegpu/src/types.ts | 25 +- packages/typegpu/tests/namespace.test.ts | 30 +-- packages/typegpu/tests/renderPipeline.test.ts | 4 +- packages/typegpu/tests/resolve.test.ts | 4 +- packages/typegpu/tests/struct.test.ts | 4 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 37 +-- 20 files changed, 296 insertions(+), 354 deletions(-) rename packages/typegpu/src/{nameRegistry.ts => nameUtils.ts} (51%) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 24f28dcbca..d8a1028e78 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -118,7 +118,7 @@ class TgpuFixedBufferImpl implements TgpuConst, } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const resolvedDataType = ctx.resolve(this.dataType).value; const resolvedValue = ctx.resolve(this.#value, this.dataType).value; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 77d676c0d5..78f0f74d20 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -3,6 +3,7 @@ import { undecorate } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { type BaseData, isWgslData, isWgslStruct, Void } from '../../data/wgslTypes.ts'; import { MissingLinksError } from '../../errors.ts'; +import { validateIdentifier } from '../../nameUtils.ts'; import { getMetaData, getName } from '../../shared/meta.ts'; import { $getNameForward } from '../../shared/symbols.ts'; import type { ResolutionCtx, TgpuShaderStage } from '../../types.ts'; @@ -74,21 +75,28 @@ export function createFnCore( applyExternals(externalMap, externals); } - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); if (typeof implementation === 'string') { if (!returnType) { throw new Error('Explicit return type is required for string implementation'); } - const validArgNames = entryInput - ? Object.fromEntries( - entryInput.positionalArgs.map((a) => [a.schemaKey, ctx.makeNameValid(a.schemaKey)]), - ) - : undefined; + if (entryInput) { + for (const arg of entryInput.positionalArgs) { + const result = validateIdentifier(arg.schemaKey); + if (!result.success) { + throw new Error( + `Invalid argument name "${arg.schemaKey}"${result.error ? `: ${result.error}` : ''}`, + ); + } + } - if (validArgNames && Object.keys(validArgNames).length > 0) { - applyExternals(externalMap, { in: validArgNames }); + applyExternals(externalMap, { + in: Object.fromEntries( + entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey]), + ), + }); } const replacedImpl = replaceExternalsInWgsl(ctx, externalMap, implementation); @@ -96,15 +104,15 @@ export function createFnCore( let header = ''; let body = ''; - if (functionType !== 'normal' && entryInput && validArgNames) { + if (functionType !== 'normal' && entryInput) { const { dataSchema, positionalArgs } = entryInput; const parts: string[] = []; if (dataSchema && isArgUsedInBody('in', replacedImpl)) { parts.push(`in: ${ctx.resolve(dataSchema).value}`); } for (const a of positionalArgs) { - const argName = validArgNames[a.schemaKey] ?? ''; - if (argName !== '' && isArgUsedInBody(argName, replacedImpl)) { + const argName = a.schemaKey; + if (isArgUsedInBody(argName, replacedImpl)) { parts.push(`${getAttributesString(a.type)}${argName}: ${ctx.resolve(a.type).value}`); } } diff --git a/packages/typegpu/src/core/resolve/namespace.ts b/packages/typegpu/src/core/resolve/namespace.ts index f112cd4fb0..90fcba08f7 100644 --- a/packages/typegpu/src/core/resolve/namespace.ts +++ b/packages/typegpu/src/core/resolve/namespace.ts @@ -1,6 +1,5 @@ import type { ResolvedSnippet } from '../../data/snippet.ts'; -import { type NameRegistry, RandomNameRegistry, StrictNameRegistry } from '../../nameRegistry.ts'; -import { getName } from '../../shared/meta.ts'; +import { bannedTokens, builtins } from '../../nameUtils.ts'; import { $internal } from '../../shared/symbols.ts'; import { ShelllessRepository } from '../../tgsl/shellless.ts'; import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; @@ -8,8 +7,9 @@ import type { TgpuLazy, TgpuSlot } from '../slot/slotTypes.ts'; type SlotToValueMap = Map, unknown>; export interface NamespaceInternal { - readonly nameRegistry: NameRegistry; + readonly takenGlobalIdentifiers: Set; readonly shelllessRepo: ShelllessRepository; + readonly strategy: 'random' | 'strict'; memoizedResolves: WeakMap< // WeakMap because if the item does not exist anymore, @@ -24,73 +24,32 @@ export interface NamespaceInternal { TgpuLazy, { slotToValueMap: SlotToValueMap; result: unknown }[] >; - - listeners: { - [K in keyof NamespaceEventMap]: Set<(event: NamespaceEventMap[K]) => void>; - }; } -type NamespaceEventMap = { - name: { target: object; name: string }; -}; - -type DetachListener = () => void; - export interface Namespace { readonly [$internal]: NamespaceInternal; - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener; } class NamespaceImpl implements Namespace { readonly [$internal]: NamespaceInternal; - constructor(nameRegistry: NameRegistry) { + constructor(strategy: 'random' | 'strict') { this[$internal] = { - nameRegistry, + strategy, + takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins]), shelllessRepo: new ShelllessRepository(), memoizedResolves: new WeakMap(), memoizedLazy: new WeakMap(), - listeners: { - name: new Set(), - }, }; } - - on( - event: TEvent, - listener: (event: NamespaceEventMap[TEvent]) => void, - ): DetachListener { - if (event === 'name') { - const listeners = this[$internal].listeners.name; - listeners.add(listener); - - return () => listeners.delete(listener); - } - - throw new Error(`Unsupported event: ${event}`); - } } export interface NamespaceOptions { names?: 'random' | 'strict' | undefined; } -export function getUniqueName(namespace: NamespaceInternal, resource: object): string { - const name = namespace.nameRegistry.makeUnique(getName(resource), true); - for (const listener of namespace.listeners.name) { - listener({ target: resource, name }); - } - return name; -} - export function namespace(options?: NamespaceOptions): Namespace { const { names = 'strict' } = options ?? {}; - return new NamespaceImpl( - names === 'strict' ? new StrictNameRegistry() : new RandomNameRegistry(), - ); + return new NamespaceImpl(names); } diff --git a/packages/typegpu/src/core/resolve/resolveData.ts b/packages/typegpu/src/core/resolve/resolveData.ts index 19e5da347d..bc5320c80d 100644 --- a/packages/typegpu/src/core/resolve/resolveData.ts +++ b/packages/typegpu/src/core/resolve/resolveData.ts @@ -38,6 +38,7 @@ import type { WgslArray, WgslStruct, } from '../../data/wgslTypes.ts'; +import { getName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { assertExhaustive } from '../../shared/utilityTypes.ts'; import type { ResolutionCtx } from '../../types.ts'; @@ -127,7 +128,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) { if (struct[$internal].isAbstruct) { throw new Error('Cannot resolve abstract struct types to WGSL.'); } - const id = ctx.getUniqueName(struct); + const id = ctx.makeUniqueIdentifier(getName(struct), 'global'); ctx.addDeclaration(`\ struct ${id} { @@ -155,7 +156,7 @@ ${Object.entries(struct.propTypes) * ``` */ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) { - const id = ctx.getUniqueName(unstruct); + const id = ctx.makeUniqueIdentifier(getName(unstruct), 'global'); ctx.addDeclaration(`\ struct ${id} { diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index 4daaf1c917..adf10ee8ce 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -99,7 +99,7 @@ export class TgpuLaidOutSamplerImpl< } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( @@ -186,7 +186,7 @@ class TgpuFixedSamplerImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( this.schema.type === 'sampler_comparison' diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index c9d457c48b..78ae4cd582 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl implements TgpuExternalTexture, SelfResolva } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const group = ctx.allocateLayoutEntry(this.#membership.layout); ctx.addDeclaration( diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 27923995d6..fbe7295d83 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -600,7 +600,7 @@ class TgpuFixedTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const { group, binding } = ctx.allocateFixedEntry( isWgslStorageTexture(this.schema) ? { @@ -642,7 +642,7 @@ export class TgpuLaidOutTextureViewImpl } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - const id = ctx.getUniqueName(this); + const id = ctx.makeUniqueIdentifier(getName(this), 'global'); const pre = `var<${this.#scope}> ${id}: ${ctx.resolve(this.#dataType).value}`; if (this.#initialValue) { diff --git a/packages/typegpu/src/data/autoStruct.ts b/packages/typegpu/src/data/autoStruct.ts index 647d13b79c..4fc745cccb 100644 --- a/packages/typegpu/src/data/autoStruct.ts +++ b/packages/typegpu/src/data/autoStruct.ts @@ -1,5 +1,5 @@ import { createIoSchema } from '../core/function/ioSchema.ts'; -import { isValidProp } from '../nameRegistry.ts'; +import { validateProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal, $repr, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; @@ -75,8 +75,9 @@ export class AutoStruct implements BaseData, SelfResolvable { `Property name '${wgslKey}' causes naming clashes. Choose a different name.`, ); } - if (!isValidProp(wgslKey)) { - throw new Error(`Property key '${key}' is a reserved WGSL word. Choose a different name.`); + const result = validateProp(wgslKey); + if (!result.success) { + throw new Error(`Invalid property key '${key}'${result.error ? `: ${result.error}` : ''}`); } this.#usedWgslKeys.add(wgslKey); diff --git a/packages/typegpu/src/data/struct.ts b/packages/typegpu/src/data/struct.ts index c044da5f8f..24b400f2d4 100644 --- a/packages/typegpu/src/data/struct.ts +++ b/packages/typegpu/src/data/struct.ts @@ -1,4 +1,4 @@ -import { isValidProp } from '../nameRegistry.ts'; +import { validateProp } from '../nameUtils.ts'; import { getName, setName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { schemaCallWrapper } from './schemaCallWrapper.ts'; @@ -40,8 +40,9 @@ export function INTERNAL_createStruct>( isAbstruct: boolean, ): WgslStruct { Object.keys(props).forEach((key) => { - if (!isValidProp(key)) { - throw new Error(`Property key '${key}' is a reserved WGSL word. Choose a different name.`); + const result = validateProp(key); + if (!result.success) { + throw new Error(`Invalid property key '${key}'${result.error ? `: ${result.error}` : ''}`); } }); diff --git a/packages/typegpu/src/nameRegistry.ts b/packages/typegpu/src/nameUtils.ts similarity index 51% rename from packages/typegpu/src/nameRegistry.ts rename to packages/typegpu/src/nameUtils.ts index aa2cd1ada0..4ec4ea7b46 100644 --- a/packages/typegpu/src/nameRegistry.ts +++ b/packages/typegpu/src/nameUtils.ts @@ -1,6 +1,4 @@ -import { invariant } from './errors.ts'; - -const bannedTokens = new Set([ +export const bannedTokens = new Set([ // keywords 'alias', 'break', @@ -181,7 +179,7 @@ const bannedTokens = new Set([ 'storage', ]); -const builtins = new Set([ +export const builtins = new Set([ // constructors 'array', 'bool', @@ -361,198 +359,102 @@ const builtins = new Set([ 'quadSwapY', ]); -export interface NameRegistry { - /** - * Creates a valid WGSL identifier, each guaranteed to be unique - * in the lifetime of a single resolution process - * (excluding non-global identifiers from popped scopes). - * Should append "_" to primer, followed by some id. - * @param primer Used in the generation process, makes the identifier more recognizable. - * @param global Whether the name should be registered in the global scope (true), or in the current function scope (false) - */ - makeUnique(primer: string | undefined, global: boolean): string; - - /** - * Creates a valid WGSL identifier. - * Renames identifiers that are WGSL reserved words. - * @param primer Used in the generation process. - * - * @example - * makeValid("notAKeyword"); // "notAKeyword" - * makeValid("struct"); // makeUnique("struct") - * makeValid("struct_1"); // makeUnique("struct_1") (to avoid potential name collisions) - * makeValid("_"); // ERROR (too difficult to make valid to care) - */ - makeValid(primer: string): string; - - pushFunctionScope(): void; - popFunctionScope(): void; - pushBlockScope(): void; - popBlockScope(): void; -} - -function sanitizePrimer(primer: string | undefined) { +/*#__NO_SIDE_EFFECTS__*/ +export function sanitizePrimer(primer: string | undefined) { if (primer) { - // sanitizing - return primer + const base = primer .replaceAll(/\s/g, '_') // whitespaces .replaceAll(/[^\w\d]/g, ''); // removing illegal characters + + if (base === '_' || base === '' || base.startsWith('__')) { + return 'item'; + } + return base; } return 'item'; } +type ValidationResult = + | { + success: true; + error?: undefined; + } + | { + success: false; + error?: string | undefined; + }; + /** * A function for checking whether an identifier needs renaming. * Throws if provided with an invalid identifier that cannot be easily renamed. * @example - * isValidIdentifier("ident"); // true - * isValidIdentifier("struct"); // false - * isValidIdentifier("struct_1"); // false - * isValidIdentifier("_"); // ERROR - * isValidIdentifier("my variable"); // ERROR + * validateIdentifier("ident"); // { success: true } + * validateIdentifier("struct"); // { success: false, error: "Identifiers cannot start with reserved keywords." } + * validateIdentifier("struct_1"); { success: false, error: "Identifiers cannot start with reserved keywords." } + * validateIdentifier("_"); // { success: false } + * validateIdentifier("my variable"); // { success: false, error: "Identifiers cannot contain whitespace." } */ -function isValidIdentifier(ident: string): boolean { - if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { - throw new Error( - `Invalid identifier '${ident}'. Choose an identifier without whitespaces or leading underscores.`, - ); +/*#__NO_SIDE_EFFECTS__*/ +export function validateIdentifier(ident: string): ValidationResult { + if (ident === '_') { + return { + success: false, + }; + } + if (/\s/.test(ident)) { + return { + success: false, + error: `Identifiers cannot contain whitespace.`, + }; + } + if (ident.startsWith('__')) { + return { + success: false, + error: `Identifiers cannot start with double underscores.`, + }; } const prefix = ident.split('_')[0] as string; - return !bannedTokens.has(prefix) && !builtins.has(prefix); + if (bannedTokens.has(prefix) || builtins.has(prefix)) { + return { + success: false, + error: `Identifiers cannot start with reserved keywords.`, + }; + } + return { + success: true, + }; } /** - * Same as `isValidIdentifier`, except does not check for builtin clashes. + * Same as `validateIdentifier`, except does not check for builtin clashes. */ -export function isValidProp(ident: string): boolean { - if (ident === '_' || ident.startsWith('__') || /\s/.test(ident)) { - throw new Error( - `Invalid identifier '${ident}'. Choose an identifier without whitespaces or leading underscores.`, - ); - } - 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; - - readonly #usedNames: Set; - readonly #scopeStack: ScopeLayer[]; - - constructor() { - this.#usedNames = new Set([...bannedTokens, ...builtins]); - this.#scopeStack = []; +/*#__NO_SIDE_EFFECTS__*/ +export function validateProp(ident: string): ValidationResult { + if (ident === '_') { + return { + success: false, + }; } - - get #usedBlockScopeNames(): Set | undefined { - return (this.#scopeStack[this.#scopeStack.length - 1] as BlockScopeLayer | undefined) - ?.usedBlockScopeNames; + if (/\s/.test(ident)) { + return { + success: false, + error: `Identifiers cannot contain whitespace.`, + }; } - - makeUnique(primer: string | undefined, global: boolean): string { - const sanitizedPrimer = sanitizePrimer(primer); - const name = this.getUniqueVariant(sanitizedPrimer); - - if (global) { - this.#usedNames.add(name); - } else { - this.#usedBlockScopeNames?.add(name); - } - - return name; + if (ident.startsWith('__')) { + return { + success: false, + error: `Identifiers cannot start with double underscores.`, + }; } - - #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.#isUsedInBlocksBefore(primer) - ) { - this.#usedBlockScopeNames?.add(primer); - return primer; - } - return this.makeUnique(primer, false); - } - - isUsed(name: string): boolean { - return this.#usedNames.has(name) || this.#isUsedInBlocksBefore(name); - } - - pushFunctionScope(): void { - this.#scopeStack.push({ type: 'functionScope' }); - this.#scopeStack.push({ - type: 'blockScope', - usedBlockScopeNames: new Set(), - }); - } - - popFunctionScope(): void { - const functionScopeIndex = this.#scopeStack.findLastIndex( - (scope) => scope.type === 'functionScope', - ); - - if (functionScopeIndex === -1) { - throw new Error('Tried to pop function scope when no scope was present.'); - } - - 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 { - let name = `${base}_${this.#lastUniqueId++}`; - while (this.isUsed(name)) { - name = `${base}_${this.#lastUniqueId++}`; - } - return name; - } -} - -export class StrictNameRegistry extends NameRegistryImpl { - getUniqueVariant(base: string): string { - let index = 0; - let name = base; - while (this.isUsed(name)) { - index++; - name = `${base}_${index}`; - } - return name; + const prefix = ident.split('_')[0] as string; + if (bannedTokens.has(prefix)) { + return { + success: false, + error: `Identifiers cannot start with reserved keywords.`, + }; } + return { + success: true, + }; } diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 5eedec298d..2b986f6ebb 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -1,5 +1,5 @@ import { isTgpuFn } from './core/function/tgpuFn.ts'; -import { getUniqueName, type Namespace, type NamespaceInternal } from './core/resolve/namespace.ts'; +import type { Namespace, NamespaceInternal } from './core/resolve/namespace.ts'; import { stitch } from './core/resolve/stitch.ts'; import { ConfigurableImpl } from './core/root/configurableImpl.ts'; import type { Configurable, ExperimentalTgpuRoot } from './core/root/rootTypes.ts'; @@ -37,6 +37,7 @@ import { coerceToSnippet, concretize, numericLiteralToSnippet } from './tgsl/gen import type { ShaderGenerator } from './tgsl/shaderGenerator.ts'; import wgslGenerator from './tgsl/wgslGenerator.ts'; import type { + BlockScopeLayer, ExecMode, ExecState, FnToWgslOptions, @@ -59,6 +60,7 @@ import type { IOData } from './core/function/fnTypes.ts'; import { AutoStruct } from './data/autoStruct.ts'; import { EntryInputRouter } from './core/function/entryInputRouter.ts'; import type { FunctionArgument } from './tgsl/shaderGenerator_members.ts'; +import { validateIdentifier, sanitizePrimer } from './nameUtils.ts'; /** * Inserted into bind group entry definitions that belong @@ -99,6 +101,10 @@ class ItemStateStackImpl implements ItemStateStack { return this._stack.findLast((e) => e.type === 'functionScope'); } + get topBlockScope(): BlockScopeLayer | undefined { + return this._stack.findLast((e) => e.type === 'blockScope'); + } + pushItem() { this._itemDepth++; this._stack.push({ @@ -136,6 +142,7 @@ class ItemStateStackImpl implements ItemStateStack { pushBlockScope() { this._stack.push({ type: 'blockScope', + takenLocalIdentifiers: new Set(), declarations: new Map(), externals: new Map(), }); @@ -213,6 +220,26 @@ class ItemStateStackImpl implements ItemStateStack { return undefined; } + isIdentifierTakenLocally(id: string): boolean { + for (let i = this._stack.length - 1; i >= 0; --i) { + const layer = this._stack[i]; + + if (layer?.type === 'functionScope') { + // Since functions cannot access resources from the calling scope, we + // return early here. + return false; + } + + if (layer?.type === 'blockScope') { + if (layer.takenLocalIdentifiers.has(id)) { + return true; + } + } + } + + return false; + } + defineBlockVariable(id: string, snippet: Snippet): void { if (snippet.dataType === UnknownData) { throw Error(`Tried to define variable '${id}' of unknown type`); @@ -375,6 +402,11 @@ export class ResolutionCtxImpl implements ResolutionCtx { public readonly enableExtensions: WgslExtension[] | undefined; public expectedType: BaseData | undefined; + /** + * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy. + */ + #lastUniqueId = 0; + constructor(opts: ResolutionCtxImplOptions) { this.enableExtensions = opts.enableExtensions; this.gen = opts.shaderGenerator ?? wgslGenerator; @@ -382,12 +414,46 @@ export class ResolutionCtxImpl implements ResolutionCtx { this.#namespaceInternal = opts.namespace[$internal]; } - getUniqueName(resource: object): string { - return getUniqueName(this.#namespaceInternal, resource); + isIdentifierTaken(name: string): boolean { + return ( + this.#namespaceInternal.takenGlobalIdentifiers.has(name) || + this._itemStateStack.isIdentifierTakenLocally(name) + ); } - makeNameValid(name: string): string { - return this.#namespaceInternal.nameRegistry.makeValid(name); + makeUniqueIdentifier(primer: string = 'item', scope: 'global' | 'block'): string { + if ( + scope === 'block' && + validateIdentifier(primer).success && + !this.isIdentifierTaken(primer) + ) { + // Preserving local definitions as they are, provided they are valid and not already taken. + this.reserveIdentifier(primer, 'block'); + return primer; + } + + const base = sanitizePrimer(primer); + let index = 0; + const random = this.#namespaceInternal.strategy === 'random'; + let name = random ? `${base}_${this.#lastUniqueId++}` : base; + while (this.isIdentifierTaken(name)) { + name = random ? `${base}_${this.#lastUniqueId++}` : `${base}_${++index}`; + } + + this.reserveIdentifier(name, scope); + return name; + } + + reserveIdentifier(name: string, scope: 'global' | 'block'): void { + if (scope === 'block') { + const blockScope = this._itemStateStack.topBlockScope; + if (blockScope) { + blockScope.takenLocalIdentifiers.add(name); + return; + } + // Fall through if no block scope is present, treating as global. + } + this.#namespaceInternal.takenGlobalIdentifiers.add(name); } get pre(): string { @@ -441,12 +507,10 @@ export class ResolutionCtxImpl implements ResolutionCtx { } pushBlockScope() { - this.#namespaceInternal.nameRegistry.pushBlockScope(); this._itemStateStack.pushBlockScope(); } popBlockScope() { - this.#namespaceInternal.nameRegistry.popBlockScope(); this._itemStateStack.pop('blockScope'); } @@ -467,19 +531,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { } fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } { - let fnScopePushed = false; - try { - this.#namespaceInternal.nameRegistry.pushFunctionScope(); + const scope = this._itemStateStack.pushFunctionScope( + options.functionType, + {}, + options.returnType, + options.externalMap, + ); + // Pushing a block scope as well, so that any identifiers declared at this point will be scoped to the function body. + this._itemStateStack.pushBlockScope(); + const args: FunctionArgument[] = []; - const argAccess: Record = {}; if (options.entryInput) { const { dataSchema, positionalArgs } = options.entryInput; const firstParam = options.params[0]; const structArg = dataSchema - ? createArgument(this.makeNameValid('_arg_0'), dataSchema) + ? createArgument(this.makeUniqueIdentifier('_arg_0', 'block'), dataSchema) : undefined; if (structArg) { @@ -491,31 +560,31 @@ export class ResolutionCtxImpl implements ResolutionCtx { for (const { name, alias } of firstParam.props) { const argInfo = positionalArgs.find((a) => a.schemaKey === name); if (argInfo) { - const arg = createArgument(this.makeNameValid(alias), argInfo.type); + const arg = createArgument(this.makeUniqueIdentifier(alias, 'block'), argInfo.type); args.push(arg); - argAccess[alias] = arg.access; + scope.argAccess[alias] = arg.access; } else if (structArg) { - argAccess[alias] = createArgumentPropAccess(structArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(structArg.access, name); } } } else if (firstParam?.type === FuncParameterType.identifier) { // Create named arg snippets, then a proxy for property access routing. const proxyEntries: Array<{ schemaKey: string; arg: FunctionArgumentAccess }> = []; for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); proxyEntries.push({ schemaKey: a.schemaKey, arg: arg.access }); } const router = new EntryInputRouter(structArg?.access, proxyEntries); - argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); + scope.argAccess[firstParam.name] = () => snip('N/A', router, 'argument'); } else { // No first param: push positional args with schema key names. for (const a of positionalArgs) { - const argName = this.makeNameValid(`_arg_${a.schemaKey}`); + const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, 'block'); const arg = createArgument(argName, a.type); args.push(arg); - argAccess[argName] = arg.access; + scope.argAccess[argName] = arg.access; } } } else { @@ -538,16 +607,24 @@ export class ResolutionCtxImpl implements ResolutionCtx { switch (astParam?.type) { case FuncParameterType.identifier: { - const arg = createArgument(this.makeNameValid(astParam.name), argType, origin); + const arg = createArgument( + this.makeUniqueIdentifier(astParam.name, 'block'), + argType, + origin, + ); args.push(arg); - argAccess[astParam.name] = arg.access; + scope.argAccess[astParam.name] = arg.access; break; } case FuncParameterType.destructuredObject: { - const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin); + const objArg = createArgument( + this.makeUniqueIdentifier(`_arg_${i}`, 'block'), + argType, + origin, + ); args.push(objArg); for (const { name, alias } of astParam.props) { - argAccess[alias] = createArgumentPropAccess(objArg.access, name); + scope.argAccess[alias] = createArgumentPropAccess(objArg.access, name); } break; } @@ -557,7 +634,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { // have any properties anyway. if (!(argType instanceof AutoStruct)) { args.push({ - name: this.makeNameValid(`_arg_${i}`), + name: this.makeUniqueIdentifier(`_arg_${i}`, 'block'), access: () => { throw new Error( `Unreachable: Accessing an argument that wasn't named in the function signature`, @@ -572,14 +649,6 @@ export class ResolutionCtxImpl implements ResolutionCtx { } } - const scope = this._itemStateStack.pushFunctionScope( - options.functionType, - argAccess, - options.returnType, - options.externalMap, - ); - fnScopePushed = true; - let returnType: BaseData | undefined; const code = this.gen.functionDefinition({ @@ -642,10 +711,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { returnType, }; } finally { - if (fnScopePushed) { - this._itemStateStack.pop('functionScope'); - } - this.#namespaceInternal.nameRegistry.popFunctionScope(); + this._itemStateStack.pop('blockScope'); + this._itemStateStack.pop('functionScope'); } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index f76d4baccd..16d0ecf6f5 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -241,8 +241,12 @@ ${this.ctx.pre}}`; } } + public _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string { + return `${this.ctx.pre}${this._block(block, externalMap)}`; + } + public refVariable(id: string, dataType: wgsl.StorableData): string { - const varName = this.ctx.makeNameValid(id); + const varName = this.ctx.makeUniqueIdentifier(id, 'block'); const ptrType = ptrFn(dataType); const snippet = snip( new RefOperator(snip(varName, dataType, 'function'), ptrType), @@ -280,7 +284,11 @@ ${this.ctx.pre}}`; varOrigin = 'runtime'; } - const snippet = snip(this.ctx.makeNameValid(id), dataType, /* origin */ varOrigin); + const snippet = snip( + this.ctx.makeUniqueIdentifier(id, 'block'), + dataType, + /* origin */ varOrigin, + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -1035,7 +1043,7 @@ Try 'return ${typeStr}(${str});' instead. return this._statement(node); } // simplify 'if (true) {A} else {B}' to '{A}' - return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`; + return this._blockStatement(blockifySingleStatement(node)); } const consequent = this._block(blockifySingleStatement(consNode)); @@ -1168,7 +1176,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.block) { - return `${this.ctx.pre}${this._block(statement)}`; + return this._blockStatement(statement); } if (statement[0] === NODE.for) { @@ -1264,12 +1272,9 @@ ${this.ctx.pre}else ${alternate}`; const blocks = elements.map( (e, i) => - `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block( - blockified, - { - [originalLoopVarName]: e, - }, - )}`, + `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { + [originalLoopVarName]: e, + })}`, ); return blocks.join('\n'); @@ -1277,7 +1282,7 @@ ${this.ctx.pre}else ${alternate}`; this.#unrolling = false; - const index = this.ctx.makeNameValid('i'); + const index = this.ctx.makeUniqueIdentifier('i', 'block'); const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`; @@ -1290,7 +1295,7 @@ ${this.ctx.pre}else ${alternate}`; } else { this.ctx.indent(); ctxIndent = true; - const loopVarName = this.ctx.makeNameValid(originalLoopVarName); + const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block'); const elementSnippet = forOfUtils.getElementSnippet( iterableSnippet, snip(index, u32, 'runtime'), @@ -1304,7 +1309,7 @@ ${this.ctx.pre}else ${alternate}`; false, )};`; - bodyStr = `{\n${loopVarDeclStr}\n${this.ctx.pre}${this._block(blockified, { + bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin), })}\n`; this.ctx.dedent(); diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 75f9e71697..d2b7892faf 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -131,6 +131,7 @@ export type SlotBindingLayer = { export type BlockScopeLayer = { type: 'blockScope'; + takenLocalIdentifiers: Set; declarations: Map; externals: Map; }; @@ -140,6 +141,7 @@ export type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | Blo export interface ItemStateStack { readonly itemDepth: number; readonly topItem: ItemLayer; + readonly topBlockScope: BlockScopeLayer | undefined; readonly topFunctionScope: FunctionScopeLayer | undefined; pushItem(): void; @@ -330,8 +332,27 @@ export interface ResolutionCtx { */ withRenamed(item: object, name: string | undefined, callback: () => T): T; - getUniqueName(resource: object): string; - makeNameValid(name: string): string; + /** + * @param primer The basis for the unique identifier. Depending on the strategy, or + * the names already taken, this may be modified to ensure uniqueness. + * @param scope The scope in which to generate the identifier. 'global' means + * the identifier is meant to be unique across the entire program, while + * 'block' means it cannot shadow any existing identifiers visible from + * within the current block. After the block is popped, any identifiers + * defined within it are no longer visible. + * @returns an identifier that is unique within the given scope + */ + makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string; + + isIdentifierTaken(name: string): boolean; + + /** + * Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier} + * within the given scope. + * @param name The name to reserve + * @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter. + */ + reserveIdentifier(name: string, scope: 'global' | 'block'): void; } /** diff --git a/packages/typegpu/tests/namespace.test.ts b/packages/typegpu/tests/namespace.test.ts index ee6fe33099..7ac6407b43 100644 --- a/packages/typegpu/tests/namespace.test.ts +++ b/packages/typegpu/tests/namespace.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, vi } from 'vitest'; +import { describe, expect } from 'vitest'; import tgpu, { d } from '../src/index.js'; import { it } from 'typegpu-testing-utility'; @@ -71,34 +71,6 @@ describe('tgpu.namespace', () => { `); }); - it('fires "name" event', () => { - const Boid = d.struct({ - pos: d.vec3f, - }); - - const names = tgpu['~unstable'].namespace(); - - const listener = vi.fn((event) => {}); - names.on('name', listener); - - const code = tgpu.resolve([Boid], { names }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledWith({ name: 'Boid', target: Boid }); - - expect(code).toMatchInlineSnapshot(` - "struct Boid { - pos: vec3f, - }" - `); - - const code2 = tgpu.resolve([Boid], { names }); - - // No more events - expect(listener).toHaveBeenCalledTimes(1); - expect(code2).toMatchInlineSnapshot(`""`); - }); - it('handles name collision', () => { let code1: string, code2: string; const names = tgpu['~unstable'].namespace(); diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index be0c2888e4..4e66956ef4 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -1327,7 +1327,7 @@ describe('root.createRenderPipeline', () => { - - renderPipeline:pipeline - renderPipelineCore - - autoVertexFn: Invalid identifier '__myProp'. Choose an identifier without whitespaces or leading underscores.] + - autoVertexFn: Invalid property key '__myProp': Identifiers cannot start with double underscores.] `); }); @@ -1349,7 +1349,7 @@ describe('root.createRenderPipeline', () => { - - renderPipeline:pipeline - renderPipelineCore - - autoVertexFn: Property key 'loop' is a reserved WGSL word. Choose a different name.] + - autoVertexFn: Invalid property key 'loop': Identifiers cannot start with reserved keywords.] `); }); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 254730def2..83e8e1e787 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from 'vitest'; import tgpu, { d } from '../src/index.js'; -import { setName } from '../src/shared/meta.ts'; +import { getName, setName } from '../src/shared/meta.ts'; import { $gpuValueOf, $internal, $ownSnippet, $resolve } from '../src/shared/symbols.ts'; import type { ResolutionCtx } from '../src/types.ts'; import { it } from 'typegpu-testing-utility'; @@ -55,7 +55,7 @@ describe('tgpu resolve', () => { } as unknown as number, [$resolve](ctx: ResolutionCtx) { - const name = ctx.getUniqueName(this); + const name = ctx.makeUniqueIdentifier(getName(this), 'global'); ctx.addDeclaration(`@group(0) @binding(0) var ${name}: f32;`); return snip(name, d.f32, /* origin */ 'runtime'); }, diff --git a/packages/typegpu/tests/struct.test.ts b/packages/typegpu/tests/struct.test.ts index 14dc0984aa..ceb029af8a 100644 --- a/packages/typegpu/tests/struct.test.ts +++ b/packages/typegpu/tests/struct.test.ts @@ -374,13 +374,13 @@ describe('struct', () => { it('throws when struct prop has whitespace in name', () => { expect(() => struct({ 'my prop': f32 })).toThrowErrorMatchingInlineSnapshot( - `[Error: Invalid identifier 'my prop'. Choose an identifier without whitespaces or leading underscores.]`, + `[Error: Invalid property key 'my prop': Identifiers cannot contain whitespace.]`, ); }); it('throws when struct prop uses a reserved word', () => { expect(() => struct({ struct: f32 })).toThrowErrorMatchingInlineSnapshot( - `[Error: Property key 'struct' is a reserved WGSL word. Choose a different name.]`, + `[Error: Invalid property key 'struct': Identifiers cannot start with reserved keywords.]`, ); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 43f7b6ce77..e03c045144 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -566,7 +566,7 @@ describe('wgslGenerator', () => { `); }); - it('throws error when "for ... of ..." loop variable name is not correct in wgsl', () => { + it('renames "for ... of ..." loop variable name when it is not correct in WGSL', () => { const main = () => { 'use gpu'; const arr = [1, 2, 3]; @@ -575,11 +575,16 @@ describe('wgslGenerator', () => { } }; - 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.] + expect(tgpu.resolve([main])).toMatchInlineSnapshot(` + "fn main() { + var arr = array(1, 2, 3); + for (var i = 0u; i < 3u; i += 1u) { + let item = arr[i]; + { + continue; + } + } + }" `); }); @@ -1166,24 +1171,24 @@ describe('wgslGenerator', () => { `); }); - it('throws when an identifier starts with underscores', () => { + it('assigns a different name when an identifier starts with underscores', () => { const main1 = tgpu.fn([])(() => { const _ = 1; }); const main2 = tgpu.fn([])(() => { - const __my_var = 1; + const __my_var = 2; }); - expect(() => tgpu.resolve([main1])).toThrowErrorMatchingInlineSnapshot(` - [Error: Resolution of the following tree failed: - - - - fn:main1: Invalid identifier '_'. Choose an identifier without whitespaces or leading underscores.] + expect(tgpu.resolve([main1])).toMatchInlineSnapshot(` + "fn main1() { + const item = 1; + }" `); - expect(() => tgpu.resolve([main2])).toThrowErrorMatchingInlineSnapshot(` - [Error: Resolution of the following tree failed: - - - - fn:main2: Invalid identifier '__my_var'. Choose an identifier without whitespaces or leading underscores.] + expect(tgpu.resolve([main2])).toMatchInlineSnapshot(` + "fn main2() { + const item = 2; + }" `); }); From ebd29ebe5d5f2fc5ed5b5b6f6462d35939e38d77 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 11 May 2026 10:45:33 +0200 Subject: [PATCH 31/34] Update tests --- .../jump-flood-distance.test.ts | 10 +++++----- packages/typegpu/tests/tgsl/wgslGenerator.test.ts | 2 +- packages/typegpu/tests/tgslFn.test.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts index 0c4a03ac45..dbabdaa849 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/jump-flood-distance.test.ts @@ -267,11 +267,11 @@ describe('jump flood (distance) example', () => { @group(2) @binding(0) var distTexture: texture_storage_2d; fn wrappedCallback(x: u32, y: u32, _arg_2: u32) { - var pos = vec2f(f32(x), f32(y)); - var size = textureDimensions(readView); - var texel = textureLoad(readView, vec2i(i32(x), i32(y))); - var insideCoord = texel.xy; - var outsideCoord = texel.zw; + let pos = vec2f(f32(x), f32(y)); + let size = textureDimensions(readView); + let texel = textureLoad(readView, vec2i(i32(x), i32(y))); + let insideCoord = texel.xy; + let outsideCoord = texel.zw; var insideDist = 3.4028234663852886e+38f; var outsideDist = 3.4028234663852886e+38f; if ((insideCoord.x >= 0f)) { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 8886555d51..881be62728 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -840,7 +840,7 @@ describe('wgslGenerator', () => { expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(` "fn testFn() -> u32 { - var arr = array(1u, 2u, 3u); + let arr = array(1u, 2u, 3u); return arr[1i]; }" `); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 417d4d4a03..265634e9db 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -437,7 +437,7 @@ describe('TGSL tgpu.fn function', () => { expect(tgpu.resolve([fragmentFn])).toMatchInlineSnapshot(` "@fragment fn fragmentFn() -> @location(0) vec4f { - var hmm = vec4f(1.25); + let hmm = vec4f(1.25); return hmm; }" `); From 48532606657cd3248cf0a4cbca3d92b5aa63748a Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 18 May 2026 11:33:38 +0200 Subject: [PATCH 32/34] Update tests --- .../react-confetti.test.ts | 4 +-- .../shifting-gradient.test.ts | 18 +++++------ .../spinning-triangle.test.ts | 10 +++--- packages/typegpu/tests/array.test.ts | 6 ++-- packages/typegpu/tests/std/copy.test.ts | 32 +++++++++---------- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/react-confetti.test.ts b/apps/typegpu-docs/tests/individual-example-tests/react-confetti.test.ts index 252f72280a..33d6657fa4 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/react-confetti.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/react-confetti.test.ts @@ -50,7 +50,7 @@ describe('react/confetti example', () => { } fn rotate(v: vec2f, angle: f32) -> vec2f { - var pos = vec2f(((v.x * cos(angle)) - (v.y * sin(angle))), ((v.x * sin(angle)) + (v.y * cos(angle)))); + let pos = vec2f(((v.x * cos(angle)) - (v.y * sin(angle))), ((v.x * sin(angle)) + (v.y * cos(angle)))); return pos; } @@ -72,7 +72,7 @@ describe('react/confetti example', () => { @vertex fn vertexShader(input: VertexIn) -> VertexOut { let width = input.tilt; let height = (input.tilt / 2f); - var verts = array(vec2f(), vec2f(width, 0f), vec2f(0f, height), vec2f(width, height)); + let verts = array(vec2f(), vec2f(width, 0f), vec2f(0f, height), vec2f(width, height)); var pos = (rotate((verts[input.vertexIndex] / 350f), input.angle) + input.center); if ((aspectRatio < 1f)) { pos.x /= aspectRatio; diff --git a/apps/typegpu-docs/tests/individual-example-tests/shifting-gradient.test.ts b/apps/typegpu-docs/tests/individual-example-tests/shifting-gradient.test.ts index 779d9f4a5e..c848d13d86 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/shifting-gradient.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/shifting-gradient.test.ts @@ -123,7 +123,7 @@ describe('react/shifting-gradient example', () => { fn findCusp(a: f32, b: f32) -> LC { let S_cusp = computeMaxSaturation(a, b); - var rgb_at_max = oklabToLinearRgb(vec3f(1f, (S_cusp * a), (S_cusp * b))); + let rgb_at_max = oklabToLinearRgb(vec3f(1f, (S_cusp * a), (S_cusp * b))); let L_cusp = cbrt((1f / max(max(rgb_at_max.x, rgb_at_max.y), rgb_at_max.z))); let C_cusp = (L_cusp * S_cusp); return LC(L_cusp, C_cusp); @@ -196,7 +196,7 @@ describe('react/shifting-gradient example', () => { let Ld = (L - 0.5f); let e1 = ((0.5f + abs(Ld)) + (alpha * C)); let L0 = (0.5f * (1f + (sign(Ld) * (e1 - sqrt(max(0f, ((e1 * e1) - (2f * abs(Ld))))))))); - var cusp = findCusp(a_, b_); + let cusp = findCusp(a_, b_); let t = clamp(findGamutIntersection(a_, b_, L, C, L0, cusp), 0f, 1f); let L_clipped = mix(L0, L, t); let C_clipped = (t * C); @@ -216,13 +216,13 @@ describe('react/shifting-gradient example', () => { } @fragment fn fragment(_arg_0: FragmentIn) -> @location(0) vec4f { - var fromStart = vec3f(0.6279553771018982, 0.22486300766468048, 0.1258462816476822); - var fromEnd = vec3f(0.45201370120048523, -0.03245693817734718, -0.31152817606925964); - var from_1 = mix(fromStart, fromEnd, ((sin(time) * 0.5f) + 0.5f)); - var toStart = vec3f(0.8664395809173584, -0.2338874489068985, 0.17949843406677246); - var toEnd = vec3f(0.7016738653182983, 0.27456632256507874, -0.16915608942508698); - var to = mix(toStart, toEnd, ((cos((time * 1.5f)) * 0.5f) + 0.5f)); - var mixed = mix(from_1, to, ((((_arg_0.uv.x * 2f) - 1f) * 0.5f) + (sin((time + (_arg_0.uv.y * 3f))) * 0.5f))); + let fromStart = vec3f(0.6279553771018982, 0.22486300766468048, 0.1258462816476822); + let fromEnd = vec3f(0.45201370120048523, -0.03245693817734718, -0.31152817606925964); + let from_1 = mix(fromStart, fromEnd, ((sin(time) * 0.5f) + 0.5f)); + let toStart = vec3f(0.8664395809173584, -0.2338874489068985, 0.17949843406677246); + let toEnd = vec3f(0.7016738653182983, 0.27456632256507874, -0.16915608942508698); + let to = mix(toStart, toEnd, ((cos((time * 1.5f)) * 0.5f) + 0.5f)); + let mixed = mix(from_1, to, ((((_arg_0.uv.x * 2f) - 1f) * 0.5f) + (sin((time + (_arg_0.uv.y * 3f))) * 0.5f))); return vec4f(oklabToRgb(mixed), 1f); }" `); diff --git a/apps/typegpu-docs/tests/individual-example-tests/spinning-triangle.test.ts b/apps/typegpu-docs/tests/individual-example-tests/spinning-triangle.test.ts index 8db20ae578..b6d81fc47a 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/spinning-triangle.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/spinning-triangle.test.ts @@ -27,7 +27,7 @@ describe('react/spinning-triangle example', () => { @group(0) @binding(0) var time: f32; fn rotate(v: vec2f, angle: f32) -> vec2f { - var pos = vec2f(((v.x * cos(angle)) - (v.y * sin(angle))), ((v.x * sin(angle)) + (v.y * cos(angle)))); + let pos = vec2f(((v.x * cos(angle)) - (v.y * sin(angle))), ((v.x * sin(angle)) + (v.y * cos(angle)))); return pos; } @@ -44,7 +44,7 @@ describe('react/spinning-triangle example', () => { @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { let local = vertices[_arg_0.vertexIndex]; - var rotated = rotate(local, (time * 0.1f)); + let rotated = rotate(local, (time * 0.1f)); return VertexOut(vec4f((rotated * 0.7f), 0f, 1f), length((local - vertices[0i])), length((local - vertices[1i])), length((local - vertices[2i]))); } @@ -135,7 +135,7 @@ describe('react/spinning-triangle example', () => { fn findCusp(a: f32, b: f32) -> LC { let S_cusp = computeMaxSaturation(a, b); - var rgb_at_max = oklabToLinearRgb(vec3f(1f, (S_cusp * a), (S_cusp * b))); + let rgb_at_max = oklabToLinearRgb(vec3f(1f, (S_cusp * a), (S_cusp * b))); let L_cusp = cbrt((1f / max(max(rgb_at_max.x, rgb_at_max.y), rgb_at_max.z))); let C_cusp = (L_cusp * S_cusp); return LC(L_cusp, C_cusp); @@ -208,7 +208,7 @@ describe('react/spinning-triangle example', () => { let Ld = (L - 0.5f); let e1 = ((0.5f + abs(Ld)) + (alpha * C)); let L0 = (0.5f * (1f + (sign(Ld) * (e1 - sqrt(max(0f, ((e1 * e1) - (2f * abs(Ld))))))))); - var cusp = findCusp(a_, b_); + let cusp = findCusp(a_, b_); let t = clamp(findGamutIntersection(a_, b_, L, C, L0, cusp), 0f, 1f); let L_clipped = mix(L0, L, t); let C_clipped = (t * C); @@ -235,7 +235,7 @@ describe('react/spinning-triangle example', () => { @fragment fn fragment(_arg_0: FragmentIn) -> @location(0) vec4f { let dist = (1f / (1.4f - min(min(_arg_0.dist0, _arg_0.dist1), _arg_0.dist2))); - var albedo = getGradientColor((((fract(((dist * 2f) - time)) * 2f) - 1f) + cos(time))); + let albedo = getGradientColor((((fract(((dist * 2f) - time)) * 2f) - 1f) + cos(time))); return vec4f(albedo, 1f); }" `); diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index d5cbd7bf83..79c6799e4c 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -217,7 +217,7 @@ describe('array', () => { fn testFn() { let myArray = array(10u); let myClone = myArray; - var myExternal = array(3u); + let myExternal = array(3u); f(myArray); return; }" @@ -444,7 +444,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo(n: u32) { const m = 1u; - var result = array(1u, n, m); + let result = array(1u, n, m); }" `); }); @@ -494,7 +494,7 @@ describe('array', () => { expect(tgpu.resolve([foo])).toMatchInlineSnapshot(` "fn foo() -> vec3f { - var x = vec3f(1, 2, 3); + let x = vec3f(1, 2, 3); let y = (&x); return (*y); }" diff --git a/packages/typegpu/tests/std/copy.test.ts b/packages/typegpu/tests/std/copy.test.ts index aacaa18c6b..c8204407fd 100644 --- a/packages/typegpu/tests/std/copy.test.ts +++ b/packages/typegpu/tests/std/copy.test.ts @@ -96,8 +96,8 @@ describe('std.copy', () => { expect(tgpu.resolve([fn])).toMatchInlineSnapshot(` "fn fn_1() { - var a = vec2u(1, 2); - var b = a; + let a = vec2u(1, 2); + let b = a; }" `); }); @@ -132,8 +132,8 @@ describe('std.copy', () => { } fn fn_1() { - var boid = Boid(vec2u(1, 2)); - var copy = boid; + let boid = Boid(vec2u(1, 2)); + let copy = boid; }" `); }); @@ -147,8 +147,8 @@ describe('std.copy', () => { expect(tgpu.resolve([fn])).toMatchInlineSnapshot(` "fn fn_1() { - var arr = array(1, 2, 3); - var copy = arr; + let arr = array(1, 2, 3); + let copy = arr; }" `); }); @@ -165,7 +165,7 @@ describe('std.copy', () => { "@group(0) @binding(0) var uniform_1: vec2u; fn fn_1() { - var v = uniform_1; + let v = uniform_1; }" `); }); @@ -191,8 +191,8 @@ describe('std.copy', () => { } fn fn_1() { - var struct_1 = Outer(Inner(vec2u(1, 2)), 3u); - var copy = struct_1; + let struct_1 = Outer(Inner(vec2u(1, 2)), 3u); + let copy = struct_1; }" `); }); @@ -212,8 +212,8 @@ describe('std.copy', () => { } fn fn_1() { - var boid = Boid(vec2u(1, 2)); - var prop = boid.pos; + let boid = Boid(vec2u(1, 2)); + let prop = boid.pos; }" `); }); @@ -227,7 +227,7 @@ describe('std.copy', () => { expect(tgpu.resolve([fn])).toMatchInlineSnapshot(` "fn fn_1() { - var arr = array(1, 2, 3); + let arr = array(1, 2, 3); let elem = arr[0i]; }" `); @@ -275,7 +275,7 @@ describe('std.copy', () => { } fn fn_2(arg: vec2u) -> vec2u { - var copy = arg; + let copy = arg; return copy; } @@ -284,14 +284,14 @@ describe('std.copy', () => { } fn fn_3(arg: Boid) -> Boid { - var copy = arg; + let copy = arg; return copy; } fn main() { let a = fn_1(1i); - var b = fn_2(vec2u()); - var c = fn_3(Boid(vec2u())); + let b = fn_2(vec2u()); + let c = fn_3(Boid(vec2u())); }" `); }); From bd7d1304ac865780c4d046e1350b1762fa849af3 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 18 May 2026 15:30:01 +0200 Subject: [PATCH 33/34] Better types --- packages/typegpu/src/tgsl/wgslGenerator.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 22f5260b14..cf87fe4648 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -252,7 +252,7 @@ ${this.ctx.pre}}`; } public blockVariable( - varType: 'var' | 'let' | 'const' | undefined, + varType: 'var' | 'let' | 'const' | '', id: string, dataType: wgsl.BaseData | UnknownData, origin: Origin, @@ -293,7 +293,7 @@ ${this.ctx.pre}}`; */ protected emitVarDecl( pre: string, - keyword: string, + keyword: 'var' | 'let' | 'const' | `#VAR_${number}#`, name: string, _dataType: wgsl.BaseData | UnknownData, rhsStr: string, @@ -1058,7 +1058,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.let || statement[0] === NODE.const) { - let varType: 'var' | 'let' | 'const' | undefined; + let varType: 'var' | 'let' | 'const' | '' = ''; const [stmtType, rawId, rawValue] = statement; const eq = rawValue !== undefined ? this._expression(rawValue) : undefined; @@ -1165,12 +1165,14 @@ ${this.ctx.pre}else ${alternate}`; const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false); const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value; - let emittedVarType: string | undefined = varType; - if (emittedVarType === undefined) { + let emittedVarType: 'var' | 'let' | 'const' | `#VAR_${number}#`; + if (varType === '') { const scope = this.ctx.topFunctionScope; invariant(scope, `Expected function scope to be present for ${rawId}`); emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`; scope.placeholderForVariable.set(snippet, emittedVarType); + } else { + emittedVarType = varType; } return this.emitVarDecl( From 112d48348c567dc229f6a458bb2fed1aa48e408c Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 18 May 2026 15:31:44 +0200 Subject: [PATCH 34/34] Update test import --- packages/typegpu/tests/mutabilityTracking.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/typegpu/tests/mutabilityTracking.test.ts b/packages/typegpu/tests/mutabilityTracking.test.ts index 8a74ba4ec2..0cd3f8a45b 100644 --- a/packages/typegpu/tests/mutabilityTracking.test.ts +++ b/packages/typegpu/tests/mutabilityTracking.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest'; -import * as d from '../src/data/index.ts'; -import tgpu, { std } from '../src/index.js'; +import tgpu, { d, std } from '../src/index.js'; const fnShell = tgpu.fn([d.vec4u], d.u32);