feat(render): Campaign V slice V6f-3 - terrain's atlas reads cross the dialect
The last thing keeping terrain_modern out of SPIR-V was how it named its two
atlases:
#define uTerrain sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexA))
#define uAlpha sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexB))
`sampler2DArray(handle)` is a GL_ARB_bindless_texture construction with no
Vulkan equivalent. Vulkan's table is an opaque descriptor array in set 2; there
is no handle, so there is nothing to construct a sampler from. The ten sample
sites now go through ACDREAM_SAMPLE_ARRAY, the dialect-neutral read V6e
introduced for mesh_modern, wrapped in two shader-local macros that keep the
call sites reading as "sample the terrain atlas" rather than "index the table".
They are SAMPLING macros, not sampler-returning ones, and that is not
cosmetic. Under Vulkan the expansion carries `nonuniformEXT` on the indexing
expression, and binding the result to a local sampler2DArray first is exactly
where an implementation may drop it. The old `#define uTerrain
sampler2DArray(...)` was textually that shape, so preserving it would have
reintroduced the hazard at every use site.
On GL nothing about the sampled result changes: the same slot resolves through
the same binding=9 table to the same handle to the same texel, and the macro
expands to the identical expression the shader wrote by hand.
With this, terrain_modern compiles for Vulkan and the manifest reads 8/9. The
remaining pair is `mesh`, which the campaign doc records as having no consumer
at all - so every production shader acdream actually draws with is now
Vulkan-expressible. That closes the obligation §5 recorded against V6e ("the one
production pair still not Vulkan-expressible after V6e is terrain_modern") and
it closes the shader half of V4d's parked content.
What this does NOT do is give the Vulkan backend a world to draw. That is
reported separately with the rest of slice V6f; the shaders were the part that
could be finished, gated and landed on GL today.
Gates. Release build clean. App tests 4,073 passed / 3 skipped over four
consecutive runs. A fifth run failed only
CurrentRenderSceneOracleTests.SurfaceOverrideFingerprint_DictionaryHotPathAllocatesNothing,
an allocation-counting test over a CPU dictionary path that touches nothing in
this diff; it passes in isolation and passed in every other full run. That is
the known #250 flake class on an otherwise unchanged tree. Offline pixel gate
against fac09407: 12 differing pixels of 563,200 compared (fraction 2.13e-05) -
below even the floor of the documented 15-23 pixel band. Cumulatively, across
all three V6f commits against 7faaaa34: 22 pixels (3.91e-05, maximumChannelDelta
52), which is the same number the first commit measured on its own. Three
changes to the shader terrain draws with, and the drift has not accumulated.
No divergence-register row: the sampled result is unchanged on GL and no
retail-facing behaviour moves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
fac0940711
commit
30e94da607
4 changed files with 39 additions and 24 deletions
|
|
@ -101,7 +101,7 @@
|
|||
},
|
||||
{
|
||||
"name": "terrain_modern",
|
||||
"vulkanReady": false,
|
||||
"vulkanReady": true,
|
||||
"stages": [
|
||||
{
|
||||
"stage": "vert",
|
||||
|
|
@ -110,9 +110,8 @@
|
|||
},
|
||||
{
|
||||
"stage": "frag",
|
||||
"sourceSha256": "dc4d38b5eb356629c83681fbc6a1d5d867792e99a8c4e659031f390333edb7b8",
|
||||
"compiled": false,
|
||||
"message": "terrain_modern.frag:150: error: \u0027sampler2DArray\u0027 : sampler-constructor requires the extension GL_ARB_bindless_texture enabled"
|
||||
"sourceSha256": "21f41dcca4d4973ad9ee148c5433e0dae015489414c164c51bde958963f03598",
|
||||
"compiled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
BIN
src/AcDream.App/Rendering/Shaders/spv/terrain_modern.frag.spv
Normal file
BIN
src/AcDream.App/Rendering/Shaders/spv/terrain_modern.frag.spv
Normal file
Binary file not shown.
BIN
src/AcDream.App/Rendering/Shaders/spv/terrain_modern.vert.spv
Normal file
BIN
src/AcDream.App/Rendering/Shaders/spv/terrain_modern.vert.spv
Normal file
Binary file not shown.
|
|
@ -5,14 +5,15 @@
|
|||
// Math identical to terrain.frag (Phase 3c per-cell maskBlend3 +
|
||||
// Phase G fog + lightning flash).
|
||||
//
|
||||
// Bindless texture handles are passed as uvec2 (low/high 32 bits) and
|
||||
// reconstructed into sampler2DArray at use sites via the GLSL
|
||||
// sampler-from-handle constructor. The alternative pattern —
|
||||
// `uniform sampler2DArray` set via glProgramUniformHandleARB — produces
|
||||
// GL_INVALID_OPERATION on at least one driver in practice (NVIDIA on
|
||||
// Windows). The uvec2 + constructor pattern is what N.5's mesh_modern
|
||||
// shader uses and is the documented "always works" form per the
|
||||
// ARB_bindless_texture spec.
|
||||
// Texture reads go through ACDREAM_SAMPLE_ARRAY (common.glsl) since slice
|
||||
// V6f-3, so this source compiles for both backends. Under GL that still expands
|
||||
// to the uvec2-handle + sampler2DArray-constructor pattern this shader has
|
||||
// always used — the documented "always works" form per the ARB_bindless_texture
|
||||
// spec, and the one that avoids the GL_INVALID_OPERATION the alternative
|
||||
// (`uniform sampler2DArray` set via glProgramUniformHandleARB) produces on at
|
||||
// least one driver in practice. Under Vulkan it indexes the set-2 descriptor
|
||||
// array instead. The extension requirement above is dropped for Vulkan by the
|
||||
// compiler's preamble, where it would be an error rather than a no-op.
|
||||
|
||||
in vec2 vBaseUV;
|
||||
in vec3 vWorldNormal;
|
||||
|
|
@ -35,8 +36,23 @@ out vec4 fragColor;
|
|||
// push-constant plumbing yet, so these stay plain uniforms for now.
|
||||
uniform uint uTextureIndexA;
|
||||
uniform uint uTextureIndexB;
|
||||
#define uTerrain sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexA))
|
||||
#define uAlpha sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexB))
|
||||
|
||||
// Campaign V slice V6f-3: the two atlases are sampled through the
|
||||
// dialect-neutral table read instead of a GL sampler-from-handle constructor.
|
||||
// `sampler2DArray(handle)` is a GL_ARB_bindless_texture form with no Vulkan
|
||||
// equivalent — Vulkan's table is an opaque descriptor array in set 2, and there
|
||||
// is no handle to construct a sampler from. ACDREAM_SAMPLE_ARRAY asks the
|
||||
// question both dialects can answer ("sample table slot N at these
|
||||
// coordinates") and expands to the right thing on each.
|
||||
//
|
||||
// A SAMPLING macro, not a sampler-returning one, for the reason common.glsl
|
||||
// records: under Vulkan the expansion carries `nonuniformEXT` on the indexing
|
||||
// expression, and binding the result to a local sampler2DArray first is exactly
|
||||
// where an implementation may drop that qualifier. The old `#define uTerrain
|
||||
// sampler2DArray(...)` was that shape textually, so keeping it would have
|
||||
// reintroduced the hazard at every use site.
|
||||
#define sampleTerrain(uvw) ACDREAM_SAMPLE_ARRAY(uTextureIndexA, uvw)
|
||||
#define sampleAlpha(uvw) ACDREAM_SAMPLE_ARRAY(uTextureIndexB, uvw)
|
||||
|
||||
// Campaign V slice V6f-2: the 36 per-layer tiling factors moved out of a loose
|
||||
// `uniform float uTexTiling[36]` and into the uniform buffer GpuBindingModel
|
||||
|
|
@ -97,23 +113,23 @@ vec4 combineOverlays(vec2 baseUV, vec4 pOverlay0, vec4 pOverlay1, vec4 pOverlay2
|
|||
vec4 t0 = vec4(0.0), t1 = vec4(0.0), t2 = vec4(0.0);
|
||||
|
||||
if (h0 > 0.0) {
|
||||
t0 = texture(uTerrain, vec3(baseUV * terrainTiling(pOverlay0.z), pOverlay0.z));
|
||||
t0 = sampleTerrain(vec3(baseUV * terrainTiling(pOverlay0.z), pOverlay0.z));
|
||||
if (pOverlay0.w >= 0.0) {
|
||||
vec4 a = texture(uAlpha, vec3(pOverlay0.xy, pOverlay0.w));
|
||||
vec4 a = sampleAlpha(vec3(pOverlay0.xy, pOverlay0.w));
|
||||
t0.a = a.a;
|
||||
}
|
||||
}
|
||||
if (h1 > 0.0) {
|
||||
t1 = texture(uTerrain, vec3(baseUV * terrainTiling(pOverlay1.z), pOverlay1.z));
|
||||
t1 = sampleTerrain(vec3(baseUV * terrainTiling(pOverlay1.z), pOverlay1.z));
|
||||
if (pOverlay1.w >= 0.0) {
|
||||
vec4 a = texture(uAlpha, vec3(pOverlay1.xy, pOverlay1.w));
|
||||
vec4 a = sampleAlpha(vec3(pOverlay1.xy, pOverlay1.w));
|
||||
t1.a = a.a;
|
||||
}
|
||||
}
|
||||
if (h2 > 0.0) {
|
||||
t2 = texture(uTerrain, vec3(baseUV * terrainTiling(pOverlay2.z), pOverlay2.z));
|
||||
t2 = sampleTerrain(vec3(baseUV * terrainTiling(pOverlay2.z), pOverlay2.z));
|
||||
if (pOverlay2.w >= 0.0) {
|
||||
vec4 a = texture(uAlpha, vec3(pOverlay2.xy, pOverlay2.w));
|
||||
vec4 a = sampleAlpha(vec3(pOverlay2.xy, pOverlay2.w));
|
||||
t2.a = a.a;
|
||||
}
|
||||
}
|
||||
|
|
@ -125,12 +141,12 @@ vec4 combineRoad(vec2 baseUV, vec4 pRoad0, vec4 pRoad1) {
|
|||
float h1 = pRoad1.z < 0.0 ? 0.0 : 1.0;
|
||||
vec4 result = vec4(0.0);
|
||||
if (h0 > 0.0) {
|
||||
result = texture(uTerrain, vec3(baseUV * terrainTiling(pRoad0.z), pRoad0.z));
|
||||
result = sampleTerrain(vec3(baseUV * terrainTiling(pRoad0.z), pRoad0.z));
|
||||
if (pRoad0.w >= 0.0) {
|
||||
vec4 a0 = texture(uAlpha, vec3(pRoad0.xy, pRoad0.w));
|
||||
vec4 a0 = sampleAlpha(vec3(pRoad0.xy, pRoad0.w));
|
||||
result.a = 1.0 - a0.a;
|
||||
if (h1 > 0.0 && pRoad1.w >= 0.0) {
|
||||
vec4 a1 = texture(uAlpha, vec3(pRoad1.xy, pRoad1.w));
|
||||
vec4 a1 = sampleAlpha(vec3(pRoad1.xy, pRoad1.w));
|
||||
result.a = 1.0 - (a0.a * a1.a);
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +168,7 @@ vec3 applyFog(vec3 lit, vec3 worldPos) {
|
|||
void main() {
|
||||
vec4 baseColor = vec4(0.0);
|
||||
if (vBaseTexIdx >= 0.0) {
|
||||
baseColor = texture(uTerrain, vec3(vBaseUV * terrainTiling(vBaseTexIdx), vBaseTexIdx));
|
||||
baseColor = sampleTerrain(vec3(vBaseUV * terrainTiling(vBaseTexIdx), vBaseTexIdx));
|
||||
}
|
||||
|
||||
vec4 overlays = vec4(0.0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue