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
uSortedIndexSlotonly 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:RenderObjectdereferences 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).uSortedIndexSlotis 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():