Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions src/strands/strands_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 9 additions & 2 deletions src/strands/strands_transpiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -1240,8 +1247,8 @@ const ASTCallbacks = {
delete node.update;
},



}

// Helper function to check if a function body contains return statements in control flow
Expand Down
6 changes: 6 additions & 0 deletions src/webgl/strands_glslBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
93 changes: 93 additions & 0 deletions test/unit/webgpu/p5.Shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
});