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

    Module data/scene-loader/commit/invalidate-render-object

    Force Three's WebGPURenderer to discard the cached RenderObject associated with a given mesh + its material, so the next render builds a fresh one with a clean vertexBuffers cache.

    Why this exists: when a commit swaps a mesh's InstancedBufferGeometry (pool grow / best-fit adoption / non-pool rebuild — the per-instance buffers, e.g. aSortedIndex, are new objects), Three's RenderObject (cached in RenderObjects.chainMap keyed by [mesh, material, …]) still holds:

    • attributes / attributesId — reset to null by setGeometry when Three's needsGeometryUpdate notices the attribute ids changed.
    • vertexBuffers — NOT reset by setGeometry. Stays as the OLD Set<InterleavedBuffer> until getAttributes() rebuilds it.

    getAttributes() is called from Geometries.updateForRender, which Renderer._renderObjectDirect (r185 common/Renderer.js lines 3690-3714) only runs when _nodes.needsRefresh(renderObject) is true. A pure-geometry / buffer-resize change does NOT mark needsRefresh, so on the next draw WebGPUBackend.draw calls renderObject.getVertexBuffers() which returns the stale vertexBuffers array (skips getAttributes() because vertexBuffers !== null). The renderer then binds the OLD GPU buffer at slot 0 and validation fails with "Instance range … requires a larger buffer than the bound buffer size (oldSize)".

    The cleanest in-userland fix is to dispatch a dispose event on the mesh's material. RenderObject.onMaterialDispose (r185 lines 324-328 in common/RenderObject.js) calls renderObject.dispose(), whose onDispose callback (set in RenderObjects.createRenderObject, r185 lines 201-209) deletes the entry from chainMap. The next RenderObjects.get(...) call returns undefined from the map and builds a fresh RenderObject whose vertexBuffers cache starts null — forcing getVertexBuffers() to call getAttributes() and pick up the current InterleavedBuffer.

    This RenderObject eviction is WebGPU-only. The chainMap vertexBuffers staleness it fixes exists only in Three's WebGPU RenderObjects cache; the classic WebGLRenderer (Luxar's production default) has no such cache and re-reads geometry attributes every draw. Worse, on the classic backend the SAME dispose event is caught by WebGLRenderer's own onMaterialDispose, which deallocates the material's compiled GL program — forcing a full GLSL recompile on the very next frame (multi-10ms hitches on every progressive / additive-ladder commit). So the dispatch is not merely unnecessary on classic WebGL, it is actively harmful, and is gated behind configureRenderObjectEviction (renderer-setup enables it only when the backend is WebGPU). On classic WebGL — and in headless/unit contexts with no renderer — the dispose dispatch is skipped entirely, so no compiled program is ever destroyed.

    When the eviction IS dispatched (WebGPU), Luxar's MaterialManager also listens for 'dispose' to unregister the material from its global-update set / caches. To prevent that side effect while still triggering Three's RenderObject eviction, we set a transient symbol-keyed flag on the material before dispatching; MaterialManager's listener checks for the flag and treats the event as a soft cache-invalidation rather than a real dispose. See SOFT_DISPOSE_FLAG in rendering/material-manager/soft-dispose-flag.ts.

    configureRenderObjectEviction
    invalidateRenderObjectFor