Skip to content

Commit 0f966a1

Browse files
authored
Merge pull request #8695 from aashu2006/fix/instance-id-fragment
Make instanceID() work in both vertex and fragment shaders
2 parents 36e3645 + 1911df1 commit 0f966a1

13 files changed

Lines changed: 156 additions & 3 deletions

File tree

src/strands/ir_types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const NodeType = {
1111
STATEMENT: 'statement',
1212
ASSIGNMENT: 'assignment',
1313
};
14+
export const INSTANCE_ID_VARYING_NAME = '_p5_instanceID';
1415
export const NodeTypeToName = Object.fromEntries(
1516
Object.entries(NodeType).map(([key, val]) => [val, key])
1617
);

src/strands/strands_codegen.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ export function generateShaderCode(strandsContext) {
9696
}
9797
}
9898

99+
// Register instanceID varying if used in a fragment hook
100+
if (strandsContext._instanceIDUsedInFragment) {
101+
hooksObj.instanceIDVarying = backend.generateInstanceIDVarying();
102+
}
103+
99104
hooksObj.vertexDeclarations = [...vertexDeclarations].join('\n');
100105
hooksObj.fragmentDeclarations = [...fragmentDeclarations].join('\n');
101106
hooksObj.computeDeclarations = [...computeDeclarations].join('\n');

src/webgl/p5.Shader.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ class Shader {
5858
// Stores an array of variable names + types passed between the vertex and fragment shader
5959
varyingVariables: options.varyingVariables || [],
6060

61+
// Stores instanceID varying info for forwarding to the fragment shader
62+
instanceIDVarying: options.instanceIDVarying || null,
63+
6164
// Stores helper functions to prepend to shaders.
6265
helpers: options.helpers || {},
6366

@@ -422,6 +425,7 @@ class Shader {
422425
if (key === 'uniforms') continue;
423426
if (key === 'storageUniforms') continue;
424427
if (key === 'varyingVariables') continue;
428+
if (key === 'instanceIDVarying') continue;
425429
if (key === 'vertexDeclarations') {
426430
newHooks.vertex.declarations =
427431
(newHooks.vertex.declarations || '') + '\n' + hooks[key];
@@ -469,6 +473,7 @@ class Shader {
469473
uniforms: Object.assign({}, this.hooks.uniforms, hooks.uniforms || {}),
470474
storageUniforms: Object.assign({}, this.hooks.storageUniforms, hooks.storageUniforms || {}),
471475
varyingVariables: (hooks.varyingVariables || []).concat(this.hooks.varyingVariables || []),
476+
instanceIDVarying: hooks.instanceIDVarying || this.hooks.instanceIDVarying || null,
472477
fragment: Object.assign({}, this.hooks.fragment, newHooks.fragment || {}),
473478
vertex: Object.assign({}, this.hooks.vertex, newHooks.vertex || {}),
474479
compute: Object.assign({}, this.hooks.compute, newHooks.compute || {}),

src/webgl/strands_glslBackend.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType } from "../strands/ir_types";
1+
import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME } from "../strands/ir_types";
22
import { getNodeDataFromID, extractNodeTypeInfo } from "../strands/ir_dag";
33
import * as FES from '../strands/strands_FES';
44
import * as build from '../strands/ir_builders';
@@ -274,6 +274,13 @@ export const glslBackend = {
274274
sharedVar.usedInFragment = true;
275275
}
276276
}
277+
278+
// Detect instanceID usage in fragment context and rewrite to varying name
279+
if (node.identifier === this.instanceIdReference() && generationContext.shaderContext === 'fragment') {
280+
generationContext.strandsContext._instanceIDUsedInFragment = true;
281+
return INSTANCE_ID_VARYING_NAME;
282+
}
283+
277284
return node.identifier;
278285
case NodeType.OPERATION:
279286
const useParantheses = node.usedBy.length > 0;
@@ -398,4 +405,8 @@ export const glslBackend = {
398405
instanceIdReference() {
399406
return 'gl_InstanceID';
400407
},
408+
409+
generateInstanceIDVarying() {
410+
return { name: INSTANCE_ID_VARYING_NAME, declaration: `int ${INSTANCE_ID_VARYING_NAME}`, source: 'gl_InstanceID', interpolation: 'flat' };
411+
},
401412
}

src/webgl/utils.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as constants from "../core/constants";
2+
import { INSTANCE_ID_VARYING_NAME } from "../strands/ir_types";
23
import { Texture } from "./p5.Texture";
34

45
/**
@@ -429,6 +430,20 @@ export function populateGLSLHooks(shader, src, shaderType) {
429430
}
430431
}
431432
}
433+
434+
// Handle instanceID varying for fragment access
435+
if (shader.hooks.instanceIDVarying) {
436+
const { declaration, source, interpolation } = shader.hooks.instanceIDVarying;
437+
const qualifier = interpolation ? `${interpolation} ` : '';
438+
if (shaderType === "vertex") {
439+
// Emit flat out declaration and inject assignment into main() body
440+
hooks += `${qualifier}OUT ${declaration};\n`;
441+
postMain = postMain.replace(/\{/, `{\n ${declaration.split(' ').pop()} = ${source};`);
442+
} else if (shaderType === "fragment") {
443+
hooks += `${qualifier}IN ${declaration};\n`;
444+
}
445+
}
446+
432447
for (const hookDef in shader.hooks.helpers) {
433448
hooks += `${hookDef}${shader.hooks.helpers[hookDef]}\n`;
434449
}

src/webgpu/p5.RendererWebGPU.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import * as constants from '../core/constants';
88
import { getStrokeDefs } from '../webgl/enums';
9-
import { DataType } from '../strands/ir_types.js';
9+
import { DataType, INSTANCE_ID_VARYING_NAME } from '../strands/ir_types.js';
1010

1111
import { colorVertexShader, colorFragmentShader } from './shaders/color';
1212
import { lineVertexShader, lineFragmentShader} from './shaders/line';
@@ -2651,6 +2651,50 @@ ${hookUniformFields}}
26512651
}
26522652
}
26532653

2654+
// Handle instanceID varying for fragment access
2655+
if (shader.hooks.instanceIDVarying) {
2656+
const { name, declaration, source, interpolation } = shader.hooks.instanceIDVarying;
2657+
const nextLocIndex = this._getNextAvailableLocation(preMain, shaderType);
2658+
const interpAttr = interpolation ? ` @interpolate(${interpolation})` : '';
2659+
const [varName, varType] = declaration.split(':').map(s => s.trim());
2660+
const structMember = `@location(${nextLocIndex})${interpAttr} ${declaration},`;
2661+
2662+
if (shaderType === 'vertex') {
2663+
// Inject into VertexOutput struct
2664+
preMain = preMain.replace(
2665+
/struct\s+VertexOutput\s+\{([^}]*)\}/,
2666+
(match, body) => `struct VertexOutput {${body}\n${structMember}}`
2667+
);
2668+
// Add private global
2669+
preMain += `var<private> ${declaration};\n`;
2670+
// Assign from built-in instanceID at start of main()
2671+
postMain = `\n ${varName} = ${source};\n` + postMain;
2672+
// Copy to output struct before return
2673+
const returnMatch = postMain.match(/return\s+(\w+)\s*;/);
2674+
if (returnMatch) {
2675+
const outputVarName = returnMatch[1];
2676+
postMain = postMain.replace(
2677+
/(return\s+\w+\s*;)/g,
2678+
`${outputVarName}.${varName} = ${varName};\n $1`
2679+
);
2680+
}
2681+
} else if (shaderType === 'fragment') {
2682+
// Inject into FragmentInput struct
2683+
preMain = preMain.replace(
2684+
/struct\s+FragmentInput\s+\{([^}]*)\}/,
2685+
(match, body) => `struct FragmentInput {${body}\n${structMember}}`
2686+
);
2687+
// Add private global
2688+
preMain += `var<private> ${declaration};\n`;
2689+
// Initialize from input struct at start of main()
2690+
const inputMatch = main.match(/fn main\s*\((\w+):\s*\w+\)/);
2691+
if (inputMatch) {
2692+
const inputVarName = inputMatch[1];
2693+
postMain = `\n ${varName} = ${inputVarName}.${varName};\n` + postMain;
2694+
}
2695+
}
2696+
}
2697+
26542698
let hooks = '';
26552699
let defines = '';
26562700
if (shader.hooks.declarations) {

src/webgpu/strands_wgslBackend.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType } from "../strands/ir_types";
1+
import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME } from "../strands/ir_types";
22
import { getNodeDataFromID, extractNodeTypeInfo } from "../strands/ir_dag";
33
import * as FES from '../strands/strands_FES';
44
import * as build from '../strands/ir_builders';
@@ -427,6 +427,12 @@ export const wgslBackend = {
427427
}
428428
}
429429

430+
// Detect instanceID usage in fragment context and rewrite to varying name
431+
if (node.identifier === this.instanceIdReference() && generationContext.shaderContext === 'fragment') {
432+
generationContext.strandsContext._instanceIDUsedInFragment = true;
433+
return INSTANCE_ID_VARYING_NAME;
434+
}
435+
430436
// Check if this is a uniform variable (but not a texture or storage buffer)
431437
const uniform = generationContext.strandsContext?.uniforms?.find(uniform => uniform.name === node.identifier);
432438
if (uniform && uniform.typeInfo.baseType !== 'sampler2D' && uniform.typeInfo.baseType !== 'storage') {
@@ -584,4 +590,8 @@ export const wgslBackend = {
584590
instanceIdReference() {
585591
return 'instanceID';
586592
},
593+
594+
generateInstanceIDVarying() {
595+
return { name: INSTANCE_ID_VARYING_NAME, declaration: `${INSTANCE_ID_VARYING_NAME}: i32`, source: 'i32(instanceID)', interpolation: 'flat' };
596+
},
587597
}

test/unit/visual/cases/webgl.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,33 @@ visualSuite('WebGL', function() {
10341034
p5.model(obj, 25);
10351035
screenshot();
10361036
});
1037+
visualTest('instanceID in fragment hook colors instances', (p5, screenshot) => {
1038+
p5.createCanvas(50, 50, p5.WEBGL);
1039+
const numInstances = 4;
1040+
const shader = p5.baseMaterialShader().modify(() => {
1041+
// Vertex hook: position instances in a horizontal row
1042+
p5.getWorldInputs((inputs) => {
1043+
const id = p5.instanceID();
1044+
const spacing = 12;
1045+
const offset = (id - (numInstances - 1) / 2.0) * spacing;
1046+
inputs.position.x += offset;
1047+
return inputs;
1048+
});
1049+
// Fragment hook: color each instance based on instanceID
1050+
p5.getFinalColor((color) => {
1051+
const id = p5.instanceID();
1052+
const t = id / (numInstances - 1.0);
1053+
color = [t, t, t, 1];
1054+
return color;
1055+
});
1056+
}, { p5, numInstances });
1057+
p5.background(128);
1058+
p5.noStroke();
1059+
p5.shader(shader);
1060+
const obj = p5.buildGeometry(() => p5.circle(0, 0, 10));
1061+
p5.model(obj, numInstances);
1062+
screenshot();
1063+
});
10371064
});
10381065

10391066
visualSuite('p5.strands', () => {

test/unit/visual/cases/webgpu.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,37 @@ visualSuite("WebGPU", function () {
272272
p5.filter(invert);
273273
await screenshot();
274274
});
275+
276+
visualTest('instanceID in fragment hook colors instances (WebGPU)', async function(p5, screenshot) {
277+
await p5.createCanvas(50, 50, p5.WEBGPU);
278+
const numInstances = 4;
279+
const shader = p5.baseMaterialShader().modify(() => {
280+
// Vertex hook: position instances in a horizontal row
281+
p5.getWorldInputs((inputs) => {
282+
const id = p5.instanceID();
283+
const spacing = 12;
284+
const offset = (id - (numInstances - 1) / 2.0) * spacing;
285+
inputs.position.x += offset;
286+
return inputs;
287+
});
288+
// Fragment hook: color each instance based on instanceID
289+
p5.getFinalColor((color) => {
290+
const id = p5.instanceID();
291+
const t = id / (numInstances - 1.0);
292+
color = [t, t, t, 1];
293+
return color;
294+
});
295+
}, { p5, numInstances });
296+
p5.background(128);
297+
p5.noStroke();
298+
p5.shader(shader);
299+
const obj = p5.buildGeometry(() => p5.circle(0, 0, 10));
300+
p5.model(obj, numInstances);
301+
await screenshot();
302+
});
275303
});
276304

305+
277306
visualSuite('filters', function() {
278307
const setupSketch = async (p5) => {
279308
await p5.createCanvas(50, 50, p5.WEBGPU);
653 Bytes
Loading

0 commit comments

Comments
 (0)