diff --git a/src/strands/strands_node.js b/src/strands/strands_node.js index 1528a0fc3c..f7638855bc 100644 --- a/src/strands/strands_node.js +++ b/src/strands/strands_node.js @@ -166,18 +166,15 @@ export class StrandsNode { } get(index) { - // Validate baseType is 'storage' const nodeData = getNodeDataFromID(this.strandsContext.dag, this.id); - if (nodeData.baseType !== 'storage') { - throw new Error('get() can only be used on storage buffers'); - } - // For struct storage, return a proxy with per-field getters/setters - if (this._schema) { + // Validate baseType is 'storage' + // For struct storage buffers, return a proxy with per-field getters/setters + if (nodeData.baseType === 'storage' && this._schema) { return createStructArrayElementProxy(this.strandsContext, this, index, this._schema); } - // Create array access node: buffer.get(index) -> buffer[index] + // Create array access node for storage and non-storage (vector) access const { id, dimension } = arrayAccessNode( this.strandsContext, this, diff --git a/src/strands/strands_transpiler.js b/src/strands/strands_transpiler.js index bf33907338..8cc0f1e76a 100644 --- a/src/strands/strands_transpiler.js +++ b/src/strands/strands_transpiler.js @@ -569,6 +569,13 @@ const ASTCallbacks = { if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { return; } + + if (node.elements.length < 2 || node.elements.length > 4) { + throw new Error( + `Array literals in shader functions are transpiled to vectors and must have 2-4 elements (got ${node.elements.length}).` + ); + } + const original = JSON.parse(JSON.stringify(node)); node.type = 'CallExpression'; node.callee = { @@ -1240,8 +1247,8 @@ const ASTCallbacks = { delete node.update; }, - - + + } // Helper function to check if a function body contains return statements in control flow diff --git a/src/webgl/strands_glslBackend.js b/src/webgl/strands_glslBackend.js index 9ec04b4fae..0876547a89 100644 --- a/src/webgl/strands_glslBackend.js +++ b/src/webgl/strands_glslBackend.js @@ -330,6 +330,12 @@ export const glslBackend = { const parentExpr = this.generateExpression(generationContext, dag, parentID); return `${parentExpr}.${node.swizzle}`; } + if (node.opCode === OpCode.Binary.ARRAY_ACCESS) { + const [bufferID, indexID] = node.dependsOn; + const bufferExpr = this.generateExpression(generationContext, dag, bufferID); + const indexExpr = this.generateExpression(generationContext, dag, indexID); + return `${bufferExpr}[${indexExpr}]`; + } if (node.dependsOn.length === 2) { const [lID, rID] = node.dependsOn; const left = this.generateExpression(generationContext, dag, lID); diff --git a/test/unit/webgpu/p5.Shader.js b/test/unit/webgpu/p5.Shader.js index 6571d92edb..eb9bb79990 100644 --- a/test/unit/webgpu/p5.Shader.js +++ b/test/unit/webgpu/p5.Shader.js @@ -1269,5 +1269,98 @@ suite('WebGPU p5.Shader', function() { }).not.toThrow(); }); }); + + suite('array indexing on non-storage vectors (#8756)', () => { + test('indexing into array returned from helper function does not throw', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGPU); + const storage = myp5.createStorage(new Float32Array(4)); + + const computeShader = myp5.buildComputeShader(() => { + const data = myp5.uniformStorage(); + function getArray() { + return [1, 2]; + } + const arr = getArray(); + data[myp5.index.x] = arr[0] + arr[1] + myp5.index.x; + }, { myp5 }); + + computeShader.setUniform('data', storage); + + expect(() => { + myp5.compute(computeShader, 4); + }).not.toThrow(); + }); + + test('inline literal indexing [1, 2][0] works end-to-end', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGPU); + const storage = myp5.createStorage(new Float32Array(4)); + + const computeShader = myp5.buildComputeShader(() => { + const data = myp5.uniformStorage(); + data[myp5.index.x] = [1, 2][0]; + }, { myp5 }); + + computeShader.setUniform('data', storage); + + expect(() => { + myp5.compute(computeShader, 4); + }).not.toThrow(); + }); + + test('array literal with 1 element throws descriptive error', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGPU); + + expect(() => { + myp5.buildComputeShader(() => { + const data = myp5.uniformStorage(); + const arr = [1]; + data[myp5.index.x] = arr[0]; + }, { myp5 }); + }).toThrow('and must have 2-4 elements (got 1)'); + }); + + test('array literal with 5 elements throws descriptive error', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGPU); + + expect(() => { + myp5.buildComputeShader(() => { + const data = myp5.uniformStorage(); + const arr = [1, 2, 3, 4, 5]; + data[myp5.index.x] = arr[0]; + }, { myp5 }); + }).toThrow('and must have 2-4 elements (got 5)'); + }); + + test('valid array lengths 2, 3, 4 do not throw', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGPU); + const storage = myp5.createStorage(new Float32Array(4)); + + expect(() => { + const s2 = myp5.buildComputeShader(() => { + const data = myp5.uniformStorage(); + const arr = [1, 2]; + data[myp5.index.x] = arr[0]; + }, { myp5 }); + s2.setUniform('data', storage); + myp5.compute(s2, 4); + + const s3 = myp5.buildComputeShader(() => { + const data = myp5.uniformStorage(); + const arr = [1, 2, 3]; + data[myp5.index.x] = arr[0]; + }, { myp5 }); + s3.setUniform('data', storage); + myp5.compute(s3, 4); + + const s4 = myp5.buildComputeShader(() => { + const data = myp5.uniformStorage(); + const arr = [1, 2, 3, 4]; + data[myp5.index.x] = arr[0]; + }, { myp5 }); + s4.setUniform('data', storage); + myp5.compute(s4, 4); + }).not.toThrow(); + }); + }); }); });