Mesh shader family for the TSL ↔ GLSL parity harness: the six variants of
docs/specs/MESH_NODE_SPEC.md §6.4 — the opaque default (alpha cutout), the
alpha-weighted emission shared by additive/luminous/normal, the max
premultiply, the derivative flat-normal build, the colormap LUT build, and
mesh-pick.
What the pick entries can and cannot show
The harness renders to an UnsignedByteType target, so the pick pass's id
channels — nodeId in R and the two 16-bit halves in G/A — clamp and quantize to
8 bits. The pick entries therefore test what survives that: whether the two
backends discard the same fragments under the cutout, whether they agree on
the brightness channel, and whether WebGL and core WebGPU agree on the
quantized original-first-corner id. The id SPLIT is pinned elsewhere, by
construction rather than by pixels — both backends route through one shared helper
(luxarElementIdSplit, single-sourced in glsl-lib.ts), the codegen snapshot
shows the TSL side's / 65536 and - hi * 65536, and a unit test round-trips the
split against voteWinner's recombination.
Five entries render under PERSPECTIVE
Every other entry uses the shared ORTHOGRAPHIC default camera, under which
perspectiveNearFade is the identity — so the near fade would ship with no
rendered parity coverage at all. mesh-near-fade, mesh-additive-near-fade and
mesh-pick-near-fade override buildCamera with the perspective
buildBehindCamera and pick a uNearCull that puts the whole quad at a partial
fade; the arithmetic is on NEAR_FADE_UNIFORMS below.
The two *-near-fade-reference entries render under the SAME perspective camera
with the fade made the identity (a uNearCull far inside the quad's depth), and
exist because the anti-vacuity
half of the parity test needs an un-faded frame of the same surface points. The
ortho default camera cannot supply one: its frame is 2.0 wide at z = 0 against the
perspective frame's 2·tan(30°) = 1.155, so pixel (i, j) is a different point on the
quad in the two framings and the "exactly 0.15625 ×" comparison would be measuring
that mismatch as well as the fade.
The fixture is a tilted-normal quad, on purpose
Two triangles in the z = 0 plane, but with per-corner normals fanned outward
(normalize(x·0.6, y·0.6, 1)). That is what makes the flat-vs-smooth pair a real
test rather than a tautology: the geometric normal of this quad is uniformly
+z, so a build that ignored shading and always shaded from derivatives would
render the flat and smooth variants IDENTICALLY. With fanned stored normals the
smooth variant carries a visible radial gradient and the flat one is uniform.
The colour attribute is float32 RGBA at four components, so vAlpha is a real
authored value (0.25 at one corner) rather than the constant 1.0 an RGB fixture
would supply — which is what gives the cutout and the max premultiply something
to act on.
Mesh shader family for the TSL ↔ GLSL parity harness: the six variants of
docs/specs/MESH_NODE_SPEC.md§6.4 — theopaquedefault (alpha cutout), the alpha-weighted emission shared byadditive/luminous/normal, themaxpremultiply, the derivative flat-normal build, the colormap LUT build, andmesh-pick.What the pick entries can and cannot show
The harness renders to an
UnsignedByteTypetarget, so the pick pass's id channels —nodeIdin R and the two 16-bit halves in G/A — clamp and quantize to 8 bits. The pick entries therefore test what survives that: whether the two backends discard the same fragments under the cutout, whether they agree on thebrightnesschannel, and whether WebGL and core WebGPU agree on the quantized original-first-corner id. The id SPLIT is pinned elsewhere, by construction rather than by pixels — both backends route through one shared helper (luxarElementIdSplit, single-sourced inglsl-lib.ts), the codegen snapshot shows the TSL side's/ 65536and- hi * 65536, and a unit test round-trips the split againstvoteWinner's recombination.Five entries render under PERSPECTIVE
Every other entry uses the shared ORTHOGRAPHIC default camera, under which
perspectiveNearFadeis the identity — so the near fade would ship with no rendered parity coverage at all.mesh-near-fade,mesh-additive-near-fadeandmesh-pick-near-fadeoverridebuildCamerawith the perspectivebuildBehindCameraand pick auNearCullthat puts the whole quad at a partial fade; the arithmetic is onNEAR_FADE_UNIFORMSbelow.The two
*-near-fade-referenceentries render under the SAME perspective camera with the fade made the identity (auNearCullfar inside the quad's depth), and exist because the anti-vacuity half of the parity test needs an un-faded frame of the same surface points. The ortho default camera cannot supply one: its frame is 2.0 wide at z = 0 against the perspective frame's 2·tan(30°) = 1.155, so pixel (i, j) is a different point on the quad in the two framings and the "exactly 0.15625 ×" comparison would be measuring that mismatch as well as the fade.The fixture is a tilted-normal quad, on purpose
Two triangles in the z = 0 plane, but with per-corner normals fanned outward (
normalize(x·0.6, y·0.6, 1)). That is what makes the flat-vs-smooth pair a real test rather than a tautology: the geometric normal of this quad is uniformly +z, so a build that ignoredshadingand always shaded from derivatives would render the flat and smooth variants IDENTICALLY. With fanned stored normals the smooth variant carries a visible radial gradient and the flat one is uniform.The colour attribute is
float32RGBA at four components, sovAlphais a real authored value (0.25 at one corner) rather than the constant 1.0 an RGB fixture would supply — which is what gives the cutout and the max premultiply something to act on.