Skip to content

Commit ed01e6f

Browse files
committed
Add support for instanceID() in WebGPU mode
1 parent e65de9d commit ed01e6f

10 files changed

Lines changed: 101 additions & 10 deletions

File tree

preview/index.html

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@
2424

2525
const sketch = function (p) {
2626
let fbo;
27-
let sh;
27+
let sh, sh2;
2828
let ssh;
2929
let tex;
3030
let font;
3131
let redFilter;
3232
let env;
33+
let instance;
3334

3435
p.setup = async function () {
3536
await p.createCanvas(400, 400, p.WEBGPU);
@@ -39,6 +40,8 @@
3940
);
4041
fbo = p.createFramebuffer();
4142

43+
instance = p.buildGeometry(() => p.sphere(5));
44+
4245
redFilter = p.baseFilterShader().modify(() => {
4346
p.getColor((inputs, canvasContent) => {
4447
let col = p.getTexture(canvasContent, inputs.texCoord);
@@ -84,6 +87,12 @@
8487
return inputs;
8588
});
8689
}, { p })
90+
sh2 = p.baseMaterialShader().modify(() => {
91+
p.getWorldInputs((inputs) => {
92+
inputs.position.x += 20 * p.instanceID();
93+
return inputs;
94+
});
95+
}, { p })
8796
/*ssh = p.baseStrokeShader().modify({
8897
uniforms: {
8998
'f32 time': () => p.millis(),
@@ -151,6 +160,13 @@
151160
}
152161
p.pop();
153162

163+
p.push();
164+
p.shader(sh2);
165+
p.noStroke();
166+
p.fill('red');
167+
p.model(instance, 10);
168+
p.pop();
169+
154170
// Test beginShape/endShape with immediate mode shapes
155171
p.push();
156172
p.translate(0, 100, 0);

src/strands/strands_api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
5050
};
5151
p5.break = fn.break;
5252
fn.instanceID = function() {
53-
const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, 'gl_InstanceID');
53+
const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference());
5454
return createStrandsNode(node.id, node.dimension, strandsContext);
5555
}
5656
// Internal methods use p5 static methods; user-facing methods use fn.

src/webgl/strands_glslBackend.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,5 +379,9 @@ export const glslBackend = {
379379
}]
380380
});
381381
return { id, dimension };
382-
}
382+
},
383+
384+
instanceIdReference() {
385+
return 'gl_InstanceID';
386+
},
383387
}

src/webgpu/p5.RendererWebGPU.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ function rendererWebGPU(p5, fn) {
289289
// Check if we already have a buffer for this data
290290
let existingBuffer = buffers[dst];
291291
const needsNewBuffer = !existingBuffer;
292-
292+
293293
// Only create new buffer and write data if buffer doesn't exist or data is dirty
294294
if (needsNewBuffer || geometry.dirtyFlags[src] !== false) {
295295
const raw = map ? map(srcData) : srcData;
@@ -1349,7 +1349,7 @@ function rendererWebGPU(p5, fn) {
13491349
}
13501350

13511351
_getShaderAttributes(shader) {
1352-
const mainMatch = /fn main\(.+:\s*(\S+)\s*\)/.exec(shader._vertSrc);
1352+
const mainMatch = /fn main\(.+:\s*([^\s\)]+)/.exec(shader._vertSrc);
13531353
if (!mainMatch) throw new Error("Can't find `fn main` in vertex shader source");
13541354
const inputType = mainMatch[1];
13551355

@@ -1740,7 +1740,12 @@ function rendererWebGPU(p5, fn) {
17401740
}
17411741
);
17421742

1743-
let [preMain, main, postMain] = src.split(/((?:@(?:vertex|fragment)\s*)?fn main)/);
1743+
let [preMain, main, postMain] = src.split(/((?:@(?:vertex|fragment)\s*)?fn main[^{]+\{)/);
1744+
if (shaderType !== 'fragment') {
1745+
if (!main.match(/\@builtin\s*\(\s*instance_index\s*\)/)) {
1746+
main = main.replace(/\)\s*(->|\{)/, ', @builtin(instance_index) instanceID: u32) $1');
1747+
}
1748+
}
17441749

17451750
let uniforms = '';
17461751
for (const key in shader.hooks.uniforms) {
@@ -1850,14 +1855,57 @@ function rendererWebGPU(p5, fn) {
18501855
shader.hooks.modified[shaderType][hookDef] ? 'true' : 'false'
18511856
};\n`;
18521857

1853-
const [_, params, body] = /^(\([^\)]*\))((?:.|\n)*)$/.exec(shader.hooks[shaderType][hookDef]);
1858+
let [_, params, body] = /^(\([^\)]*\))((?:.|\n)*)$/.exec(shader.hooks[shaderType][hookDef]);
1859+
1860+
if (shaderType !== 'fragment') {
1861+
// Splice the instance ID in as a final parameter to every WGSL hook function
1862+
let hasParams = !!params.match(/^\(\s*\S+.*\)$/);
1863+
params = params.slice(0, -1) + (hasParams ? ', ' : '') + 'instanceID: u32)';
1864+
}
1865+
18541866
if (hookType === 'void') {
18551867
hooks += `fn HOOK_${hookName}${params}${body}\n`;
18561868
} else {
18571869
hooks += `fn HOOK_${hookName}${params} -> ${hookType}${body}\n`;
18581870
}
18591871
}
18601872

1873+
// Add the instance ID as a final parameter to each hook call
1874+
if (shaderType !== 'fragment') {
1875+
const addInstanceIDParam = (src) => {
1876+
let result = src;
1877+
let idx = 0;
1878+
let match;
1879+
do {
1880+
match = /HOOK_\w+\(/.exec(result.slice(idx));
1881+
if (match) {
1882+
idx += match.index + match[0].length - 1;
1883+
let nesting = 0;
1884+
let hasParams = false;
1885+
while (idx < result.length) {
1886+
if (result[idx] === '(') {
1887+
nesting++;
1888+
} else if (result[idx] === ')') {
1889+
nesting--;
1890+
} else if (result[idx].match(/\S/)) {
1891+
hasParams = true;
1892+
}
1893+
idx++;
1894+
if (nesting === 0) {
1895+
break;
1896+
}
1897+
}
1898+
const insertion = (hasParams ? ', ' : '') + 'instanceID';
1899+
result = result.slice(0, idx-1) + insertion + result.slice(idx-1);
1900+
idx += insertion.length;
1901+
}
1902+
} while (match);
1903+
return result;
1904+
};
1905+
preMain = addInstanceIDParam(preMain);
1906+
postMain = addInstanceIDParam(postMain);
1907+
}
1908+
18611909
return preMain + '\n' + defines + hooks + main + postMain;
18621910
}
18631911

src/webgpu/shaders/color.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ fn main(input: VertexInput) -> VertexOutput {
4646
HOOK_beforeVertex();
4747
var output: VertexOutput;
4848
49-
let useVertexColor = (uniforms.uUseVertexColor != 0);
49+
let useVertexColor = (uniforms.uUseVertexColor != 0 && input.aVertexColor.x >= 0.0);
5050
var inputs = Vertex(
5151
input.aPosition,
5252
input.aNormal,

src/webgpu/shaders/material.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fn main(input: VertexInput) -> VertexOutput {
8787
HOOK_beforeVertex();
8888
var output: VertexOutput;
8989
90-
let useVertexColor = (uniforms.uUseVertexColor != 0);
90+
let useVertexColor = (uniforms.uUseVertexColor != 0 && input.aVertexColor.x >= 0.0);
9191
var inputs = Vertex(
9292
input.aPosition,
9393
input.aNormal,

src/webgpu/strands_wgslBackend.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,5 +503,9 @@ export const wgslBackend = {
503503
}]
504504
});
505505
return { id, dimension };
506-
}
506+
},
507+
508+
instanceIdReference() {
509+
return 'instanceID';
510+
},
507511
}

test/unit/visual/cases/webgpu.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ visualSuite("WebGPU", function () {
114114
await screenshot();
115115
},
116116
);
117+
118+
visualTest('Instanced rendering', async function(p5, screenshot) {
119+
await p5.createCanvas(50, 50, p5.WEBGPU);
120+
const model = p5.buildGeometry(() => p5.sphere(5));
121+
const shader = p5.baseMaterialShader().modify(() => {
122+
p5.getWorldInputs((inputs) => {
123+
inputs.position += (p5.instanceID() - 1) * 15
124+
return inputs;
125+
});
126+
}, { p5 });
127+
p5.noStroke();
128+
p5.fill(0);
129+
p5.shader(shader);
130+
p5.model(model, 3);
131+
await screenshot();
132+
});
117133
});
118134

119135
visualSuite('filters', function() {
556 Bytes
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"numScreenshots": 1
3+
}

0 commit comments

Comments
 (0)