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

    Module rendering/element-texture-row-upload

    Rows-only element-texture uploads on three's WebGPU backends (#2944).

    Element data lives in RGBA32F DataTextures whose writers register the dirty span as per-row updateRanges (./element-storage). The classic WebGLRenderer honours them; three r185's WebGPURenderer backends do not — both the native WebGPU backend (WebGPUTextureUtils.updateTexture → one queue.writeTexture of the whole image) and the WebGL2 fallback (WebGLTextureUtils.updateTexture → one whole-image texSubImage2D) re-upload the full CAPACITY on every needsUpdate, so a playback commit moved ~3x the bytes of the same commit on WebGL.

    Interception point: installElementTextureRowUploads replaces the backend instance's updateTexture(texture, options) ONCE, at renderer creation. That is the single call three's common Textures.updateTexture makes to move texel bytes, and it runs AFTER three has done all its own bookkeeping for the update — createTexture on first use, the version / generation stamps, and (after we return) onUpdate. The wrapper therefore changes only WHICH bytes reach the GPU resource three already allocated, never three's view of the texture's state: nothing three tracks can go stale, and no later full re-upload is triggered.

    Why the GPU contents equal a full upload: by induction over a GPU resource's uploads. Every CPU write to an element texture registers its span as a range, and ranges are consumed only by an upload, so the texels OUTSIDE the pending ranges already hold their current CPU value on the GPU whenever the resource is "in sync". Uploading the whole rows the ranges touch, from the same image.data, then leaves every texel equal to what a full upload would have written, and the ranges are CONSUMED (cleared), as the classic renderer does, so the next commit uploads only its own rows. A resource is in sync when either

    • this wrapper last brought exactly this resource object fully up to date (a full upload or a ranged one), or
    • the texture has NEVER been uploaded by any renderer (its onUpdate has never fired): its backing store was zero-filled at attach and every write since is still in the pending ranges, while three has just allocated the resource and both APIs zero-initialise new textures. This is the ranged first upload the classic renderer already does, and it is what keeps growth-headroom rows off a progressive load.

    Three's full upload is used instead (the original method, verbatim) whenever any of these holds:

    • the texture is not an element texture (every other texture);
    • no ranges are pending, or a full upload is pending (markElementTextureFullDirty, the ≥75%-dirty fold, context restore);
    • the resource is not in sync — a reallocation (three hands back a new GPUTexture / WebGLTexture), a device / context loss that recreates resources, or a texture another renderer or backend already uploaded;
    • the texture or its GPU resource is not the exact shape this writer knows (RGBA32F 2D, no mips, no flipY, image.data a full-size Float32Array, descriptor size equal to the image);
    • a range is out of bounds;
    • the row write throws.

    Pool re-acquire is ordinary here: the geometry keeps its texture and GPU resource, and the new tenant's writes register their spans like any other commit.

    The rows are written through the backend's own device.queue / gl, whose upload methods ./upload-counters wraps, and are passed as an exact subarray so the counters tally the bytes actually written.

    The classic WebGLRenderer has no backend and is left alone.

    RowSpan
    UpdateOptions
    BackendLike
    ElementImage
    RowWriter
    UpdateTextureFn
    elementImageForRowUpload
    planDirtyRows
    webgpuRowWriter
    webglFallbackRowWriter
    createRowUploadUpdateTexture
    installElementTextureRowUploads