WGSLNodeBuilder: Add polyfill for storage pointer when not available
WGSL specification does not allow ptr<storage, ...> (storage pointers) as function parameters (https://github.com/gpuweb/gpuweb/issues/2268). The naga WGSL validator (used by WebGPU) now strictly enforces this, causing errors like:
Argument 'bvh_index' at index 0 is a pointer of space Storage, which can't be passed into functions.
The issue wasn't brought yet as it seems to be patched internally in Chrome since r123 https://developer.chrome.com/blog/new-in-webgpu-123#unrestricted_pointer_parameters_in_wgsl but crashes silently Safari and explicitly in Firefox:
When using wgslFn with storage buffers passed as ptr<storage, array<T>, access> parameters, Three.js was generating invalid WGSL code.
The Solution
Storage buffers must be declared as module-level variables with @group/@binding attributes and accessed directly, not passed as function parameters.
How It Works
- When a
wgslFnis called with storage buffer parameters,FunctionCallNodebuilds those buffers (registering them as module-level bindings) and creates a mapping of parameter names to binding names - This map is propagated to all included/nested functions
- When generating WGSL code,
WGSLNodeFunction.getCode()transforms the code:- Removes
ptr<storage, ...>parameters from signatures - Replaces variable references with the global binding names
- Removes storage arguments from internal function calls
- Removes
Alternative 1:
- Add the possibility in WGSL to create storage buffer as module-level variables vua
wgsl()orcode()and automatically bind them so we can access these buffers within thewgslFnwithout having to forward them as parameters. - Add proper documentation and catch error if
ptr<storage,...>and such are being used.
Alternative 2:
It's possible to bypass the issue using a mix of Fn and wgslFn but then requires very advanced knowledge and tricks with the NodeBuilder, and I don't think any developer coming from WebGPU will understand as it's pretty counterintuitive especially having to use TSL when trying to use plain wgsl().
This contribution is funded by Utsubo && Threejs Blocks
📦 Bundle size
Full ESM build, minified and gzipped.
| Before | After | Diff | |
|---|---|---|---|
| WebGL | 350.93 83.27 |
350.93 83.27 |
+0 B +0 B |
| WebGPU | 616.43 171.09 |
618.27 171.72 |
+1.84 kB +633 B |
| WebGPU Nodes | 615.03 170.83 |
616.87 171.47 |
+1.84 kB +641 B |
🌳 Bundle size after tree-shaking
Minimal build including a renderer, camera, empty scene, and dependencies.
| Before | After | Diff | |
|---|---|---|---|
| WebGL | 483.23 118.17 |
483.23 118.17 |
+0 B +0 B |
| WebGPU | 687.34 186.69 |
689.18 187.32 |
+1.84 kB +640 B |
| WebGPU Nodes | 637.18 173.9 |
639.02 174.53 |
+1.84 kB +638 B |
Do you think we could implement the keyword functions created in the past for string replacement? https://github.com/mrdoob/three.js/pull/23766 Perhaps we need to orient her towards the function call.
Is the PR considering what happens if we have different buffers and function calls being executed within the same function? I believe that in some cases, under this principle, we will have more than one code for the same function since we are replacing its source code?