Skip to content

NodeBuilder: Reduce allocations and lookups while building nodes. - #35441

Open
Makio64 wants to merge 1 commit into
mrdoob:devfrom
Makio64:webgpu-node-build
Open

Makio64 wants to merge 1 commit into
mrdoob:devfrom
Makio64:webgpu-node-build

Conversation

@Makio64

@Makio64 Makio64 commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Related issue: #35436

Description

Less bookkeeping per node in Node.build() and NodeBuilder. The generated WGSL is identical for all 21 test materials.

  • hashNodes is a Map and only holds nodes with a custom getHash(). The default hash is the node id, so such a node can only find itself.
  • getCacheKey() caches the hash of each property name.
  • The per-node data object has a stable shape and is read once in getDataFromNode().
  • Node properties are iterated without Object.values(), and the finished build stages are bits instead of an object per node and stage.
WGSLNodeBuilder.build() dev PR
MeshBasicNodeMaterial 0.20 ms 0.17 ms −18%
MeshStandardNodeMaterial, 5 lights 2.46 ms 1.89 ms −23%
MeshStandardNodeMaterial, 5 lights, shadows 3.74 ms 2.78 ms −26%
MeshPhysicalNodeMaterial, all features 6.80 ms 5.66 ms −17%
Custom TSL material 3.10 ms 2.50 ms −19%
All 21 materials 37.02 ms 29.76 ms −20%

CPU time in Node 24 on an Apple M3 Max, best of 4 interleaved runs of 16 builds each.

🤖 Generated with Claude Code

- Keep `hashNodes` in a Map and register only nodes with a custom hash. The
  default hash is the node id, so such a node can only ever find itself.
- Cache the hashes of the property names used by `Node.getCacheKey()`.
- Give the per node data a stable shape and read it once in `getDataFromNode()`.
- Iterate node properties without `Object.values()` and track the finished
  build stages as bits instead of an object per node and stage.

The generated shaders are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📦 Bundle size

Full ESM build including core, minified and gzipped.

Before After Diff
Core 395.78
103.48
395.78
103.48
+0 B
+0 B
WebGL 783.91
195.68
783.91
195.68
+0 B
+0 B
WebGPU 1118.92
300.91
1119.14
301.01
+222 B
+102 B
WebGPU Nodes 1116.87
300.56
1117.09
300.66
+222 B
+101 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 520.26
125.10
520.26
125.10
+0 B
+0 B
WebGPU 794.54
212.89
794.76
212.99
+222 B
+91 B
WebGPU Nodes 740.58
199.35
740.80
199.44
+222 B
+87 B

@Mugen87

Mugen87 commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

These are still too many changes at once. And I doubt all of them make sense. Stuff like if ( node.getHash !== Node.prototype.getHash ) { is really hard to read and looks like a hack.

How about just doing the following in NodeBuilder for now?

  • Make hashNodes a Map.
  • Define the full properties for nodeData to avoid runtime permutation.

kvnloo added a commit to kvnloo/three.js that referenced this pull request Nov 10, 2026
Scope mrdoob#35441 by Makio64 to Mugen87's requested
hashNodes Map and stable nodeData shape; retain fork dev Node build
and scheduling semantics. Exercise numeric/string/special hashes and
four-stage data shape. Browser tests and shaders not executed.

Prepared downstream for Kevin's OSS factory; no upstream publication.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants