Double-buffered draw-slot → storage-slot index — the TSL twin of
GLSL_SORTED_INDEX (depth-sorting spec §2.1 tier 3).
A permutation must swap ATOMICALLY: a half-applied ordering is not a
reordering but a corrupt permutation (elements drawn twice / not at
all). Each new ordering therefore streams into the INACTIVE attribute
across frames, and uSortedIndexSlot flips only once that buffer holds
the whole permutation.
BOTH attributes are referenced unconditionally, which is required, not
merely tidy: the WebGPU backend only uploads graph-referenced
attributes, so an unreferenced back buffer would never receive its
chunked uploads — and RenderObject dereferences a referenced
attribute before its undefined guard, so every geometry must carry
both. attachElementStorage allocates them as two DISTINCT buffers
together — never aliased, never added later, since the WebGPU vertex
layout is cached from the attribute set at first draw and a set that
grows afterwards renders the scene black.
uSortedIndexSlot must stay a RUNTIME uniform: a compile-time flag
would rebuild the graph on every swap. (The lines material treats
uIsOrtho as compile-time — deliberately NOT the pattern here.)
BRANCHLESS on purpose. .select() emits an if/else STATEMENT, and the
pick factories consume this index at varying(float(...)) — evaluated
OUTSIDE their Fn() body, where a statement cannot legally land. That
produced a shader which built and code-generated fine but rendered
zero pixels (the same vacuous-black failure mode the gsplat harness
comment in tests/e2e/harnesses/tsl-harness/gsplats.ts warns about).
A pure arithmetic mix is position-independent, so the one helper is
safe both inside and outside an Fn(). The multiplier is a uniform,
so there is no per-vertex divergence to save by branching anyway.
Returns an INT node (not uint): the downstream int(...) wraps and
float(...) casts accept it unchanged.
Double-buffered draw-slot → storage-slot index — the TSL twin of
GLSL_SORTED_INDEX(depth-sorting spec §2.1 tier 3).A permutation must swap ATOMICALLY: a half-applied ordering is not a reordering but a corrupt permutation (elements drawn twice / not at all). Each new ordering therefore streams into the INACTIVE attribute across frames, and
uSortedIndexSlotflips only once that buffer holds the whole permutation.BOTH attributes are referenced unconditionally, which is required, not merely tidy: the WebGPU backend only uploads graph-referenced attributes, so an unreferenced back buffer would never receive its chunked uploads — and
RenderObjectdereferences a referenced attribute before its undefined guard, so every geometry must carry both.attachElementStorageallocates them as two DISTINCT buffers together — never aliased, never added later, since the WebGPU vertex layout is cached from the attribute set at first draw and a set that grows afterwards renders the scene black.uSortedIndexSlotmust stay a RUNTIME uniform: a compile-time flag would rebuild the graph on every swap. (The lines material treatsuIsOrthoas compile-time — deliberately NOT the pattern here.)BRANCHLESS on purpose.
.select()emits an if/else STATEMENT, and the pick factories consume this index atvarying(float(...))— evaluated OUTSIDE theirFn()body, where a statement cannot legally land. That produced a shader which built and code-generated fine but rendered zero pixels (the same vacuous-black failure mode the gsplat harness comment intests/e2e/harnesses/tsl-harness/gsplats.tswarns about). A pure arithmetic mix is position-independent, so the one helper is safe both inside and outside anFn(). The multiplier is a uniform, so there is no per-vertex divergence to save by branching anyway.Returns an INT node (not uint): the downstream
int(...)wraps andfloat(...)casts accept it unchanged.