diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts index 5ad9b7fa7..a166f37ec 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts @@ -2,7 +2,7 @@ import tgpu from 'typegpu'; import * as d from 'typegpu/data'; import * as std from 'typegpu/std'; import * as p from './params.ts'; -import { computeBindGroupLayout as layout, ModelData } from './schemas.ts'; +import { computeBindGroupLayout as layout } from './schemas.ts'; import { projectPointOnLine } from './tgsl-helpers.ts'; export const computeShader = tgpu['~unstable'].computeFn({ @@ -10,17 +10,7 @@ export const computeShader = tgpu['~unstable'].computeFn({ workgroupSize: [p.workGroupSize], })((input) => { const fishIndex = input.gid.x; - // TODO: replace it with struct copy when Chromium is fixed - const fishData = ModelData({ - position: layout.$.currentFishData[fishIndex].position, - direction: layout.$.currentFishData[fishIndex].direction, - scale: layout.$.currentFishData[fishIndex].scale, - variant: layout.$.currentFishData[fishIndex].variant, - applySeaDesaturation: - layout.$.currentFishData[fishIndex].applySeaDesaturation, - applySeaFog: layout.$.currentFishData[fishIndex].applySeaFog, - applySinWave: layout.$.currentFishData[fishIndex].applySinWave, - }); + const fishData = layout.$.currentFishData[fishIndex]; let separation = d.vec3f(); let alignment = d.vec3f(); let alignmentCount = 0; @@ -34,34 +24,22 @@ export const computeShader = tgpu['~unstable'].computeFn({ continue; } - // TODO: replace it with struct copy when Chromium is fixed - const other = ModelData({ - position: layout.$.currentFishData[i].position, - direction: layout.$.currentFishData[i].direction, - scale: layout.$.currentFishData[i].scale, - variant: layout.$.currentFishData[i].variant, - applySeaDesaturation: layout.$.currentFishData[i].applySeaDesaturation, - applySeaFog: layout.$.currentFishData[i].applySeaFog, - applySinWave: layout.$.currentFishData[i].applySinWave, - }); - const dist = std.length(std.sub(fishData.position, other.position)); + const other = layout.$.currentFishData[i]; + const dist = std.length(fishData.position.sub(other.position)); if (dist < layout.$.fishBehavior.separationDist) { - separation = std.add( - separation, - std.sub(fishData.position, other.position), - ); + separation = separation.add(fishData.position.sub(other.position)); } if (dist < layout.$.fishBehavior.alignmentDist) { - alignment = std.add(alignment, other.direction); + alignment = alignment.add(other.direction); alignmentCount = alignmentCount + 1; } if (dist < layout.$.fishBehavior.cohesionDist) { - cohesion = std.add(cohesion, other.position); + cohesion = cohesion.add(other.position); cohesionCount = cohesionCount + 1; } } if (alignmentCount > 0) { - alignment = std.mul(1 / d.f32(alignmentCount), alignment); + alignment = alignment.mul(1 / d.f32(alignmentCount)); } if (cohesionCount > 0) { cohesion = std.sub( @@ -79,12 +57,12 @@ export const computeShader = tgpu['~unstable'].computeFn({ if (axisPosition > axisAquariumSize - distance) { const str = axisPosition - (axisAquariumSize - distance); - wallRepulsion = std.sub(wallRepulsion, std.mul(str, repulsion)); + wallRepulsion = wallRepulsion.sub(repulsion.mul(str)); } if (axisPosition < -axisAquariumSize + distance) { const str = -axisAquariumSize + distance - axisPosition; - wallRepulsion = std.add(wallRepulsion, std.mul(str, repulsion)); + wallRepulsion = wallRepulsion.add(repulsion.mul(str)); } } @@ -93,42 +71,38 @@ export const computeShader = tgpu['~unstable'].computeFn({ fishData.position, layout.$.mouseRay.line, ); - const diff = std.sub(fishData.position, proj); + const diff = fishData.position.sub(proj); const limit = p.fishMouseRayRepulsionDistance; const str = std.pow(2, std.clamp(limit - std.length(diff), 0, limit)) - 1; - rayRepulsion = std.mul(str, std.normalize(diff)); + rayRepulsion = std.normalize(diff).mul(str); } - fishData.direction = std.add( - fishData.direction, - std.mul(layout.$.fishBehavior.separationStr, separation), + let direction = d.vec3f(fishData.direction); + + direction = direction.add( + separation.mul(layout.$.fishBehavior.separationStr), ); - fishData.direction = std.add( - fishData.direction, - std.mul(layout.$.fishBehavior.alignmentStr, alignment), + direction = direction.add( + alignment.mul(layout.$.fishBehavior.alignmentStr), ); - fishData.direction = std.add( - fishData.direction, - std.mul(layout.$.fishBehavior.cohesionStr, cohesion), + direction = direction.add( + cohesion.mul(layout.$.fishBehavior.cohesionStr), ); - fishData.direction = std.add( - fishData.direction, - std.mul(p.fishWallRepulsionStrength, wallRepulsion), + direction = direction.add( + wallRepulsion.mul(p.fishWallRepulsionStrength), ); - fishData.direction = std.add( - fishData.direction, - std.mul(p.fishMouseRayRepulsionStrength, rayRepulsion), + direction = direction.add( + rayRepulsion.mul(p.fishMouseRayRepulsionStrength), ); - - fishData.direction = std.mul( - std.clamp(std.length(fishData.direction), 0.0, 0.01), - std.normalize(fishData.direction), + direction = std.normalize(direction).mul( + std.clamp(std.length(fishData.direction), 0, 0.01), ); - const translation = std.mul( + const translation = direction.mul( d.f32(std.min(999, layout.$.timePassed)) / 8, - fishData.direction, ); - fishData.position = std.add(fishData.position, translation); - layout.$.nextFishData[fishIndex] = fishData; + + const nextFishData = layout.$.nextFishData[fishIndex]; + nextFishData.position = fishData.position.add(translation); + nextFishData.direction = d.vec3f(direction); }); diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts index e7e8550b1..8f2c5c82a 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts @@ -118,8 +118,8 @@ const randomizeFishPositionsOnGPU = prepareDispatch(root, (x) => { applySeaFog: 1, applySeaDesaturation: 1, }); - buffer0mutable.$[x] = data; - buffer1mutable.$[x] = data; + buffer0mutable.$[x] = ModelData(data); + buffer1mutable.$[x] = ModelData(data); }); const randomizeFishPositions = () => { diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts index bb810faa2..cc94f65c7 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts @@ -135,7 +135,7 @@ export const fragmentShader = tgpu['~unstable'].fragmentFn({ std.sub(layout.$.camera.position.xyz, input.worldPosition), ); - let desaturatedColor = lightedColor; + let desaturatedColor = d.vec3f(lightedColor); if (input.applySeaDesaturation === 1) { const desaturationFactor = -std.atan2((distanceFromCamera - 5) / 10, 1) / 3; @@ -147,7 +147,7 @@ export const fragmentShader = tgpu['~unstable'].fragmentFn({ desaturatedColor = hsvToRgb(hsv); } - let foggedColor = desaturatedColor; + let foggedColor = d.vec3f(desaturatedColor); if (input.applySeaFog === 1) { const fogParameter = std.max(0, (distanceFromCamera - 1.5) * 0.2); const fogFactor = fogParameter / (1 + fogParameter); diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts index e313ffd86..e06a12c12 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts @@ -133,7 +133,7 @@ export class IcosphereGenerator { in: { gid: d.builtin.globalInvocationId }, workgroupSize: [WORKGROUP_SIZE, 1, 1], })((input) => { - const triangleCount = d.u32(std.arrayLength(prevVertices.value) / 3); + const triangleCount = d.u32(prevVertices.$.length / 3); const triangleIndex = input.gid.x + input.gid.y * MAX_DISPATCH; if (triangleIndex >= triangleCount) { return; @@ -142,13 +142,13 @@ export class IcosphereGenerator { const baseIndexPrev = triangleIndex * 3; const v1 = unpackVec2u( - prevVertices.value[baseIndexPrev].position, + prevVertices.$[baseIndexPrev].position, ); const v2 = unpackVec2u( - prevVertices.value[baseIndexPrev + 1].position, + prevVertices.$[baseIndexPrev + 1].position, ); const v3 = unpackVec2u( - prevVertices.value[baseIndexPrev + 2].position, + prevVertices.$[baseIndexPrev + 2].position, ); const v12 = d.vec4f( @@ -188,8 +188,8 @@ export class IcosphereGenerator { const reprojectedVertex = newVertices[i]; const triBase = i - (i % 3); - let normal = reprojectedVertex; - if (smoothFlag.value === 0) { + let normal = d.vec4f(reprojectedVertex); + if (smoothFlag.$ === 0) { normal = getAverageNormal( newVertices[triBase], newVertices[triBase + 1], @@ -198,12 +198,12 @@ export class IcosphereGenerator { } const outIndex = baseIndexNext + i; - const nextVertex = nextVertices.value[outIndex]; + const nextVertex = nextVertices.$[outIndex]; nextVertex.position = packVec2u(reprojectedVertex); nextVertex.normal = packVec2u(normal); - nextVertices.value[outIndex] = nextVertex; + nextVertices.$[outIndex] = ComputeVertex(nextVertex); } }); diff --git a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts index 5631c45c6..646a4c9b3 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts +++ b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts @@ -129,7 +129,7 @@ export const mainFragment4 = tgpu['~unstable'].fragmentFn({ std.abs(std.fract(aspectUv.x * 1.2) - 0.5), std.abs(std.fract(aspectUv.y * 1.2) - 0.5), ).mul(2).sub(1); - aspectUv = mirroredUv; + aspectUv = d.vec2f(mirroredUv); const originalUv = aspectUv; let accumulatedColor = d.vec3f(0, 0, 0); const time = timeAccess.$; diff --git a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts index 798e0579d..990f10203 100644 --- a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts @@ -127,13 +127,13 @@ const rayMarch = (ro: d.v3f, rd: d.v3f): Shape => { }); for (let i = 0; i < MAX_STEPS; i++) { - const p = std.add(ro, std.mul(rd, dO)); + const p = ro.add(rd.mul(dO)); const scene = getSceneDist(p); dO += scene.dist; if (dO > MAX_DIST || scene.dist < SURF_DIST) { result.dist = dO; - result.color = scene.color; + result.color = d.vec3f(scene.color); break; } } @@ -154,7 +154,7 @@ const softShadow = ( for (let i = 0; i < 100; i++) { if (t >= maxT) break; - const h = getSceneDist(std.add(ro, std.mul(rd, t))).dist; + const h = getSceneDist(ro.add(rd.mul(t))).dist; if (h < 0.001) return 0; res = std.min(res, k * h / t); t += std.max(h, 0.001); @@ -169,9 +169,9 @@ const getNormal = (p: d.v3f): d.v3f => { const e = 0.01; const n = d.vec3f( - getSceneDist(std.add(p, d.vec3f(e, 0, 0))).dist - dist, - getSceneDist(std.add(p, d.vec3f(0, e, 0))).dist - dist, - getSceneDist(std.add(p, d.vec3f(0, 0, e))).dist - dist, + getSceneDist(p.add(d.vec3f(e, 0, 0))).dist - dist, + getSceneDist(p.add(d.vec3f(0, e, 0))).dist - dist, + getSceneDist(p.add(d.vec3f(0, 0, e))).dist - dist, ); return std.normalize(n); @@ -223,17 +223,17 @@ const fragmentMain = tgpu['~unstable'].fragmentFn({ // Lighting with orbiting light const lightPos = getOrbitingLightPos(time.$); - const l = std.normalize(std.sub(lightPos, p)); + const l = std.normalize(lightPos.sub(p)); const diff = std.max(std.dot(n, l), 0); // Soft shadows const shadowRo = p; const shadowRd = l; - const shadowDist = std.length(std.sub(lightPos, p)); + const shadowDist = std.length(lightPos.sub(p)); const shadow = softShadow(shadowRo, shadowRd, 0.1, shadowDist, d.f32(16)); // Combine lighting with shadows and color - const litColor = std.mul(march.color, diff); + const litColor = march.color.mul(diff); const finalColor = std.mix( std.mul(litColor, 0.5), // Shadow color litColor, // Lit color diff --git a/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts b/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts index 4ec15685e..11494df9a 100644 --- a/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts @@ -78,7 +78,7 @@ const fragmentMain = tgpu['~unstable'].fragmentFn({ const p = sub(mul(z, dir), scale.$); p.x -= time.$ + 3; p.z -= time.$ + 3; - let q = p; + let q = d.vec3f(p); let prox = p.y; for (let i = 40.1; i > 0.01; i *= 0.2) { q = sub(i * 0.9, abs(sub(mod(q, i + i), i))); diff --git a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts index 183712439..171923a17 100644 --- a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts +++ b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts @@ -91,7 +91,7 @@ const rayMarch = tgpu.fn( if (scene.dist < c.SURF_DIST) { result.dist = distOrigin; - result.color = scene.color; + result.color = d.vec3f(scene.color); break; } } diff --git a/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts b/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts index fd84ecfb9..f54374c1a 100644 --- a/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts @@ -182,7 +182,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ workgroupSize: [1], })((input) => { const index = input.gid.x; - const instanceInfo = currentTrianglePos.value[index]; + const instanceInfo = TriangleData(currentTrianglePos.value[index]); let separation = d.vec2f(); let alignment = d.vec2f(); let cohesion = d.vec2f(); @@ -249,7 +249,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ instanceInfo.position = std.add(instanceInfo.position, instanceInfo.velocity); - nextTrianglePos.value[index] = instanceInfo; + nextTrianglePos.value[index] = TriangleData(instanceInfo); }); const computePipeline = root['~unstable'] diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index 1a350d8f1..2603c8d34 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -43,13 +43,13 @@ const coordsToIndex = (x: number, y: number) => { const getCell = (x: number, y: number): d.v4f => { 'use gpu'; - return inputGridSlot.$[coordsToIndex(x, y)]; + return d.vec4f(inputGridSlot.$[coordsToIndex(x, y)]); }; const setCell = (x: number, y: number, value: d.v4f) => { 'use gpu'; const index = coordsToIndex(x, y); - outputGridSlot.$[index] = value; + outputGridSlot.$[index] = d.vec4f(value); }; const setVelocity = (x: number, y: number, velocity: d.v2f) => { @@ -202,7 +202,7 @@ const mainInitWorld = tgpu['~unstable'].computeFn({ } } - outputGridSlot.$[index] = value; + outputGridSlot.$[index] = d.vec4f(value); }); const mainMoveObstacles = tgpu['~unstable'].computeFn({ workgroupSize: [1] })( @@ -366,7 +366,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ const minInflow = getMinimumInFlow(x, y); next.z = std.max(minInflow, next.z); - outputGridSlot.$[index] = next; + outputGridSlot.$[index] = d.vec4f(next); }); const OBSTACLE_BOX = 0; diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index 54042282d..f03cfda44 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -41,7 +41,6 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ destroyed: computeLayout.$.inState[currentId].destroyed, }); - const updatedCurrent = current; if (current.destroyed === 0) { for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { const otherId = d.u32(i); @@ -76,7 +75,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ // bounce occurs // push the smaller object outside if (isSmaller(currentId, otherId)) { - updatedCurrent.position = std.add( + current.position = std.add( other.position, std.mul( radiusOf(current) + radiusOf(other), @@ -85,10 +84,10 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ ); } // bounce with tiny damping - updatedCurrent.velocity = std.mul( + current.velocity = std.mul( 0.99, std.sub( - updatedCurrent.velocity, + current.velocity, std.mul( (((2 * other.mass) / (current.mass + other.mass)) * std.dot( @@ -107,22 +106,22 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ isSmaller(currentId, otherId)); if (isCurrentAbsorbed) { // absorbed by the other - updatedCurrent.destroyed = 1; + current.destroyed = 1; } else { // absorbs the other - const m1 = updatedCurrent.mass; + const m1 = current.mass; const m2 = other.mass; - updatedCurrent.velocity = std.add( - std.mul(m1 / (m1 + m2), updatedCurrent.velocity), + current.velocity = std.add( + std.mul(m1 / (m1 + m2), current.velocity), std.mul(m2 / (m1 + m2), other.velocity), ); - updatedCurrent.mass = m1 + m2; + current.mass = m1 + m2; } } } } - computeLayout.$.outState[input.gid.x] = updatedCurrent; + computeLayout.$.outState[input.gid.x] = CelestialBody(current); }); export const computeGravityShader = tgpu['~unstable'].computeFn({ @@ -182,5 +181,5 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ ); } - computeLayout.$.outState[input.gid.x] = updatedCurrent; + computeLayout.$.outState[input.gid.x] = CelestialBody(updatedCurrent); }); diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts index 18e5ede65..574ddafd7 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts @@ -111,10 +111,10 @@ export const advectFn = tgpu['~unstable'].computeFn({ const clampedPos = std.clamp( prevPos, d.vec2f(-0.5), - d.vec2f(std.sub(d.vec2f(texSize.xy), d.vec2f(0.5))), + d.vec2f(texSize.xy).sub(0.5), ); const normalizedPos = std.div( - std.add(clampedPos, d.vec2f(0.5)), + clampedPos.add(0.5), d.vec2f(texSize.xy), ); diff --git a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts index 57a5d233c..4bd8873a0 100644 --- a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts +++ b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts @@ -4,10 +4,10 @@ import * as std from 'typegpu/std'; const SimpleStruct = d.struct({ vec: d.vec2f }); -const modifyNumFn = tgpu.fn([d.ptrFn(d.u32)])((ptr) => { - // biome-ignore lint/style/noParameterAssign: it's just a test - ptr += 1; -}); +// const modifyNumFn = tgpu.fn([d.ptrFn(d.u32)])((ptr) => { +// // biome-ignore lint/style/noParameterAssign: it's just a test +// ptr += 1; +// }); const modifyVecFn = tgpu.fn([d.ptrFn(d.vec2f)])((ptr) => { ptr.x += 1; @@ -38,9 +38,10 @@ export const pointersTest = tgpu.fn([], d.bool)(() => { let s = true; // function pointers - const num = d.u32(); - modifyNumFn(num); - s = s && (num === 1); + // TODO: Uncomment this when boxed values are implemented + // const num = d.u32(); + // modifyNumFn(num); + // s = s && (num === 1); const vec = d.vec2f(); modifyVecFn(vec); diff --git a/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts b/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts index b9a69e70a..7f2e51cec 100644 --- a/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts +++ b/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts @@ -165,7 +165,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ instanceInfo.position = std.add(instanceInfo.position, instanceInfo.velocity); - nextTrianglePos.value[index] = instanceInfo; + nextTrianglePos.value[index] = TriangleData(instanceInfo); }).$name('compute shader'); // WGSL resolution diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 4c3578a50..e7eeb878c 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -1,7 +1,11 @@ import type { AnyData } from '../../data/dataTypes.ts'; import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import type { AnyWgslData, BaseData } from '../../data/wgslTypes.ts'; +import { + type AnyWgslData, + type BaseData, + isNaturallyRef, +} from '../../data/wgslTypes.ts'; import { IllegalBufferAccessError } from '../../errors.ts'; import { getExecMode, inCodegenMode, isInsideTgpuFn } from '../../execMode.ts'; import { isUsableAsStorage, type StorageFlag } from '../../extension.ts'; @@ -126,7 +130,11 @@ class TgpuFixedBufferImpl< };`, ); - return snip(id, dataType); + return snip( + id, + dataType, + /* ref */ isNaturallyRef(dataType) ? this.usage : 'runtime', + ); } toString(): string { @@ -135,11 +143,16 @@ class TgpuFixedBufferImpl< get [$gpuValueOf](): InferGPU { const dataType = this.buffer.dataType; + const usage = this.usage; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType); + return snip( + this, + dataType, + /* ref */ isNaturallyRef(dataType) ? usage : 'runtime', + ); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.usage}:${getName(this) ?? ''}.$`, @@ -246,7 +259,11 @@ export class TgpuLaidOutBufferImpl< };`, ); - return snip(id, dataType); + return snip( + id, + dataType, + /* ref */ isNaturallyRef(dataType) ? this.usage : 'runtime', + ); } toString(): string { @@ -255,11 +272,16 @@ export class TgpuLaidOutBufferImpl< get [$gpuValueOf](): InferGPU { const schema = this.dataType as unknown as AnyData; + const usage = this.usage; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, schema); + return snip( + this, + schema, + /* ref */ isNaturallyRef(schema) ? usage : 'runtime', + ); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.usage}:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/core/constant/tgpuConstant.ts b/packages/typegpu/src/core/constant/tgpuConstant.ts index 475b3afa0..3680b6832 100644 --- a/packages/typegpu/src/core/constant/tgpuConstant.ts +++ b/packages/typegpu/src/core/constant/tgpuConstant.ts @@ -1,5 +1,5 @@ import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import type { AnyWgslData } from '../../data/wgslTypes.ts'; +import { type AnyWgslData, isNaturallyRef } from '../../data/wgslTypes.ts'; import { inCodegenMode } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, setName } from '../../shared/meta.ts'; @@ -17,11 +17,17 @@ import { valueProxyHandler } from '../valueProxyUtils.ts'; // Public API // ---------- +type DeepReadonly = T extends { [$internal]: unknown } ? T + : T extends unknown[] ? ReadonlyArray> + : T extends Record + ? { readonly [K in keyof T]: DeepReadonly } + : T; + export interface TgpuConst extends TgpuNamable { - readonly [$gpuValueOf]: InferGPU; - readonly value: InferGPU; - readonly $: InferGPU; + readonly [$gpuValueOf]: DeepReadonly>; + readonly value: DeepReadonly>; + readonly $: DeepReadonly>; readonly [$internal]: { /** Makes it differentiable on the type level. Does not exist at runtime. */ @@ -43,16 +49,35 @@ export function constant( // Implementation // -------------- +function deepFreeze(object: T): T { + // Retrieve the property names defined on object + const propNames = Reflect.ownKeys(object); + + // Freeze properties before freezing self + for (const name of propNames) { + // biome-ignore lint/suspicious/noExplicitAny: chill TypeScript + const value = (object as any)[name]; + + if ((value && typeof value === 'object') || typeof value === 'function') { + deepFreeze(value); + } + } + + return Object.freeze(object); +} + class TgpuConstImpl implements TgpuConst, SelfResolvable { readonly [$internal] = {}; - readonly #value: InferGPU; + readonly #value: DeepReadonly>; constructor( public readonly dataType: TDataType, value: InferGPU, ) { - this.#value = value; + this.#value = value && typeof value === 'object' + ? deepFreeze(value) as DeepReadonly> + : value as DeepReadonly>; } $name(label: string) { @@ -67,27 +92,38 @@ class TgpuConstImpl ctx.addDeclaration(`const ${id}: ${resolvedDataType} = ${resolvedValue};`); - return snip(id, this.dataType); + // Why not a ref? + // 1. On the WGSL side, we cannot take pointers to constants. + // 2. On the JS side, we copy the constant each time we access it, so we're safe. + return snip( + id, + this.dataType, + /* ref */ isNaturallyRef(this.dataType) ? 'constant-ref' : 'constant', + ); } toString() { return `const:${getName(this) ?? ''}`; } - get [$gpuValueOf](): InferGPU { + get [$gpuValueOf](): DeepReadonly> { const dataType = this.dataType; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType); + return snip( + this, + dataType, + /* ref */ isNaturallyRef(dataType) ? 'constant-ref' : 'constant', + ); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `const:${getName(this) ?? ''}.$`, - }, valueProxyHandler) as InferGPU; + }, valueProxyHandler) as DeepReadonly>; } - get value(): InferGPU { + get $(): DeepReadonly> { if (inCodegenMode()) { return this[$gpuValueOf]; } @@ -95,7 +131,7 @@ class TgpuConstImpl return this.#value; } - get $(): InferGPU { - return this.value; + get value(): DeepReadonly> { + return this.$; } } diff --git a/packages/typegpu/src/core/declare/tgpuDeclare.ts b/packages/typegpu/src/core/declare/tgpuDeclare.ts index f36d707e8..aaa40c6f0 100644 --- a/packages/typegpu/src/core/declare/tgpuDeclare.ts +++ b/packages/typegpu/src/core/declare/tgpuDeclare.ts @@ -60,7 +60,7 @@ class TgpuDeclareImpl implements TgpuDeclare, SelfResolvable { ); ctx.addDeclaration(replacedDeclaration); - return snip('', Void); + return snip('', Void, /* ref */ 'constant'); } toString() { diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index efd4dc6fa..7eff45c88 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -5,16 +5,12 @@ import { type Snippet, } from '../../data/snippet.ts'; import { inCodegenMode } from '../../execMode.ts'; -import { type FnArgsConversionHint, getOwnSnippet } from '../../types.ts'; +import { type FnArgsConversionHint, isKnownAtComptime } from '../../types.ts'; import { setName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { tryConvertSnippet } from '../../tgsl/conversion.ts'; import type { AnyData } from '../../data/dataTypes.ts'; -function isKnownAtComptime(value: unknown): boolean { - return typeof value !== 'string' && getOwnSnippet(value) === undefined; -} - export function createDualImpl unknown>( jsImpl: T, gpuImpl: (...args: MapValueToSnippet>) => Snippet, @@ -52,6 +48,12 @@ interface DualImplOptions unknown> { | (( ...inArgTypes: MapValueToDataType> ) => { argTypes: AnyData[]; returnType: AnyData }); + /** + * Whether the function should skip trying to execute the "normal" implementation if + * all arguments are known at compile time. + * @default false + */ + readonly noComptime?: boolean | undefined; readonly ignoreImplicitCastWarning?: boolean | undefined; } @@ -73,22 +75,25 @@ export function dualImpl unknown>( : options.signature; const argSnippets = args as MapValueToSnippet>; - const converted = argSnippets.map((s, idx) => - tryConvertSnippet( - s, - argTypes[idx] as AnyData, - !options.ignoreImplicitCastWarning, - ) - ) as MapValueToSnippet>; + const converted = argSnippets.map((s, idx) => { + const argType = argTypes[idx] as AnyData | undefined; + if (!argType) { + throw new Error('Function called with invalid arguments'); + } + return tryConvertSnippet(s, argType, !options.ignoreImplicitCastWarning); + }) as MapValueToSnippet>; if ( - converted.every((s) => isKnownAtComptime(s.value)) && + !options.noComptime && + converted.every((s) => isKnownAtComptime(s)) && typeof options.normalImpl === 'function' ) { try { return snip( options.normalImpl(...converted.map((s) => s.value) as never[]), returnType, + // Functions give up ownership of their return value + /* ref */ 'constant', ); } catch (e) { // cpuImpl may in some cases be present but implemented only partially. @@ -100,7 +105,12 @@ export function dualImpl unknown>( } } - return snip(options.codegenImpl(...converted), returnType); + return snip( + options.codegenImpl(...converted), + returnType, + // Functions give up ownership of their return value + /* ref */ 'runtime', + ); }; const impl = ((...args: Parameters) => { @@ -119,6 +129,9 @@ export function dualImpl unknown>( value: { jsImpl: options.normalImpl, gpuImpl, + strictSignature: typeof options.signature !== 'function' + ? options.signature + : undefined, argConversionHint: 'keep', }, }); diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 7cc3e652f..049316c83 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -7,6 +7,7 @@ import { type Snippet, } from '../../data/snippet.ts'; import { + isPtr, isWgslData, isWgslStruct, Void, @@ -135,7 +136,7 @@ export function createFnCore( } ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); - return snip(id, returnType); + return snip(id, returnType, /* ref */ 'runtime'); } // get data generated by the plugin @@ -188,11 +189,19 @@ export function createFnCore( for (const [i, argType] of argTypes.entries()) { const astParam = ast.params[i]; + // We know if arguments are passed by reference or by value, because we + // enforce that based on the whether the argument is a pointer or not. + // + // It still applies for shell-less functions, since we determine the type + // of the argument based on the argument's referentiality. + // In other words, if we pass a reference to a function, it's typed as a pointer, + // otherwise it's typed as a value. + const ref = isPtr(argType) ? 'function' : 'runtime'; switch (astParam?.type) { case FuncParameterType.identifier: { const rawName = astParam.name; - const snippet = snip(ctx.makeNameValid(rawName), argType); + const snippet = snip(ctx.makeNameValid(rawName), argType, ref); args.push(snippet); if (snippet.value !== rawName) { argAliases.push([rawName, snippet]); @@ -200,7 +209,7 @@ export function createFnCore( break; } case FuncParameterType.destructuredObject: { - args.push(snip(`_arg_${i}`, argType)); + args.push(snip(`_arg_${i}`, argType, ref)); argAliases.push(...astParam.props.map(({ name, alias }) => [ alias, @@ -208,13 +217,14 @@ export function createFnCore( `_arg_${i}.${name}`, (argTypes[i] as WgslStruct) .propTypes[name], + ref, ), ] as [string, Snippet] )); break; } case undefined: - args.push(snip(`_arg_${i}`, argType)); + args.push(snip(`_arg_${i}`, argType, ref)); } } @@ -232,7 +242,7 @@ export function createFnCore( }`, ); - return snip(id, actualReturnType); + return snip(id, actualReturnType, /* ref */ 'runtime'); }, }; diff --git a/packages/typegpu/src/core/function/shelllessImpl.ts b/packages/typegpu/src/core/function/shelllessImpl.ts index 02b249f60..14e4f15c8 100644 --- a/packages/typegpu/src/core/function/shelllessImpl.ts +++ b/packages/typegpu/src/core/function/shelllessImpl.ts @@ -23,6 +23,7 @@ import { createFnCore } from './fnCore.ts'; */ export interface ShelllessImpl extends SelfResolvable { readonly resourceType: 'shellless-impl'; + readonly argTypes: AnyData[]; readonly [$getNameForward]: unknown; } @@ -36,6 +37,7 @@ export function createShelllessImpl( [$internal]: true, [$getNameForward]: core, resourceType: 'shellless-impl' as const, + argTypes, [$resolve](ctx: ResolutionCtx): ResolvedSnippet { return core.resolve(ctx, argTypes, undefined); diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index 4c5a3cc93..cd83970ce 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -1,14 +1,10 @@ import type { AnyData } from '../../data/dataTypes.ts'; import type { DualFn } from '../../data/dualFn.ts'; -import { - type ResolvedSnippet, - snip, - type Snippet, -} from '../../data/snippet.ts'; +import type { ResolvedSnippet } from '../../data/snippet.ts'; import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { Void } from '../../data/wgslTypes.ts'; import { ExecutionError } from '../../errors.ts'; -import { provideInsideTgpuFn } from '../../execMode.ts'; +import { getResolutionCtx, provideInsideTgpuFn } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, setName } from '../../shared/meta.ts'; import { isMarkedInternal } from '../../shared/symbols.ts'; @@ -16,7 +12,6 @@ import type { Infer } from '../../shared/repr.ts'; import { $getNameForward, $internal, - $ownSnippet, $providing, $resolve, } from '../../shared/symbols.ts'; @@ -36,7 +31,7 @@ import { type TgpuAccessor, type TgpuSlot, } from '../slot/slotTypes.ts'; -import { createDualImpl } from './dualImpl.ts'; +import { dualImpl } from './dualImpl.ts'; import { createFnCore, type FnCore } from './fnCore.ts'; import type { AnyFn, @@ -215,8 +210,11 @@ function createFn( }, } as This; - const call = createDualImpl>( - (...args) => + const call = dualImpl>({ + name: 'tgpuFnCall', + noComptime: true, + signature: { argTypes: shell.argTypes, returnType: shell.returnType }, + normalImpl: (...args) => provideInsideTgpuFn(() => { try { if (typeof implementation === 'string') { @@ -239,10 +237,14 @@ function createFn( throw new ExecutionError(err, [fn]); } }), - (...args) => snip(new FnCall(fn, args), shell.returnType), - 'tgpuFnCall', - shell.argTypes, - ); + codegenImpl: (...args) => { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + return ctx.withResetIndentLevel(() => + stitch`${ctx.resolve(fn).value}(${args})` + ); + }, + }); const fn = Object.assign(call, fnBase as This) as unknown as TgpuFn< ImplSchema @@ -296,12 +298,22 @@ function createBoundFunction( }, }; - const call = createDualImpl>( - (...args) => innerFn(...args), - (...args) => snip(new FnCall(fn, args), innerFn.shell.returnType), - 'tgpuFnCall', - innerFn.shell.argTypes, - ); + const call = dualImpl>({ + name: 'tgpuFnCall', + noComptime: true, + signature: { + argTypes: innerFn.shell.argTypes, + returnType: innerFn.shell.returnType, + }, + normalImpl: innerFn, + codegenImpl: (...args) => { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + return ctx.withResetIndentLevel(() => + stitch`${ctx.resolve(fn).value}(${args})` + ); + }, + }); const fn = Object.assign(call, fnBase) as unknown as TgpuFn; fn[$internal].implementation = innerFn[$internal].implementation; @@ -314,40 +326,5 @@ function createBoundFunction( }, }); - fn[$internal].implementation = innerFn[$internal].implementation; - return fn; } - -class FnCall implements SelfResolvable { - readonly [$internal] = true; - readonly [$ownSnippet]: Snippet; - readonly [$getNameForward]: unknown; - readonly #fn: TgpuFnBase; - readonly #params: Snippet[]; - - constructor( - fn: TgpuFnBase, - params: Snippet[], - ) { - this.#fn = fn; - this.#params = params; - this[$getNameForward] = fn; - this[$ownSnippet] = snip(this, this.#fn.shell.returnType); - } - - [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - // We need to reset the indentation level during function body resolution to ignore the indentation level of the function call - return ctx.withResetIndentLevel(() => { - // TODO: Resolve the params first, then the function (just for consistency) - return snip( - stitch`${ctx.resolve(this.#fn).value}(${this.#params})`, - this.#fn.shell.returnType, - ); - }); - } - - toString() { - return `call:${getName(this) ?? ''}`; - } -} diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index 7abe63512..53348a6e8 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -248,7 +248,7 @@ class ComputePipelineCore implements SelfResolvable { [$resolve](ctx: ResolutionCtx) { return ctx.withSlots(this._slotBindings, () => { ctx.resolve(this._entryFn); - return snip('', Void); + return snip('', Void, /* ref */ 'runtime'); }); } diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index b5b78addd..0000d8585 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -702,7 +702,7 @@ class RenderPipelineCore implements SelfResolvable { if (fragmentFn) { ctx.resolve(fragmentFn); } - return snip('', Void); + return snip('', Void, /* ref */ 'runtime'); }), ); } diff --git a/packages/typegpu/src/core/resolve/tgpuResolve.ts b/packages/typegpu/src/core/resolve/tgpuResolve.ts index 146d9a8d4..89c991deb 100644 --- a/packages/typegpu/src/core/resolve/tgpuResolve.ts +++ b/packages/typegpu/src/core/resolve/tgpuResolve.ts @@ -107,6 +107,7 @@ export function resolveWithContext( return snip( replaceExternalsInWgsl(ctx, dependencies, template ?? ''), Void, + /* ref */ 'runtime', ); }, diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts index 9b047914a..fe8805edc 100644 --- a/packages/typegpu/src/core/root/init.ts +++ b/packages/typegpu/src/core/root/init.ts @@ -42,6 +42,7 @@ import { type VertexFlag, } from '../buffer/buffer.ts'; import { + type TgpuBufferShorthand, TgpuBufferShorthandImpl, type TgpuMutable, type TgpuReadonly, @@ -109,7 +110,12 @@ class WithBindingImpl implements WithBinding { with( slot: TgpuSlot | TgpuAccessor, - value: T | TgpuFn<() => T> | TgpuBufferUsage | Infer, + value: + | T + | TgpuFn<() => T> + | TgpuBufferUsage + | TgpuBufferShorthand + | Infer, ): WithBinding { return new WithBindingImpl(this._getRoot, [ ...this._slotBindings, diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index a2991e9bf..04a1b2c40 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -156,7 +156,7 @@ export class TgpuLaidOutSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -168,7 +168,7 @@ export class TgpuLaidOutSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ 'handle'); } toString() { @@ -188,7 +188,7 @@ export class TgpuLaidOutComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -200,7 +200,7 @@ export class TgpuLaidOutComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ 'handle'); } toString() { @@ -238,7 +238,7 @@ class TgpuFixedSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -256,7 +256,7 @@ class TgpuFixedSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ 'handle'); } $name(label: string) { @@ -293,7 +293,7 @@ class TgpuFixedComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); $name(label: string) { setName(this, label); @@ -313,7 +313,7 @@ class TgpuFixedComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ 'handle'); } toString() { diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index dc711a421..8cb792682 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -1,3 +1,4 @@ +import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import type { AnyWgslData } from '../../data/wgslTypes.ts'; import { getResolutionCtx, inCodegenMode } from '../../execMode.ts'; @@ -10,7 +11,12 @@ import { $ownSnippet, $resolve, } from '../../shared/symbols.ts'; -import type { ResolutionCtx, SelfResolvable } from '../../types.ts'; +import { + getOwnSnippet, + type ResolutionCtx, + type SelfResolvable, +} from '../../types.ts'; +import type { TgpuBufferShorthand } from '../buffer/bufferShorthand.ts'; import type { TgpuBufferUsage } from '../buffer/bufferUsage.ts'; import { isTgpuFn, type TgpuFn } from '../function/tgpuFn.ts'; import { @@ -26,7 +32,11 @@ import type { TgpuAccessor, TgpuSlot } from './slotTypes.ts'; export function accessor( schema: T, - defaultValue?: TgpuFn<() => T> | TgpuBufferUsage | Infer, + defaultValue?: + | TgpuFn<() => T> + | TgpuBufferUsage + | TgpuBufferShorthand + | Infer, ): TgpuAccessor { return new TgpuAccessorImpl(schema, defaultValue); } @@ -40,13 +50,16 @@ export class TgpuAccessorImpl readonly [$internal] = true; readonly [$getNameForward]: unknown; readonly resourceType = 'accessor'; - readonly slot: TgpuSlot T> | TgpuBufferUsage | Infer>; + readonly slot: TgpuSlot< + TgpuFn<() => T> | TgpuBufferUsage | TgpuBufferShorthand | Infer + >; constructor( public readonly schema: T, public readonly defaultValue: | TgpuFn<() => T> | TgpuBufferUsage + | TgpuBufferShorthand | Infer | undefined = undefined, ) { @@ -75,7 +88,16 @@ export class TgpuAccessorImpl return value[$internal].gpuImpl(); } - return snip(value, this.schema); + const ownSnippet = getOwnSnippet(value); + if (ownSnippet) { + return ownSnippet; + } + + // Doing a deep copy each time so that we don't have to deal with refs + return schemaCallWrapper( + this.schema, + snip(value, this.schema, /* ref */ 'constant'), + ); } $name(label: string) { @@ -106,6 +128,7 @@ export class TgpuAccessorImpl return snip( ctx.resolve(snippet.value, snippet.dataType).value, snippet.dataType as T, + snippet.ref, ); } } diff --git a/packages/typegpu/src/core/slot/slotTypes.ts b/packages/typegpu/src/core/slot/slotTypes.ts index efe335745..4008dfbc4 100644 --- a/packages/typegpu/src/core/slot/slotTypes.ts +++ b/packages/typegpu/src/core/slot/slotTypes.ts @@ -2,6 +2,7 @@ import type { AnyData } from '../../data/dataTypes.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import type { GPUValueOf, Infer, InferGPU } from '../../shared/repr.ts'; import { $gpuValueOf, $internal, $providing } from '../../shared/symbols.ts'; +import type { TgpuBufferShorthand } from '../buffer/bufferShorthand.ts'; import type { TgpuFn } from '../function/tgpuFn.ts'; import type { TgpuBufferUsage } from './../buffer/bufferUsage.ts'; @@ -50,9 +51,12 @@ export interface TgpuAccessor extends TgpuNamable { readonly defaultValue: | TgpuFn<() => T> | TgpuBufferUsage + | TgpuBufferShorthand | Infer | undefined; - readonly slot: TgpuSlot T> | TgpuBufferUsage | Infer>; + readonly slot: TgpuSlot< + TgpuFn<() => T> | TgpuBufferUsage | TgpuBufferShorthand | Infer + >; readonly [$gpuValueOf]: InferGPU; readonly value: InferGPU; diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index 0ece389fa..1892b5df9 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -58,7 +58,7 @@ export class TgpuExternalTextureImpl };`, ); - return snip(id, textureExternal()); + return snip(id, textureExternal(), 'handle'); } get [$gpuValueOf](): Infer { @@ -68,7 +68,7 @@ export class TgpuExternalTextureImpl { [$internal]: true, get [$ownSnippet]() { - return snip(this, schema); + return snip(this, schema, 'handle'); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `textureExternal:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index b47c186af..0fa0f6dbe 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -647,7 +647,7 @@ class TgpuFixedTextureViewImpl { [$internal]: true, get [$ownSnippet]() { - return snip(this, schema); + return snip(this, schema, /* ref */ 'handle'); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.toString()}.$`, @@ -695,7 +695,7 @@ class TgpuFixedTextureViewImpl };`, ); - return snip(id, this.schema); + return snip(id, this.schema, /* ref */ 'handle'); } } @@ -730,7 +730,7 @@ export class TgpuLaidOutTextureViewImpl< };`, ); - return snip(id, this.schema); + return snip(id, this.schema, /* ref */ 'handle'); } get [$gpuValueOf](): Infer { @@ -739,7 +739,7 @@ export class TgpuLaidOutTextureViewImpl< { [$internal]: true, get [$ownSnippet]() { - return snip(this, schema); + return snip(this, schema, /* ref */ 'handle'); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.toString()}.$`, diff --git a/packages/typegpu/src/core/valueProxyUtils.ts b/packages/typegpu/src/core/valueProxyUtils.ts index 6cf30fe17..7851a6233 100644 --- a/packages/typegpu/src/core/valueProxyUtils.ts +++ b/packages/typegpu/src/core/valueProxyUtils.ts @@ -1,8 +1,7 @@ -import type { AnyData } from '../data/dataTypes.ts'; -import { snip, type Snippet } from '../data/snippet.ts'; +import type { Snippet } from '../data/snippet.ts'; import { getGPUValue } from '../getGPUValue.ts'; import { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts'; -import { getTypeForPropAccess } from '../tgsl/generationHelpers.ts'; +import { accessProp } from '../tgsl/generationHelpers.ts'; import { getOwnSnippet, type SelfResolvable, @@ -30,20 +29,16 @@ export const valueProxyHandler: ProxyHandler< } const targetSnippet = getOwnSnippet(target) as Snippet; - const targetDataType = targetSnippet.dataType as AnyData; - const propType = getTypeForPropAccess(targetDataType, String(prop)); - if (propType.type === 'unknown') { + const accessed = accessProp(targetSnippet, String(prop)); + if (!accessed) { // Prop was not found, must be missing from this object return undefined; } return new Proxy({ [$internal]: true, - [$resolve]: (ctx) => - snip(`${ctx.resolve(target).value}.${String(prop)}`, propType), - get [$ownSnippet]() { - return snip(this, propType); - }, + [$resolve]: (ctx) => ctx.resolve(accessed.value, accessed.dataType), + [$ownSnippet]: accessed, toString: () => `${String(target)}.${prop}`, }, valueProxyHandler); }, diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index d82bb154c..12eec3ed2 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -1,5 +1,6 @@ import type { AnyData } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; +import { isNaturallyRef } from '../../data/wgslTypes.ts'; import { IllegalVarAccessError } from '../../errors.ts'; import { getExecMode, isInsideTgpuFn } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; @@ -103,7 +104,11 @@ class TgpuVarImpl ctx.addDeclaration(`${pre};`); } - return snip(id, this.#dataType); + return snip( + id, + this.#dataType, + /* ref */ isNaturallyRef(this.#dataType) ? this.#scope : 'runtime', + ); } $name(label: string) { @@ -117,11 +122,12 @@ class TgpuVarImpl get [$gpuValueOf](): InferGPU { const dataType = this.#dataType; + const ref = isNaturallyRef(dataType) ? this.#scope : 'runtime'; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType); + return snip(this, dataType, ref); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `var:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/data/array.ts b/packages/typegpu/src/data/array.ts index 52873b4cb..ff6430bf3 100644 --- a/packages/typegpu/src/data/array.ts +++ b/packages/typegpu/src/data/array.ts @@ -53,7 +53,7 @@ export const arrayOf = createDualImpl( // Marking so the WGSL generator lets this function through partial[$internal] = true; - return snip(partial, UnknownData); + return snip(partial, UnknownData, /* ref*/ 'runtime'); } if (typeof elementCount.value !== 'number') { @@ -65,6 +65,7 @@ export const arrayOf = createDualImpl( return snip( cpu_arrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, + /* ref */ 'runtime', ); }, 'arrayOf', diff --git a/packages/typegpu/src/data/dataTypes.ts b/packages/typegpu/src/data/dataTypes.ts index e03d21c67..f852938df 100644 --- a/packages/typegpu/src/data/dataTypes.ts +++ b/packages/typegpu/src/data/dataTypes.ts @@ -134,6 +134,21 @@ export function undecorate(data: AnyData): AnyData { return data; } +export function unptr(data: AnyData): AnyData { + if (data.type === 'ptr') { + return data.inner as AnyData; + } + return data; +} + +export function toStorable(schema: AnyData): AnyData { + return undecorate(unptr(undecorate(schema))); +} + +export function toStorables(schemas: T): T { + return schemas.map(toStorable) as T; +} + const looseTypeLiterals = [ 'unstruct', 'disarray', diff --git a/packages/typegpu/src/data/disarray.ts b/packages/typegpu/src/data/disarray.ts index 68cf26317..cfd0b3444 100644 --- a/packages/typegpu/src/data/disarray.ts +++ b/packages/typegpu/src/data/disarray.ts @@ -59,7 +59,7 @@ export const disarrayOf = createDualImpl( // Marking so the WGSL generator lets this function through partial[$internal] = true; - return snip(partial, UnknownData); + return snip(partial, UnknownData, /* ref */ 'runtime'); } if (typeof elementCount.value !== 'number') { @@ -71,6 +71,7 @@ export const disarrayOf = createDualImpl( return snip( cpu_disarrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, + /* ref */ 'runtime', ); }, 'disarrayOf', diff --git a/packages/typegpu/src/data/dualFn.ts b/packages/typegpu/src/data/dualFn.ts index d20846055..e63e3a330 100644 --- a/packages/typegpu/src/data/dualFn.ts +++ b/packages/typegpu/src/data/dualFn.ts @@ -1,13 +1,19 @@ import type { $internal } from '../shared/symbols.ts'; import type { FnArgsConversionHint } from '../types.ts'; +import type { AnyData } from './dataTypes.ts'; import type { MapValueToSnippet, Snippet } from './snippet.ts'; -export type DualFn unknown> = +export type DualFn< + TImpl extends (...args: never[]) => unknown = (...args: never[]) => unknown, +> = & TImpl & { readonly [$internal]: { jsImpl: TImpl; gpuImpl: (...args: MapValueToSnippet>) => Snippet; argConversionHint: FnArgsConversionHint; + strictSignature?: + | { argTypes: AnyData[]; returnType: AnyData } + | undefined; }; }; diff --git a/packages/typegpu/src/data/index.ts b/packages/typegpu/src/data/index.ts index 3fcb13c89..ebab5abc1 100644 --- a/packages/typegpu/src/data/index.ts +++ b/packages/typegpu/src/data/index.ts @@ -2,7 +2,10 @@ * @module typegpu/data */ -import { type InfixOperator, infixOperators } from '../tgsl/wgslGenerator.ts'; +import { + type InfixOperator, + infixOperators, +} from '../tgsl/generationHelpers.ts'; import { $internal } from '../shared/symbols.ts'; import { MatBase } from './matrix.ts'; import { VecBase } from './vectorImpl.ts'; diff --git a/packages/typegpu/src/data/matrix.ts b/packages/typegpu/src/data/matrix.ts index 507231ef9..760331669 100644 --- a/packages/typegpu/src/data/matrix.ts +++ b/packages/typegpu/src/data/matrix.ts @@ -93,7 +93,11 @@ function createMatSchema< }, // CODEGEN implementation (...args) => - snip(stitch`${options.type}(${args})`, schema as unknown as AnyData), + snip( + stitch`${options.type}(${args})`, + schema as unknown as AnyData, + /* ref */ 'runtime', + ), options.type, ); @@ -178,6 +182,7 @@ abstract class mat2x2Impl extends MatBase .join(', ') })`, mat2x2f, + /* ref */ 'runtime', ); } @@ -327,6 +332,7 @@ abstract class mat3x3Impl extends MatBase this[5] }, ${this[6]}, ${this[8]}, ${this[9]}, ${this[10]})`, mat3x3f, + /* ref */ 'runtime', ); } @@ -525,6 +531,7 @@ abstract class mat4x4Impl extends MatBase .join(', ') })`, mat4x4f, + /* ref */ 'runtime', ); } @@ -553,7 +560,7 @@ export const identity2 = createDualImpl( // CPU implementation () => mat2x2f(1, 0, 0, 1), // CODEGEN implementation - () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f), + () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f, /* ref */ 'runtime'), 'identity2', ); @@ -565,7 +572,8 @@ export const identity3 = createDualImpl( // CPU implementation () => mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1), // CODEGEN implementation - () => snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f), + () => + snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f, /* ref */ 'runtime'), 'identity3', ); @@ -578,7 +586,11 @@ export const identity4 = createDualImpl( () => mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), // CODEGEN implementation () => - snip('mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)', mat4x4f), + snip( + 'mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)', + mat4x4f, + /* ref */ 'runtime', + ), 'identity4', ); @@ -608,6 +620,7 @@ export const translation4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ${v}.x, ${v}.y, ${v}.z, 1)`, mat4x4f, + /* ref */ 'runtime', ), 'translation4', ); @@ -632,6 +645,7 @@ export const scaling4 = createDualImpl( snip( stitch`mat4x4f(${v}.x, 0, 0, 0, 0, ${v}.y, 0, 0, 0, 0, ${v}.z, 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ 'runtime', ), 'scaling4', ); @@ -656,6 +670,7 @@ export const rotationX4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ 'runtime', ), 'rotationX4', ); @@ -680,6 +695,7 @@ export const rotationY4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), 0, -sin(${a}), 0, 0, 1, 0, 0, sin(${a}), 0, cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ 'runtime', ), 'rotationY4', ); @@ -704,6 +720,7 @@ export const rotationZ4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ 'runtime', ), 'rotationZ4', ); diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index ed5c28f1a..4fc9c4a64 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -38,7 +38,7 @@ export function ptrHandle( return INTERNAL_createPtr('handle', inner, 'read'); } -function INTERNAL_createPtr< +export function INTERNAL_createPtr< TAddressSpace extends AddressSpace, TInner extends StorableData, TAccess extends Access, @@ -53,5 +53,6 @@ function INTERNAL_createPtr< addressSpace, inner, access, + toString: () => `ptr<${addressSpace}, ${inner}, ${access}>`, } as Ptr; } diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index bd0b65170..8d8b7a982 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -3,6 +3,44 @@ import type { AnyData, UnknownData } from './dataTypes.ts'; import { DEV } from '../shared/env.ts'; import { isNumericSchema } from './wgslTypes.ts'; +export type RefSpace = + | 'uniform' + | 'readonly' // equivalent to ptr + | 'mutable' // equivalent to ptr + | 'workgroup' + | 'private' + | 'function' + | 'handle' + // more specific version of 'function', telling us that the ref is + // to a value defined in the function + | 'this-function' + // not a ref to anything, known at runtime + | 'runtime' + // not a ref to anything, known at pipeline creation time + // (not to be confused with 'comptime') + // note that this doesn't automatically mean the value can be stored in a `const` + // variable, more so that it's valid to do so in WGSL (but not necessarily safe to do in TGSL) + | 'constant' + | 'constant-ref'; + +export function isSpaceRef(space: RefSpace) { + return space !== 'runtime' && space !== 'constant'; +} + +export function isRef(snippet: Snippet) { + return isSpaceRef(snippet.ref); +} + +export const refSpaceToPtrParams = { + uniform: { space: 'uniform', access: 'read' }, + readonly: { space: 'storage', access: 'read' }, + mutable: { space: 'storage', access: 'read-write' }, + workgroup: { space: 'workgroup', access: 'read-write' }, + private: { space: 'private', access: 'read-write' }, + function: { space: 'function', access: 'read-write' }, + 'this-function': { space: 'function', access: 'read-write' }, +} as const; + export interface Snippet { readonly value: unknown; /** @@ -10,6 +48,7 @@ export interface Snippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData | UnknownData; + readonly ref: RefSpace; } export interface ResolvedSnippet { @@ -19,6 +58,7 @@ export interface ResolvedSnippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData; + readonly ref: RefSpace; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; @@ -27,6 +67,7 @@ class SnippetImpl implements Snippet { constructor( readonly value: unknown, readonly dataType: AnyData | UnknownData, + readonly ref: RefSpace, ) {} } @@ -38,11 +79,20 @@ export function isSnippetNumeric(snippet: Snippet) { return isNumericSchema(snippet.dataType); } -export function snip(value: string, dataType: AnyData): ResolvedSnippet; -export function snip(value: unknown, dataType: AnyData | UnknownData): Snippet; +export function snip( + value: string, + dataType: AnyData, + ref: RefSpace, +): ResolvedSnippet; +export function snip( + value: unknown, + dataType: AnyData | UnknownData, + ref: RefSpace, +): Snippet; export function snip( value: unknown, dataType: AnyData | UnknownData, + ref: RefSpace, ): Snippet | ResolvedSnippet { if (DEV && isSnippet(value)) { // An early error, but not worth checking every time in production @@ -53,5 +103,6 @@ export function snip( value, // We don't care about attributes in snippet land, so we discard that information. undecorate(dataType as AnyData), + ref, ); } diff --git a/packages/typegpu/src/data/vector.ts b/packages/typegpu/src/data/vector.ts index 2ced53362..00a256599 100644 --- a/packages/typegpu/src/data/vector.ts +++ b/packages/typegpu/src/data/vector.ts @@ -1,7 +1,7 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; import { $repr } from '../shared/symbols.ts'; -import { type AnyData, undecorate } from './dataTypes.ts'; +import { type AnyData, toStorable } from './dataTypes.ts'; import { bool, f16, f32, i32, u32 } from './numeric.ts'; import { Vec2bImpl, @@ -311,13 +311,19 @@ function makeVecSchema( name: type, signature: (...args) => ({ argTypes: args.map((arg) => { - const argType = undecorate(arg); + const argType = toStorable(arg); return isVec(argType) ? argType : primitive; }), returnType: schema as AnyData, }), normalImpl: cpuConstruct, - codegenImpl: (...args) => stitch`${type}(${args})`, + codegenImpl: (...args) => { + if (args.length === 1 && args[0]?.dataType === schema) { + // Already typed as the schema + return stitch`${args[0]}`; + } + return stitch`${type}(${args})`; + }, }); const schema: diff --git a/packages/typegpu/src/data/vectorImpl.ts b/packages/typegpu/src/data/vectorImpl.ts index 2e58e9cc6..f0b7ce5d5 100644 --- a/packages/typegpu/src/data/vectorImpl.ts +++ b/packages/typegpu/src/data/vectorImpl.ts @@ -41,12 +41,12 @@ export abstract class VecBase extends Array implements SelfResolvable { [$resolve](): ResolvedSnippet { const schema = this[$internal].elementSchema; if (this.every((e) => !e)) { - return snip(`${this.kind}()`, schema); + return snip(`${this.kind}()`, schema, /* ref */ 'constant'); } if (this.every((e) => this[0] === e)) { - return snip(`${this.kind}(${this[0]})`, schema); + return snip(`${this.kind}(${this[0]})`, schema, /* ref */ 'runtime'); } - return snip(`${this.kind}(${this.join(', ')})`, schema); + return snip(`${this.kind}(${this.join(', ')})`, schema, /* ref */ 'runtime'); } toString() { diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index f78bdbcbb..00dc030e6 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1,5 +1,4 @@ import type { TgpuNamable } from '../shared/meta.ts'; -import { isMarkedInternal } from '../shared/symbols.ts'; import type { ExtractInvalidSchemaError, Infer, @@ -24,7 +23,7 @@ import type { $validUniformSchema, $validVertexSchema, } from '../shared/symbols.ts'; -import { $internal } from '../shared/symbols.ts'; +import { $internal, isMarkedInternal } from '../shared/symbols.ts'; import type { Prettify, SwapNever } from '../shared/utilityTypes.ts'; import type { DualFn } from './dualFn.ts'; import type { @@ -1598,7 +1597,8 @@ export type StorableData = | ScalarData | VecData | MatData - | Atomic + | Atomic + | Atomic | WgslArray | WgslStruct; @@ -1898,3 +1898,23 @@ export function isHalfPrecisionSchema( type === 'vec4h') ); } + +const valueTypes = [ + 'abstractInt', + 'abstractFloat', + 'f32', + 'f16', + 'i32', + 'u32', + 'bool', +]; + +/** + * Returns true for schemas that are naturally referential in JS. + * @param schema + * @returns + */ +export function isNaturallyRef(schema: unknown): boolean { + return isMarkedInternal(schema) && + !valueTypes.includes((schema as BaseData)?.type); +} diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index a25252031..a5004d049 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -653,7 +653,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { // If we got here, no item with the given slot-to-value combo exists in cache yet let result: ResolvedSnippet; if (isData(item)) { - result = snip(resolveData(this, item), Void); + // Ref is arbitrary, as we're resolving a schema + result = snip(resolveData(this, item), Void, /* ref */ 'runtime'); } else if (isDerived(item) || isSlot(item)) { result = this.resolve(this.unwrap(item)); } else if (isSelfResolvable(item)) { @@ -732,6 +733,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( `${[...this._declarations].join('\n\n')}${result.value}`, Void, + /* ref */ 'runtime', // arbitrary ); } finally { this.popMode('codegen'); @@ -757,13 +759,13 @@ export class ResolutionCtxImpl implements ResolutionCtx { ); if (reinterpretedType.type === 'abstractInt') { - return snip(`${item}`, realSchema); + return snip(`${item}`, realSchema, /* ref */ 'constant'); } if (reinterpretedType.type === 'u32') { - return snip(`${item}u`, realSchema); + return snip(`${item}u`, realSchema, /* ref */ 'constant'); } if (reinterpretedType.type === 'i32') { - return snip(`${item}i`, realSchema); + return snip(`${item}i`, realSchema, /* ref */ 'constant'); } const exp = item.toExponential(); @@ -775,21 +777,21 @@ export class ResolutionCtxImpl implements ResolutionCtx { // Just picking the shorter one const base = exp.length < decimal.length ? exp : decimal; if (reinterpretedType.type === 'f32') { - return snip(`${base}f`, realSchema); + return snip(`${base}f`, realSchema, /* ref */ 'constant'); } if (reinterpretedType.type === 'f16') { - return snip(`${base}h`, realSchema); + return snip(`${base}h`, realSchema, /* ref */ 'constant'); } - return snip(base, realSchema); + return snip(base, realSchema, /* ref */ 'constant'); } if (typeof item === 'boolean') { - return snip(item ? 'true' : 'false', bool); + return snip(item ? 'true' : 'false', bool, /* ref */ 'constant'); } if (typeof item === 'string') { // Already resolved - return snip(item, Void); + return snip(item, Void, /* ref */ 'runtime'); } if (schema && isWgslArray(schema)) { @@ -808,9 +810,16 @@ export class ResolutionCtxImpl implements ResolutionCtx { const elementTypeString = this.resolve(schema.elementType); return snip( stitch`array<${elementTypeString}, ${schema.elementCount}>(${ - item.map((element) => snip(element, schema.elementType as AnyData)) + item.map((element) => + snip( + element, + schema.elementType as AnyData, + /* ref */ 'runtime', + ) + ) })`, schema, + /* ref */ 'runtime', ); } @@ -818,6 +827,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( stitch`array(${item.map((element) => this.resolve(element))})`, UnknownData, + /* ref */ 'runtime', ) as ResolvedSnippet; } @@ -825,10 +835,15 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( stitch`${this.resolve(schema)}(${ Object.entries(schema.propTypes).map(([key, propType]) => - snip((item as Infer)[key], propType as AnyData) + snip( + (item as Infer)[key], + propType as AnyData, + /* ref */ 'runtime', + ) ) })`, schema, + /* ref */ 'runtime', // a new struct, not referenced from anywhere ); } diff --git a/packages/typegpu/src/std/atomic.ts b/packages/typegpu/src/std/atomic.ts index ee17a07b6..373b0cffd 100644 --- a/packages/typegpu/src/std/atomic.ts +++ b/packages/typegpu/src/std/atomic.ts @@ -16,7 +16,7 @@ export const workgroupBarrier = createDualImpl( // CPU implementation () => console.warn('workgroupBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('workgroupBarrier()', Void), + () => snip('workgroupBarrier()', Void, /* ref */ 'runtime'), 'workgroupBarrier', ); @@ -24,7 +24,7 @@ export const storageBarrier = createDualImpl( // CPU implementation () => console.warn('storageBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('storageBarrier()', Void), + () => snip('storageBarrier()', Void, /* ref */ 'runtime'), 'storageBarrier', ); @@ -32,7 +32,7 @@ export const textureBarrier = createDualImpl( // CPU implementation () => console.warn('textureBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('textureBarrier()', Void), + () => snip('textureBarrier()', Void, /* ref */ 'runtime'), 'textureBarrier', ); @@ -46,7 +46,11 @@ export const atomicLoad = createDualImpl( // CODEGEN implementation (a) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicLoad(&${a})`, a.dataType.inner); + return snip( + stitch`atomicLoad(&${a})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -69,7 +73,11 @@ export const atomicStore = createDualImpl( `Invalid atomic type: ${safeStringify(a.dataType)}`, ); } - return snip(stitch`atomicStore(&${a}, ${value})`, Void); + return snip( + stitch`atomicStore(&${a}, ${value})`, + Void, + /* ref */ 'runtime', + ); }, 'atomicStore', ); @@ -91,7 +99,11 @@ export const atomicAdd = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicAdd(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicAdd(&${a}, ${value})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -111,7 +123,11 @@ export const atomicSub = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicSub(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicSub(&${a}, ${value})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -131,7 +147,11 @@ export const atomicMax = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicMax(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicMax(&${a}, ${value})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -151,7 +171,11 @@ export const atomicMin = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicMin(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicMin(&${a}, ${value})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -171,7 +195,11 @@ export const atomicAnd = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicAnd(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicAnd(&${a}, ${value})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -191,7 +219,11 @@ export const atomicOr = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicOr(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicOr(&${a}, ${value})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -211,7 +243,11 @@ export const atomicXor = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicXor(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicXor(&${a}, ${value})`, + a.dataType.inner, + /* ref */ 'runtime', + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, diff --git a/packages/typegpu/src/std/boolean.ts b/packages/typegpu/src/std/boolean.ts index 973c3f498..f18f6010f 100644 --- a/packages/typegpu/src/std/boolean.ts +++ b/packages/typegpu/src/std/boolean.ts @@ -1,6 +1,6 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import type { AnyData } from '../data/dataTypes.ts'; +import { type AnyData, toStorables } from '../data/dataTypes.ts'; import { bool, f32 } from '../data/numeric.ts'; import { isSnippetNumeric, snip } from '../data/snippet.ts'; import { vec2b, vec3b, vec4b } from '../data/vector.ts'; @@ -19,6 +19,7 @@ import { type v4b, } from '../data/wgslTypes.ts'; import { $internal } from '../shared/symbols.ts'; +import { unify } from '../tgsl/conversion.ts'; import { sub } from './operators.ts'; function correspondingBooleanVectorSchema(dataType: AnyData) { @@ -283,7 +284,11 @@ export const isCloseTo = dualImpl({ return false; }, // GPU implementation - codegenImpl: (lhs, rhs, precision = snip(0.01, f32)) => { + codegenImpl: ( + lhs, + rhs, + precision = snip(0.01, f32, /* ref */ 'constant'), + ) => { if (isSnippetNumeric(lhs) && isSnippetNumeric(rhs)) { return stitch`(abs(f32(${lhs}) - f32(${rhs})) <= ${precision})`; } @@ -333,7 +338,11 @@ function cpuSelect( */ export const select = dualImpl({ name: 'select', - signature: (...argTypes) => ({ argTypes, returnType: argTypes[0] }), + signature: (...args) => { + const [f, t, cond] = toStorables(args); + const [uf, ut] = unify<[AnyData, AnyData]>([f, t]) ?? [f, t]; + return ({ argTypes: [uf, ut, cond], returnType: uf }); + }, normalImpl: cpuSelect, codegenImpl: (f, t, cond) => stitch`select(${f}, ${t}, ${cond})`, }); diff --git a/packages/typegpu/src/std/derivative.ts b/packages/typegpu/src/std/derivative.ts index f29408b28..97d1bfaa6 100644 --- a/packages/typegpu/src/std/derivative.ts +++ b/packages/typegpu/src/std/derivative.ts @@ -11,7 +11,7 @@ function cpuDpdx(value: T): T { export const dpdx = createDualImpl( cpuDpdx, - (value) => snip(stitch`dpdx(${value})`, value.dataType), + (value) => snip(stitch`dpdx(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdx', ); @@ -25,7 +25,8 @@ function cpuDpdxCoarse( export const dpdxCoarse = createDualImpl( cpuDpdxCoarse, - (value) => snip(stitch`dpdxCoarse(${value})`, value.dataType), + (value) => + snip(stitch`dpdxCoarse(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdxCoarse', ); @@ -37,7 +38,8 @@ function cpuDpdxFine(value: T): T { export const dpdxFine = createDualImpl( cpuDpdxFine, - (value) => snip(stitch`dpdxFine(${value})`, value.dataType), + (value) => + snip(stitch`dpdxFine(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdxFine', ); @@ -49,7 +51,7 @@ function cpuDpdy(value: T): T { export const dpdy = createDualImpl( cpuDpdy, - (value) => snip(stitch`dpdy(${value})`, value.dataType), + (value) => snip(stitch`dpdy(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdy', ); @@ -63,7 +65,8 @@ function cpuDpdyCoarse( export const dpdyCoarse = createDualImpl( cpuDpdyCoarse, - (value) => snip(stitch`dpdyCoarse(${value})`, value.dataType), + (value) => + snip(stitch`dpdyCoarse(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdyCoarse', ); @@ -75,7 +78,8 @@ function cpuDpdyFine(value: T): T { export const dpdyFine = createDualImpl( cpuDpdyFine, - (value) => snip(stitch`dpdyFine(${value})`, value.dataType), + (value) => + snip(stitch`dpdyFine(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdyFine', ); @@ -87,7 +91,8 @@ function cpuFwidth(value: T): T { export const fwidth = createDualImpl( cpuFwidth, - (value) => snip(stitch`fwidth(${value})`, value.dataType), + (value) => + snip(stitch`fwidth(${value})`, value.dataType, /* ref */ 'runtime'), 'fwidth', ); @@ -101,7 +106,8 @@ function cpuFwidthCoarse( export const fwidthCoarse = createDualImpl( cpuFwidthCoarse, - (value) => snip(stitch`fwidthCoarse(${value})`, value.dataType), + (value) => + snip(stitch`fwidthCoarse(${value})`, value.dataType, /* ref */ 'runtime'), 'fwidthCoarse', ); @@ -115,6 +121,7 @@ function cpuFwidthFine( export const fwidthFine = createDualImpl( cpuFwidthFine, - (value) => snip(stitch`fwidthFine(${value})`, value.dataType), + (value) => + snip(stitch`fwidthFine(${value})`, value.dataType, /* ref */ 'runtime'), 'fwidthFine', ); diff --git a/packages/typegpu/src/std/discard.ts b/packages/typegpu/src/std/discard.ts index e95977586..2d5e25bcc 100644 --- a/packages/typegpu/src/std/discard.ts +++ b/packages/typegpu/src/std/discard.ts @@ -10,6 +10,6 @@ export const discard = createDualImpl( ); }, // GPU - () => snip('discard;', Void), + () => snip('discard;', Void, /* ref */ 'runtime'), 'discard', ); diff --git a/packages/typegpu/src/std/extensions.ts b/packages/typegpu/src/std/extensions.ts index 78d1829e7..d42c616fc 100644 --- a/packages/typegpu/src/std/extensions.ts +++ b/packages/typegpu/src/std/extensions.ts @@ -28,7 +28,7 @@ export const extensionEnabled: DualFn< `extensionEnabled has to be called with a string literal representing a valid WGSL extension name. Got: ${value}`, ); } - return snip(jsImpl(value as WgslExtension), bool); + return snip(jsImpl(value as WgslExtension), bool, /* ref */ 'constant'); }; const impl = (extensionName: WgslExtension) => { diff --git a/packages/typegpu/src/std/matrix.ts b/packages/typegpu/src/std/matrix.ts index 86e0e169a..ef643521b 100644 --- a/packages/typegpu/src/std/matrix.ts +++ b/packages/typegpu/src/std/matrix.ts @@ -40,7 +40,11 @@ export const translate4 = createDualImpl( (matrix: m4x4f, vector: v3f) => cpuMul(cpuTranslation4(vector), matrix), // GPU implementation (matrix, vector) => - snip(stitch`(${gpuTranslation4(vector)} * ${matrix})`, matrix.dataType), + snip( + stitch`(${gpuTranslation4(vector)} * ${matrix})`, + matrix.dataType, + /* ref */ 'runtime', + ), 'translate4', ); @@ -55,7 +59,11 @@ export const scale4 = createDualImpl( (matrix: m4x4f, vector: v3f) => cpuMul(cpuScaling4(vector), matrix), // GPU implementation (matrix, vector) => - snip(stitch`(${(gpuScaling4(vector))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuScaling4(vector))} * ${matrix})`, + matrix.dataType, + /* ref */ 'runtime', + ), 'scale4', ); @@ -70,7 +78,11 @@ export const rotateX4 = createDualImpl( (matrix: m4x4f, angle: number) => cpuMul(cpuRotationX4(angle), matrix), // GPU implementation (matrix, angle) => - snip(stitch`(${(gpuRotationX4(angle))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuRotationX4(angle))} * ${matrix})`, + matrix.dataType, + /* ref */ 'runtime', + ), 'rotateX4', ); @@ -85,7 +97,11 @@ export const rotateY4 = createDualImpl( (matrix: m4x4f, angle: number) => cpuMul(cpuRotationY4(angle), matrix), // GPU implementation (matrix, angle) => - snip(stitch`(${(gpuRotationY4(angle))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuRotationY4(angle))} * ${matrix})`, + matrix.dataType, + /* ref */ 'runtime', + ), 'rotateY4', ); @@ -100,6 +116,10 @@ export const rotateZ4 = createDualImpl( (matrix: m4x4f, angle: number) => cpuMul(cpuRotationZ4(angle), matrix), // GPU implementation (matrix, angle) => - snip(stitch`(${(gpuRotationZ4(angle))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuRotationZ4(angle))} * ${matrix})`, + matrix.dataType, + /* ref */ 'runtime', + ), 'rotateZ4', ); diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index 4af9353db..2db3ae014 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -4,6 +4,7 @@ import { MissingCpuImplError, } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; +import { type AnyData, toStorable, toStorables } from '../data/dataTypes.ts'; import { smoothstepScalar } from '../data/numberOps.ts'; import { abstractFloat, @@ -64,9 +65,17 @@ function cpuAbs(value: T): T { return VectorOps.abs[value.kind](value) as T; } +const unaryStorableSignature = (arg: AnyData) => { + const sarg = toStorable(arg); + return { + argTypes: [sarg], + returnType: sarg, + }; +}; + export const abs = dualImpl({ name: 'abs', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAbs, codegenImpl: (value) => stitch`abs(${value})`, }); @@ -82,7 +91,7 @@ function cpuAcos(value: T): T { export const acos = dualImpl({ name: 'acos', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAcos, codegenImpl: (value) => stitch`acos(${value})`, }); @@ -98,7 +107,7 @@ function cpuAcosh(value: T): T { export const acosh = dualImpl({ name: 'acosh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAcosh, codegenImpl: (value) => stitch`acosh(${value})`, }); @@ -114,7 +123,7 @@ function cpuAsin(value: T): T { export const asin = dualImpl({ name: 'asin', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAsin, codegenImpl: (value) => stitch`asin(${value})`, }); @@ -130,7 +139,7 @@ function cpuAsinh(value: T): T { export const asinh = dualImpl({ name: 'asinh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAsinh, codegenImpl: (value) => stitch`asinh(${value})`, }); @@ -146,7 +155,7 @@ function cpuAtan(value: T): T { export const atan = dualImpl({ name: 'atan', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAtan, codegenImpl: (value) => stitch`atan(${value})`, }); @@ -162,7 +171,7 @@ function cpuAtanh(value: T): T { export const atanh = dualImpl({ name: 'atanh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAtanh, codegenImpl: (value) => stitch`atanh(${value})`, }); @@ -182,7 +191,8 @@ function cpuAtan2(y: T, x: T): T { export const atan2 = dualImpl({ name: 'atan2', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0], @@ -203,7 +213,7 @@ function cpuCeil(value: T): T { export const ceil = dualImpl({ name: 'ceil', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuCeil, codegenImpl: (value) => stitch`ceil(${value})`, }); @@ -224,7 +234,8 @@ function cpuClamp(value: T, low: T, high: T): T { export const clamp = dualImpl({ name: 'clamp', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return { argTypes: uargs, returnType: uargs[0] }; }, normalImpl: cpuClamp, @@ -242,7 +253,7 @@ function cpuCos(value: T): T { export const cos = dualImpl({ name: 'cos', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuCos, codegenImpl: (value) => stitch`cos(${value})`, }); @@ -258,7 +269,7 @@ function cpuCosh(value: T): T { export const cosh = dualImpl({ name: 'cosh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuCosh, codegenImpl: (value) => stitch`cosh(${value})`, }); @@ -273,7 +284,7 @@ function cpuCountLeadingZeros( export const countLeadingZeros = dualImpl({ name: 'countLeadingZeros', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for countLeadingZeros not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countLeadingZeros(${value})`, @@ -289,7 +300,7 @@ function cpuCountOneBits( export const countOneBits = dualImpl({ name: 'countOneBits', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for countOneBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countOneBits(${value})`, @@ -305,7 +316,7 @@ function cpuCountTrailingZeros( export const countTrailingZeros = dualImpl({ name: 'countTrailingZeros', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for countTrailingZeros not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countTrailingZeros(${value})`, @@ -313,7 +324,10 @@ export const countTrailingZeros = dualImpl({ export const cross = dualImpl({ name: 'cross', - signature: (lhs, rhs) => ({ argTypes: [lhs, rhs], returnType: lhs }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ argTypes: sargs, returnType: sargs[0] }); + }, normalImpl: (a: T, b: T): T => VectorOps.cross[a.kind](a, b), codegenImpl: (a, b) => stitch`cross(${a}, ${b})`, @@ -332,7 +346,7 @@ function cpuDegrees(value: T): T { export const degrees = dualImpl({ name: 'degrees', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuDegrees, codegenImpl: (value) => stitch`degrees(${value})`, }); @@ -340,7 +354,7 @@ export const degrees = dualImpl({ export const determinant = dualImpl<(value: AnyMatInstance) => number>({ name: 'determinant', // TODO: The return type is potentially wrong here, it should return whatever the matrix element type is. - signature: (arg) => ({ argTypes: [arg], returnType: f32 }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for determinant not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`determinant(${value})`, @@ -362,20 +376,26 @@ function cpuDistance( export const distance = dualImpl({ name: 'distance', - signature: (lhs, rhs) => ({ - argTypes: [lhs, rhs], - returnType: isHalfPrecisionSchema(lhs) ? f16 : f32, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: isHalfPrecisionSchema(sargs[0]) ? f16 : f32, + }); + }, normalImpl: cpuDistance, codegenImpl: (a, b) => stitch`distance(${a}, ${b})`, }); export const dot = dualImpl({ name: 'dot', - signature: (e1, e2) => ({ - argTypes: [e1, e2], - returnType: (e1 as VecData).primitive, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: (sargs[0] as VecData).primitive, + }); + }, normalImpl: (lhs: T, rhs: T): number => VectorOps.dot[lhs.kind](lhs, rhs), codegenImpl: (lhs, rhs) => stitch`dot(${lhs}, ${rhs})`, @@ -383,7 +403,7 @@ export const dot = dualImpl({ export const dot4U8Packed = dualImpl<(e1: number, e2: number) => number>({ name: 'dot4U8Packed', - signature: (lhs, rhs) => ({ argTypes: [u32, u32], returnType: u32 }), + signature: { argTypes: [u32, u32], returnType: u32 }, normalImpl: 'CPU implementation for dot4U8Packed not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e1, e2) => stitch`dot4U8Packed(${e1}, ${e2})`, @@ -391,7 +411,7 @@ export const dot4U8Packed = dualImpl<(e1: number, e2: number) => number>({ export const dot4I8Packed = dualImpl<(e1: number, e2: number) => number>({ name: 'dot4I8Packed', - signature: (lhs, rhs) => ({ argTypes: [u32, u32], returnType: i32 }), + signature: { argTypes: [u32, u32], returnType: i32 }, normalImpl: 'CPU implementation for dot4I8Packed not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e1, e2) => stitch`dot4I8Packed(${e1}, ${e2})`, @@ -408,7 +428,7 @@ function cpuExp(value: T): T { export const exp = dualImpl({ name: 'exp', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuExp, codegenImpl: (value) => stitch`exp(${value})`, }); @@ -424,7 +444,7 @@ function cpuExp2(value: T): T { export const exp2 = dualImpl({ name: 'exp2', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuExp2, codegenImpl: (value) => stitch`exp2(${value})`, }); @@ -445,10 +465,13 @@ function cpuExtractBits( export const extractBits = dualImpl({ name: 'extractBits', - signature: (arg, offset, count) => ({ - argTypes: [arg, u32, u32], - returnType: arg, - }), + signature: (arg, _offset, _count) => { + const sarg = toStorable(arg); + return ({ + argTypes: [sarg, u32, u32], + returnType: sarg, + }); + }, normalImpl: 'CPU implementation for extractBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e, offset, count) => @@ -459,10 +482,13 @@ export const faceForward = dualImpl< (e1: T, e2: T, e3: T) => T >({ name: 'faceForward', - signature: (arg1, arg2, arg3) => ({ - argTypes: [arg1, arg2, arg3], - returnType: arg1, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: sargs[0], + }); + }, normalImpl: 'CPU implementation for faceForward not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e1, e2, e3) => stitch`faceForward(${e1}, ${e2}, ${e3})`, @@ -478,7 +504,7 @@ function cpuFirstLeadingBit( export const firstLeadingBit = dualImpl({ name: 'firstLeadingBit', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for firstLeadingBit not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`firstLeadingBit(${value})`, @@ -494,7 +520,7 @@ function cpuFirstTrailingBit( export const firstTrailingBit = dualImpl({ name: 'firstTrailingBit', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for firstTrailingBit not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`firstTrailingBit(${value})`, @@ -511,7 +537,7 @@ function cpuFloor(value: T): T { export const floor = dualImpl({ name: 'floor', - signature: (...argTypes) => ({ argTypes, returnType: argTypes[0] }), + signature: unaryStorableSignature, normalImpl: cpuFloor, codegenImpl: (arg) => stitch`floor(${arg})`, }); @@ -533,10 +559,13 @@ function cpuFma( export const fma = dualImpl({ name: 'fma', - signature: (arg1, arg2, arg3) => ({ - argTypes: [arg1, arg2, arg3], - returnType: arg1, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: sargs[0], + }); + }, normalImpl: cpuFma, codegenImpl: (e1, e2, e3) => stitch`fma(${e1}, ${e2}, ${e3})`, }); @@ -552,7 +581,7 @@ function cpuFract(value: T): T { export const fract = dualImpl({ name: 'fract', - signature: (...argTypes) => ({ argTypes, returnType: argTypes[0] }), + signature: unaryStorableSignature, normalImpl: cpuFract, codegenImpl: (a) => stitch`fract(${a})`, }); @@ -597,7 +626,7 @@ export const frexp: FrexpOverload = createDualImpl( ); } - return snip(stitch`frexp(${value})`, returnType); + return snip(stitch`frexp(${value})`, returnType, /* ref */ 'runtime'); }, 'frexp', ); @@ -625,10 +654,13 @@ function cpuInsertBits( export const insertBits = dualImpl({ name: 'insertBits', - signature: (e, newbits, offset, count) => ({ - argTypes: [e, newbits, u32, u32], - returnType: e, - }), + signature: (e, newbits, _offset, _count) => { + const se = toStorable(e); + return ({ + argTypes: [se, toStorable(newbits), u32, u32], + returnType: se, + }); + }, normalImpl: 'CPU implementation for insertBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e, newbits, offset, count) => @@ -648,7 +680,7 @@ function cpuInverseSqrt(value: T): T { export const inverseSqrt = dualImpl({ name: 'inverseSqrt', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuInverseSqrt, codegenImpl: (value) => stitch`inverseSqrt(${value})`, }); @@ -666,22 +698,23 @@ function cpuLdexp( export const ldexp = dualImpl({ name: 'ldexp', - signature: (e1, e2) => { - switch (e1.type) { + signature: (e1, _e2) => { + const se1 = toStorable(e1); + switch (se1.type) { case 'abstractFloat': - return { argTypes: [abstractFloat, abstractInt], returnType: e1 }; + return { argTypes: [se1, abstractInt], returnType: se1 }; case 'f32': case 'f16': - return { argTypes: [e1, i32], returnType: e1 }; + return { argTypes: [se1, i32], returnType: se1 }; case 'vec2f': case 'vec2h': - return { argTypes: [e1, vec2i], returnType: e1 }; + return { argTypes: [se1, vec2i], returnType: se1 }; case 'vec3f': case 'vec3h': - return { argTypes: [e1, vec3i], returnType: e1 }; + return { argTypes: [se1, vec3i], returnType: se1 }; case 'vec4f': case 'vec4h': - return { argTypes: [e1, vec4i], returnType: e1 }; + return { argTypes: [se1, vec4i], returnType: se1 }; default: throw new Error( `Unsupported data type for ldexp: ${e1.type}. Supported types are abstractFloat, f32, f16, vec2f, vec2h, vec3f, vec3h, vec4f, vec4h.`, @@ -704,10 +737,13 @@ function cpuLength(value: T): number { export const length = dualImpl({ name: 'length', - signature: (arg) => ({ - argTypes: [arg], - returnType: isHalfPrecisionSchema(arg) ? f16 : f32, - }), + signature: (arg) => { + const sarg = toStorable(arg); + return ({ + argTypes: [sarg], + returnType: isHalfPrecisionSchema(sarg) ? f16 : f32, + }); + }, normalImpl: cpuLength, codegenImpl: (arg) => stitch`length(${arg})`, }); @@ -723,7 +759,7 @@ function cpuLog(value: T): T { export const log = dualImpl({ name: 'log', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuLog, codegenImpl: (value) => stitch`log(${value})`, }); @@ -739,7 +775,7 @@ function cpuLog2(value: T): T { export const log2 = dualImpl({ name: 'log2', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuLog2, codegenImpl: (value) => stitch`log2(${value})`, }); @@ -756,7 +792,8 @@ function cpuMax(a: T, b: T): T { export const max = dualImpl({ name: 'max', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0], @@ -778,7 +815,8 @@ function cpuMin(a: T, b: T): T { export const min = dualImpl({ name: 'min', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0], @@ -814,7 +852,14 @@ function cpuMix( export const mix = dualImpl({ name: 'mix', - signature: (e1, e2, e3) => ({ argTypes: [e1, e2, e3], returnType: e1 }), + signature: (...args) => { + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; + return ({ + argTypes: uargs, + returnType: uargs[0], + }); + }, normalImpl: cpuMix, codegenImpl: (e1, e2, e3) => stitch`mix(${e1}, ${e2}, ${e3})`, }); @@ -850,15 +895,16 @@ function cpuModf( export const modf: ModfOverload = dualImpl({ name: 'modf', signature: (e) => { - const returnType = ModfResult[e.type as keyof typeof ModfResult]; + const se = toStorable(e); + const returnType = ModfResult[se.type as keyof typeof ModfResult]; if (!returnType) { throw new Error( - `Unsupported data type for modf: ${e.type}. Supported types are f32, f16, abstractFloat, vec2f, vec3f, vec4f, vec2h, vec3h, vec4h.`, + `Unsupported data type for modf: ${se.type}. Supported types are f32, f16, abstractFloat, vec2f, vec3f, vec4f, vec2h, vec3h, vec4h.`, ); } - return { argTypes: [e], returnType }; + return { argTypes: [se], returnType }; }, normalImpl: 'CPU implementation for modf not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', @@ -867,7 +913,7 @@ export const modf: ModfOverload = dualImpl({ export const normalize = dualImpl({ name: 'normalize', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: (v: T): T => VectorOps.normalize[v.kind](v), codegenImpl: (v) => stitch`normalize(${v})`, @@ -894,7 +940,8 @@ function powCpu( export const pow = dualImpl({ name: 'pow', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -913,7 +960,7 @@ function cpuQuantizeToF16( export const quantizeToF16 = dualImpl({ name: 'quantizeToF16', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for quantizeToF16 not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`quantizeToF16(${value})`, @@ -933,7 +980,8 @@ function cpuRadians(value: T): T { export const radians = dualImpl({ name: 'radians', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0] }); }, normalImpl: cpuRadians, @@ -942,7 +990,10 @@ export const radians = dualImpl({ export const reflect = dualImpl({ name: 'reflect', - signature: (lhs, rhs) => ({ argTypes: [lhs, rhs], returnType: lhs }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ argTypes: sargs, returnType: sargs[0] }); + }, normalImpl: (e1: T, e2: T): T => sub(e1, mul(2 * dot(e2, e1), e2)), codegenImpl: (e1, e2) => stitch`reflect(${e1}, ${e2})`, @@ -956,7 +1007,12 @@ export const refract = createDualImpl( ); }, // GPU implementation - (e1, e2, e3) => snip(stitch`refract(${e1}, ${e2}, ${e3})`, e1.dataType), + (e1, e2, e3) => + snip( + stitch`refract(${e1}, ${e2}, ${e3})`, + e1.dataType, + /* ref */ 'runtime', + ), 'refract', (e1, e2, e3) => [ e1.dataType as AnyWgslData, @@ -972,7 +1028,7 @@ function cpuReverseBits(value: T): T { export const reverseBits = dualImpl({ name: 'reverseBits', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for reverseBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`reverseBits(${value})`, @@ -991,7 +1047,7 @@ function cpuRound(value: T): T { export const round = dualImpl({ name: 'round', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuRound, codegenImpl: (value) => stitch`round(${value})`, }); @@ -1009,7 +1065,7 @@ function cpuSaturate(value: T): T { export const saturate = dualImpl({ name: 'saturate', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSaturate, codegenImpl: (value) => stitch`saturate(${value})`, }); @@ -1025,7 +1081,7 @@ function cpuSign(e: T): T { export const sign = dualImpl({ name: 'sign', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSign, codegenImpl: (e) => stitch`sign(${e})`, }); @@ -1041,7 +1097,7 @@ function cpuSin(value: T): T { export const sin = dualImpl({ name: 'sin', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSin, codegenImpl: (value) => stitch`sin(${value})`, }); @@ -1059,7 +1115,7 @@ function cpuSinh(value: T): T { export const sinh = dualImpl({ name: 'sinh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSinh, codegenImpl: (value) => stitch`sinh(${value})`, }); @@ -1091,10 +1147,13 @@ function cpuSmoothstep( export const smoothstep = dualImpl({ name: 'smoothstep', - signature: (edge0, edge1, x) => ({ - argTypes: [edge0, edge1, x], - returnType: x, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: sargs[2], + }); + }, normalImpl: cpuSmoothstep, codegenImpl: (edge0, edge1, x) => stitch`smoothstep(${edge0}, ${edge1}, ${x})`, @@ -1111,7 +1170,7 @@ function cpuSqrt(value: T): T { export const sqrt = dualImpl({ name: 'sqrt', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSqrt, codegenImpl: (value) => stitch`sqrt(${value})`, }); @@ -1130,7 +1189,8 @@ function cpuStep(edge: T, x: T): T { export const step = dualImpl({ name: 'step', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return { argTypes: uargs, returnType: uargs[0] }; }, normalImpl: cpuStep, @@ -1150,7 +1210,7 @@ function cpuTan(value: T): T { export const tan = dualImpl({ name: 'tan', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuTan, codegenImpl: (value) => stitch`tan(${value})`, }); @@ -1166,14 +1226,14 @@ function cpuTanh(value: T): T { export const tanh = dualImpl({ name: 'tanh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuTanh, codegenImpl: (value) => stitch`tanh(${value})`, }); export const transpose = dualImpl<(e: T) => T>({ name: 'transpose', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for transpose not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e) => stitch`transpose(${e})`, @@ -1187,7 +1247,7 @@ function cpuTrunc(value: T): T { export const trunc = dualImpl({ name: 'trunc', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for trunc not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`trunc(${value})`, diff --git a/packages/typegpu/src/std/operators.ts b/packages/typegpu/src/std/operators.ts index f050d3687..89893de63 100644 --- a/packages/typegpu/src/std/operators.ts +++ b/packages/typegpu/src/std/operators.ts @@ -1,5 +1,6 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch, stitchWithExactTypes } from '../core/resolve/stitch.ts'; +import { toStorable, toStorables } from '../data/dataTypes.ts'; import { abstractFloat, f16, f32 } from '../data/numeric.ts'; import { vecTypeToConstructor } from '../data/vector.ts'; import { VectorOps } from '../data/vectorOps.ts'; @@ -54,7 +55,8 @@ function cpuAdd(lhs: number | NumVec | Mat, rhs: number | NumVec | Mat) { export const add = dualImpl({ name: 'add', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -138,7 +140,8 @@ function cpuMul(lhs: number | NumVec | Mat, rhs: number | NumVec | Mat) { export const mul = dualImpl({ name: 'mul', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; const returnType = isNumericSchema(uargs[0]) // Scalar * Scalar/Vector/Matrix ? uargs[1] @@ -185,7 +188,8 @@ function cpuDiv(lhs: NumVec | number, rhs: NumVec | number): NumVec | number { export const div = dualImpl({ name: 'div', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return ({ argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -210,7 +214,8 @@ type ModOverload = { export const mod: ModOverload = dualImpl({ name: 'mod', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -253,7 +258,13 @@ function cpuNeg(value: NumVec | number): NumVec | number { export const neg = dualImpl({ name: 'neg', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: (arg) => { + const sarg = toStorable(arg); + return { + argTypes: [sarg], + returnType: sarg, + }; + }, normalImpl: cpuNeg, codegenImpl: (arg) => stitch`-(${arg})`, }); diff --git a/packages/typegpu/src/std/packing.ts b/packages/typegpu/src/std/packing.ts index af12a7452..6f7a88e9d 100644 --- a/packages/typegpu/src/std/packing.ts +++ b/packages/typegpu/src/std/packing.ts @@ -20,7 +20,7 @@ export const unpack2x16float = createDualImpl( return vec2f(reader.readFloat16(), reader.readFloat16()); }, // GPU implementation - (e) => snip(stitch`unpack2x16float(${e})`, vec2f), + (e) => snip(stitch`unpack2x16float(${e})`, vec2f, /* ref */ 'runtime'), 'unpack2x16float', ); @@ -39,7 +39,7 @@ export const pack2x16float = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack2x16float(${e})`, u32), + (e) => snip(stitch`pack2x16float(${e})`, u32, /* ref */ 'runtime'), 'pack2x16float', ); @@ -62,7 +62,7 @@ export const unpack4x8unorm = createDualImpl( ); }, // GPU implementation - (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f), + (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f, /* ref */ 'runtime'), 'unpack4x8unorm', ); @@ -83,6 +83,6 @@ export const pack4x8unorm = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack4x8unorm(${e})`, u32), + (e) => snip(stitch`pack4x8unorm(${e})`, u32, /* ref */ 'runtime'), 'pack4x8unorm', ); diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index d49143472..de90012ca 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -38,10 +38,8 @@ export class LogGeneratorNullImpl implements LogGenerator { return undefined; } generateLog(): Snippet { - console.warn( - "'console.log' is currently only supported in compute pipelines.", - ); - return snip('/* console.log() */', Void); + console.warn("'console.log' is only supported when resolving pipelines."); + return snip('/* console.log() */', Void, /* ref */ 'runtime'); } } @@ -101,7 +99,11 @@ export class LogGeneratorImpl implements LogGenerator { ), ); - return snip(stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, Void); + return snip( + stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, + Void, + /* ref */ 'runtime', + ); } get logResources(): LogResources | undefined { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index a4502c9f3..5d2cfcde2 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -229,14 +229,20 @@ function applyActionToSnippet( targetType: AnyData, ): Snippet { if (action.action === 'none') { - return snip(snippet.value, targetType); + return snip( + snippet.value, + targetType, + // if it was a ref, then it's still a ref + /* ref */ snippet.ref, + ); } switch (action.action) { case 'ref': - return snip(stitch`&${snippet}`, targetType); + return snip(stitch`(&${snippet})`, targetType, /* ref */ snippet.ref); case 'deref': - return snip(stitch`*${snippet}`, targetType); + // Dereferencing a pointer does not return a copy of the value, it's still a reference. + return snip(stitch`(*${snippet})`, targetType, /* ref */ snippet.ref); case 'cast': { // Casting means calling the schema with the snippet as an argument. return (targetType as unknown as (val: Snippet) => Snippet)(snippet); @@ -313,19 +319,25 @@ export function tryConvertSnippet( verbose = true, ): Snippet { if (targetDataType === snippet.dataType) { - return snip(snippet.value, targetDataType); + return snip(snippet.value, targetDataType, /* ref */ snippet.ref); } if (snippet.dataType.type === 'unknown') { // This is it, it's now or never. We expect a specific type, and we're going to get it - return snip(stitch`${snip(snippet.value, targetDataType)}`, targetDataType); + return snip( + stitch`${snip(snippet.value, targetDataType, /* ref */ snippet.ref)}`, + targetDataType, + /* ref */ snippet.ref, + ); } const converted = convertToCommonType([snippet], [targetDataType], verbose); if (!converted) { throw new WgslTypeError( - `Cannot convert value of type '${snippet.dataType.type}' to type '${targetDataType.type}'`, + `Cannot convert value of type '${ + String(snippet.dataType) + }' to type '${targetDataType.type}'`, ); } diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 6022c4525..642660735 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -1,7 +1,10 @@ import { type AnyData, + InfixDispatch, isDisarray, isUnstruct, + MatrixColumnsAccess, + undecorate, UnknownData, } from '../data/dataTypes.ts'; import { mat2x2f, mat3x3f, mat4x4f } from '../data/matrix.ts'; @@ -14,7 +17,7 @@ import { i32, u32, } from '../data/numeric.ts'; -import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { isRef, isSnippet, snip, type Snippet } from '../data/snippet.ts'; import { vec2b, vec2f, @@ -36,15 +39,23 @@ import { type AnyWgslData, type F32, type I32, + isMat, isMatInstance, - isNumericSchema, + isNaturallyRef, isVec, isVecInstance, isWgslArray, isWgslStruct, } from '../data/wgslTypes.ts'; -import { getOwnSnippet, type ResolutionCtx } from '../types.ts'; +import { + getOwnSnippet, + isKnownAtComptime, + type ResolutionCtx, +} from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; +import { add, div, mul, sub } from '../std/operators.ts'; +import { $internal } from '../shared/symbols.ts'; +import { stitch } from '../core/resolve/stitch.ts'; type SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b'; type SwizzleLength = 1 | 2 | 3 | 4; @@ -106,36 +117,129 @@ const kindToSchema = { mat4x4f: mat4x4f, } as const; -export function getTypeForPropAccess( - targetType: AnyData, +const infixKinds = [ + 'vec2f', + 'vec3f', + 'vec4f', + 'vec2h', + 'vec3h', + 'vec4h', + 'vec2i', + 'vec3i', + 'vec4i', + 'vec2u', + 'vec3u', + 'vec4u', + 'mat2x2f', + 'mat3x3f', + 'mat4x4f', +]; + +export const infixOperators = { + add, + sub, + mul, + div, +} as const; + +export type InfixOperator = keyof typeof infixOperators; + +export function accessProp( + target: Snippet, propName: string, -): AnyData | UnknownData { - if (isWgslStruct(targetType) || isUnstruct(targetType)) { - return targetType.propTypes[propName] as AnyData ?? UnknownData; +): Snippet | undefined { + if ( + infixKinds.includes(target.dataType.type) && + propName in infixOperators + ) { + return snip( + new InfixDispatch( + propName, + target, + infixOperators[propName as InfixOperator][$internal].gpuImpl, + ), + UnknownData, + /* ref */ target.ref, + ); } - if (targetType === bool || isNumericSchema(targetType)) { - // No props to be accessed here - return UnknownData; + if (isWgslArray(target.dataType) && propName === 'length') { + if (target.dataType.elementCount === 0) { + // Dynamically-sized array + return snip( + stitch`arrayLength(&${target})`, + u32, + /* ref */ 'runtime', + ); + } + + return snip( + target.dataType.elementCount, + abstractInt, + /* ref */ 'constant', + ); + } + + if (isMat(target.dataType) && propName === 'columns') { + return snip( + new MatrixColumnsAccess(target), + UnknownData, + /* ref */ target.ref, + ); + } + + if (isWgslStruct(target.dataType) || isUnstruct(target.dataType)) { + let propType = target.dataType.propTypes[propName]; + if (!propType) { + return undefined; + } + propType = undecorate(propType); + + return snip( + stitch`${target}.${propName}`, + propType, + /* ref */ isRef(target) && isNaturallyRef(propType) + ? target.ref + : target.ref === 'constant' || target.ref === 'constant-ref' + ? 'constant' + : 'runtime', + ); } const propLength = propName.length; if ( - isVec(targetType) && + isVec(target.dataType) && propLength >= 1 && propLength <= 4 ) { - const swizzleTypeChar = targetType.type.includes('bool') + const swizzleTypeChar = target.dataType.type.includes('bool') ? 'b' - : (targetType.type[4] as SwizzleableType); + : (target.dataType.type[4] as SwizzleableType); const swizzleType = swizzleLenToType[swizzleTypeChar][propLength as SwizzleLength]; - if (swizzleType) { - return swizzleType; + if (!swizzleType) { + return undefined; } + + return snip( + isKnownAtComptime(target) + // biome-ignore lint/suspicious/noExplicitAny: it's fine, the prop is there + ? (target.value as any)[propName] + : stitch`${target}.${propName}`, + swizzleType, + // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) + /* ref */ target.ref === 'constant' || target.ref === 'constant-ref' + ? 'constant' + : 'runtime', + ); + } + + if (isKnownAtComptime(target) || target.dataType.type === 'unknown') { + // biome-ignore lint/suspicious/noExplicitAny: we either know exactly what it is, or have no idea at all + return coerceToSnippet((target.value as any)[propName]); } - return UnknownData; + return undefined; } const indexableTypeToResult = { @@ -144,27 +248,74 @@ const indexableTypeToResult = { mat4x4f: vec4f, } as const; -export function getTypeForIndexAccess( - dataType: AnyData, -): AnyData | UnknownData { +export function accessIndex( + target: Snippet, + index: Snippet, +): Snippet | undefined { // array - if (isWgslArray(dataType) || isDisarray(dataType)) { - return dataType.elementType as AnyData; + if (isWgslArray(target.dataType) || isDisarray(target.dataType)) { + const elementType = target.dataType.elementType as AnyData; + + return snip( + isKnownAtComptime(target) && isKnownAtComptime(index) + // biome-ignore lint/suspicious/noExplicitAny: it's fine, it's there + ? (target.value as any)[index.value as number] + : stitch`${target}[${index}]`, + elementType, + /* ref */ isRef(target) && isNaturallyRef(elementType) + ? target.ref + : target.ref === 'constant' || target.ref === 'constant-ref' + ? 'constant' + : 'runtime', + ); } // vector - if (isVec(dataType)) { - return dataType.primitive; + if (isVec(target.dataType)) { + return snip( + isKnownAtComptime(target) && isKnownAtComptime(index) + // biome-ignore lint/suspicious/noExplicitAny: it's fine, it's there + ? (target.value as any)[index.value as any] + : stitch`${target}[${index}]`, + target.dataType.primitive, + /* ref */ target.ref === 'constant' || target.ref === 'constant-ref' + ? 'constant' + : 'runtime', + ); } - // matrix - if (dataType.type in indexableTypeToResult) { - return indexableTypeToResult[ - dataType.type as keyof typeof indexableTypeToResult + // matrix.columns + if (target.value instanceof MatrixColumnsAccess) { + const propType = indexableTypeToResult[ + target.value.matrix.dataType.type as keyof typeof indexableTypeToResult ]; + + return snip( + stitch`${target.value.matrix}[${index}]`, + propType, + /* ref */ target.ref, + ); + } + + // matrix + if (target.dataType.type in indexableTypeToResult) { + throw new Error( + "The only way of accessing matrix elements in TGSL is through the 'columns' property.", + ); + } + + if ( + (isKnownAtComptime(target) && isKnownAtComptime(index)) || + target.dataType.type === 'unknown' + ) { + // No idea what the type is, so we act on the snippet's value and try to guess + return coerceToSnippet( + // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value + (target.value as any)[index.value as number], + ); } - return UnknownData; + return undefined; } export function numericLiteralToSnippet(value: number): Snippet { @@ -176,9 +327,9 @@ export function numericLiteralToSnippet(value: number): Snippet { `The integer ${value} exceeds the safe integer range and may have lost precision.`, ); } - return snip(value, abstractInt); + return snip(value, abstractInt, /* ref */ 'constant'); } - return snip(value, abstractFloat); + return snip(value, abstractFloat, /* ref */ 'constant'); } export function concretize(type: T): T | F32 | I32 { @@ -195,7 +346,11 @@ export function concretize(type: T): T | F32 | I32 { export function concretizeSnippets(args: Snippet[]): Snippet[] { return args.map((snippet) => - snip(snippet.value, concretize(snippet.dataType as AnyWgslData)) + snip( + snippet.value, + concretize(snippet.dataType as AnyWgslData), + /* ref */ snippet.ref, + ) ); } @@ -243,7 +398,7 @@ export function coerceToSnippet(value: unknown): Snippet { } if (isVecInstance(value) || isMatInstance(value)) { - return snip(value, kindToSchema[value.kind]); + return snip(value, kindToSchema[value.kind], /* ref */ 'constant'); } if ( @@ -252,7 +407,7 @@ export function coerceToSnippet(value: unknown): Snippet { typeof value === 'undefined' || value === null ) { // Nothing representable in WGSL as-is, so unknown - return snip(value, UnknownData); + return snip(value, UnknownData, /* ref */ 'constant'); } if (typeof value === 'number') { @@ -260,8 +415,8 @@ export function coerceToSnippet(value: unknown): Snippet { } if (typeof value === 'boolean') { - return snip(value, bool); + return snip(value, bool, /* ref */ 'constant'); } - return snip(value, UnknownData); + return snip(value, UnknownData, /* ref */ 'constant'); } diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 04b7e3ccd..9380fa737 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -3,19 +3,35 @@ import { type ShelllessImpl, } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; -import type { Snippet } from '../data/snippet.ts'; +import { INTERNAL_createPtr } from '../data/ptr.ts'; +import { refSpaceToPtrParams, type Snippet } from '../data/snippet.ts'; +import { isPtr, type StorableData } from '../data/wgslTypes.ts'; +import { getResolutionCtx } from '../execMode.ts'; import { getMetaData, getName } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; -interface ShelllessVariant { - argTypes: AnyData[]; - value: ShelllessImpl; -} - type AnyFn = (...args: never[]) => unknown; +function shallowEqualSchemas(a: AnyData, b: AnyData): boolean { + if (a.type !== b.type) return false; + if (a.type === 'ptr' && b.type === 'ptr') { + return a.access === b.access && + a.addressSpace === b.addressSpace && + shallowEqualSchemas(a.inner, b.inner); + } + if (a.type === 'array' && b.type === 'array') { + return a.elementCount === b.elementCount && + shallowEqualSchemas(a.elementType as AnyData, b.elementType as AnyData); + } + if (a.type === 'struct' && b.type === 'struct') { + // Only structs with the same identity are considered equal + return a === b; + } + return true; +} + export class ShelllessRepository { - cache = new Map(); + cache = new Map(); get( fn: AnyFn, @@ -31,23 +47,49 @@ export class ShelllessRepository { ); } - const argTypes = (argSnippets ?? []).map((s) => - concretize(s.dataType as AnyData) - ); + const argTypes = (argSnippets ?? []).map((s) => { + const type = concretize(s.dataType as AnyData); + const ptrParams = s.ref in refSpaceToPtrParams + ? refSpaceToPtrParams[s.ref as keyof typeof refSpaceToPtrParams] + : undefined; + + if (s.ref === 'constant-ref') { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + throw new Error( + `Cannot pass constant references as function arguments. Explicitly copy them by wrapping them in a schema: '${ + ctx.resolve(type).value + }(...)'`, + ); + } + + return ptrParams !== undefined && !isPtr(type) + ? INTERNAL_createPtr( + ptrParams.space, + type as StorableData, + ptrParams.access, + ) + : type; + }); let cache = this.cache.get(fn); if (cache) { const variant = cache.find((v) => - v.argTypes.every((t, i) => t === argTypes[i]) + v.argTypes.length === argTypes.length && + v.argTypes.every((t, i) => + shallowEqualSchemas(t, argTypes[i] as AnyData) + ) ); - if (variant) return variant.value; + if (variant) { + return variant; + } } else { cache = []; this.cache.set(fn, cache); } const shellless = createShelllessImpl(argTypes, fn); - cache.push({ argTypes, value: shellless }); + cache.push(shellless); return shellless; } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 7564ca2d7..c76009415 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -5,13 +5,19 @@ import { type AnyData, ConsoleLog, InfixDispatch, - isData, isLooseData, - MatrixColumnsAccess, + toStorable, UnknownData, } from '../data/dataTypes.ts'; -import { abstractInt, bool, u32 } from '../data/numeric.ts'; -import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { bool, i32, u32 } from '../data/numeric.ts'; +import { + isRef, + isSnippet, + isSpaceRef, + type RefSpace, + snip, + type Snippet, +} from '../data/snippet.ts'; import * as wgsl from '../data/wgslTypes.ts'; import { invariant, ResolutionError, WgslTypeError } from '../errors.ts'; import { getName } from '../shared/meta.ts'; @@ -27,14 +33,15 @@ import { tryConvertSnippet, } from './conversion.ts'; import { - coerceToSnippet, + accessIndex, + accessProp, concretize, type GenerationCtx, - getTypeForIndexAccess, - getTypeForPropAccess, numericLiteralToSnippet, } from './generationHelpers.ts'; import type { ShaderGenerator } from './shaderGenerator.ts'; +import type { DualFn } from '../data/dualFn.ts'; +import { ptrFn } from '../data/ptr.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -61,33 +68,6 @@ const parenthesizedOps = [ const binaryLogicalOps = ['&&', '||', '==', '!=', '<', '<=', '>', '>=']; -const infixKinds = [ - 'vec2f', - 'vec3f', - 'vec4f', - 'vec2h', - 'vec3h', - 'vec4h', - 'vec2i', - 'vec3i', - 'vec4i', - 'vec2u', - 'vec3u', - 'vec4u', - 'mat2x2f', - 'mat3x3f', - 'mat4x4f', -]; - -export const infixOperators = { - add, - sub, - mul, - div, -} as const; - -export type InfixOperator = keyof typeof infixOperators; - type Operator = | tinyest.BinaryOperator | tinyest.AssignmentOperator @@ -161,10 +141,27 @@ ${this.ctx.pre}}`; } public blockVariable( + varType: 'var' | 'let' | 'const', id: string, dataType: wgsl.AnyWgslData | UnknownData, + ref: RefSpace, ): Snippet { - const snippet = snip(this.ctx.makeNameValid(id), dataType); + let varRef: RefSpace = 'runtime'; + if (ref === 'constant-ref') { + // Even types that aren't naturally referential (like vectors or structs) should + // be treated as constant references when assigned to a const. + varRef = 'constant-ref'; + } else if (wgsl.isNaturallyRef(dataType)) { + varRef = isSpaceRef(ref) ? ref : 'this-function'; + } else if (ref === 'constant' && varType === 'const') { + varRef = 'constant'; + } + + const snippet = snip( + this.ctx.makeNameValid(id), + dataType, + /* ref */ varRef, + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -209,7 +206,7 @@ ${this.ctx.pre}}`; } if (typeof expression === 'boolean') { - return snip(expression, bool); + return snip(expression, bool, /* ref */ 'constant'); } if ( @@ -218,19 +215,27 @@ ${this.ctx.pre}}`; expression[0] === NODE.assignmentExpr ) { // Logical/Binary/Assignment Expression - const [_, lhs, op, rhs] = expression; + const [exprType, lhs, op, rhs] = expression; const lhsExpr = this.expression(lhs); const rhsExpr = this.expression(rhs); + if (lhsExpr.dataType.type === 'unknown') { + throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`); + } + + if (rhsExpr.dataType.type === 'unknown') { + throw new WgslTypeError( + `Right-hand side of '${op}' is of unknown type`, + ); + } + const codegen = opCodeToCodegen[op as keyof typeof opCodeToCodegen]; if (codegen) { return codegen(lhsExpr, rhsExpr); } - const forcedType = expression[0] === NODE.assignmentExpr - ? lhsExpr.dataType.type === 'ptr' - ? [lhsExpr.dataType.inner as AnyData] - : [lhsExpr.dataType as AnyData] + const forcedType = exprType === NODE.assignmentExpr + ? [toStorable(lhsExpr.dataType)] : undefined; const [convLhs, convRhs] = @@ -241,11 +246,29 @@ ${this.ctx.pre}}`; const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value; const type = operatorToType(convLhs.dataType, op, convRhs.dataType); + if (exprType === NODE.assignmentExpr) { + if (convLhs.ref === 'constant' || convLhs.ref === 'constant-ref') { + throw new WgslTypeError( + `'${lhsStr} = ${rhsStr}' is invalid, because ${lhsStr} is a constant.`, + ); + } + + if (isRef(rhsExpr)) { + throw new WgslTypeError( + `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ + this.ctx.resolve(rhsExpr.dataType).value + }(${rhsStr})' instead.\n-----`, + ); + } + } + return snip( parenthesizedOps.includes(op) ? `(${lhsStr} ${op} ${rhsStr})` : `${lhsStr} ${op} ${rhsStr}`, type, + // Result of an operation, so not a reference to anything + /* ref */ 'runtime', ); } @@ -255,7 +278,8 @@ ${this.ctx.pre}}`; const argExpr = this.expression(arg); const argStr = this.ctx.resolve(argExpr.value).value; - return snip(`${argStr}${op}`, argExpr.dataType); + // Result of an operation, so not a reference to anything + return snip(`${argStr}${op}`, argExpr.dataType, /* ref */ 'runtime'); } if (expression[0] === NODE.unaryExpr) { @@ -265,131 +289,63 @@ ${this.ctx.pre}}`; const argStr = this.ctx.resolve(argExpr.value).value; const type = operatorToType(argExpr.dataType, op); - return snip(`${op}${argStr}`, type); + // Result of an operation, so not a reference to anything + return snip(`${op}${argStr}`, type, /* ref */ 'runtime'); } if (expression[0] === NODE.memberAccess) { // Member Access const [_, targetNode, property] = expression; - const target = this.expression(targetNode); + let target = this.expression(targetNode); if (target.value === console) { - return snip(new ConsoleLog(), UnknownData); - } - - if ( - infixKinds.includes(target.dataType.type) && - property in infixOperators - ) { - return { - value: new InfixDispatch( - property, - target, - infixOperators[property as InfixOperator][$internal].gpuImpl, - ), - dataType: UnknownData, - }; - } - - if (target.dataType.type === 'unknown') { - // No idea what the type is, so we act on the snippet's value and try to guess - - // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value - const propValue = (target.value as any)[property]; - - // We try to extract any type information based on the prop's value - return coerceToSnippet(propValue); + return snip(new ConsoleLog(), UnknownData, /* ref */ 'runtime'); } if (wgsl.isPtr(target.dataType)) { - return snip( - `(*${this.ctx.resolve(target.value).value}).${property}`, - getTypeForPropAccess(target.dataType.inner as AnyData, property), - ); - } - - if (wgsl.isWgslArray(target.dataType) && property === 'length') { - if (target.dataType.elementCount === 0) { - // Dynamically-sized array - return snip( - `arrayLength(&${this.ctx.resolve(target.value).value})`, - u32, - ); - } - - return snip(String(target.dataType.elementCount), abstractInt); - } - - if (wgsl.isMat(target.dataType) && property === 'columns') { - return snip(new MatrixColumnsAccess(target), UnknownData); + // De-referencing the pointer + target = tryConvertSnippet(target, target.dataType.inner, false); } - if ( - wgsl.isVec(target.dataType) && wgsl.isVecInstance(target.value) - ) { - // We're operating on a vector that's known at resolution time - // biome-ignore lint/suspicious/noExplicitAny: it's probably a swizzle - return coerceToSnippet((target.value as any)[property]); + const accessed = accessProp(target, property); + if (!accessed) { + throw new Error( + `Property '${property}' not found on type ${ + this.ctx.resolve(target.dataType) + }`, + ); } - - return snip( - `${this.ctx.resolve(target.value).value}.${property}`, - getTypeForPropAccess(target.dataType, property), - ); + return accessed; } if (expression[0] === NODE.indexAccess) { // Index Access const [_, targetNode, propertyNode] = expression; - const target = this.expression(targetNode); - const property = this.expression(propertyNode); - const propertyStr = - this.ctx.resolve(property.value, property.dataType).value; + let target = this.expression(targetNode); + const inProperty = this.expression(propertyNode); + const property = convertToCommonType( + [inProperty], + [u32, i32], + /* verbose */ false, + )?.[0] ?? inProperty; - if (target.value instanceof MatrixColumnsAccess) { - return snip( - stitch`${target.value.matrix}[${propertyStr}]`, - getTypeForIndexAccess(target.value.matrix.dataType as AnyData), - ); + if (wgsl.isPtr(target.dataType)) { + // De-referencing the pointer + target = tryConvertSnippet(target, target.dataType.inner, false); } - const targetStr = this.ctx.resolve(target.value, target.dataType).value; - - if (target.dataType.type === 'unknown') { - // No idea what the type is, so we act on the snippet's value and try to guess - - if ( - Array.isArray(propertyNode) && propertyNode[0] === NODE.numericLiteral - ) { - return coerceToSnippet( - // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value - (target.value as any)[propertyNode[1] as number], - ); - } - throw new Error( - `Cannot index value ${targetStr} of unknown type with index ${propertyStr}`, - ); - } + const accessed = accessIndex(target, property); + if (!accessed) { + const targetStr = this.ctx.resolve(target.value, target.dataType).value; + const propertyStr = + this.ctx.resolve(property.value, property.dataType).value; - if (wgsl.isMat(target.dataType)) { throw new Error( - "The only way of accessing matrix elements in TGSL is through the 'columns' property.", + `Cannot index value ${targetStr} with index ${propertyStr}`, ); } - if (wgsl.isPtr(target.dataType)) { - return snip( - `(*${targetStr})[${propertyStr}]`, - getTypeForIndexAccess(target.dataType.inner as AnyData), - ); - } - - return snip( - `${targetStr}[${propertyStr}]`, - isData(target.dataType) - ? getTypeForIndexAccess(target.dataType) - : UnknownData, - ); + return accessed; } if (expression[0] === NODE.numericLiteral) { @@ -422,6 +378,8 @@ ${this.ctx.pre}}`; return snip( `${this.ctx.resolve(callee.value).value}()`, callee.value, + // A new struct, so not a reference + /* ref */ 'runtime', ); } @@ -435,6 +393,8 @@ ${this.ctx.pre}}`; return snip( this.ctx.resolve(arg.value, callee.value).value, callee.value, + // A new struct, so not a reference + /* ref */ 'runtime', ); } @@ -456,9 +416,18 @@ ${this.ctx.pre}}`; args, ); if (shellless) { + const converted = args.map((s, idx) => { + const argType = shellless.argTypes[idx] as AnyData; + return tryConvertSnippet(s, argType, /* verbose */ false); + }); + return this.ctx.withResetIndentLevel(() => { const snippet = this.ctx.resolve(shellless); - return snip(stitch`${snippet.value}(${args})`, snippet.dataType); + return snip( + stitch`${snippet.value}(${converted})`, + snippet.dataType, + /* ref */ 'runtime', + ); }); } @@ -474,10 +443,27 @@ ${this.ctx.pre}}`; const argConversionHint = (callee.value[$internal] as Record) ?.argConversionHint as FnArgsConversionHint ?? 'keep'; + const strictSignature = (callee.value as DualFn)[$internal] + ?.strictSignature; + try { let convertedArguments: Snippet[]; - if (Array.isArray(argConversionHint)) { + if (strictSignature) { + // The function's signature does not depend on the context, so it can be used to + // give a hint to the argument expressions that a specific type is expected. + convertedArguments = argNodes.map((arg, i) => { + const argType = strictSignature.argTypes[i]; + if (!argType) { + throw new WgslTypeError( + `Function '${ + getName(callee.value) + }' was called with too many arguments`, + ); + } + return this.typedExpression(arg, argType); + }); + } else if (Array.isArray(argConversionHint)) { // The hint is an array of schemas. convertedArguments = argNodes.map((arg, i) => { const argType = argConversionHint[i]; @@ -523,9 +509,13 @@ ${this.ctx.pre}}`; ); } return fnRes; - } catch (error) { - throw new ResolutionError(error, [{ - toString: () => getName(callee.value), + } catch (err) { + if (err instanceof ResolutionError) { + throw err; + } + + throw new ResolutionError(err, [{ + toString: () => `fn:${getName(callee.value)}`, }]); } } @@ -565,6 +555,7 @@ ${this.ctx.pre}}`; return snip( stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, + /* ref */ 'runtime', ); } @@ -620,11 +611,12 @@ ${this.ctx.pre}}`; elemType as wgsl.AnyWgslData, values.length, ) as wgsl.AnyWgslData, + /* ref */ 'runtime', ); } if (expression[0] === NODE.stringLiteral) { - return snip(expression[1], UnknownData); + return snip(expression[1], UnknownData, /* ref */ 'runtime'); // arbitrary ref } if (expression[0] === NODE.preUpdate) { @@ -658,13 +650,39 @@ ${this.ctx.pre}}`; if (returnNode !== undefined) { const expectedReturnType = this.ctx.topFunctionReturnType; - const returnSnippet = expectedReturnType + let returnSnippet = expectedReturnType ? this.typedExpression( returnNode, expectedReturnType, ) : this.expression(returnNode); + if ( + !expectedReturnType && + isRef(returnSnippet) && + returnSnippet.ref !== 'this-function' + ) { + const str = this.ctx.resolve( + returnSnippet.value, + returnSnippet.dataType, + ).value; + const typeStr = this.ctx.resolve( + toStorable(returnSnippet.dataType as wgsl.StorableData), + ).value; + throw new WgslTypeError( + `'return ${str};' is invalid, cannot return references. +----- +Try 'return ${typeStr}(${str});' instead. +-----`, + ); + } + + returnSnippet = tryConvertSnippet( + returnSnippet, + toStorable(returnSnippet.dataType as wgsl.StorableData), + false, + ); + invariant( returnSnippet.dataType.type !== 'unknown', 'Return type should be known', @@ -706,7 +724,8 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.let || statement[0] === NODE.const) { - const [_, rawId, rawValue] = statement; + let varType: 'var' | 'let' | 'const' = 'var'; + const [stmtType, rawId, rawValue] = statement; const eq = rawValue !== undefined ? this.expression(rawValue) : undefined; if (!eq) { @@ -721,12 +740,53 @@ ${this.ctx.pre}else ${alternate}`; ); } + let dataType = eq.dataType as wgsl.AnyWgslData; + // Assigning a reference to a `const` variable means we store the pointer + // of the rhs. + if (isRef(eq)) { + // Referential + if (stmtType === NODE.let) { + const rhsStr = this.ctx.resolve(eq.value).value; + const rhsTypeStr = + this.ctx.resolve(toStorable(eq.dataType as wgsl.StorableData)) + .value; + + throw new WgslTypeError( + `'let ${rawId} = ${rhsStr}' is invalid, because references cannot be assigned to 'let' variable declarations. +----- +- Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr})' if you need to reassign '${rawId}' later +- Try 'const ${rawId} = ${rhsStr}' if you won't reassign '${rawId}' later. +-----`, + ); + } + + if (eq.ref === 'constant-ref') { + varType = 'const'; + } else { + varType = 'let'; + if (!wgsl.isPtr(dataType)) { + dataType = ptrFn(concretize(dataType) as wgsl.StorableData); + } + } + } else { + // Non-referential + if ( + stmtType === NODE.const && + !wgsl.isNaturallyRef(dataType) && + eq.ref === 'constant' + ) { + varType = 'const'; + } + } + const snippet = this.blockVariable( + varType, rawId, - concretize(eq.dataType as wgsl.AnyWgslData), + concretize(dataType), + eq.ref, ); - return stitchWithExactTypes`${this.ctx.pre}var ${snippet - .value as string} = ${eq};`; + return stitchWithExactTypes`${this.ctx.pre}${varType} ${snippet + .value as string} = ${tryConvertSnippet(eq, dataType, false)};`; } if (statement[0] === NODE.block) { diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 377e1905c..31f310e69 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -317,6 +317,11 @@ export function getOwnSnippet(value: unknown): Snippet | undefined { return (value as WithOwnSnippet)?.[$ownSnippet]; } +export function isKnownAtComptime(snippet: Snippet): boolean { + return typeof snippet.value !== 'string' && + getOwnSnippet(snippet.value) === undefined; +} + export function isWgsl(value: unknown): value is Wgsl { return ( typeof value === 'number' || diff --git a/packages/typegpu/tests/accessor.test.ts b/packages/typegpu/tests/accessor.test.ts index 902e05b41..dd96a48af 100644 --- a/packages/typegpu/tests/accessor.test.ts +++ b/packages/typegpu/tests/accessor.test.ts @@ -136,9 +136,9 @@ describe('tgpu.accessor', () => { fn main() { var color = vec3f(1, 0, 0); - var color2 = redUniform; + let color2 = (&redUniform); var color3 = getColor(); - var colorX = 1; + const colorX = 1f; var color2X = redUniform.x; var color3X = getColor().x; }" @@ -155,7 +155,7 @@ describe('tgpu.accessor', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() { - var foo = 1f; + const foo = 1f; }" `); }); diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index 230c3eac9..bcf27b4e0 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -255,7 +255,7 @@ describe('array', () => { [Error: Resolution of the following tree failed: - - fn:foo - - arrayOf: Cannot create array schema with count unknown at compile-time: 'count'] + - fn:arrayOf: Cannot create array schema with count unknown at compile-time: 'count'] `); }); @@ -431,7 +431,7 @@ describe('array.length', () => { "@group(0) @binding(0) var values: array; fn testFn() -> u32 { - return arrayLength(&values); + return arrayLength((&values)); }" `); }); diff --git a/packages/typegpu/tests/bufferUsage.test.ts b/packages/typegpu/tests/bufferUsage.test.ts index 22b7fb063..7d6272be7 100644 --- a/packages/typegpu/tests/bufferUsage.test.ts +++ b/packages/typegpu/tests/bufferUsage.test.ts @@ -69,7 +69,7 @@ describe('TgpuBufferUniform', () => { @group(0) @binding(0) var boid: Boid; fn func() { - var pos = boid.pos; + let pos = (&boid.pos); var velX = boid.vel.x; }" `); @@ -140,7 +140,7 @@ describe('TgpuBufferMutable', () => { @group(0) @binding(0) var boid: Boid; fn func() { - var pos = boid.pos; + let pos = (&boid.pos); var velX = boid.vel.x; }" `); @@ -229,7 +229,7 @@ describe('TgpuBufferReadonly', () => { @group(0) @binding(0) var boid: Boid; fn func() { - var pos = boid.pos; + let pos = (&boid.pos); var velX = boid.vel.x; }" `); diff --git a/packages/typegpu/tests/constant.test.ts b/packages/typegpu/tests/constant.test.ts index c242c4022..2b84e086a 100644 --- a/packages/typegpu/tests/constant.test.ts +++ b/packages/typegpu/tests/constant.test.ts @@ -3,8 +3,13 @@ import * as d from '../src/data/index.ts'; import tgpu from '../src/index.ts'; import { asWgsl } from './utils/parseResolved.ts'; +const Boid = d.struct({ + pos: d.vec3f, + vel: d.vec3u, +}); + describe('tgpu.const', () => { - it('should inject const declaration when used in functions', () => { + it('should inject const declaration when used in shelled WGSL functions', () => { const x = tgpu.const(d.u32, 2); const fn1 = tgpu.fn([], d.u32)`() { return x; }`.$uses({ x }); @@ -15,12 +20,7 @@ describe('tgpu.const', () => { `); }); - it('allows accessing constants in tgsl through .value', () => { - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3u, - }); - + it('allows accessing constants in TypeGPU functions through .$', () => { const boid = tgpu.const(Boid, { pos: d.vec3f(1, 2, 3), vel: d.vec3u(4, 5, 6), @@ -41,10 +41,55 @@ describe('tgpu.const', () => { const boid: Boid = Boid(vec3f(1, 2, 3), vec3u(4, 5, 6)); fn func() { - var pos = boid; - var vel = boid.vel; - var velX = boid.vel.x; + const pos = boid; + const vel = boid.vel; + const velX = boid.vel.x; }" `); }); + + it('cannot be passed directly to shellless functions', () => { + const fn1 = (v: d.v3f) => { + 'use gpu'; + return v.x * v.y * v.z; + }; + + const foo = tgpu.const(d.vec3f, d.vec3f(1, 2, 3)); + const fn2 = () => { + 'use gpu'; + return fn1(foo.$); + }; + + expect(() => asWgsl(fn2)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:fn2 + - fn*:fn2: Cannot pass constant references as function arguments. Explicitly copy them by wrapping them in a schema: 'vec3f(...)'] + `); + }); + + it('cannot be mutated', () => { + const boid = tgpu.const(Boid, { + pos: d.vec3f(1, 2, 3), + vel: d.vec3u(4, 5, 6), + }); + + const fn = () => { + 'use gpu'; + // @ts-expect-error: Cannot assign to read-only property + boid.$.pos = d.vec3f(0, 0, 0); + }; + + expect(() => asWgsl(fn)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:fn + - fn*:fn: 'boid.pos = vec3f()' is invalid, because boid.pos is a constant.] + `); + + // Since we freeze the object, we cannot mutate when running the function in JS either + expect(() => fn()).toThrowErrorMatchingInlineSnapshot( + `[TypeError: Cannot assign to read only property 'pos' of object '#']`, + ); + }); }); diff --git a/packages/typegpu/tests/derived.test.ts b/packages/typegpu/tests/derived.test.ts index cd3da8a32..1381ec4e6 100644 --- a/packages/typegpu/tests/derived.test.ts +++ b/packages/typegpu/tests/derived.test.ts @@ -150,10 +150,10 @@ describe('TgpuDerived', () => { fn func() { var pos = vec3f(2, 4, 6); - var posX = 2; - var vel = boid.vel; + const posX = 2f; + let vel = (&boid.vel); var velX = boid.vel.x; - var vel_ = boid.vel; + let vel_ = (&boid.vel); var velX_ = boid.vel.x; }" `); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index ca4f2da64..9c5653a7b 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -118,10 +118,10 @@ describe('3d fish example', () => { @group(0) @binding(2) var mouseRay_5: MouseRay_6; - fn projectPointOnLine_8(point: vec3f, line: Line3_7) -> vec3f { - var pointVector = (point - line.origin); - var projection = dot(pointVector, line.dir); - return (line.origin + (line.dir * projection)); + fn projectPointOnLine_8(point: ptr, line: ptr) -> vec3f { + var pointVector = ((*point) - (*line).origin); + var projection = dot(pointVector, (*line).dir); + return ((*line).origin + ((*line).dir * projection)); } @group(0) @binding(3) var timePassed_9: f32; @@ -134,7 +134,7 @@ describe('3d fish example', () => { @compute @workgroup_size(256) fn computeShader_0(input: computeShader_Input_11) { var fishIndex = input.gid.x; - var fishData = ModelData_2(currentFishData_1[fishIndex].position, currentFishData_1[fishIndex].direction, currentFishData_1[fishIndex].scale, currentFishData_1[fishIndex].variant, currentFishData_1[fishIndex].applySinWave, currentFishData_1[fishIndex].applySeaFog, currentFishData_1[fishIndex].applySeaDesaturation); + let fishData = (¤tFishData_1[fishIndex]); var separation = vec3f(); var alignment = vec3f(); var alignmentCount = 0; @@ -146,57 +146,59 @@ describe('3d fish example', () => { if ((u32(i) == fishIndex)) { continue; } - var other = ModelData_2(currentFishData_1[i].position, currentFishData_1[i].direction, currentFishData_1[i].scale, currentFishData_1[i].variant, currentFishData_1[i].applySinWave, currentFishData_1[i].applySeaFog, currentFishData_1[i].applySeaDesaturation); - var dist = length((fishData.position - other.position)); + let other = (¤tFishData_1[i]); + var dist = length(((*fishData).position - (*other).position)); if ((dist < fishBehavior_3.separationDist)) { - separation = (separation + (fishData.position - other.position)); + separation = (separation + ((*fishData).position - (*other).position)); } if ((dist < fishBehavior_3.alignmentDist)) { - alignment = (alignment + other.direction); + alignment = (alignment + (*other).direction); alignmentCount = (alignmentCount + 1); } if ((dist < fishBehavior_3.cohesionDist)) { - cohesion = (cohesion + other.position); + cohesion = (cohesion + (*other).position); cohesionCount = (cohesionCount + 1); } } if ((alignmentCount > 0)) { - alignment = ((1f / f32(alignmentCount)) * alignment); + alignment = (alignment * (1f / f32(alignmentCount))); } if ((cohesionCount > 0)) { - cohesion = (((1f / f32(cohesionCount)) * cohesion) - fishData.position); + cohesion = (((1f / f32(cohesionCount)) * cohesion) - (*fishData).position); } for (var i = 0; (i < 3); i += 1) { var repulsion = vec3f(); repulsion[i] = 1; var axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); - var axisPosition = fishData.position[i]; - var distance = 0.1; + var axisPosition = (*fishData).position[i]; + const distance = 0.1; if ((axisPosition > (axisAquariumSize - distance))) { var str = (axisPosition - (axisAquariumSize - distance)); - wallRepulsion = (wallRepulsion - (str * repulsion)); + wallRepulsion = (wallRepulsion - (repulsion * str)); } if ((axisPosition < (-axisAquariumSize + distance))) { var str = ((-axisAquariumSize + distance) - axisPosition); - wallRepulsion = (wallRepulsion + (str * repulsion)); + wallRepulsion = (wallRepulsion + (repulsion * str)); } } if ((mouseRay_5.activated == 1)) { - var proj = projectPointOnLine_8(fishData.position, mouseRay_5.line); - var diff = (fishData.position - proj); - var limit = 0.9; + var proj = projectPointOnLine_8((&(*fishData).position), (&mouseRay_5.line)); + var diff = ((*fishData).position - proj); + const limit = 0.9; var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); - rayRepulsion = (str * normalize(diff)); + rayRepulsion = (normalize(diff) * str); } - fishData.direction = (fishData.direction + (fishBehavior_3.separationStr * separation)); - fishData.direction = (fishData.direction + (fishBehavior_3.alignmentStr * alignment)); - fishData.direction = (fishData.direction + (fishBehavior_3.cohesionStr * cohesion)); - fishData.direction = (fishData.direction + (1e-4 * wallRepulsion)); - fishData.direction = (fishData.direction + (5e-4 * rayRepulsion)); - fishData.direction = (clamp(length(fishData.direction), 0, 0.01) * normalize(fishData.direction)); - var translation = ((min(999, timePassed_9) / 8f) * fishData.direction); - fishData.position = (fishData.position + translation); - nextFishData_10[fishIndex] = fishData; + var direction = (*fishData).direction; + direction = (direction + (separation * fishBehavior_3.separationStr)); + direction = (direction + (alignment * fishBehavior_3.alignmentStr)); + direction = (direction + (cohesion * fishBehavior_3.cohesionStr)); + direction = (direction + (wallRepulsion * 1e-4)); + direction = (direction + (rayRepulsion * 5e-4)); + direction = (normalize(direction) * clamp(length((*fishData).direction), 0, 0.01)); + var translation = (direction * (min(999, timePassed_9) / 8f)); + let nextFishData = (&nextFishData_10[fishIndex]); + (*nextFishData).position = ((*fishData).position + translation); + (*nextFishData).direction = direction; } struct ModelData_2 { @@ -218,8 +220,8 @@ describe('3d fish example', () => { fn applySinWave_4(index: u32, vertex: PosAndNormal_3, time: f32) -> PosAndNormal_3 { var a = -60.1; - var b = 0.8; - var c = 6.1; + const b = 0.8; + const c = 6.1; var posMod = vec3f(); posMod.z = (sin((f32(index) + (((time / a) + vertex.position.x) / b))) / c); var coeff = (cos((f32(index) + (((time / a) + vertex.position.x) / b))) / c); @@ -274,8 +276,8 @@ describe('3d fish example', () => { 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); var worldPosition = (translationMatrix * (yawMatrix * (pitchMatrix * (scaleMatrix * vec4f(wavedVertex.position, 1))))); var worldNormal = normalize((yawMatrix * (pitchMatrix * vec4f(wavedVertex.normal, 1))).xyz); - var worldPositionUniform = worldPosition; - var canvasPosition = (camera_6.projection * (camera_6.view * worldPositionUniform)); + let worldPositionUniform = (&worldPosition); + var canvasPosition = (camera_6.projection * (camera_6.view * (*worldPositionUniform))); return vertexShader_Output_8(worldPosition.xyz, worldNormal, canvasPosition, currentModelData.variant, input.textureUV, currentModelData.applySeaFog, currentModelData.applySeaDesaturation); } diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index dae2f9b23..6994f46d6 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -42,17 +42,17 @@ describe('blur example', () => { } @compute @workgroup_size(32, 1, 1) fn computeFn_0(_arg_0: computeFn_Input_8) { - var settings2 = settingsUniform_1; - var filterOffset = i32((f32((settings2.filterDim - 1)) / 2f)); + let settings2 = (&settingsUniform_1); + var filterOffset = i32((f32(((*settings2).filterDim - 1)) / 2f)); var dims = vec2i(textureDimensions(inTexture_3)); - var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u(settings2.blockDim, 4)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0)); + var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u((*settings2).blockDim, 4)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0)); for (var r = 0; (r < 4); r++) { for (var c = 0; (c < 4); c++) { var loadIndex = (baseIndex + vec2i(c, r)); if ((flip_4 != 0)) { loadIndex = loadIndex.yx; } - tileData_5[r][((_arg_0.lid.x * 4) + u32(c))] = textureSampleLevel(inTexture_3, sampler_6, vec2f(((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims))), 0).xyz; + tileData_5[r][((_arg_0.lid.x * 4) + u32(c))] = textureSampleLevel(inTexture_3, sampler_6, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).xyz; } } workgroupBarrier(); @@ -65,9 +65,9 @@ describe('blur example', () => { var center = (i32((4 * _arg_0.lid.x)) + c); if ((((center >= filterOffset) && (center < (128 - filterOffset))) && all((writeIndex < dims)))) { var acc = vec3f(); - for (var f = 0; (f < settings2.filterDim); f++) { + for (var f = 0; (f < (*settings2).filterDim); f++) { var i = ((center + f) - filterOffset); - acc = (acc + (tileData_5[r][i] * (1f / f32(settings2.filterDim)))); + acc = (acc + (tileData_5[r][i] * (1f / f32((*settings2).filterDim)))); } textureStore(outTexture_7, writeIndex, vec4f(acc, 1)); } diff --git a/packages/typegpu/tests/examples/individual/boids-next.test.ts b/packages/typegpu/tests/examples/individual/boids-next.test.ts index a66d077aa..28b750246 100644 --- a/packages/typegpu/tests/examples/individual/boids-next.test.ts +++ b/packages/typegpu/tests/examples/individual/boids-next.test.ts @@ -53,17 +53,17 @@ describe('boids next example', () => { if ((i == index)) { continue; } - var other = currentTrianglePos_1[i]; - var dist = distance(instanceInfo.position, other.position); + let other = (¤tTrianglePos_1[i]); + var dist = distance(instanceInfo.position, (*other).position); if ((dist < paramsBuffer_3.separationDistance)) { - separation = (separation + (instanceInfo.position - other.position)); + separation = (separation + (instanceInfo.position - (*other).position)); } if ((dist < paramsBuffer_3.alignmentDistance)) { - alignment = (alignment + other.velocity); + alignment = (alignment + (*other).velocity); alignmentCount++; } if ((dist < paramsBuffer_3.cohesionDistance)) { - cohesion = (cohesion + other.position); + cohesion = (cohesion + (*other).position); cohesionCount++; } } diff --git a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts index 6e16b5755..d1b1c1758 100644 --- a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts +++ b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts @@ -168,7 +168,7 @@ describe('box raytracing example', () => { } var linear = (vec3f(1) / invColor); var srgb = linearToSrgb_12(linear); - var gamma = 2.2; + const gamma = 2.2; var corrected = pow(srgb, vec3f((1f / gamma))); if (intersectionFound) { return (min(density, 1) * vec4f(min(corrected, vec3f(1)), 1)); diff --git a/packages/typegpu/tests/examples/individual/caustics.test.ts b/packages/typegpu/tests/examples/individual/caustics.test.ts index 56efdd284..a3f62cd88 100644 --- a/packages/typegpu/tests/examples/individual/caustics.test.ts +++ b/packages/typegpu/tests/examples/individual/caustics.test.ts @@ -106,34 +106,41 @@ describe('caustics example', () => { return mix(x, X, smoothPartial.x); } - fn caustics_7(uv: vec2f, time2: f32, profile: vec3f) -> vec3f { + fn caustics_7(uv: ptr, time2: f32, profile: vec3f) -> vec3f { + var distortion = sample_8(vec3f(((*uv) * 0.5), (time2 * 0.2))); + var uv2 = ((*uv) + distortion); + var noise = abs(sample_8(vec3f((uv2 * 5), time2))); + return pow(vec3f((1 - noise)), profile); + } + + fn caustics_17(uv: vec2f, time2: f32, profile: vec3f) -> vec3f { var distortion = sample_8(vec3f((uv * 0.5), (time2 * 0.2))); var uv2 = (uv + distortion); var noise = abs(sample_8(vec3f((uv2 * 5), time2))); return pow(vec3f((1 - noise)), profile); } - fn rotateXY_17(angle2: f32) -> mat2x2f { + fn rotateXY_18(angle2: f32) -> mat2x2f { return mat2x2f(vec2f(cos(angle2), sin(angle2)), vec2f(-sin(angle2), cos(angle2))); } - struct mainFragment_Input_18 { + struct mainFragment_Input_19 { @location(0) uv: vec2f, } - @fragment fn mainFragment_3(_arg_0: mainFragment_Input_18) -> @location(0) vec4f { + @fragment fn mainFragment_3(_arg_0: mainFragment_Input_19) -> @location(0) vec4f { var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f(((-0.19866933079506122 * 10) + (_arg_0.uv.x * 3)), 4.900332889206208)); var skewedUv = (skewMat * _arg_0.uv); var tile = tilePattern_5((skewedUv * tileDensity_4)); var albedo = mix(vec3f(0.10000000149011612), vec3f(1), tile); var cuv = vec2f(((_arg_0.uv.x * (pow((_arg_0.uv.y * 1.5), 3) + 0.1)) * 5), (pow((((_arg_0.uv.y * 1.5) + 0.1) * 1.5), 3) * 1)); - var c1 = (caustics_7(cuv, (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); - var c2 = (caustics_7((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); + var c1 = (caustics_7((&cuv), (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); + var c2 = (caustics_17((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); var blendCoord = vec3f((_arg_0.uv * vec2f(5, 10)), ((time_6 * 0.2) + 5)); var blend = saturate((sample_8(blendCoord) + 0.3)); var noFogColor = (albedo * mix(vec3f(0.20000000298023224, 0.5, 1), (c1 + c2), blend)); var fog = min((pow(_arg_0.uv.y, 0.5) * 1.2), 1); - var godRayUv = ((rotateXY_17(-0.3) * _arg_0.uv) * vec2f(15, 3)); + var godRayUv = ((rotateXY_18(-0.3) * _arg_0.uv) * vec2f(15, 3)); var godRayFactor = pow(_arg_0.uv.y, 1); var godRay1 = ((sample_8(vec3f(godRayUv, (time_6 * 0.5))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * godRayFactor)); var godRay2 = ((sample_8(vec3f((godRayUv * 2), (time_6 * 0.3))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * (godRayFactor * 0.4))); diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index 50ac234b5..3657d4463 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -29,29 +29,29 @@ describe('cubemap reflection example', () => { @group(0) @binding(0) var prevVertices_1: array; - fn unpackVec2u_3(packed: vec2u) -> vec4f { - var xy = unpack2x16float(packed.x); - var zw = unpack2x16float(packed.y); + fn unpackVec2u_3(packed: ptr) -> vec4f { + var xy = unpack2x16float((*packed).x); + var zw = unpack2x16float((*packed).y); return vec4f(xy, zw); } - fn calculateMidpoint_4(v1: vec4f, v2: vec4f) -> vec4f { - return vec4f((0.5 * (v1.xyz + v2.xyz)), 1); + fn calculateMidpoint_4(v1: ptr, v2: ptr) -> vec4f { + return vec4f((0.5 * ((*v1).xyz + (*v2).xyz)), 1); } @group(0) @binding(2) var smoothFlag_5: u32; - fn getAverageNormal_6(v1: vec4f, v2: vec4f, v3: vec4f) -> vec4f { - var edge1 = (v2.xyz - v1.xyz); - var edge2 = (v3.xyz - v1.xyz); + fn getAverageNormal_6(v1: ptr, v2: ptr, v3: ptr) -> vec4f { + var edge1 = ((*v2).xyz - (*v1).xyz); + var edge2 = ((*v3).xyz - (*v1).xyz); return normalize(vec4f(cross(edge1, edge2), 0)); } @group(0) @binding(1) var nextVertices_7: array; - fn packVec2u_8(toPack: vec4f) -> vec2u { - var xy = pack2x16float(toPack.xy); - var zw = pack2x16float(toPack.zw); + fn packVec2u_8(toPack: ptr) -> vec2u { + var xy = pack2x16float((*toPack).xy); + var zw = pack2x16float((*toPack).zw); return vec2u(xy, zw); } @@ -66,26 +66,26 @@ describe('cubemap reflection example', () => { return; } var baseIndexPrev = (triangleIndex * 3); - var v1 = unpackVec2u_3(prevVertices_1[baseIndexPrev].position); - var v2 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 1)].position); - var v3 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 2)].position); - var v12 = vec4f(normalize(calculateMidpoint_4(v1, v2).xyz), 1); - var v23 = vec4f(normalize(calculateMidpoint_4(v2, v3).xyz), 1); - var v31 = vec4f(normalize(calculateMidpoint_4(v3, v1).xyz), 1); + var v1 = unpackVec2u_3((&prevVertices_1[baseIndexPrev].position)); + var v2 = unpackVec2u_3((&prevVertices_1[(baseIndexPrev + 1)].position)); + var v3 = unpackVec2u_3((&prevVertices_1[(baseIndexPrev + 2)].position)); + var v12 = vec4f(normalize(calculateMidpoint_4((&v1), (&v2)).xyz), 1); + var v23 = vec4f(normalize(calculateMidpoint_4((&v2), (&v3)).xyz), 1); + var v31 = vec4f(normalize(calculateMidpoint_4((&v3), (&v1)).xyz), 1); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); var baseIndexNext = (triangleIndex * 12); for (var i = 0u; (i < 12); i++) { - var reprojectedVertex = newVertices[i]; + let reprojectedVertex = (&newVertices[i]); var triBase = (i - (i % 3)); - var normal = reprojectedVertex; + var normal = (*reprojectedVertex); if ((smoothFlag_5 == 0)) { - normal = getAverageNormal_6(newVertices[triBase], newVertices[(triBase + 1)], newVertices[(triBase + 2)]); + normal = getAverageNormal_6((&newVertices[triBase]), (&newVertices[(triBase + 1)]), (&newVertices[(triBase + 2)])); } var outIndex = (baseIndexNext + i); - var nextVertex = nextVertices_7[outIndex]; - nextVertex.position = packVec2u_8(reprojectedVertex); - nextVertex.normal = packVec2u_8(normal); - nextVertices_7[outIndex] = nextVertex; + let nextVertex = (&nextVertices_7[outIndex]); + (*nextVertex).position = packVec2u_8(reprojectedVertex); + (*nextVertex).normal = packVec2u_8((&normal)); + nextVertices_7[outIndex] = (*nextVertex); } } diff --git a/packages/typegpu/tests/examples/individual/dispatch.test.ts b/packages/typegpu/tests/examples/individual/dispatch.test.ts index fb2961986..81bcc9de1 100644 --- a/packages/typegpu/tests/examples/individual/dispatch.test.ts +++ b/packages/typegpu/tests/examples/individual/dispatch.test.ts @@ -136,7 +136,7 @@ describe('tgsl parsing test example', () => { @group(1) @binding(0) var buffer_3: array; fn wrappedCallback_2(_arg_0: u32, _arg_1: u32, _arg_2: u32) { - for (var i = 0u; (i < arrayLength(&buffer_3)); i++) { + for (var i = 0u; (i < arrayLength((&buffer_3))); i++) { buffer_3[i] *= 2; } } diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 01c094bef..ca3a859fb 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -35,14 +35,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_4(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_5[obsIdx]; - if ((obs.enabled == 0)) { + let obs = (&obstacles_5[obsIdx]); + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -119,14 +119,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_11(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_12[obsIdx]; - if ((obs.enabled == 0)) { + let obs = (&obstacles_12[obsIdx]); + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -157,33 +157,33 @@ describe('fluid double buffering example', () => { } fn computeVelocity_8(x: i32, y: i32) -> vec2f { - var gravityCost = 0.5; + const gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; (i < 4); i++) { - var offset = neighborOffsets[i]; - var neighborDensity = getCell_6((x + offset.x), (y + offset.y)); - var cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); - if (!isValidFlowOut_9((x + offset.x), (y + offset.y))) { + let offset = (&neighborOffsets[i]); + var neighborDensity = getCell_6((x + (*offset).x), (y + (*offset).y)); + var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut_9((x + (*offset).x), (y + (*offset).y))) { continue; } if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[0] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount = 1; } } } - var leastCostDir = dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; - return leastCostDir; + let leastCostDir = (&dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]); + return (*leastCostDir); } fn flowFromCell_16(myX: i32, myY: i32, x: i32, y: i32) -> f32 { @@ -216,7 +216,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var sourceParams_18: item_19; fn getMinimumInFlow_17(x: i32, y: i32) -> f32 { - var gridSizeF = 256f; + const gridSizeF = 256f; var sourceRadius2 = max(1, (sourceParams_18.radius * gridSizeF)); var sourcePos = vec2f((sourceParams_18.center.x * gridSizeF), (sourceParams_18.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius2)) { @@ -286,14 +286,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_11(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_12[obsIdx]; - if ((obs.enabled == 0)) { + let obs = (&obstacles_12[obsIdx]); + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -324,33 +324,33 @@ describe('fluid double buffering example', () => { } fn computeVelocity_8(x: i32, y: i32) -> vec2f { - var gravityCost = 0.5; + const gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); var leastCost = cell.z; var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; (i < 4); i++) { - var offset = neighborOffsets[i]; - var neighborDensity = getCell_6((x + offset.x), (y + offset.y)); - var cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); - if (!isValidFlowOut_9((x + offset.x), (y + offset.y))) { + let offset = (&neighborOffsets[i]); + var neighborDensity = getCell_6((x + (*offset).x), (y + (*offset).y)); + var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut_9((x + (*offset).x), (y + (*offset).y))) { continue; } if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[0] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount = 1; } } } - var leastCostDir = dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; - return leastCostDir; + let leastCostDir = (&dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]); + return (*leastCostDir); } fn flowFromCell_16(myX: i32, myY: i32, x: i32, y: i32) -> f32 { @@ -383,7 +383,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var sourceParams_18: item_19; fn getMinimumInFlow_17(x: i32, y: i32) -> f32 { - var gridSizeF = 256f; + const gridSizeF = 256f; var sourceRadius2 = max(1, (sourceParams_18.radius * gridSizeF)); var sourcePos = vec2f((sourceParams_18.center.x * gridSizeF), (sourceParams_18.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius2)) { @@ -448,14 +448,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_6(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_7[obsIdx]; - if ((obs.enabled == 0)) { + let obs = (&obstacles_7[obsIdx]); + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -471,16 +471,16 @@ describe('fluid double buffering example', () => { var x = i32((input.uv.x * 256)); var y = i32((input.uv.y * 256)); var index = coordsToIndex_4(x, y); - var cell = gridAlphaBuffer_5[index]; - var density = max(0, cell.z); + let cell = (&gridAlphaBuffer_5[index]); + var density = max(0, (*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); - var firstThreshold = 2f; - var secondThreshold = 10f; - var thirdThreshold = 20f; + const firstThreshold = 2f; + const secondThreshold = 10f; + const thirdThreshold = 20f; if (isInsideObstacle_6(x, y)) { return obstacleColor; } diff --git a/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts b/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts index 25bc0548b..fcac496ff 100644 --- a/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts @@ -17,94 +17,94 @@ describe('fluid with atomics example', () => { }, device); expect(shaderCodes).toMatchInlineSnapshot(` - "@group(0) @binding(0) var size_5: vec2u; + "@group(0) @binding(0) var size_6: vec2u; - fn getIndex_4(x: u32, y: u32) -> u32 { - var h = size_5.y; - var w = size_5.x; + fn getIndex_5(x: u32, y: u32) -> u32 { + var h = size_6.y; + var w = size_6.x; return (((y % h) * w) + (x % w)); } - @group(0) @binding(1) var nextState_6: array, 1048576>; + @group(0) @binding(1) var currentStateBuffer_7: array; - fn updateCell_3(x: u32, y: u32, value: u32) { - atomicStore(&nextState_6[getIndex_4(x, y)], value); + fn getCell_4(x: u32, y: u32) -> u32 { + return currentStateBuffer_7[getIndex_5(x, y)]; } - @group(0) @binding(2) var currentStateBuffer_9: array; + fn isClearCell_3(x: u32, y: u32) -> bool { + return ((getCell_4(x, y) >> 24) == 4); + } + + @group(0) @binding(2) var nextState_9: array, 1048576>; - fn getCell_8(x: u32, y: u32) -> u32 { - return currentStateBuffer_9[getIndex_4(x, y)]; + fn updateCell_8(x: u32, y: u32, value: u32) { + atomicStore(&nextState_9[getIndex_5(x, y)], value); } - fn isClearCell_7(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 4); + fn isWall_10(x: u32, y: u32) -> bool { + return ((getCell_4(x, y) >> 24) == 1); } - const MAX_WATER_LEVEL_11: u32 = 16777215; + const MAX_WATER_LEVEL_12: u32 = 16777215; - fn persistFlags_10(x: u32, y: u32) { - var cell = getCell_8(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_11); + fn persistFlags_11(x: u32, y: u32) { + var cell = getCell_4(x, y); + var waterLevel = (cell & MAX_WATER_LEVEL_12); var flags = (cell >> 24); - updateCell_3(x, y, ((flags << 24) | waterLevel)); - } - - fn isWall_12(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 1); + updateCell_8(x, y, ((flags << 24) | waterLevel)); } - fn getCellNext_14(x: u32, y: u32) -> u32 { - return atomicLoad(&nextState_6[getIndex_4(x, y)]); + fn isWaterSource_13(x: u32, y: u32) -> bool { + return ((getCell_4(x, y) >> 24) == 2); } - fn addToCell_13(x: u32, y: u32, value: u32) { - var cell = getCellNext_14(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_11); - var newWaterLevel = min((waterLevel + value), MAX_WATER_LEVEL_11); - atomicAdd(&nextState_6[getIndex_4(x, y)], (newWaterLevel - waterLevel)); + fn getCellNext_15(x: u32, y: u32) -> u32 { + return atomicLoad(&nextState_9[getIndex_5(x, y)]); } - fn isWaterSource_15(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 2); + fn addToCell_14(x: u32, y: u32, value: u32) { + var cell = getCellNext_15(x, y); + var waterLevel = (cell & MAX_WATER_LEVEL_12); + var newWaterLevel = min((waterLevel + value), MAX_WATER_LEVEL_12); + atomicAdd(&nextState_9[getIndex_5(x, y)], (newWaterLevel - waterLevel)); } fn isWaterDrain_16(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 3); + return ((getCell_4(x, y) >> 24) == 3); } - fn subtractFromCell_17(x: u32, y: u32, value: u32) { - var cell = getCellNext_14(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_11); - var newWaterLevel = max((waterLevel - min(value, waterLevel)), 0); - atomicSub(&nextState_6[getIndex_4(x, y)], (waterLevel - newWaterLevel)); + fn getWaterLevel_17(x: u32, y: u32) -> u32 { + return (getCell_4(x, y) & MAX_WATER_LEVEL_12); } - fn getWaterLevel_18(x: u32, y: u32) -> u32 { - return (getCell_8(x, y) & MAX_WATER_LEVEL_11); + fn subtractFromCell_18(x: u32, y: u32, value: u32) { + var cell = getCellNext_15(x, y); + var waterLevel = (cell & MAX_WATER_LEVEL_12); + var newWaterLevel = max((waterLevel - min(value, waterLevel)), 0); + atomicSub(&nextState_9[getIndex_5(x, y)], (waterLevel - newWaterLevel)); } fn checkForFlagsAndBounds_2(x: u32, y: u32) -> bool { - if (isClearCell_7(x, y)) { - updateCell_3(x, y, 0); + if (isClearCell_3(x, y)) { + updateCell_8(x, y, 0); return true; } - if (isWall_12(x, y)) { - persistFlags_10(x, y); + if (isWall_10(x, y)) { + persistFlags_11(x, y); return true; } - if (isWaterSource_15(x, y)) { - persistFlags_10(x, y); - addToCell_13(x, y, 20); + if (isWaterSource_13(x, y)) { + persistFlags_11(x, y); + addToCell_14(x, y, 20); return false; } if (isWaterDrain_16(x, y)) { - persistFlags_10(x, y); - updateCell_3(x, y, (3 << 24)); + persistFlags_11(x, y); + updateCell_8(x, y, (3 << 24)); return true; } - if (((((y == 0) || (y == (size_5.y - 1))) || (x == 0)) || (x == (size_5.x - 1)))) { - subtractFromCell_17(x, y, getWaterLevel_18(x, y)); + if (((((y == 0) || (y == (size_6.y - 1))) || (x == 0)) || (x == (size_6.x - 1)))) { + subtractFromCell_18(x, y, getWaterLevel_17(x, y)); return true; } return false; @@ -131,18 +131,18 @@ describe('fluid with atomics example', () => { if (checkForFlagsAndBounds_2(x, y)) { return; } - var remainingWater = getWaterLevel_18(x, y); + var remainingWater = getWaterLevel_17(x, y); if ((remainingWater == 0)) { return; } - if (!isWall_12(x, (y - 1))) { - var waterLevelBelow = getWaterLevel_18(x, (y - 1)); + if (!isWall_10(x, (y - 1))) { + var waterLevelBelow = getWaterLevel_17(x, (y - 1)); var stable = getStableStateBelow_19(remainingWater, waterLevelBelow); if ((waterLevelBelow < stable)) { var change = (stable - waterLevelBelow); var flow = min(change, viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13(x, (y - 1), flow); + subtractFromCell_18(x, y, flow); + addToCell_14(x, (y - 1), flow); remainingWater -= flow; } } @@ -150,38 +150,38 @@ describe('fluid with atomics example', () => { return; } var waterLevelBefore = remainingWater; - if (!isWall_12((x - 1), y)) { - var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_18((x - 1), y))); + if (!isWall_10((x - 1), y)) { + var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x - 1), y))); if ((flowRaw > 0)) { var change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); var flow = min(change, viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13((x - 1), y, flow); + subtractFromCell_18(x, y, flow); + addToCell_14((x - 1), y, flow); remainingWater -= flow; } } if ((remainingWater == 0)) { return; } - if (!isWall_12((x + 1), y)) { - var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_18((x + 1), y))); + if (!isWall_10((x + 1), y)) { + var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x + 1), y))); if ((flowRaw > 0)) { var change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); var flow = min(change, viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13((x + 1), y, flow); + subtractFromCell_18(x, y, flow); + addToCell_14((x + 1), y, flow); remainingWater -= flow; } } if ((remainingWater == 0)) { return; } - if (!isWall_12(x, (y + 1))) { - var stable = getStableStateBelow_19(getWaterLevel_18(x, (y + 1)), remainingWater); + if (!isWall_10(x, (y + 1))) { + var stable = getStableStateBelow_19(getWaterLevel_17(x, (y + 1)), remainingWater); if ((stable < remainingWater)) { var flow = min((remainingWater - stable), viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13(x, (y + 1), flow); + subtractFromCell_18(x, y, flow); + addToCell_14(x, (y + 1), flow); remainingWater -= flow; } } diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 6c75d4ff8..6a7599a20 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -65,7 +65,6 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { var currentId = input.gid.x; var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); - var updatedCurrent = current; if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_3); i++) { var otherId = u32(i); @@ -75,25 +74,25 @@ describe('gravity example', () => { } if (((current.collisionBehavior == 1) && (other.collisionBehavior == 1))) { if (isSmaller_5(currentId, otherId)) { - updatedCurrent.position = (other.position + ((radiusOf_4(current) + radiusOf_4(other)) * normalize((current.position - other.position)))); + current.position = (other.position + ((radiusOf_4(current) + radiusOf_4(other)) * normalize((current.position - other.position)))); } - updatedCurrent.velocity = (0.99 * (updatedCurrent.velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); + current.velocity = (0.99 * (current.velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); } else { var isCurrentAbsorbed = ((current.collisionBehavior == 1) || ((current.collisionBehavior == 2) && isSmaller_5(currentId, otherId))); if (isCurrentAbsorbed) { - updatedCurrent.destroyed = 1; + current.destroyed = 1; } else { - var m1 = updatedCurrent.mass; + var m1 = current.mass; var m2 = other.mass; - updatedCurrent.velocity = (((m1 / (m1 + m2)) * updatedCurrent.velocity) + ((m2 / (m1 + m2)) * other.velocity)); - updatedCurrent.mass = (m1 + m2); + current.velocity = (((m1 / (m1 + m2)) * current.velocity) + ((m2 / (m1 + m2)) * other.velocity)); + current.mass = (m1 + m2); } } } } - outState_6[input.gid.x] = updatedCurrent; + outState_6[input.gid.x] = current; } struct CelestialBody_2 { @@ -131,7 +130,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { var current = CelestialBody_2(inState_1[input.gid.x].destroyed, inState_1[input.gid.x].position, inState_1[input.gid.x].velocity, inState_1[input.gid.x].mass, inState_1[input.gid.x].radiusMultiplier, inState_1[input.gid.x].collisionBehavior, inState_1[input.gid.x].textureIndex, inState_1[input.gid.x].ambientLightFactor); var dt = (time_3.passed * time_3.multiplier); - var updatedCurrent = current; + let updatedCurrent = (¤t); if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_5); i++) { var other = CelestialBody_2(inState_1[i].destroyed, inState_1[i].position, inState_1[i].velocity, inState_1[i].mass, inState_1[i].radiusMultiplier, inState_1[i].collisionBehavior, inState_1[i].textureIndex, inState_1[i].ambientLightFactor); @@ -141,11 +140,11 @@ describe('gravity example', () => { var dist = max((radiusOf_6(current) + radiusOf_6(other)), distance(current.position, other.position)); var gravityForce = (((current.mass * other.mass) / dist) / dist); var direction = normalize((other.position - current.position)); - updatedCurrent.velocity = (updatedCurrent.velocity + (((gravityForce / current.mass) * dt) * direction)); + (*updatedCurrent).velocity = ((*updatedCurrent).velocity + (((gravityForce / current.mass) * dt) * direction)); } - updatedCurrent.position = (updatedCurrent.position + (dt * updatedCurrent.velocity)); + (*updatedCurrent).position = ((*updatedCurrent).position + (dt * (*updatedCurrent).velocity)); } - outState_7[input.gid.x] = updatedCurrent; + outState_7[input.gid.x] = (*updatedCurrent); } struct Camera_2 { @@ -228,8 +227,8 @@ describe('gravity example', () => { @vertex fn mainVertex_0(input: mainVertex_Input_7) -> mainVertex_Output_6 { var currentBody = CelestialBody_2(celestialBodies_1[input.instanceIndex].destroyed, celestialBodies_1[input.instanceIndex].position, celestialBodies_1[input.instanceIndex].velocity, celestialBodies_1[input.instanceIndex].mass, celestialBodies_1[input.instanceIndex].radiusMultiplier, celestialBodies_1[input.instanceIndex].collisionBehavior, celestialBodies_1[input.instanceIndex].textureIndex, celestialBodies_1[input.instanceIndex].ambientLightFactor); var worldPosition = ((radiusOf_3(currentBody) * input.position.xyz) + currentBody.position); - var camera = camera_4; - var positionOnCanvas = (camera.projection * (camera.view * vec4f(worldPosition, 1))); + let camera = (&camera_4); + var positionOnCanvas = ((*camera).projection * ((*camera).view * vec4f(worldPosition, 1))); return mainVertex_Output_6(positionOnCanvas, input.uv, input.normal, worldPosition, currentBody.textureIndex, currentBody.destroyed, currentBody.ambientLightFactor); } diff --git a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts index 6e58bd5b8..a1b829f74 100644 --- a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts +++ b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts @@ -105,10 +105,10 @@ describe('liquid-glass example', () => { @group(0) @binding(3) var sampler_11: sampler; - fn sampleWithChromaticAberration_12(tex: texture_2d, uv: vec2f, offset: f32, dir: vec2f, blur: f32) -> vec3f { + fn sampleWithChromaticAberration_12(tex: texture_2d, uv: vec2f, offset: f32, dir: ptr, blur: f32) -> vec3f { var samples = array(); for (var i = 0; (i < 3); i++) { - var channelOffset = (dir * ((f32(i) - 1) * offset)); + var channelOffset = ((*dir) * ((f32(i) - 1) * offset)); samples[i] = textureSampleBias(tex, sampler_11, (uv - channelOffset), blur).xyz; } return vec3f(samples[0].x, samples[1].y, samples[2].z); @@ -119,15 +119,19 @@ describe('liquid-glass example', () => { strength: f32, } - fn applyTint_14(color: vec3f, tint: TintParams_13) -> vec4f { - return mix(vec4f(color, 1), vec4f(tint.color, 1), tint.strength); + fn applyTint_14(color: vec3f, tint: ptr) -> vec4f { + return mix(vec4f(color, 1), vec4f((*tint).color, 1), (*tint).strength); } - struct fragmentShader_Input_15 { + fn applyTint_15(color: ptr, tint: ptr) -> vec4f { + return mix(vec4f((*color), 1), vec4f((*tint).color, 1), (*tint).strength); + } + + struct fragmentShader_Input_16 { @location(0) uv: vec2f, } - @fragment fn fragmentShader_3(_arg_0: fragmentShader_Input_15) -> @location(0) vec4f { + @fragment fn fragmentShader_3(_arg_0: fragmentShader_Input_16) -> @location(0) vec4f { var posInBoxSpace = (_arg_0.uv - mousePosUniform_4); var sdfDist = sdRoundedBox2d_7(posInBoxSpace, paramsUniform_5.rectDims, paramsUniform_5.radius); var dir = normalize((posInBoxSpace * paramsUniform_5.rectDims.yx)); @@ -136,11 +140,11 @@ describe('liquid-glass example', () => { var featherUV = (paramsUniform_5.edgeFeather / f32(max(texDim.x, texDim.y))); var weights = calculateWeights_9(sdfDist, paramsUniform_5.start, paramsUniform_5.end, featherUV); var blurSample = textureSampleBias(sampledView_8, sampler_11, _arg_0.uv, paramsUniform_5.blur); - var refractedSample = sampleWithChromaticAberration_12(sampledView_8, (_arg_0.uv + (dir * (paramsUniform_5.refractionStrength * normalizedDist))), (paramsUniform_5.chromaticStrength * normalizedDist), dir, (paramsUniform_5.blur * paramsUniform_5.edgeBlurMultiplier)); + var refractedSample = sampleWithChromaticAberration_12(sampledView_8, (_arg_0.uv + (dir * (paramsUniform_5.refractionStrength * normalizedDist))), (paramsUniform_5.chromaticStrength * normalizedDist), (&dir), (paramsUniform_5.blur * paramsUniform_5.edgeBlurMultiplier)); var normalSample = textureSampleLevel(sampledView_8, sampler_11, _arg_0.uv, 0); var tint = TintParams_13(paramsUniform_5.tintColor, paramsUniform_5.tintStrength); - var tintedBlur = applyTint_14(blurSample.xyz, tint); - var tintedRing = applyTint_14(refractedSample, tint); + var tintedBlur = applyTint_14(blurSample.xyz, (&tint)); + var tintedRing = applyTint_15((&refractedSample), (&tint)); return (((tintedBlur * weights.inside) + (tintedRing * weights.ring)) + (normalSample * weights.outside)); }" `); diff --git a/packages/typegpu/tests/examples/individual/matrix-next.test.ts b/packages/typegpu/tests/examples/individual/matrix-next.test.ts index 8a7615851..cdc334efe 100644 --- a/packages/typegpu/tests/examples/individual/matrix-next.test.ts +++ b/packages/typegpu/tests/examples/individual/matrix-next.test.ts @@ -51,8 +51,8 @@ describe('matrix(next) example', () => { } @compute @workgroup_size(16, 16) fn computeSharedMemory_0(input: computeSharedMemory_Input_10) { - var dimensions = dimensions_1; - var numTiles = u32((f32(((dimensions.firstColumnCount + 16) - 1)) / 16f)); + let dimensions = (&dimensions_1); + var numTiles = u32((f32((((*dimensions).firstColumnCount + 16) - 1)) / 16f)); var globalRow = ((input.wid.x * 16) + input.lid.x); var globalCol = ((input.wid.y * 16) + input.lid.y); var localRow = input.lid.x; @@ -62,20 +62,20 @@ describe('matrix(next) example', () => { for (var tileIndex = 0u; (tileIndex < numTiles); tileIndex++) { var matrixACol = ((tileIndex * 16) + localCol); var valueA = 0; - if (((globalRow < dimensions.firstRowCount) && (matrixACol < dimensions.firstColumnCount))) { - var indexA = getIndex_4(globalRow, matrixACol, dimensions.firstColumnCount); + if (((globalRow < (*dimensions).firstRowCount) && (matrixACol < (*dimensions).firstColumnCount))) { + var indexA = getIndex_4(globalRow, matrixACol, (*dimensions).firstColumnCount); valueA = firstMatrix_5[indexA]; } tileA_6[tileIdx] = valueA; var matrixBRow = ((tileIndex * 16) + localRow); var valueB = 0; - if (((matrixBRow < dimensions.firstColumnCount) && (globalCol < dimensions.secondColumnCount))) { - var indexB = getIndex_4(matrixBRow, globalCol, dimensions.secondColumnCount); + if (((matrixBRow < (*dimensions).firstColumnCount) && (globalCol < (*dimensions).secondColumnCount))) { + var indexB = getIndex_4(matrixBRow, globalCol, (*dimensions).secondColumnCount); valueB = secondMatrix_7[indexB]; } tileB_8[tileIdx] = valueB; workgroupBarrier(); - var effectiveTileSize = min(16, (dimensions.firstColumnCount - (tileIndex * 16))); + var effectiveTileSize = min(16, ((*dimensions).firstColumnCount - (tileIndex * 16))); for (var k = 0u; (k < effectiveTileSize); k++) { var tileA_element = tileA_6[getTileIndex_3(localRow, k)]; var tileB_element = tileB_8[getTileIndex_3(k, localCol)]; @@ -83,8 +83,8 @@ describe('matrix(next) example', () => { } workgroupBarrier(); } - if (((globalRow < dimensions.firstRowCount) && (globalCol < dimensions.secondColumnCount))) { - var outputIndex = getIndex_4(globalRow, globalCol, dimensions.secondColumnCount); + if (((globalRow < (*dimensions).firstRowCount) && (globalCol < (*dimensions).secondColumnCount))) { + var outputIndex = getIndex_4(globalRow, globalCol, (*dimensions).secondColumnCount); resultMatrix_9[outputIndex] = accumulatedResult; } }" diff --git a/packages/typegpu/tests/examples/individual/oklab.test.ts b/packages/typegpu/tests/examples/individual/oklab.test.ts index dcb3b2f5d..7dbc0ba07 100644 --- a/packages/typegpu/tests/examples/individual/oklab.test.ts +++ b/packages/typegpu/tests/examples/individual/oklab.test.ts @@ -136,7 +136,7 @@ describe('oklab example', () => { } fn findGamutIntersection_13(a: f32, b: f32, L1: f32, C1: f32, L0: f32, cusp: LC_12) -> f32 { - var FLT_MAX = 3.4028234663852886e+38f; + const FLT_MAX = 3.4028234663852886e+38f; var t = 0f; if (((((L1 - L0) * cusp.C) - ((cusp.L - L0) * C1)) <= 0)) { t = ((cusp.C * L0) / ((C1 * cusp.L) + (cusp.C * (L0 - L1)))); @@ -193,9 +193,9 @@ describe('oklab example', () => { } fn gamutClipAdaptiveL05_8(lab: vec3f) -> vec3f { - var alpha = 0.2f; + const alpha = 0.20000000298023224f; var L = lab.x; - var eps = 1e-5; + const eps = 1e-5; var C = max(eps, length(lab.yz)); var a_ = (lab.y / C); var b_ = (lab.z / C); diff --git a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts index 64cc5251b..7bae0e8e0 100644 --- a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts +++ b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts @@ -58,8 +58,8 @@ describe('perlin noise example', () => { } @compute @workgroup_size(1, 1, 1) fn mainCompute_0(input: mainCompute_Input_9) { - var size = size_1; - var idx = ((input.gid.x + (input.gid.y * size.x)) + ((input.gid.z * size.x) * size.y)); + let size = (&size_1); + var idx = ((input.gid.x + (input.gid.y * (*size).x)) + ((input.gid.z * (*size).x) * (*size).y)); memory_2[idx] = computeJunctionGradient_3(vec3i(input.gid.xyz)); } diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index ed3fe6084..74e92b40e 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -52,18 +52,18 @@ describe('ray-marching example', () => { return min(min(d1, d2), d3); } - fn smoothShapeUnion_11(a: Shape_6, b: Shape_6, k: f32) -> Shape_6 { - var h = (max((k - abs((a.dist - b.dist))), 0) / k); + fn smoothShapeUnion_11(a: ptr, b: ptr, k: f32) -> Shape_6 { + var h = (max((k - abs(((*a).dist - (*b).dist))), 0) / k); var m = (h * h); - var dist = (min(a.dist, b.dist) - ((m * k) * 0.25)); - var weight = (m + select(0, (1 - m), (a.dist > b.dist))); - var color = mix(a.color, b.color, weight); + var dist = (min((*a).dist, (*b).dist) - ((m * k) * 0.25)); + var weight = (m + select(0, (1 - m), ((*a).dist > (*b).dist))); + var color = mix((*a).color, (*b).color, weight); return Shape_6(color, dist); } - fn getMorphingShape_8(p: vec3f, t: f32) -> Shape_6 { + fn getMorphingShape_8(p: ptr, t: f32) -> Shape_6 { var center = vec3f(0, 2, 6); - var localP = (p - center); + 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.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; @@ -73,8 +73,8 @@ describe('ray-marching example', () => { var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); - var spheres = smoothShapeUnion_11(sphere1, sphere2, 0.1); - return smoothShapeUnion_11(spheres, box, 0.2); + var spheres = smoothShapeUnion_11((&sphere1), (&sphere2), 0.1); + return smoothShapeUnion_11((&spheres), (&box), 0.2); } @group(0) @binding(1) var time_12: f32; @@ -88,22 +88,22 @@ describe('ray-marching example', () => { return (dot(p, n) + h); } - fn shapeUnion_15(a: Shape_6, b: Shape_6) -> Shape_6 { - return Shape_6(select(a.color, b.color, (a.dist > b.dist)), min(a.dist, b.dist)); + fn shapeUnion_15(a: ptr, b: ptr) -> Shape_6 { + return Shape_6(select((*a).color, (*b).color, ((*a).dist > (*b).dist)), min((*a).dist, (*b).dist)); } - fn getSceneDist_7(p: vec3f) -> Shape_6 { + fn getSceneDist_7(p: ptr) -> Shape_6 { var shape = getMorphingShape_8(p, time_12); - var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13((p.xz * 2))), sdPlane_14(p, vec3f(0, 1, 0), 0)); - return shapeUnion_15(shape, floor); + var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13(((*p).xz * 2))), sdPlane_14((*p), vec3f(0, 1, 0), 0)); + return shapeUnion_15((&shape), (&floor)); } - fn rayMarch_5(ro: vec3f, rd: vec3f) -> Shape_6 { + fn rayMarch_5(ro: ptr, rd: ptr) -> Shape_6 { var dO = 0f; var result = Shape_6(vec3f(), 30); for (var i = 0; (i < 1000); i++) { - var p = (ro + (rd * dO)); - var scene = getSceneDist_7(p); + var p = ((*ro) + ((*rd) * dO)); + var scene = getSceneDist_7((&p)); dO += scene.dist; if (((dO > 30) || (scene.dist < 1e-3))) { result.dist = dO; @@ -114,28 +114,50 @@ describe('ray-marching example', () => { return result; } - fn getNormal_16(p: vec3f) -> vec3f { + fn getMorphingShape_18(p: vec3f, t: f32) -> Shape_6 { + 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.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); + var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; + var boxSize = vec3f(0.699999988079071); + var sphere1Offset = vec3f((cos((t * 2)) * 0.8), (sin((t * 3)) * 0.3), (sin((t * 2)) * 0.8)); + var sphere2Offset = vec3f((cos(((t * 2) + 3.14)) * 0.8), (sin(((t * 3) + 1.57)) * 0.3), (sin(((t * 2) + 3.14)) * 0.8)); + var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); + var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); + var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); + var spheres = smoothShapeUnion_11((&sphere1), (&sphere2), 0.1); + return smoothShapeUnion_11((&spheres), (&box), 0.2); + } + + fn getSceneDist_17(p: vec3f) -> Shape_6 { + var shape = getMorphingShape_18(p, time_12); + var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13((p.xz * 2))), sdPlane_14(p, vec3f(0, 1, 0), 0)); + return shapeUnion_15((&shape), (&floor)); + } + + fn getNormal_16(p: ptr) -> vec3f { var dist = getSceneDist_7(p).dist; - var e = 0.01; - var n = vec3f((getSceneDist_7((p + vec3f(e, 0, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, e, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, 0, e))).dist - dist)); + const e = 0.01; + var n = vec3f((getSceneDist_17(((*p) + vec3f(e, 0, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, e, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, 0, e))).dist - dist)); return normalize(n); } - fn getOrbitingLightPos_17(t: f32) -> vec3f { - var radius = 3f; - var height = 6f; - var speed = 1f; + fn getOrbitingLightPos_19(t: f32) -> vec3f { + const radius = 3f; + const height = 6f; + const speed = 1f; return vec3f((cos((t * speed)) * radius), (height + (sin((t * speed)) * radius)), 4); } - fn softShadow_18(ro: vec3f, rd: vec3f, minT: f32, maxT: f32, k: f32) -> f32 { + fn softShadow_20(ro: ptr, rd: ptr, minT: f32, maxT: f32, k: f32) -> f32 { var res = 1f; var t = minT; for (var i = 0; (i < 100); i++) { if ((t >= maxT)) { break; } - var h = getSceneDist_7((ro + (rd * t))).dist; + var h = getSceneDist_17(((*ro) + ((*rd) * t))).dist; if ((h < 1e-3)) { return 0; } @@ -145,26 +167,26 @@ describe('ray-marching example', () => { return res; } - struct fragmentMain_Input_19 { + struct fragmentMain_Input_21 { @location(0) uv: vec2f, } - @fragment fn fragmentMain_3(input: fragmentMain_Input_19) -> @location(0) vec4f { + @fragment fn fragmentMain_3(input: fragmentMain_Input_21) -> @location(0) vec4f { var uv = ((input.uv * 2) - 1); uv.x *= (resolution_4.x / resolution_4.y); var ro = vec3f(0, 2, 3); var rd = normalize(vec3f(uv.x, uv.y, 1)); - var march = rayMarch_5(ro, rd); + var march = rayMarch_5((&ro), (&rd)); var fog = pow(min((march.dist / 30f), 1), 0.7); var p = (ro + (rd * march.dist)); - var n = getNormal_16(p); - var lightPos = getOrbitingLightPos_17(time_12); + var n = getNormal_16((&p)); + var lightPos = getOrbitingLightPos_19(time_12); var l = normalize((lightPos - p)); var diff = max(dot(n, l), 0); - var shadowRo = p; - var shadowRd = l; + let shadowRo = (&p); + let shadowRd = (&l); var shadowDist = length((lightPos - p)); - var shadow = softShadow_18(shadowRo, shadowRd, 0.1, shadowDist, 16); + var shadow = softShadow_20(shadowRo, shadowRd, 0.1, shadowDist, 16); var litColor = (march.color * diff); var finalColor = mix((litColor * 0.5), litColor, shadow); return mix(vec4f(finalColor, 1), vec4f(0.699999988079071, 0.800000011920929, 0.8999999761581421, 1), fog); diff --git a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts index 1b382bf08..4306d166b 100644 --- a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts +++ b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts @@ -87,11 +87,11 @@ describe('simple shadow example', () => { } @vertex fn mainVert_0(_arg_0: mainVert_Input_7) -> mainVert_Output_6 { - var modelMatrixUniform = instanceInfo_1.modelMatrix; - var worldPos = (modelMatrixUniform * _arg_0.position); + let modelMatrixUniform = (&instanceInfo_1.modelMatrix); + var worldPos = ((*modelMatrixUniform) * _arg_0.position); var viewPos = (cameraUniform_4.view * worldPos); var clipPos = (cameraUniform_4.projection * viewPos); - var transformedNormal = (modelMatrixUniform * _arg_0.normal); + var transformedNormal = ((*modelMatrixUniform) * _arg_0.normal); return mainVert_Output_6(clipPos, transformedNormal, worldPos.xyz); } @@ -125,7 +125,7 @@ describe('simple shadow example', () => { } @fragment fn mainFrag_8(_arg_0: mainFrag_Input_17) -> @location(0) vec4f { - var instanceInfo = instanceInfo_1; + let instanceInfo = (&instanceInfo_1); var N = normalize(_arg_0.normal.xyz); var L = normalize(-(light_9.direction)); var V = normalize((cameraUniform_4.position - _arg_0.worldPos)); @@ -140,11 +140,11 @@ describe('simple shadow example', () => { if (!inBounds) { shadowFactor = 1; } - var ambient = (instanceInfo.material.ambient * light_9.color); + var ambient = ((*instanceInfo).material.ambient * light_9.color); var diff = max(0, dot(N, L)); - var diffuse = ((instanceInfo.material.diffuse * light_9.color) * diff); - var spec = pow(max(0, dot(V, R)), instanceInfo.material.shininess); - var specular = ((instanceInfo.material.specular * light_9.color) * spec); + var diffuse = (((*instanceInfo).material.diffuse * light_9.color) * diff); + var spec = pow(max(0, dot(V, R)), (*instanceInfo).material.shininess); + var specular = (((*instanceInfo).material.specular * light_9.color) * spec); var lit = ((diffuse + specular) * shadowFactor); var finalColor = (ambient + lit); if ((paramsUniform_15.shadowOnly == 1)) { diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index 74a857747..352c2456f 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -155,11 +155,11 @@ describe('slime mold 3d example', () => { return item_8(); } - fn getPerpendicular_10(dir: vec3f) -> vec3f { + fn getPerpendicular_10(dir: ptr) -> vec3f { var axis = vec3f(1, 0, 0); - var absX = abs(dir.x); - var absY = abs(dir.y); - var absZ = abs(dir.z); + var absX = abs((*dir).x); + var absY = abs((*dir).y); + var absZ = abs((*dir).z); if (((absY <= absX) && (absY <= absZ))) { axis = vec3f(0, 1, 0); } @@ -168,7 +168,7 @@ describe('slime mold 3d example', () => { axis = vec3f(0, 0, 1); } } - return normalize(cross(dir, axis)); + return normalize(cross((*dir), axis)); } struct Params_12 { @@ -187,19 +187,19 @@ describe('slime mold 3d example', () => { totalWeight: f32, } - fn sense3D_9(pos: vec3f, direction: vec3f) -> SenseResult_13 { + fn sense3D_9(pos: ptr, direction: ptr) -> SenseResult_13 { var dims = textureDimensions(oldState_4); var dimsf = vec3f(dims); var weightedDir = vec3f(); var totalWeight = 0f; var perp1 = getPerpendicular_10(direction); - var perp2 = cross(direction, perp1); - var numSamples = 8; + var perp2 = cross((*direction), perp1); + const numSamples = 8; for (var i = 0; (i < numSamples); i++) { var theta = (((f32(i) / f32(numSamples)) * 2) * 3.141592653589793); var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); - var sensorDir = normalize((direction + (coneOffset * sin(params_11.sensorAngle)))); - var sensorPos = (pos + (sensorDir * params_11.sensorDistance)); + var sensorDir = normalize(((*direction) + (coneOffset * sin(params_11.sensorAngle)))); + var sensorPos = ((*pos) + (sensorDir * params_11.sensorDistance)); var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); var weight = textureLoad(oldState_4, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); @@ -244,20 +244,20 @@ describe('slime mold 3d example', () => { randSeed_1(((f32(_arg_0.gid.x) / 8e+5f) + 0.1)); var dims = textureDimensions(oldState_4); var dimsf = vec3f(dims); - var agent = agentsData_5[_arg_0.gid.x]; + let agent = (&agentsData_5[_arg_0.gid.x]); var random = randFloat01_7(); - var direction = normalize(agent.direction); - var senseResult = sense3D_9(agent.position, direction); + var direction = normalize((*agent).direction); + var senseResult = sense3D_9((&(*agent).position), (&direction)); if ((senseResult.totalWeight > 0.01)) { var targetDir = normalize(senseResult.weightedDir); direction = normalize((direction + (targetDir * (params_11.turnSpeed * params_11.deltaTime)))); } else { - var perp = getPerpendicular_10(direction); + var perp = getPerpendicular_10((&direction)); var randomOffset = (perp * ((((random * 2) - 1) * params_11.turnSpeed) * params_11.deltaTime)); direction = normalize((direction + randomOffset)); } - var newPos = (agent.position + (direction * (params_11.moveSpeed * params_11.deltaTime))); + var newPos = ((*agent).position + (direction * (params_11.moveSpeed * params_11.deltaTime))); var center = (dimsf / 2); if (((newPos.x < 0) || (newPos.x >= dimsf.x))) { newPos.x = clamp(newPos.x, 0, (dimsf.x - 1)); @@ -324,10 +324,10 @@ describe('slime mold 3d example', () => { hit: bool, } - fn rayBoxIntersection_6(rayOrigin: vec3f, rayDir: vec3f, boxMin: vec3f, boxMax: vec3f) -> RayBoxResult_7 { - var invDir = (vec3f(1) / rayDir); - var t0 = ((boxMin - rayOrigin) * invDir); - var t1 = ((boxMax - rayOrigin) * invDir); + fn rayBoxIntersection_6(rayOrigin: ptr, rayDir: ptr, boxMin: ptr, boxMax: ptr) -> RayBoxResult_7 { + var invDir = (vec3f(1) / (*rayDir)); + var t0 = (((*boxMin) - (*rayOrigin)) * invDir); + var t1 = (((*boxMax) - (*rayOrigin)) * invDir); var tmin = min(t0, t1); var tmax = max(t0, t1); var tNear = max(max(tmin.x, tmin.y), tmin.z); @@ -355,22 +355,22 @@ describe('slime mold 3d example', () => { var rayDir = normalize((rayEnd - rayOrigin)); var boxMin = vec3f(); var boxMax = vec3f(256); - var isect = rayBoxIntersection_6(rayOrigin, rayDir, boxMin, boxMax); + var isect = rayBoxIntersection_6((&rayOrigin), (&rayDir), (&boxMin), (&boxMax)); if (!isect.hit) { return vec4f(); } var tStart = max(isect.tNear, 0); var tEnd = isect.tFar; - var numSteps = 128; + const numSteps = 128; var stepSize = ((tEnd - tStart) / f32(numSteps)); - var thresholdLo = 0.05999999865889549f; - var thresholdHi = 0.25f; - var gamma = 1.399999976158142f; - var sigmaT = 0.05000000074505806f; + const thresholdLo = 0.05999999865889549f; + const thresholdHi = 0.25f; + const gamma = 1.399999976158142f; + const sigmaT = 0.05000000074505806f; var albedo = vec3f(0.5699999928474426, 0.4399999976158142, 0.9599999785423279); var transmittance = 1f; var accum = vec3f(); - var TMin = 0.0010000000474974513f; + const TMin = 0.0010000000474974513f; for (var i = 0; (i < numSteps); i++) { if ((transmittance <= TMin)) { break; diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index 3bec7a362..fbebf8820 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -46,8 +46,8 @@ describe('stable-fluid example', () => { var velocity = textureLoad(src_1, pixelPos, 0); var timeStep = simParams_3.dt; var prevPos = (vec2f(pixelPos) - (timeStep * velocity.xy)); - var clampedPos = clamp(prevPos, vec2f(-0.5), vec2f((vec2f(texSize.xy) - vec2f(0.5)))); - var normalizedPos = ((clampedPos + vec2f(0.5)) / vec2f(texSize.xy)); + var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - 0.5)); + var normalizedPos = ((clampedPos + 0.5) / vec2f(texSize.xy)); var prevVelocity = textureSampleLevel(src_1, linSampler_5, normalizedPos, 0); textureStore(dst_2, pixelPos, prevVelocity); } @@ -240,14 +240,14 @@ describe('stable-fluid example', () => { } @fragment fn fragmentImageFn_3(input: fragmentImageFn_Input_7) -> @location(0) vec4f { - var pixelStep = 0.001953125f; + const pixelStep = 0.001953125f; var leftSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x - pixelStep), input.uv.y)).x; var rightSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x + pixelStep), input.uv.y)).x; var upSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y + pixelStep))).x; var downSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y - pixelStep))).x; var gradientX = (rightSample - leftSample); var gradientY = (upSample - downSample); - var distortStrength = 0.8; + const distortStrength = 0.8; var distortVector = vec2f(gradientX, gradientY); var distortedUV = (input.uv + (distortVector * vec2f(distortStrength, -distortStrength))); var outputColor = textureSample(background_6, linSampler_5, vec2f(distortedUV.x, (1 - distortedUV.y))); diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index a40e37757..ee427313e 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -162,61 +162,54 @@ describe('tgsl parsing test example', () => { return s; } - fn modifyNumFn_12(ptr: ptr) { - *ptr += 1; - } - - fn modifyVecFn_13(ptr: ptr) { + fn modifyVecFn_12(ptr: ptr) { (*ptr).x += 1; } - struct SimpleStruct_14 { + struct SimpleStruct_13 { vec: vec2f, } - fn modifyStructFn_15(ptr: ptr) { + fn modifyStructFn_14(ptr: ptr) { (*ptr).vec.x += 1; } - var privateNum_16: u32; + var privateNum_15: u32; - fn modifyNumPrivate_17(ptr: ptr) { - *ptr += 1; + fn modifyNumPrivate_16(ptr: ptr) { + (*ptr) += 1; } - var privateVec_18: vec2f; + var privateVec_17: vec2f; - fn modifyVecPrivate_19(ptr: ptr) { + fn modifyVecPrivate_18(ptr: ptr) { (*ptr).x += 1; } - var privateStruct_20: SimpleStruct_14; + var privateStruct_19: SimpleStruct_13; - fn modifyStructPrivate_21(ptr: ptr) { + fn modifyStructPrivate_20(ptr: ptr) { (*ptr).vec.x += 1; } fn pointersTest_11() -> bool { var s = true; - var num = 0u; - modifyNumFn_12(&num); - s = (s && (num == 1)); var vec = vec2f(); - modifyVecFn_13(&vec); + modifyVecFn_12((&vec)); s = (s && all(vec == vec2f(1, 0))); - var myStruct = SimpleStruct_14(); - modifyStructFn_15(&myStruct); + var myStruct = SimpleStruct_13(); + modifyStructFn_14((&myStruct)); s = (s && all(myStruct.vec == vec2f(1, 0))); - modifyNumPrivate_17(&privateNum_16); - s = (s && (privateNum_16 == 1)); - modifyVecPrivate_19(&privateVec_18); - s = (s && all(privateVec_18 == vec2f(1, 0))); - modifyStructPrivate_21(&privateStruct_20); - s = (s && all(privateStruct_20.vec == vec2f(1, 0))); + modifyNumPrivate_16((&privateNum_15)); + s = (s && (privateNum_15 == 1)); + modifyVecPrivate_18((&privateVec_17)); + s = (s && all(privateVec_17 == vec2f(1, 0))); + modifyStructPrivate_20((&privateStruct_19)); + s = (s && all(privateStruct_19.vec == vec2f(1, 0))); return s; } - @group(0) @binding(0) var result_22: i32; + @group(0) @binding(0) var result_21: i32; @compute @workgroup_size(1) fn computeRunTests_0() { var s = true; @@ -226,10 +219,10 @@ describe('tgsl parsing test example', () => { s = (s && arrayAndStructConstructorsTest_8()); s = (s && pointersTest_11()); if (s) { - result_22 = 1; + result_21 = 1; } else { - result_22 = 0; + result_21 = 0; } }" `); diff --git a/packages/typegpu/tests/examples/individual/vaporrave.test.ts b/packages/typegpu/tests/examples/individual/vaporrave.test.ts index 415d70a7f..8dfb4751e 100644 --- a/packages/typegpu/tests/examples/individual/vaporrave.test.ts +++ b/packages/typegpu/tests/examples/individual/vaporrave.test.ts @@ -194,7 +194,7 @@ describe('vaporrave example', () => { var p = ((rd * distOrigin) + ro); var scene = getSceneRay_7(p); var sphereDist = getSphere_12(p, sphereColorUniform_21, vec3f(0, 6, 12), sphereAngleUniform_22); - glow = ((vec3f(sphereColorUniform_21) * exp(-sphereDist.dist)) + glow); + glow = ((sphereColorUniform_21 * exp(-sphereDist.dist)) + glow); distOrigin += scene.dist; if ((distOrigin > 19)) { result.dist = 19; diff --git a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts index 9ca5b822b..50c30f420 100644 --- a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts +++ b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts @@ -84,7 +84,7 @@ describe('wgsl resolution example', () => { @compute @workgroup_size(1) fn compute_shader_8(input: compute_shader_Input_14) { var index = input.gid.x; - var instanceInfo = currentTrianglePos_9[index]; + let instanceInfo = (¤tTrianglePos_9[index]); var separation = vec2f(); var alignment = vec2f(); var cohesion = vec2f(); @@ -94,17 +94,17 @@ describe('wgsl resolution example', () => { if ((i == index)) { continue; } - var other = currentTrianglePos_9[i]; - var dist = distance(instanceInfo.position, other.position); + let other = (¤tTrianglePos_9[i]); + var dist = distance((*instanceInfo).position, (*other).position); if ((dist < paramsBuffer_11.separationDistance)) { - separation = (separation + (instanceInfo.position - other.position)); + separation = (separation + ((*instanceInfo).position - (*other).position)); } if ((dist < paramsBuffer_11.alignmentDistance)) { - alignment = (alignment + other.velocity); + alignment = (alignment + (*other).velocity); alignmentCount++; } if ((dist < paramsBuffer_11.cohesionDistance)) { - cohesion = (cohesion + other.position); + cohesion = (cohesion + (*other).position); cohesionCount++; } } @@ -113,27 +113,27 @@ describe('wgsl resolution example', () => { } if ((cohesionCount > 0)) { cohesion = ((1f / f32(cohesionCount)) * cohesion); - cohesion = (cohesion - instanceInfo.position); + cohesion = (cohesion - (*instanceInfo).position); } var velocity = (paramsBuffer_11.separationStrength * separation); velocity = (velocity + (paramsBuffer_11.alignmentStrength * alignment)); velocity = (velocity + (paramsBuffer_11.cohesionStrength * cohesion)); - instanceInfo.velocity = (instanceInfo.velocity + velocity); - instanceInfo.velocity = (clamp(length(instanceInfo.velocity), 0, 0.01) * normalize(instanceInfo.velocity)); - if ((instanceInfo.position.x > 1.03)) { - instanceInfo.position.x = (-1 - 0.03); + (*instanceInfo).velocity = ((*instanceInfo).velocity + velocity); + (*instanceInfo).velocity = (clamp(length((*instanceInfo).velocity), 0, 0.01) * normalize((*instanceInfo).velocity)); + if (((*instanceInfo).position.x > 1.03)) { + (*instanceInfo).position.x = (-1 - 0.03); } - if ((instanceInfo.position.y > 1.03)) { - instanceInfo.position.y = (-1 - 0.03); + if (((*instanceInfo).position.y > 1.03)) { + (*instanceInfo).position.y = (-1 - 0.03); } - if ((instanceInfo.position.x < (-1 - 0.03))) { - instanceInfo.position.x = 1.03; + if (((*instanceInfo).position.x < (-1 - 0.03))) { + (*instanceInfo).position.x = 1.03; } - if ((instanceInfo.position.y < (-1 - 0.03))) { - instanceInfo.position.y = 1.03; + if (((*instanceInfo).position.y < (-1 - 0.03))) { + (*instanceInfo).position.y = 1.03; } - instanceInfo.position = (instanceInfo.position + instanceInfo.velocity); - nextTrianglePos_13[index] = instanceInfo; + (*instanceInfo).position = ((*instanceInfo).position + (*instanceInfo).velocity); + nextTrianglePos_13[index] = (*instanceInfo); }" `); }); diff --git a/packages/typegpu/tests/indent.test.ts b/packages/typegpu/tests/indent.test.ts index 1aaae166c..973e285ea 100644 --- a/packages/typegpu/tests/indent.test.ts +++ b/packages/typegpu/tests/indent.test.ts @@ -116,8 +116,8 @@ describe('indents', () => { fn main_0() { for (var i = 0; (i < 100); i++) { - var particle = systemData_1.particles[i]; - systemData_1.particles[i] = updateParicle_4(particle, systemData_1.gravity, systemData_1.deltaTime); + let particle = (&systemData_1.particles[i]); + systemData_1.particles[i] = updateParicle_4((*particle), systemData_1.gravity, systemData_1.deltaTime); } }" `); @@ -241,8 +241,8 @@ describe('indents', () => { fn main_0() { incrementCounter_1(); for (var i = 0; (i < 100); i++) { - var particle = systemData_3.particles[i]; - systemData_3.particles[i] = updateParticle_7(particle, systemData_3.gravity, systemData_3.deltaTime); + let particle = (&systemData_3.particles[i]); + systemData_3.particles[i] = updateParticle_7((*particle), systemData_3.gravity, systemData_3.deltaTime); } }" `); @@ -411,16 +411,16 @@ describe('indents', () => { } @vertex fn someVertex_0(input: someVertex_Input_7) -> someVertex_Output_6 { - var uniBoid = boids_1; + let uniBoid = (&boids_1); for (var i = 0u; (i < -1); i++) { var sampled = textureSample(sampled_3, sampler_4, vec2f(0.5), i); var someVal = textureLoad(smoothRender_5, vec2i(), 0); if (((someVal.x + sampled.x) > 0.5)) { - var newPos = (uniBoid.position + vec4f(1, 2, 3, 4)); + var newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); } else { while (true) { - var newPos = (uniBoid.position + vec4f(1, 2, 3, 4)); + var newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); if ((newPos.x > 0)) { var evenNewer = (newPos + input.position); } diff --git a/packages/typegpu/tests/numeric.test.ts b/packages/typegpu/tests/numeric.test.ts index 67e77cdad..d06a010fe 100644 --- a/packages/typegpu/tests/numeric.test.ts +++ b/packages/typegpu/tests/numeric.test.ts @@ -72,11 +72,11 @@ describe('TGSL', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() { - var f = 0f; - var h = 0h; - var i = 0i; - var u = 0u; - var b = false; + const f = 0f; + const h = 0h; + const i = 0i; + const u = 0u; + const b = false; }" `); }); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 4103fc768..6b6abbc14 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -57,7 +57,7 @@ describe('tgpu resolve', () => { [$gpuValueOf]: { [$internal]: true, get [$ownSnippet]() { - return snip(this, d.f32); + return snip(this, d.f32, /* ref */ 'runtime'); }, [$resolve]: (ctx: ResolutionCtx) => ctx.resolve(intensity), } as unknown as number, @@ -67,7 +67,7 @@ describe('tgpu resolve', () => { ctx.addDeclaration( `@group(0) @binding(0) var ${name}: f32;`, ); - return snip(name, d.f32); + return snip(name, d.f32, /* ref */ 'runtime'); }, get value(): number { diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts index a9a70bee8..4a1733ea0 100644 --- a/packages/typegpu/tests/slot.test.ts +++ b/packages/typegpu/tests/slot.test.ts @@ -317,10 +317,10 @@ describe('tgpu.slot', () => { fn func() { var pos = vec3f(1, 2, 3); - var posX = 1; - var vel = boid.vel; + const posX = 1f; + let vel = (&boid.vel); var velX = boid.vel.x; - var vel_ = boid.vel; + let vel_ = (&boid.vel); var velX_ = boid.vel.x; var color = getColor(); }" diff --git a/packages/typegpu/tests/std/matrix/rotate.test.ts b/packages/typegpu/tests/std/matrix/rotate.test.ts index d3f53117d..8e400aef7 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(asWgsl(rotateFn)).toMatchInlineSnapshot(` "fn rotateFn() { - var angle = 4; + const angle = 4; var resultExpression = (mat4x4f(1, 0, 0, 0, 0, cos(angle), sin(angle), 0, 0, -sin(angle), cos(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(asWgsl(rotateFn)).toMatchInlineSnapshot(` "fn rotateFn() { - var angle = 4; + const angle = 4; var resultExpression = (mat4x4f(cos(angle), 0, -sin(angle), 0, 0, 1, 0, 0, sin(angle), 0, cos(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(asWgsl(rotateFn)).toMatchInlineSnapshot(` "fn rotateFn() { - var angle = 4; + const angle = 4; var resultExpression = (mat4x4f(cos(angle), sin(angle), 0, 0, -sin(angle), cos(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/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index b39c5fa12..d7f6ea6d1 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -33,7 +33,7 @@ describe('wgslGenerator with console.log', () => { `); expect(consoleWarnSpy).toHaveBeenCalledWith( - "'console.log' is currently only supported in compute pipelines.", + "'console.log' is only supported when resolving pipelines.", ); expect(consoleWarnSpy).toHaveBeenCalledTimes(1); }); @@ -265,7 +265,7 @@ describe('wgslGenerator with console.log', () => { .createPipeline(); expect(asWgsl(pipeline)).toMatchInlineSnapshot(` - + "@group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -602,7 +602,7 @@ describe('wgslGenerator with console.log', () => { - computePipeline:pipeline - computePipelineCore - computeFn:fn - - consoleLog: Logged data needs to fit in 252 bytes (one of the logs requires 256 bytes). Consider increasing the limit by passing appropriate options to tgpu.init().] + - fn:consoleLog: Logged data needs to fit in 252 bytes (one of the logs requires 256 bytes). Consider increasing the limit by passing appropriate options to tgpu.init().] `); }); }); diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index ff610ab96..4c45260e7 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -185,13 +185,17 @@ describe('getBestConversion', () => { }); describe('convertToCommonType', () => { - const snippetF32 = snip('2.22', d.f32); - const snippetI32 = snip('-12', d.i32); - const snippetU32 = snip('33', d.u32); - const snippetAbsFloat = snip('1.1', abstractFloat); - const snippetAbsInt = snip('1', abstractInt); - const snippetPtrF32 = snip('ptr_f32', d.ptrPrivate(d.f32)); - const snippetUnknown = snip('?', UnknownData); + const snippetF32 = snip('2.22', d.f32, /* ref */ 'runtime'); + const snippetI32 = snip('-12', d.i32, /* ref */ 'runtime'); + const snippetU32 = snip('33', d.u32, /* ref */ 'runtime'); + const snippetAbsFloat = snip('1.1', abstractFloat, /* ref */ 'runtime'); + const snippetAbsInt = snip('1', abstractInt, /* ref */ 'runtime'); + const snippetPtrF32 = snip( + 'ptr_f32', + d.ptrPrivate(d.f32), + /* ref */ 'function', + ); + const snippetUnknown = snip('?', UnknownData, /* ref */ 'runtime'); it('converts identical types', () => { const result = convertToCommonType([snippetF32, snippetF32]); @@ -235,13 +239,13 @@ describe('convertToCommonType', () => { expect(result).toBeDefined(); expect(result?.length).toBe(2); expect(result?.[0]?.dataType).toBe(d.f32); - expect(result?.[0]?.value).toBe('*ptr_f32'); // Deref applied + expect(result?.[0]?.value).toBe('(*ptr_f32)'); // Deref applied expect(result?.[1]?.dataType).toBe(d.f32); expect(result?.[1]?.value).toBe('2.22'); }); it('returns undefined for incompatible types', () => { - const snippetVec2f = snip('v2', d.vec2f); + const snippetVec2f = snip('v2', d.vec2f, /* ref */ 'runtime'); const result = convertToCommonType([snippetF32, snippetVec2f]); expect(result).toBeUndefined(); }); @@ -281,7 +285,10 @@ describe('convertToCommonType', () => { }); it('handles void gracefully', () => { - const result = convertToCommonType([snippetF32, snip('void', d.Void)]); + const result = convertToCommonType([ + snippetF32, + snip('void', d.Void, /* ref */ 'runtime'), + ]); expect(result).toBeUndefined(); }); @@ -301,10 +308,10 @@ describe('convertStructValues', () => { it('maps values matching types exactly', () => { const snippets: Record = { - a: snip('1.0', d.f32), - b: snip('2', d.i32), - c: snip('vec2f(1.0, 1.0)', d.vec2f), - d: snip('true', d.bool), + a: snip('1.0', d.f32, /* ref */ 'runtime'), + b: snip('2', d.i32, /* ref */ 'runtime'), + c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ 'runtime'), + d: snip('true', d.bool, /* ref */ 'runtime'), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); @@ -316,25 +323,25 @@ describe('convertStructValues', () => { it('maps values requiring implicit casts and warns', () => { const snippets: Record = { - a: snip('1', d.i32), // i32 -> f32 (cast) - b: snip('2', d.u32), // u32 -> i32 (cast) - c: snip('2.22', d.f32), - d: snip('true', d.bool), + a: snip('1', d.i32, /* ref */ 'runtime'), // i32 -> f32 (cast) + b: snip('2', d.u32, /* ref */ 'runtime'), // u32 -> i32 (cast) + c: snip('2.22', d.f32, /* ref */ 'runtime'), + d: snip('true', d.bool, /* ref */ 'runtime'), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); - expect(res[0]).toEqual(snip('f32(1)', d.f32)); // Cast applied - expect(res[1]).toEqual(snip('i32(2)', d.i32)); // Cast applied + expect(res[0]).toEqual(snip('f32(1)', d.f32, /* ref */ 'runtime')); // Cast applied + expect(res[1]).toEqual(snip('i32(2)', d.i32, /* ref */ 'runtime')); // Cast applied expect(res[2]).toEqual(snippets.c); expect(res[3]).toEqual(snippets.d); }); it('throws on missing property', () => { const snippets: Record = { - a: snip('1.0', d.f32), + a: snip('1.0', d.f32, /* ref */ 'runtime'), // b is missing - c: snip('vec2f(1.0, 1.0)', d.vec2f), - d: snip('true', d.bool), + c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ 'runtime'), + d: snip('true', d.bool, /* ref */ 'runtime'), }; expect(() => convertStructValues(structType, snippets)).toThrow( /Missing property b/, diff --git a/packages/typegpu/tests/tgsl/createDualImpl.test.ts b/packages/typegpu/tests/tgsl/createDualImpl.test.ts index b9b09bd30..94d8c10e1 100644 --- a/packages/typegpu/tests/tgsl/createDualImpl.test.ts +++ b/packages/typegpu/tests/tgsl/createDualImpl.test.ts @@ -41,7 +41,7 @@ describe('dualImpl', () => { expect(asWgsl(myFn)).toMatchInlineSnapshot(` "fn myFn() { - var a = 5; + const a = 5; }" `); }); @@ -122,7 +122,7 @@ describe('dualImpl', () => { [Error: Resolution of the following tree failed: - - fn:myFn - - myDualImpl: Division by zero] + - fn:myDualImpl: Division by zero] `); }); }); diff --git a/packages/typegpu/tests/tgsl/generationHelpers.test.ts b/packages/typegpu/tests/tgsl/generationHelpers.test.ts index 9cf27f8d8..06620c475 100644 --- a/packages/typegpu/tests/tgsl/generationHelpers.test.ts +++ b/packages/typegpu/tests/tgsl/generationHelpers.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { arrayOf } from '../../src/data/array.ts'; import { mat2x2f, mat3x3f, mat4x4f } from '../../src/data/matrix.ts'; import { @@ -20,41 +20,23 @@ import { vec4h, } from '../../src/data/vector.ts'; import { + accessIndex, + accessProp, coerceToSnippet, - type GenerationCtx, - getTypeForIndexAccess, - getTypeForPropAccess, numericLiteralToSnippet, } from '../../src/tgsl/generationHelpers.ts'; import { UnknownData } from '../../src/data/dataTypes.ts'; import { snip } from '../../src/data/snippet.ts'; -import { CodegenState } from '../../src/types.ts'; import { INTERNAL_setCtx } from '../../src/execMode.ts'; - -const mockCtx = { - indent: () => '', - dedent: () => '', - pushBlockScope: () => {}, - popBlockScope: () => {}, - mode: new CodegenState(), - getById: vi.fn(), - defineVariable: vi.fn((id, dataType) => ({ value: id, dataType })), - resolve: vi.fn((val) => { - if ( - (typeof val === 'function' || typeof val === 'object') && - 'type' in val - ) { - return val.type; - } - return val; - }), - unwrap: vi.fn((val) => val), - pre: '', -} as unknown as GenerationCtx; +import { ResolutionCtxImpl } from '../../src/resolutionCtx.ts'; +import { namespace } from '../../src/core/resolve/namespace.ts'; describe('generationHelpers', () => { beforeEach(() => { - INTERNAL_setCtx(mockCtx); + const ctx = new ResolutionCtxImpl({ + namespace: namespace(), + }); + INTERNAL_setCtx(ctx); }); afterEach(() => { @@ -64,77 +46,123 @@ describe('generationHelpers', () => { describe('numericLiteralToSnippet', () => { it('should convert numeric literals to correct snippets', () => { expect(numericLiteralToSnippet(1)).toEqual( - snip(1, abstractInt), + snip(1, abstractInt, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(1.1)).toEqual( - snip(1.1, abstractFloat), + snip(1.1, abstractFloat, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(1e10)).toEqual( - snip(1e10, abstractInt), + snip(1e10, abstractInt, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(0.5)).toEqual( - snip(0.5, abstractFloat), + snip(0.5, abstractFloat, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(-45)).toEqual( - snip(-45, abstractInt), + snip(-45, abstractInt, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(0x1A)).toEqual( - snip(0x1A, abstractInt), + snip(0x1A, abstractInt, /* ref */ 'constant'), ); - expect(numericLiteralToSnippet(0b101)).toEqual(snip(5, abstractInt)); + expect(numericLiteralToSnippet(0b101)).toEqual( + snip(5, abstractInt, /* ref */ 'constant'), + ); }); }); - describe('getTypeForPropAccess', () => { + describe('accessProp', () => { const MyStruct = struct({ foo: f32, bar: vec3f, }); it('should return struct property types', () => { - expect(getTypeForPropAccess(MyStruct, 'foo')).toBe(f32); - expect(getTypeForPropAccess(MyStruct, 'bar')).toBe(vec3f); - expect(getTypeForPropAccess(MyStruct, 'notfound')).toBe(UnknownData); + const target = snip('foo', MyStruct, 'this-function'); + expect(accessProp(target, 'foo')).toStrictEqual( + snip('foo.foo', f32, /* ref */ 'runtime'), + ); + expect(accessProp(target, 'bar')).toStrictEqual( + snip('foo.bar', vec3f, /* ref */ 'this-function'), + ); + expect(accessProp(target, 'notfound')).toStrictEqual(undefined); }); it('should return swizzle types on vectors', () => { - expect(getTypeForPropAccess(vec4f, 'x')).toBe(f32); - expect(getTypeForPropAccess(vec4f, 'yz')).toBe(vec2f); - expect(getTypeForPropAccess(vec4f, 'xyzw')).toBe(vec4f); + const target = snip('foo', vec4f, 'this-function'); + + expect(accessProp(target, 'x')).toStrictEqual( + snip('foo.x', f32, /* ref */ 'runtime'), + ); + expect(accessProp(target, 'yz')).toStrictEqual( + snip('foo.yz', vec2f, /* ref */ 'runtime'), + ); + expect(accessProp(target, 'xyzw')).toStrictEqual( + snip('foo.xyzw', vec4f, /* ref */ 'runtime'), + ); }); - it('should return UnknownData when applied to primitives or invalid', () => { - expect(getTypeForPropAccess(u32, 'x')).toBe(UnknownData); - expect(getTypeForPropAccess(bool, 'x')).toBe(UnknownData); + it('should return undefined when applied to primitives or invalid', () => { + const target1 = snip('foo', u32, /* ref */ 'runtime'); + const target2 = snip('foo', bool, /* ref */ 'runtime'); + expect(accessProp(target1, 'x')).toBe(undefined); + expect(accessProp(target2, 'x')).toBe(undefined); }); }); - describe('getTypeForIndexAccess', () => { + describe('accessIndex', () => { const arr = arrayOf(f32, 2); + const index = snip('0', u32, /* ref */ 'runtime'); it('returns element type for arrays', () => { - expect(getTypeForIndexAccess(arr)).toBe(f32); + const target = snip('foo', arr, /* ref */ 'runtime'); + expect(accessIndex(target, index)).toStrictEqual( + snip('foo[0]', f32, 'runtime'), + ); }); it('returns vector component', () => { - expect(getTypeForIndexAccess(vec2i)).toBe(i32); - expect(getTypeForIndexAccess(vec4h)).toBe(f16); + const target1 = snip('foo', vec2i, /* ref */ 'runtime'); + const target2 = snip('foo', vec4h, /* ref */ 'runtime'); + expect(accessIndex(target1, index)).toStrictEqual( + snip('foo[0]', i32, 'runtime'), + ); + expect(accessIndex(target2, index)).toStrictEqual( + snip('foo[0]', f16, 'runtime'), + ); }); it('returns matrix column type', () => { - expect(getTypeForIndexAccess(mat2x2f)).toBe(vec2f); - expect(getTypeForIndexAccess(mat3x3f)).toBe(vec3f); - expect(getTypeForIndexAccess(mat4x4f)).toBe(vec4f); + const target1 = accessProp( + snip('foo', mat2x2f, /* ref */ 'runtime'), + 'columns', + ); + const target2 = accessProp( + snip('foo', mat3x3f, /* ref */ 'runtime'), + 'columns', + ); + const target3 = accessProp( + snip('foo', mat4x4f, /* ref */ 'runtime'), + 'columns', + ); + expect(target1 && accessIndex(target1, index)).toStrictEqual( + snip('foo[0]', vec2f, 'runtime'), + ); + expect(target2 && accessIndex(target2, index)).toStrictEqual( + snip('foo[0]', vec3f, 'runtime'), + ); + expect(target3 && accessIndex(target3, index)).toStrictEqual( + snip('foo[0]', vec4f, 'runtime'), + ); }); - it('returns UnknownData otherwise', () => { - expect(getTypeForIndexAccess(f32)).toBe(UnknownData); + it('returns undefined otherwise', () => { + const target = snip('foo', f32, /* ref */ 'runtime'); + expect(accessIndex(target, index)).toBe(undefined); }); }); @@ -142,21 +170,39 @@ describe('generationHelpers', () => { const arr = arrayOf(f32, 2); it('coerces JS numbers', () => { - expect(coerceToSnippet(1)).toEqual(snip(1, abstractInt)); - expect(coerceToSnippet(2.5)).toEqual(snip(2.5, abstractFloat)); - expect(coerceToSnippet(-10)).toEqual(snip(-10, abstractInt)); - expect(coerceToSnippet(0.0)).toEqual(snip(0, abstractInt)); + expect(coerceToSnippet(1)).toEqual( + snip(1, abstractInt, /* ref */ 'constant'), + ); + expect(coerceToSnippet(2.5)).toEqual( + snip(2.5, abstractFloat, /* ref */ 'constant'), + ); + expect(coerceToSnippet(-10)).toEqual( + snip(-10, abstractInt, /* ref */ 'constant'), + ); + expect(coerceToSnippet(0.0)).toEqual( + snip(0, abstractInt, /* ref */ 'constant'), + ); }); it('coerces JS booleans', () => { - expect(coerceToSnippet(true)).toEqual(snip(true, bool)); - expect(coerceToSnippet(false)).toEqual(snip(false, bool)); + expect(coerceToSnippet(true)).toEqual( + snip(true, bool, /* ref */ 'constant'), + ); + expect(coerceToSnippet(false)).toEqual( + snip(false, bool, /* ref */ 'constant'), + ); }); it(`coerces schemas to UnknownData (as they're not instance types)`, () => { - expect(coerceToSnippet(f32)).toEqual(snip(f32, UnknownData)); - expect(coerceToSnippet(vec3i)).toEqual(snip(vec3i, UnknownData)); - expect(coerceToSnippet(arr)).toEqual(snip(arr, UnknownData)); + expect(coerceToSnippet(f32)).toEqual( + snip(f32, UnknownData, /* ref */ 'constant'), + ); + expect(coerceToSnippet(vec3i)).toEqual( + snip(vec3i, UnknownData, /* ref */ 'constant'), + ); + expect(coerceToSnippet(arr)).toEqual( + snip(arr, UnknownData, /* ref */ 'constant'), + ); }); it('coerces arrays to unknown', () => { @@ -180,12 +226,22 @@ describe('generationHelpers', () => { }); it('returns UnknownData for other types', () => { - expect(coerceToSnippet('foo')).toEqual(snip('foo', UnknownData)); - expect(coerceToSnippet({})).toEqual(snip({}, UnknownData)); - expect(coerceToSnippet(null)).toEqual(snip(null, UnknownData)); - expect(coerceToSnippet(undefined)).toEqual(snip(undefined, UnknownData)); + expect(coerceToSnippet('foo')).toEqual( + snip('foo', UnknownData, /* ref */ 'constant'), + ); + expect(coerceToSnippet({})).toEqual( + snip({}, UnknownData, /* ref */ 'constant'), + ); + expect(coerceToSnippet(null)).toEqual( + snip(null, UnknownData, /* ref */ 'constant'), + ); + expect(coerceToSnippet(undefined)).toEqual( + snip(undefined, UnknownData, /* ref */ 'constant'), + ); const fn = () => {}; - expect(coerceToSnippet(fn)).toEqual(snip(fn, UnknownData)); + expect(coerceToSnippet(fn)).toEqual( + snip(fn, UnknownData, /* ref */ 'constant'), + ); }); }); }); diff --git a/packages/typegpu/tests/tgsl/memberAccess.test.ts b/packages/typegpu/tests/tgsl/memberAccess.test.ts new file mode 100644 index 000000000..a8192e18b --- /dev/null +++ b/packages/typegpu/tests/tgsl/memberAccess.test.ts @@ -0,0 +1,82 @@ +import { describe } from 'vitest'; +import { it } from '../utils/extendedIt.ts'; +import { expectSnippetOf } from '../utils/parseResolved.ts'; +import * as d from '../../src/data/index.ts'; +import { snip } from '../../src/data/snippet.ts'; +import tgpu from '../../src/index.ts'; + +describe('Member Access', () => { + const Boid = d.struct({ + pos: d.vec3f, + }); + + it('should access member properties of literals', () => { + expectSnippetOf(() => { + 'use gpu'; + Boid().pos; + }).toStrictEqual(snip('Boid().pos', d.vec3f, 'runtime')); + + expectSnippetOf(() => { + 'use gpu'; + Boid().pos.xyz; + }).toStrictEqual(snip('Boid().pos.xyz', d.vec3f, 'runtime')); + }); + + it('should access member properties of externals', () => { + const boid = Boid({ pos: d.vec3f(1, 2, 3) }); + + expectSnippetOf(() => { + 'use gpu'; + boid.pos; + }).toStrictEqual(snip(d.vec3f(1, 2, 3), d.vec3f, 'constant')); + + expectSnippetOf(() => { + 'use gpu'; + boid.pos.zyx; + }).toStrictEqual(snip(d.vec3f(3, 2, 1), d.vec3f, 'constant')); + }); + + it('should access member properties of variables', () => { + const boidVar = tgpu.privateVar(Boid); + + expectSnippetOf(() => { + 'use gpu'; + boidVar.$.pos; + }).toStrictEqual(snip('boidVar.pos', d.vec3f, 'private')); + + expectSnippetOf(() => { + 'use gpu'; + boidVar.$.pos.xyz; + }).toStrictEqual(snip('boidVar.pos.xyz', d.vec3f, 'runtime')); // < swizzles are new objects + }); + + it('derefs access to local variables with proper address space', () => { + expectSnippetOf(() => { + 'use gpu'; + // Creating a new Boid instance + const boid = Boid(); + // Taking a reference that is local to this function + const boidRef = boid; + boidRef.pos; + }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'this-function')); + }); + + it('derefs access to storage with proper address space', ({ root }) => { + const boidReadonly = root.createReadonly(Boid); + const boidMutable = root.createMutable(Boid); + + expectSnippetOf(() => { + 'use gpu'; + // Taking a reference to a storage variable + const boidRef = boidReadonly.$; + boidRef.pos; + }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'readonly')); + + expectSnippetOf(() => { + 'use gpu'; + // Taking a reference to a storage variable + const boidRef = boidMutable.$; + boidRef.pos; + }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'mutable')); + }); +}); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index 6a22229b1..418f5b263 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -158,6 +158,33 @@ describe('shellless', () => { `); }); + it('generates pointer type to handle references', () => { + const advance = (pos: d.v3f, vel: d.v3f) => { + 'use gpu'; + pos.x += vel.x; + pos.y += vel.y; + pos.z += vel.z; + }; + + const main = tgpu.fn([])(() => { + const pos = d.vec3f(0, 0, 0); + advance(pos, d.vec3f(1, 2, 3)); + }); + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn advance(pos: ptr, vel: vec3f) { + (*pos).x += vel.x; + (*pos).y += vel.y; + (*pos).z += vel.z; + } + + fn main() { + var pos = vec3f(); + advance((&pos), vec3f(1, 2, 3)); + }" + `); + }); + it('resolves when accepting no arguments', () => { const main = () => { 'use gpu'; diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index bcc0463d4..61e3c944b 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -294,7 +294,11 @@ describe('wgslGenerator', () => { } const args = astInfo.ast.params.map((arg) => - snip((arg as { type: 'i'; name: string }).name, d.u32) + snip( + (arg as { type: 'i'; name: string }).name, + d.u32, + /* ref */ 'runtime', + ) ); provideCtx(ctx, () => { @@ -316,7 +320,7 @@ describe('wgslGenerator', () => { // Check for: const vec = std.mix(d.vec4f(), testUsage.value.a, value); // ^ this part should be a vec4f ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('value', d.i32); + wgslGenerator.blockVariable('var', 'value', d.i32, 'runtime'); const res2 = wgslGenerator.expression( (astInfo.ast?.body[1][1] as tinyest.Const)[2], ); @@ -328,7 +332,7 @@ describe('wgslGenerator', () => { // ^ this part should be an atomic u32 // ^ this part should be void ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('vec', d.vec4f); + wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'this-function'); const res3 = wgslGenerator.expression( (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, ); @@ -492,7 +496,7 @@ describe('wgslGenerator', () => { provideCtx(ctx, () => { ctx[$internal].itemStateStack.pushFunctionScope( - [snip('idx', d.u32)], + [snip('idx', d.u32, /* ref */ 'runtime')], {}, d.f32, astInfo.externals ?? {}, @@ -587,7 +591,7 @@ describe('wgslGenerator', () => { expect(asWgsl(testFn)).toMatchInlineSnapshot(` "fn testFn() -> u32 { var a = 12u; - var b = 2.5f; + const b = 2.5f; a = u32(b); return a; }" @@ -897,7 +901,7 @@ describe('wgslGenerator', () => { [Error: Resolution of the following tree failed: - - fn:testFn - - internalTestFn: Cannot convert value of type 'array' to type 'vec2f'] + - fn:internalTestFn: Cannot convert value of type 'arrayOf(i32, 3)' to type 'vec2f'] `); }); @@ -911,7 +915,7 @@ describe('wgslGenerator', () => { [Error: Resolution of the following tree failed: - - fn:testFn - - translate4: Cannot read properties of undefined (reading 'dataType')] + - fn:translate4: Cannot read properties of undefined (reading 'dataType')] `); }); @@ -926,7 +930,7 @@ describe('wgslGenerator', () => { [Error: Resolution of the following tree failed: - - fn:testFn - - vec4f: Cannot convert value of type 'array' to type 'f32'] + - fn:vec4f: Cannot convert value of type 'arrayOf(i32, 4)' to type 'f32'] `); }); @@ -938,7 +942,7 @@ describe('wgslGenerator', () => { expect(asWgsl(increment)).toMatchInlineSnapshot(` "fn increment(val: ptr) { - *val += 1; + (*val) += 1; }" `); }); @@ -952,8 +956,8 @@ describe('wgslGenerator', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() -> i32 { - var notAKeyword = 0; - var struct_1 = 1; + const notAKeyword = 0; + const struct_1 = 1; return struct_1; }" `); @@ -1033,9 +1037,9 @@ describe('wgslGenerator', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() { - var mut_1 = 1; - var mut_1_1 = 2; - var mut_1_2 = 2; + const mut_1 = 1; + const mut_1_1 = 2; + const mut_1_2 = 2; }" `); }); @@ -1060,8 +1064,8 @@ describe('wgslGenerator', () => { expect(asWgsl(power)).toMatchInlineSnapshot(` "fn power() { - var a = 10f; - var b = 3f; + const a = 10f; + const b = 3f; var n = pow(a, b); }" `); @@ -1075,7 +1079,7 @@ describe('wgslGenerator', () => { expect(asWgsl(power)).toMatchInlineSnapshot(` "fn power() { - var n = 16.; + const n = 16.; }" `); }); @@ -1089,8 +1093,8 @@ describe('wgslGenerator', () => { expect(asWgsl(power)).toMatchInlineSnapshot(` "fn power() { - var a = 3u; - var b = 5i; + const a = 3u; + const b = 5i; var m = pow(f32(a), f32(b)); }" `); @@ -1121,8 +1125,8 @@ describe('wgslGenerator', () => { expect(asWgsl(testFn)).toMatchInlineSnapshot(` "fn testFn() { var matrix = mat4x4f(); - var column = matrix[1]; - var element = column[0]; + let column = (&matrix[1]); + var element = (*column)[0]; var directElement = matrix[1][0]; }" `); @@ -1137,12 +1141,12 @@ describe('wgslGenerator', () => { }); expect(asWgsl(testFn)).toMatchInlineSnapshot(` - "var index: u32; + "var matrix: mat4x4f; - var matrix: mat4x4f; + var index: u32; fn testFn() { - var element = matrix[index]; + let element = (&matrix[index]); }" `); }); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index cb5e1e982..b9358ded0 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -301,8 +301,8 @@ describe('TGSL tgpu.fn function', () => { @compute @workgroup_size(24) fn compute_fn(input: compute_fn_Input) { var index = input.gid.x; - var iterationF = 0f; - var sign = 0; + const iterationF = 0f; + const sign = 0; var change = vec4f(); }" `); @@ -328,8 +328,8 @@ describe('TGSL tgpu.fn function', () => { @compute @workgroup_size(24) fn compute_fn(_arg_0: compute_fn_Input) { var index = _arg_0.gid.x; - var iterationF = 0f; - var sign = 0; + const iterationF = 0f; + const sign = 0; var change = vec4f(); }" `); @@ -672,7 +672,7 @@ describe('TGSL tgpu.fn function', () => { fn callAddOnes() { var someVec = vec3f(1, 2, 3); - addOnes(&someVec); + addOnes((&someVec)); }" `); }); @@ -947,10 +947,9 @@ describe('tgsl fn when using plugin', () => { [Error: Resolution of the following tree failed: - - fn:bar - - call:foo - fn:foo - - call:bar: Recursive function fn:bar detected. Recursion is not allowed on the GPU.] - `); + - fn:bar: Recursive function fn:bar detected. Recursion is not allowed on the GPU.] + `); }); it('throws when it detects a cyclic dependency (when using slots)', () => { @@ -970,13 +969,10 @@ describe('tgsl fn when using plugin', () => { [Error: Resolution of the following tree failed: - - fn:one - - call:two - fn:two - - call:three - fn:three - - call:inner - fn:inner - - call:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] + - fn:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] `); }); @@ -1002,9 +998,8 @@ describe('tgsl fn when using plugin', () => { [Error: Resolution of the following tree failed: - - fn:one - - call:fallbackFn - fn:fallbackFn - - call:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] + - fn:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] `); const boundOne = one.with(flagSlot, true); diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index 5b5219501..c8ee7bf91 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -9,6 +9,8 @@ import { CodegenState, type Wgsl } from '../../src/types.ts'; import { getMetaData } from '../../src/shared/meta.ts'; import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { namespace } from '../../src/core/resolve/namespace.ts'; +import type { Snippet } from '../../src/data/snippet.ts'; +import { $internal } from '../../src/shared/symbols.ts'; /** * Just a shorthand for tgpu.resolve @@ -23,37 +25,69 @@ export function asWgsl(...values: unknown[]): string { }); } -export function expectDataTypeOf( - cb: () => unknown, -): Assertion { +function extractSnippetFromFn(cb: () => unknown): Snippet { const ctx = new ResolutionCtxImpl({ namespace: namespace({ names: 'strict' }), }); - const dataType = provideCtx( + return provideCtx( ctx, () => { + let pushedFnScope = false; try { + const meta = getMetaData(cb); + + if (!meta || !meta.ast) { + throw new Error('No metadata found for the function'); + } + ctx.pushMode(new CodegenState()); - // Extracting the first expression from the block - const statements = (getMetaData(cb)?.ast?.body as tinyest.Block)[1]; - if (statements.length !== 1) { + ctx[$internal].itemStateStack.pushItem(); + ctx[$internal].itemStateStack.pushFunctionScope( + [], + {}, + undefined, + meta.externals ?? {}, + ); + ctx.pushBlockScope(); + pushedFnScope = true; + + // Extracting the last expression from the block + const statements = meta.ast.body[1] ?? []; + if (statements.length === 0) { throw new Error( - `Expected exactly one expression, got ${statements.length}`, + `Expected at least one expression, got ${statements.length}`, ); } wgslGenerator.initGenerator(ctx); - const exprSnippet = wgslGenerator.expression( - statements[0] as tinyest.Expression, + // Prewarming statements + for (const statement of statements) { + wgslGenerator.statement(statement); + } + return wgslGenerator.expression( + statements[statements.length - 1] as tinyest.Expression, ); - - return exprSnippet.dataType; } finally { + if (pushedFnScope) { + ctx.popBlockScope(); + ctx[$internal].itemStateStack.popFunctionScope(); + ctx[$internal].itemStateStack.popItem(); + } ctx.popMode('codegen'); } }, ); +} - return expect(dataType); +export function expectSnippetOf( + cb: () => unknown, +): Assertion { + return expect(extractSnippetFromFn(cb)); +} + +export function expectDataTypeOf( + cb: () => unknown, +): Assertion { + return expect(extractSnippetFromFn(cb).dataType); } diff --git a/packages/typegpu/tests/variable.test.ts b/packages/typegpu/tests/variable.test.ts index 03494606f..6ee0e8270 100644 --- a/packages/typegpu/tests/variable.test.ts +++ b/packages/typegpu/tests/variable.test.ts @@ -123,8 +123,8 @@ var x: array = array(s(1, vec2i(2, 3)), s(4, vec2i(5, 6))); var boid: Boid = Boid(vec3f(1, 2, 3), vec3u(4, 5, 6)); fn func() { - var pos = boid; - var vel = boid.vel; + let pos = (&boid); + let vel = (&boid.vel); var velX = boid.vel.x; }" `);