Visitar URL original
Mesh: Add .setDrawRange, support shared scene geometry by donmccurdy · Pull Request #35112 · mrdoob/three.js · GitHub
Skip to content

Mesh: Add .setDrawRange, support shared scene geometry - #35112

Open
donmccurdy wants to merge 1 commit into
mrdoob:devfrom
donmccurdy:feat/mesh-drawrange
Open

donmccurdy wants to merge 1 commit into
mrdoob:devfrom
donmccurdy:feat/mesh-drawrange

Conversation

@donmccurdy

@donmccurdy donmccurdy commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

As discussed in #17089, scenes with many meshes incur overhead from rebinding WebGL buffers for each mesh, because each BufferGeometry is uploaded to the GPU as (at best!) a single WebGL buffer. A few earlier PRs (now stalled) tried to reduce the number of WebGL buffers by allowing BufferGeometries to share BufferAttributes and InterleavedBuffers.

I'm hoping solve the problem from the other direction instead: given a single BufferGeometry containing a larger vertex stream, allow any Mesh to draw any vertex range of that geometry, using a per-mesh drawRange property. By reusing a single BufferGeometry (and a single WebGL buffer) the cost of binding WebGL buffers can be mostly avoided.

If both mesh.drawRange and geometry.drawRange are set, the intersection of the two ranges is used (more an arbitrary choice than a preference, open to change this).

Implications for Loaders

When loading 3D models from more optimized exporters (see: gltfpack or glTF Transform) it should be possible (in a future PR) for GLTFLoader to use a single shared BufferGeometry, without any runtime work to merge many small meshes. Users of glTF could then get an optimized scene with just one BufferGeometry, regardless of how many objects were in the source model. Some care is required on the part of the exporter to format buffers for this purpose.

Comparison to BatchedMesh

BatchedMesh is still going to be more performant; it eliminates more work than just binding WebGL buffers. However, for draw-call-heavy applications that can't use BatchedMesh, sharing a BufferGeometry for many objects in a scene provides a major performance optimization for relatively little effort.

If we also update loaders as described above, users already running gltfpack or glTF Transform may receive 30–40% of BatchedMesh's benefits with no code changes at all.

Performance results

Measured by generating 1000 distinct geometries, then merging them with BufferGeometryUtils.mergeGeometries. The scene was rendered with the 1000 distinct geometries, and then with a single merged geometry for the entire scene; in both cases the scene contained 1000 meshes with unique transforms:

mesh-drawrange-screenshot

After each render, I read back one pixel (WebGL) or awaited renderer.backend.device.queue.onSubmittedWorkDone() (WebGPU).

Results formatted as <separate geometry median ms> → <shared geometry median ms>.

renderer Chromium Firefox Safari on macOS Safari on iOS
WebGLRenderer 5.80ms → 3.80ms 5.00ms → 3.00ms 5.00ms → 2.00ms 7.00ms → 6.00ms
WebGPURenderer
(WebGPU)
8.30ms → 5.20ms – 8.00ms → 5.00ms –
WebGPURenderer
(WebGL)
7.60ms → 6.00ms 8.00ms → 5.00ms 6.00ms → 4.00ms 11.00ms → 10.00ms

Devices: Apple MacBook Pro 2021 (M1 Pro), and iPhone 16e.

I had some trouble collecting accurate timing on WebGPU in Firefox and Safari on iOS, but haven't spent time investigating that. My results on iOS are not as strongly positive, for whatever reason.

Related

@github-actions

Copy link
Copy Markdown

📦 Bundle size

Full ESM build including core, minified and gzipped.

Before After Diff
Core 392.41
102.86
392.76
102.91
+352 B
+46 B
WebGL 778.72
194.39
779.18
194.49
+455 B
+94 B
WebGPU 1107.83
298.07
1108.29
298.18
+455 B
+106 B
WebGPU Nodes 1105.78
297.71
1106.23
297.82
+455 B
+111 B

🌳 Bundle size after tree-shaking

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

Before After Diff
WebGL 518.86
124.64
519.31
124.74
+455 B
+92 B
WebGPU 785.50
210.22
785.95
210.35
+455 B
+126 B
WebGPU Nodes 731.61
196.74
732.06
196.83
+455 B
+90 B

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

🖼️ E2E screenshot tests

❌ 1 example(s) failed (full artifacts).

Example Expected Actual Diff
webgpu_camera

@donmccurdy donmccurdy changed the title Mesh: Shared geometry with per-mesh drawRange Mesh: Add .setDrawRange, support shared scene geometry Oct 20, 2026
@sunag sunag added this to the r187 milestone Oct 20, 2026
@Mugen87

Mugen87 commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Doesn't today's API already cover the use case?

You can create N BufferGeometry objects that share the same attributes and index, each with its own draw range.

Can you please inlcude this setup in your performance test?

@Mugen87

Mugen87 commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Also a separate draw range API on Mesh level would make the draw range level on BufferGeometry somewhat obsolete. It seems only consistent to deprecate the geometry API and provide the draw range on points and lines as well.

This will be quite a large change because a lot of code relies on the BufferGeometry API.

Also, how would you implement view frustum culling with a single combined BufferGeometry? Bounding volumes get more coarse (and are probably useless in certain combined cases), ray casting gets slower if you don't apply modifications.

@mrdoob mrdoob modified the milestones: r187, r188 Oct 30, 2026

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.

4 participants