You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Draw calls scale with wall count: batch a level's static walls per material #588
Split out of #492 as cause 2. #556 fixed cause 1 (level-wide recompute per frame); this is the remaining half and a design change rather than a regression.
Measurement
All numbers from @toycenterboss-bot's instrumented profiling in #492. macOS, Chrome, WebGPU renderer (three r185), frameloop="never" — so 50 FPS is a ceiling, and a 50.x reading means "hit the ceiling", not "hit the hardware limit".
Scene
Nodes
Contents
Draw calls
Orbit FPS
empty
0
grid and helpers only
24
50.3
zones
142
flat polygons
130
50.2
outer walls
88
perimeter only
534
50.0
real plan
310
walls and rooms
1362
50.2
full import
1089
outer + inner walls
6520
42.7
Cost tracks visible meshes, not node count — a 142-node zone scene costs 130 calls, an 88-node wall scene costs 534. Every scene under roughly 1400 calls holds the ceiling; the 1089-wall import falls off it.
A scene-graph walk on that scene found 8 meshes with one material group, 1081 with three, and 24 with six: 3395 draw units, doubled by the shadow pass → 6520 calls.
Direction
Merging a level's static walls per material collapses those 3243 units to three, which took the same scene to 25 draw calls in the reporter's experiment.
The constraint to design around: the merge has to invalidate cleanly on edit, and walls are exactly the geometry users mutate most. So the win only holds if the merged batch
That ordering is why this is a follow-up to #556 rather than part of it.
Also worth checking while in here: the shadow pass doubling every unit means anything that reduces shadow-casting mesh count pays twice.
Fixture
A 1089-wall scene JSON reproducing the last row has been offered in #492 and should be added as a profiling fixture so this is measurable in CI rather than by hand.
Credit to @toycenterboss-bot for the profiling and the decomposition — separating "work repeated per frame with unchanged inputs" from "draw calls scale with mesh count" is what made both halves actionable.
Andrei Efremov (@toycenterboss-bot) designed and built the wall batching work, supplied the anonymized 1,089-wall fixture, and added the draw-call budget coverage. The submitted benchmark reduced the full fixture from 6,520 draw calls to 25 by grouping static wall geometry per level while retaining per-wall range mapping.
The final maintainer pass also covers the failure modes discussed here: actively edited and tinted walls stay out of the batch; isolation and non-up wall modes stand batching down; visibility changes participate in dirty tracking; picking, measurements, shadows, themes, and material changes retain their contracts; and the batched layer is documented.
Validation on the combined merge train: formatting, types, production build, 1,089 focused tests, and a browser smoke using the contributed fixture. In the browser smoke the scene settled to six meshes, reached DIRTY 0, and switched 3D/2D/3D without runtime errors.
Thank you again, Andrei. This issue is closed by the merged contribution. Cutaway-mode batching remains intentionally out of scope because its camera-dependent material behavior needs a different geometry ordering strategy.
Activity
Aymericr commentedon Aug 18, 2026
Implemented and merged in #608.
Andrei Efremov (@toycenterboss-bot) designed and built the wall batching work, supplied the anonymized 1,089-wall fixture, and added the draw-call budget coverage. The submitted benchmark reduced the full fixture from 6,520 draw calls to 25 by grouping static wall geometry per level while retaining per-wall range mapping.
The final maintainer pass also covers the failure modes discussed here: actively edited and tinted walls stay out of the batch; isolation and non-up wall modes stand batching down; visibility changes participate in dirty tracking; picking, measurements, shadows, themes, and material changes retain their contracts; and the batched layer is documented.
Validation on the combined merge train: formatting, types, production build, 1,089 focused tests, and a browser smoke using the contributed fixture. In the browser smoke the scene settled to six meshes, reached DIRTY 0, and switched 3D/2D/3D without runtime errors.
Thank you again, Andrei. This issue is closed by the merged contribution. Cutaway-mode batching remains intentionally out of scope because its camera-dependent material behavior needs a different geometry ordering strategy.
perf(viewer): sew a level wall set into one mesh (pascalorg#608)
perf(viewer): sew a level wall set into one mesh (pascalorg#608)