Point geometry's index at indices, reusing the existing buffer when it can.
Why not simply setIndex(createMeshIndexAttribute(...)) every epoch
Because that leaks GPU memory on every slice move. Three caches attribute buffers
in a WeakMap keyed by the attribute object and only ever calls gl.deleteBuffer
from WebGLAttributes.remove() — which runs on geometry disposal (for whichever
index is current at that moment) and, notably, when the wireframe attribute is
replaced. Nothing calls it when geometry.index itself is replaced: the old
attribute becomes unreachable, its WeakMap entry is collected, and the GPU buffer
it owned is never freed. Mesh is the only geometry type that rewrites its index per
epoch — the other three update pooled attributes in place — so nothing in the tree
had hit this before.
So the index buffer is allocated once at the node's full face-count capacity and
the visible prefix is drawn with setDrawRange. The tail past the range keeps
stale indices, which is safe precisely because drawRange bounds the draw; three
clamps it to index.count.
Reuse also means the index attribute OBJECT is stable after the first commit, so a
slice move rebinds nothing. It does not participate in the attributesRebuilt
eviction contract either way: the index contributes only its PRESENCE to three's
geometry cache key, and the WebGPU backend re-derives indexFormat from
index.array on every draw, so even the dtype-widening fall-through below needs no
RenderObject eviction — its only cost is the orphaned buffer.
A consequence worth knowing when reading counts elsewhere: index.count is now the
CAPACITY, not what is drawn. drawRange.count is the drawn quantity — which is why
camera-framing.ts reads that instead.
Point
geometry's index atindices, reusing the existing buffer when it can.Why not simply
setIndex(createMeshIndexAttribute(...))every epochBecause that leaks GPU memory on every slice move. Three caches attribute buffers in a
WeakMapkeyed by the attribute object and only ever callsgl.deleteBufferfromWebGLAttributes.remove()— which runs on geometry disposal (for whichever index is current at that moment) and, notably, when the wireframe attribute is replaced. Nothing calls it whengeometry.indexitself is replaced: the old attribute becomes unreachable, itsWeakMapentry is collected, and the GPU buffer it owned is never freed. Mesh is the only geometry type that rewrites its index per epoch — the other three update pooled attributes in place — so nothing in the tree had hit this before.So the index buffer is allocated once at the node's full face-count capacity and the visible prefix is drawn with
setDrawRange. The tail past the range keeps stale indices, which is safe precisely becausedrawRangebounds the draw; three clamps it toindex.count.Reuse also means the index attribute OBJECT is stable after the first commit, so a slice move rebinds nothing. It does not participate in the
attributesRebuilteviction contract either way: the index contributes only its PRESENCE to three's geometry cache key, and the WebGPU backend re-derivesindexFormatfromindex.arrayon every draw, so even the dtype-widening fall-through below needs noRenderObjecteviction — its only cost is the orphaned buffer.A consequence worth knowing when reading counts elsewhere:
index.countis now the CAPACITY, not what is drawn.drawRange.countis the drawn quantity — which is whycamera-framing.tsreads that instead.