perf(FragmentsModels): draw fragmented shell tiles with one compacted index instead of a group per visibility run - #281
Open
rihokirss wants to merge 1 commit into
Open
Conversation
… index instead of a group per visibility run A shell tile whose samples are partly culled or at a different LOD was drawn as one geometry.group per visible run — each its own draw call. On a large model with many small items this is 10–40 draw calls per tile (measured 289 shell meshes → 4 739 draw calls on a 38 MB IFC). The tile index is now kept on the CPU and, when a tile has three or more visible runs, the visible ranges are copied into the GPU index buffer in place and drawn as a single group (drawRange bounds the count). Highlighted tiles and tiles with fewer runs keep the group path, restoring the full index first. The GL buffer is never reallocated, so nothing leaks across updates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #278.
A shell tile is drawn through one
geometry.groupper contiguous run of samples visible at the GEOMETRY level, so a tile whose samples are partly culled or at a different LOD costs one draw call per run. On real models the runs are heavily interleaved (samples are appended in file order, the LOD decision is per sample and screen-size based), which is what makes the frame time scale with model count.This keeps the tile's index on the CPU and, when a tile has three or more visible runs, copies the visible ranges into the existing GPU index buffer in place and draws them as a single group bounded by
setDrawRange. The buffer never changes size, so no reallocation and nothing to leak;needsUpdatere-uploads the same-sized array.Kept on the group path (with the full index restored first, so switching back and forth is safe):
MaterialManager.createHighlightsadds groups with amaterialIndexaddressing the full index;LODManagerowns their groups;Measurements
Headless Chromium, Radeon 780M, 1920×1080, postproduction off, 50 FPS cap, medians of 3 runs. "1 model" is a 38 MB
.fragarchitecture model converted from IFC, "8 models" adds seven interior models of the same building.geometry.groups, 1 modelgeometry.groups, 8 modelsScreenshots of six fixed camera poses are pixel-identical (0.000 % differing pixels) to the current build. Group-load wall time −12 %,
update(true)settle after an orbit unchanged.Memory cost: one extra
Uint16Arrayindex copy per shell tile — ~11 MB on that model. (The heap number above drops anyway, because far fewer tile geometries are kept alive.)Note for
getItemDrawChunksconsumersThe chunk positions returned by
getItemDrawChunksrefer to the full index. With this change a compacted tile'sgeometry.indexholds only the visible ranges, so a renderer cloning the tile mesh should take the index fromgeometry.userData.fullIndex. Happy to add that to the JSDoc if you want it worded differently.I also have a small worker-side change that reduces the run count at the source (fill tiles in size-sorted sample order) — separate PR, they compose: together the 8-model scene is 1 817 calls at the 50 FPS cap.
I can add a unit test around the compaction (visible ranges in, compacted index + single group out) if you'd like one; it needs a tiny seam in
MeshManagerto be testable in isolation, so I left it out of this diff.The companion PR for #278 is #282 (worker-side sample order). They are independent — either can go in alone — and compose.
🤖 Generated with Claude Code
https://claude.ai/code/session_013rtNQqhSQRM2t6E98DESNE