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

    Variable GLSL_SORTED_INDEXConst

    GLSL_SORTED_INDEX: "\n// Element index split into two 16-bit halves, low in .x and high in .y.\n// The pick pass carries the index through an RGBA32F buffer, and float32\n// has a 24-bit mantissa — so a single float channel cannot represent\n// consecutive indices past 16,777,216, while a node's capacity reaches\n// 2^25 on a 32768-texel device. Both halves are <= 65535, hence exact,\n// and the pick decoder recombines them (see picking-system/pick-render.ts).\n// Kept in INT space: doing the split on a float would already have lost\n// the bit it is meant to preserve.\nvec2 luxarElementIdSplit(uint i) {\n return vec2(float(i & 0xFFFFu), float(i >> 16u));\n}\n\nin uint aSortedIndex;\nin uint aSortedIndexB;\nuniform int uSortedIndexSlot;\n\nuint luxarSortedIndex() {\n return uSortedIndexSlot == 1 ? aSortedIndexB : aSortedIndex;\n}\n\n// The STORAGE slot's id parts — the id the rest of the pipeline (loaders,\n// selection) addresses elements by, not the transient draw slot. Mesh reads\n// the shared split directly instead, off gl_VertexID (spec §6.5).\nvec2 luxarElementIdParts() {\n return luxarElementIdSplit(luxarSortedIndex());\n}\n// Projected-density thinning (scene/density-guard.ts): the fraction of this\n// node's elements to DROP, chosen per STORAGE index with a deterministic\n// integer hash so the kept subset is stable under depth re-sorting, identical\n// across the visual and picking passes, and spatially uniform (storage order\n// is Hilbert/BSP-coherent, so a prefix would be a hole). A material that does\n// not set the uniform reads 0 and drops nothing.\nuniform float uDensityDrop;\nbool luxarDensityDropped() {\n if (uDensityDrop <= 0.0) return false;\n uint h = luxarSortedIndex();\n h ^= h >> 16u;\n h *= 0x7feb352du;\n h ^= h >> 15u;\n h *= 0x846ca68bu;\n h ^= h >> 16u;\n return float(h) * (1.0 / 4294967296.0) < uDensityDrop;\n}\n" = ...

    Double-buffered draw-slot → storage-slot mapping (depth-sorting spec §2.1 tier 3). Declares BOTH ordering attributes plus the slot selector, and exposes luxarSortedIndex() as the single read point.

    A permutation must swap ATOMICALLY: a half-applied ordering is not a reordering but a corrupt permutation (elements drawn twice / not at all). So the coordinator streams each new ordering into the INACTIVE attribute across frames and flips uSortedIndexSlot only once that buffer holds the whole permutation — the attribute being read is therefore always complete.

    Both attributes are ALWAYS present on the geometry, as two DISTINCT buffers allocated together by attachElementStorage — never aliased onto one and never materialised later. That is load-bearing on WebGPU twice over: RenderObject dereferences a graph-referenced attribute before its undefined guard, and the vertex-buffer layout is cached from the attribute set at first draw and never rebuilt, so a set that grows afterwards renders the scene black (element-storage.ts).

    uSortedIndexSlot is a RUNTIME uniform, never a define: a flip must not recompile the program.

    Inject once per vertex shader that indexes an element texture, then read the index via luxarSortedIndex():

    const vertexShader = `
    ${GLSL_SORTED_INDEX}
    void main() { int base = int(luxarSortedIndex()) * 4; ... }
    `;