Luxar Viewer API Documentation - v2026.10.19
    Preparing search index...

    GPU buffer pool for reusing THREE.BufferGeometry objects.

    Manages separate pools for Points, Lines, and GSplats geometries, each with different attribute layouts and update patterns.

    Index
    • Parameters

      • maxPoolSize: number = 20
      • evictionCommits: number = 300
      • evictBatchSize: number = 5
      • getByteBudget: () => number = ...

      Returns GPUBufferPool

    — points-specific pool state and methods.

    — lines-specific pool state and methods.

    — gsplats-specific pool state and methods.

    activeBuffers: ActiveBufferMap = ...

    nodeId → active geometry, keeping a running byte total (see gpu-buffer-pool/byte-tracked-maps.ts). — shared with the per-type adapters.

    meshGeometries: Map<
        string,
        BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>,
    > = ...

    Committed, non-pooled mesh geometries. The scene graph owns their lifetime.

    trackedMeshGeometries: WeakSet<
        BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>,
    > = ...
    commitCount: number = 0

    Atomic-commit counter — the pool's unit of age (#2939). — shared with the per-type adapters; increments via beginCommit.

    maxPoolSize: number
    evictionCommits: number

    LRU age limit, in atomic commits (see beginCommit).

    evictBatchSize: number

    Maximum geometries the pool will dispose in a single evictUnused() call when not over the hard limit. Without this cap, a single eviction sweep can dispose dozens of buffers synchronously — each geometry.dispose() is 5–20 ms on slow GPUs, so a burst stutters visibly. Remaining evictable buffers are deferred to the next eviction sweep. The mustEvict (pool-over-limit) path ignores this cap so the pool never grows unbounded.

    getByteBudget: () => number

    Live byte-budget getter (the single VRAM authority shared with LOD retention). The byte-eviction pass targets TOTAL resident bytes (active + pooled) against this value: pooled buffers are disposed (largest-first) only while active + pooled exceeds the budget, and retained for reuse otherwise. Read live (not snapshotted) so the WebGL-context-loss budget backoff applies immediately. Returns 0 to disable byte-budget eviction (count-only behavior).

    stats: {
        allocations: number;
        reuses: number;
        evictions: number;
        capacityGrowths: number;
        deferredEvictions: number;
        byteBudgetEvictions: number;
    } = ...

    — shared mutable stats; adapters bump fields here.

    Type Declaration

    • allocations: number
    • reuses: number
    • evictions: number
    • capacityGrowths: number
    • deferredEvictions: number

      Pooled buffers skipped this evictUnused call due to batch cap.

    • byteBudgetEvictions: number

      Evictions attributable to the BYTE-budget pass alone (a subset of evictions) — see PoolStats.byteBudgetEvictions for why the two must not be conflated.

    _lastAcquireRebuilt: boolean = false

    — written by adapters from acquire paths.

    largePoolWarningEmitted: boolean = false

    One-shot guard: have we already logged the >100MB pooled-buffer warning? Re-checked per evictUnused so the noise stays bounded.

    typeStats: {
        points: { allocations: number; reuses: number; evictions: number };
        lines: { allocations: number; reuses: number; evictions: number };
        gsplats: { allocations: number; reuses: number; evictions: number };
    } = ...
    • Advance the commit counter. Call once per ATOMIC COMMIT (a non-discarded update pass), before its acquire calls — the only production caller is data/scene-loader/update-view/atomic-commit.ts.

      The pool therefore ages pooled buffers in commits, not rendered frames (#2939): evictionCommits (default 300) is 300 commits, and the over-limit mustEvict grace below is OVER_LIMIT_GRACE_COMMITS (60). That is deliberate — buffers are only acquired and released on commits — but it has two consequences worth knowing when tuning it:

      • while the view orbits or sits idle no commit runs, so pooled buffers do NOT age; they are freed only by the byte budget or maxPoolSize;
      • during playback or scrubbing (many commits per second) 300 commits can elapse in seconds. (There is no idle time-based sweep; one would be a separate policy.)

      Returns void

    • Whether the most recent acquire*Geometry call returned a geometry whose GPU-visible buffers (lines' interleaved buffer, the points/gsplat element texture + aSortedIndex) differ from what was previously in use for that node — the returned geometry is a fresh allocation or a different pool candidate (growth is release + reacquire for all three types).

      Callers that hold a THREE.Mesh pointing at the previously-active geometry should call invalidateRenderObjectFor(mesh) (in data/scene-loader/commit/invalidate-render-object.ts) when this returns true. That forces Three's WebGPURenderer to discard the cached RenderObject.vertexBuffers set (a no-op on the classic WebGL backend, which has no such cache); without it, WebGPU binds the old GPU buffer next draw and validation fails with "Instance range … requires a larger buffer than the bound buffer size".

      The flag is overwritten on every acquire call, so consume it immediately after acquirePointsGeometry / acquireLinesGeometry / acquireGSplatsGeometry returns.

      Returns boolean

    • Acquire Points geometry from pool (capacity-aware; the fixed texel layout means any pooled points geometry fits any points node). See PointsBufferAdapter.acquireGeometry for implementation.

      Parameters

      • nodeId: string
      • pointCount: number

      Returns InstancedBufferGeometry

    • Update Points geometry attributes in-place (zero GPU allocations).

      Parameters

      • geometry: InstancedBufferGeometry
      • data: LoadedPointsData
      • count: number
      • Optionaloptions: InstancedOrderingOptions

        The commit ordering the commit path chose (writeInstancedCommitOrdering, shared by all three types): preserveOrdering / repairFromCount keep or rebuild the existing permutation; fromInstance (append fast path) writes & uploads only the [fromInstance, count) suffix and HOLDS the draw at the previous population until the grown population's ordering lands; seedOrdering holds a GROWN geometry on the previous geometry's drawn permutation.

      Returns void

    • Acquire geometry for Lines (texture-backed per-segment storage — the fixed 6-texel layout means capacity is the only matching criterion; see lines-adapter.ts).

      Parameters

      • nodeId: string
      • segmentCount: number

      Returns InstancedBufferGeometry

    • Acquire geometry for GSplats (splat texture + aSortedIndex).

      Parameters

      • nodeId: string
      • splatCount: number

      Returns InstancedBufferGeometry

    • Get size bucket for capacity-based pooling. Buckets: 1K, 5K, 10K, 50K, 100K, 500K, 1M. — called by adapters; public to satisfy PoolAdapterHost.

      Parameters

      • count: number

      Returns number

    • Evict unused geometries using LRU policy. Returns number of geometries evicted.

      Public for testing and manual pool management.

      Parameters

      • fromAcquire: boolean = false

      Returns number

    • Parameters

      • budget: number
      • fromAcquire: boolean = false

      Returns number

    • Sum of bytes held by active (in-use) buffers. O(1): the map keeps the total incrementally (B9b) — it used to walk every active buffer.

      Returns number

    • Track a committed mesh without making it eligible for buffer reuse.

      Parameters

      • nodeId: string
      • geometry: BufferGeometry

      Returns void

    • Total resident VRAM bytes (active + pooled), using real per-geometry capacities. The single source of truth for budget enforcement: the LOD registry queries it to decide when to demote cold levels, and the pool's own byte-eviction pass keeps it under the live budget. Cheaper than getStats (no per-type breakdown), so it is safe to call once per frame.

      Returns number

    • Register a one-time self-invalidation dispose listener on a freshly created pooled geometry. Called by the per-type adapters at fresh allocation — the single choke point every pooled geometry passes through exactly once (adopted/reused geometries were registered at their original creation).

      The scene-graph disposal helpers (scene-disposal.ts) are pure over a THREE.Scene with NO pool reference, so on a dataset switch / teardown they call geometry.dispose() directly on meshes that still hold pool-owned geometries. Without this guard the pool keeps the disposed geometry recorded in activeBuffers, and a later acquire*Geometry for the same nodeId hands it back as reusable — a use-after-dispose that also over-counts resident bytes.

      When dispose fires this:

      • flags the geometry userData.luxarInvalidated (the acquire fast paths treat a flagged active entry as a miss — defense-in-depth against an ordering where the entry is somehow still present);
      • drops any activeBuffers entry that still references THIS geometry. Matching by identity is inherently safe — a geometry appears in activeBuffers at most once, so a newer entry that replaced it (a grow moves the old geometry to the free list first) is never clobbered (the guard the issue asks for);
      • otherwise drops the geometry's free-bucket entry (a RELEASED buffer disposed out-of-band). Leaving it would keep the disposed geometry counted by getStats / getResidentBytes (phantom bytes that pressure the byte-budget pass into evicting live buffers), holding a maxPoolSize slot (which can flip the LRU sweep into aggressive mustEvict mode), and queued for a second dispose() by the evictors.

      Resident-byte / stats accounting follows activeBuffers + free-bucket membership (see getStats / getResidentBytes; the byte totals are kept incrementally BY those containers), so removing the entry is the entire correction — there is no separate counter to decrement. On the normal release/evict/dispose paths the entry (or its free-bucket slot) is already gone before dispose() runs, so this listener finds nothing and is a safe no-op there — it never double-counts eviction stats and never mutates a bucket array those paths are still iterating (they all commit removals BEFORE disposing). It never calls dispose() again (no re-entrancy) and removes itself on first fire (idempotent).

      — called by the per-type adapters via their host interface.

      Parameters

      • geometry: BufferGeometry

      Returns void

    • Remove the free-bucket entry referencing geometry, if any. A geometry appears in at most one bucket of one type pool, so the scan stops at the first identity match. No eviction counter moves: this is out-of-band disposal bookkeeping, not an eviction.

      Parameters

      • geometry: BufferGeometry

      Returns void