Add paletteLerp for p5.strands (closes #8751)#8817
Conversation
davepagurek
left a comment
There was a problem hiding this comment.
Thanks for taking this on! I had a few questions at a high level around the approach. We should probably also add some visual tests in WebGL and WebGPU mode too.
| `Array literals in shader functions are transpiled to vectors and must have 2-4 elements (got ${node.elements.length}).` | ||
| ); | ||
| // Don't wrap paletteLerp's pre-split color/position arrays as vectors | ||
| if (node._isPaletteLerpArg) { |
There was a problem hiding this comment.
We talked a bit in #8751 (comment) about a more generalized way of doing this that wouldn't be specific to paletteLerp, is that something we could add?
There was a problem hiding this comment.
That's a good point. I'll implement the parameter type registry approach we discussed. The plan is to add a strandsParamTypes map (or co-locate type descriptors with each function's definition) so the ArrayExpression visitor can look up whether a given array argument should be treated as a typed array rather than a vector, without any function-specific special-casing. I'll update the PR with that generalization.
| if (t <= p[0]) return c[0]; if (t >= p[1]) return c[1]; | ||
| return _p5s_paletteLerpSeg(c[0],c[1],p[0],p[1],t); | ||
| } | ||
| vec4 paletteLerp(vec4[3] c, float[3] p, float t) { |
There was a problem hiding this comment.
One other thought: is it necessary that these be functions? We could potentially just generate the if statement/loop and return the final result when you call paletteLerp. Then we wouldn't need to generate a GLSL and WGSL version. (If we do stick with this format, we'd need to create a WGSL version too.)
There was a problem hiding this comment.
That's a cleaner approach, generating the interpolation inline would remove the GLSL/WGSL split entirely. For N stops, the transpiler would emit something like:
// paletteLerp with 3 stops, inlined
(t <= p0 ? c0 : t >= p2 ? c2 : t < p1 ? mix(c0, c1, clamp((t-p0)/(p1-p0), 0., 1.)) : mix(c1, c2, clamp((t-p1)/(p2-p1), 0., 1.)))I'll switch to inline emission in strands_api.js instead of the GLSL helper functions. That also simplifies the backend significantly.
d8f9772 to
80c7e89
Compare
80c7e89 to
645ccd1
Compare
|
Updated. Both comments addressed:
All 157 existing shader tests pass. |
Resolves #8751
Changes
Implements
paletteLerpfor p5.strands, giving GPU-side feature parity with the CPUpaletteLerp.strands_transpiler.jsAdds a pre-pass that rewrites
paletteLerp([[color, pos], ...], t)into parallel color/position arrays before the mainArrayExpressionpass fires. This is necessary because acorn-walk visits children before parents — the outer array would be mis-typed as a vector before thepaletteLerphandler could split the pairs. A_isPaletteLerpArgflag prevents the resulting arrays from being wrapped instrandsNode.strands_api.jsRegisters
paletteLerpas a dual-mode runtime function. When inside a strands context, builds an IRFUNCTION_CALLnode and injects the GLSL helper functions into the shader preamble once viagetPaletteLerpGLSL(). CPU fallback is preserved.strands_glslBackend.jsAdds
getPaletteLerpGLSL()which returns overloaded GLSL helpers for 2–8 color stops using typed array constructors (vec4[N],float[N]). Overloads are required because GLSL ES 3.0 has no variadic functions.strands_builtins.jsMinor cleanup.
Usage
This matches the ergonomics of the CPU
paletteLerp:Notes
Screenshots
No visual rendering screenshot included — browser cache issues prevented local verification. The full pipeline (transpiler → IR → GLSL emission) was verified by code review and the existing test suite passes cleanly.
PR Checklist
npm run lintpasses