acdream/src/AcDream.App/Rendering/Shaders
Erik 935f4dc3d9 feat(render): V6e — move the world mesh's texture lookup to where Vulkan can express it
Campaign V slice V6e, first of three. mesh_modern is the shader every world
static, every piece of scenery and every EnvCell surface draws through, and it
was one of the four production pairs the SPIR-V toolchain still refused.

The blocker was a varying. Since V2 the vertex stage looked a batch's table slot
up in the binding=9 handle table and forwarded the resulting 64-bit
GL_ARB_bindless_texture handle to the fragment stage as a `flat uvec2`. That
works on GL because a bindless handle is just a number a shader may carry
anywhere. It cannot work on Vulkan at all: the equivalent object is a descriptor
in set 2, and a descriptor is not a value a stage can hand to another stage. So
what travels between the stages is now the SLOT — a `flat uint` — and the
fragment stage does the lookup at the point of sampling.

That relocation needs one shared idea, because the two backends disagree about
what the lookup IS. `ACDREAM_SAMPLE_ARRAY(slot, uvw)` asks the dialect-neutral
question — "sample table slot N" — and expands to
`texture(sampler2DArray(gTextureTable[slot]), uvw)` under GL and to
`texture(uTextures[nonuniformEXT(slot)], uvw)` under Vulkan. It is deliberately
a SAMPLING macro rather than a sampler-returning one: `nonuniformEXT` belongs on
the indexing expression itself, and binding the result to a local
`sampler2DArray` first is exactly where an implementation is free to drop it.
That is the same shape V6d already used for the retained UI's 2-D reads, and it
now covers the array reads the world path needs.

`ACDREAM_TEXTURE_NONE` lands alongside it, unused here and used by the next
commit. GL can ask "does this slot hold a texture" of the payload, because an
unregistered slot holds the null handle; Vulkan cannot, because set 2 is opaque
and reading an unwritten element of a partially-bound array is undefined rather
than zero. The sentinel moves that answer into the index, where both dialects
test it identically.

On GL nothing about the sampled result changes — the same slot resolves to the
same handle to the same texel. The SSBO read simply happens one stage later,
and `flat` keeps it one scalar load per primitive rather than per fragment.

Also: RenderBootstrap has been loading mesh_modern without common.glsl since V2,
which cannot have linked — `ACDREAM_UBO_SET` sits inside a layout qualifier
there. The UI Studio path is the only caller. One argument, same pair, same way
WorldRenderComposition has always loaded it.

Gates: Release build clean; App tests 4,057 passed / 3 skipped (baseline);
offline pixel gate against 95f8c25f differing fraction 3.37e-05 (~19 px of
563,200), inside the documented 15–23 px same-commit noise band and ~30x under
the 0.001 threshold. mesh_modern is the shader that gate covers most heavily,
so this is the strongest automated evidence any V6e commit gets.

Manifest: 4/9 pairs compile (debug_line, mesh_modern, ui_text, vk_probe).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:21:22 +02:00
..
spv feat(render): V6e — move the world mesh's texture lookup to where Vulkan can express it 2026-07-28 09:21:22 +02:00
common.glsl feat(render): V6e — move the world mesh's texture lookup to where Vulkan can express it 2026-07-28 09:21:22 +02:00
debug_line.frag feat(ui): debug overlay + refined input controls 2026-04-17 18:45:38 +02:00
debug_line.vert feat(render): put the retained UI and debug lines on both backends 2026-07-28 08:51:32 +02:00
mesh.frag fix(render): A7 point-light shape — per-vertex Gouraud + faithful calc_point_light (wrap + norm) 2026-06-15 22:27:27 +02:00
mesh.vert feat(render): Phase G.1/G.2 — SceneLighting UBO + sky renderer + shader integration 2026-04-19 10:39:48 +02:00
mesh_modern.frag feat(render): V6e — move the world mesh's texture lookup to where Vulkan can express it 2026-07-28 09:21:22 +02:00
mesh_modern.vert feat(render): V6e — move the world mesh's texture lookup to where Vulkan can express it 2026-07-28 09:21:22 +02:00
particle.frag perf(rendering): preserve alpha order with bindless particles 2026-07-18 08:32:21 +02:00
particle.vert feat(render): Campaign V slice V2c - particle texture-index migration 2026-07-27 16:02:59 +02:00
particle_mesh.frag feat(render): Campaign V slice V2c - particle texture-index migration 2026-07-27 16:02:59 +02:00
particle_mesh.vert fix(ui): restore radar, retail wield switching, and protection meshes 2026-07-14 20:52:45 +02:00
sky.frag feat(vfx): Phase C.1 — PES particle renderer + post-review fixes 2026-04-28 22:47:11 +02:00
sky.vert feat(render): Phase W Stage 4 — sky/weather portal-clip seal (LScape through the doorway) 2026-06-02 16:15:08 +02:00
terrain_modern.frag Revert "feat(render): Campaign V slice V4d-2 - move terrain onto the RHI" 2026-07-27 22:38:02 +02:00
terrain_modern.vert Revert "feat(render): Campaign V slice V4d-1 - converge terrain's view and projection" 2026-07-27 22:38:02 +02:00
ui_text.frag feat(render): put the retained UI and debug lines on both backends 2026-07-28 08:51:32 +02:00
ui_text.vert feat(render): put the retained UI and debug lines on both backends 2026-07-28 08:51:32 +02:00
vk_probe.frag feat(render): Campaign V slice V6c - SPIR-V, pipelines, passes, and a Vulkan frame that draws 2026-07-28 08:17:24 +02:00
vk_probe.vert feat(render): Campaign V slice V6c - SPIR-V, pipelines, passes, and a Vulkan frame that draws 2026-07-28 08:17:24 +02:00