diff --git a/src/strands/ir_types.js b/src/strands/ir_types.js index 8bede07242..20a432ffdc 100644 --- a/src/strands/ir_types.js +++ b/src/strands/ir_types.js @@ -12,6 +12,7 @@ export const NodeType = { ASSIGNMENT: 'assignment', }; export const INSTANCE_ID_VARYING_NAME = '_p5_instanceID'; +export const HOOK_PARAM_PREFIX = '_p5_param_'; export const NodeTypeToName = Object.fromEntries( Object.entries(NodeType).map(([key, val]) => [val, key]) ); diff --git a/src/strands/strands_api.js b/src/strands/strands_api.js index 69bb2f4179..8675dc69b6 100644 --- a/src/strands/strands_api.js +++ b/src/strands/strands_api.js @@ -10,6 +10,7 @@ import { OpCode, StatementType, NodeType, + HOOK_PARAM_PREFIX, // isNativeType } from './ir_types' import { strandsBuiltinFunctions } from './strands_builtins' @@ -647,7 +648,7 @@ function createHookArguments(strandsContext, parameters){ for (const param of parameters) { if(isStructType(param.type)) { const structTypeInfo = structType(param); - const { id, dimension } = build.structInstanceNode(strandsContext, structTypeInfo, param.name, []); + const { id, dimension } = build.structInstanceNode(strandsContext, structTypeInfo, `${HOOK_PARAM_PREFIX}${param.name}`, []); const structNode = createStrandsNode(id, dimension, strandsContext).withStructProperties( structTypeInfo.properties.map(prop => prop.name) ); @@ -660,7 +661,7 @@ function createHookArguments(strandsContext, parameters){ const oldDeps = dag.dependsOn[structNode.id]; const newDeps = oldDeps.slice(); newDeps[i] = newFieldID; - const rebuilt = build.structInstanceNode(strandsContext, structTypeInfo, param.name, newDeps); + const rebuilt = build.structInstanceNode(strandsContext, structTypeInfo, `${HOOK_PARAM_PREFIX}${param.name}`, newDeps); structNode.id = rebuilt.id; }; // TODO: implement member access operations @@ -681,7 +682,7 @@ function createHookArguments(strandsContext, parameters){ newValueID = newVal.id; } newDependsOn[i] = newValueID; - const newStructInfo = build.structInstanceNode(strandsContext, structTypeInfo, param.name, newDependsOn); + const newStructInfo = build.structInstanceNode(strandsContext, structTypeInfo, `${HOOK_PARAM_PREFIX}${param.name}`, newDependsOn); structNode.id = newStructInfo.id; } }) @@ -697,7 +698,7 @@ function createHookArguments(strandsContext, parameters){ throw new Error(`Missing dataType for parameter ${param.name} of type ${param.type.typeName}`); } const typeInfo = param.type.dataType; - const { id, dimension } = build.variableNode(strandsContext, typeInfo, param.name); + const { id, dimension } = build.variableNode(strandsContext, typeInfo, `${HOOK_PARAM_PREFIX}${param.name}`); const arg = createStrandsNode(id, dimension, strandsContext); args.push(arg); } diff --git a/src/webgl/strands_glslBackend.js b/src/webgl/strands_glslBackend.js index 0876547a89..bbd05a5950 100644 --- a/src/webgl/strands_glslBackend.js +++ b/src/webgl/strands_glslBackend.js @@ -1,7 +1,7 @@ import noiseGLSL from './shaders/functions/noise3DGLSL.glsl'; import randomGLSL from './shaders/functions/randomGLSL.glsl'; import randomVertGLSL from './shaders/functions/randomVertGLSL.glsl'; -import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME } from "../strands/ir_types"; +import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME, HOOK_PARAM_PREFIX } from "../strands/ir_types"; import { getNodeDataFromID, extractNodeTypeInfo } from "../strands/ir_dag"; import * as FES from '../strands/strands_FES'; import * as build from '../strands/ir_builders'; @@ -168,7 +168,7 @@ const cfgHandlers = { export const glslBackend = { hookEntry(hookType) { const firstLine = `(${hookType.parameters.flatMap((param) => { - return `${param.qualifiers?.length ? param.qualifiers.join(' ') : ''}${param.type.typeName} ${param.name}`; + return `${param.qualifiers?.length ? param.qualifiers.join(' ') : ''}${param.type.typeName} ${HOOK_PARAM_PREFIX}${param.name}`; }).join(', ')}) {`; return firstLine; }, diff --git a/src/webgpu/strands_wgslBackend.js b/src/webgpu/strands_wgslBackend.js index 59aedde92c..79d0f2816b 100644 --- a/src/webgpu/strands_wgslBackend.js +++ b/src/webgpu/strands_wgslBackend.js @@ -2,7 +2,7 @@ import noiseWGSL from './shaders/functions/noise3DWGSL.js'; import randomWGSL from './shaders/functions/randomWGSL'; import randomVertWGSL from './shaders/functions/randomVertWGSL'; import randomComputeWGSL from './shaders/functions/randomComputeWGSL'; -import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME } from "../strands/ir_types"; +import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME, HOOK_PARAM_PREFIX } from "../strands/ir_types"; import { getNodeDataFromID, extractNodeTypeInfo } from "../strands/ir_dag"; import * as FES from '../strands/strands_FES'; import * as build from '../strands/ir_builders'; @@ -190,16 +190,16 @@ export const wgslBackend = { hookEntry(hookType) { const params = hookType.parameters.map((param) => { // For struct types, use a raw prefix since we'll create a mutable copy - const paramName = param.type.properties ? `_p5_strands_raw_${param.name}` : param.name; + const paramName = param.type.properties ? `_p5_strands_raw_${param.name}` : `${HOOK_PARAM_PREFIX}${param.name}`; return `${paramName}: ${param.type.typeName}`; }).join(', '); const firstLine = `(${params}) {`; - // Generate mutable copies for struct parameters with original names + // Generate mutable copies for struct parameters const mutableCopies = hookType.parameters .filter(param => param.type.properties) // Only struct types - .map(param => ` var ${param.name} = _p5_strands_raw_${param.name};`) + .map(param => ` var ${HOOK_PARAM_PREFIX}${param.name} = _p5_strands_raw_${param.name};`) .join('\n'); return mutableCopies ? firstLine + '\n' + mutableCopies : firstLine; diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index aaa229bcec..93582483b6 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -89,6 +89,33 @@ suite('p5.RendererGL', function() { }); }); + suite('p5.strands', function() { + test('a uniform whose name matches a hook parameter name does not break', function() { + myp5.createCanvas(10, 10, myp5.WEBGL); + myp5.pixelDensity(1); + + // 'color' is the GLSL parameter name of the getFinalColor hook's first argument. + // Creating a uniform with the same name used to cause a GLSL name clash. + const myShader = myp5.baseColorShader().modify(() => { + const color = myp5.uniformFloat('color', 0.5); + myp5.finalColor.begin(); + myp5.finalColor.set([color, color, color, 1]); + myp5.finalColor.end(); + }, { myp5 }); + + myp5.background(0); + myp5.noStroke(); + myp5.shader(myShader); + myp5.plane(myp5.width, myp5.height); + + const pixel = myp5.get(5, 5); + assert.approximately(pixel[0], 128, 1); + assert.equal(pixel[0], pixel[1]); + assert.equal(pixel[1], pixel[2]); + assert.equal(pixel[3], 255); + }); + }); + suite('texture binding', function() { test('setting a custom texture works', function() { myp5.createCanvas(10, 10, myp5.WEBGL); diff --git a/test/unit/webgpu/p5.RendererWebGPU.js b/test/unit/webgpu/p5.RendererWebGPU.js index f21503be60..3a21f174f6 100644 --- a/test/unit/webgpu/p5.RendererWebGPU.js +++ b/test/unit/webgpu/p5.RendererWebGPU.js @@ -321,4 +321,30 @@ suite('WebGPU p5.RendererWebGPU', function() { expect(() => buf.set(0, 42)).to.throw(); }); }); + + suite('p5.strands', function() { + test('a uniform whose name matches a hook parameter name does not break', async function() { + myp5.pixelDensity(1); + + // 'color' is the WGSL parameter name of the getFinalColor hook's first argument. + // Creating a uniform with the same name used to cause a WGSL name clash. + const myShader = myp5.baseColorShader().modify(() => { + const color = myp5.uniformFloat('color', 0.5); + myp5.finalColor.begin(); + myp5.finalColor.set([color, color, color, 1]); + myp5.finalColor.end(); + }, { myp5 }); + + myp5.background(0); + myp5.noStroke(); + myp5.shader(myShader); + myp5.plane(myp5.width, myp5.height); + + const pixel = await myp5.get(5, 5); + expect(pixel[0]).to.be.closeTo(128, 1); + expect(pixel[0]).to.equal(pixel[1]); + expect(pixel[1]).to.equal(pixel[2]); + expect(pixel[3]).to.equal(255); + }); + }); });