diff --git a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json index 15d29592..f6a65f17 100644 --- a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json +++ b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json @@ -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 } ] }, diff --git a/src/AcDream.App/Rendering/Shaders/spv/terrain_modern.frag.spv b/src/AcDream.App/Rendering/Shaders/spv/terrain_modern.frag.spv new file mode 100644 index 00000000..4eee3319 Binary files /dev/null and b/src/AcDream.App/Rendering/Shaders/spv/terrain_modern.frag.spv differ diff --git a/src/AcDream.App/Rendering/Shaders/spv/terrain_modern.vert.spv b/src/AcDream.App/Rendering/Shaders/spv/terrain_modern.vert.spv new file mode 100644 index 00000000..6c4f766e Binary files /dev/null and b/src/AcDream.App/Rendering/Shaders/spv/terrain_modern.vert.spv differ diff --git a/src/AcDream.App/Rendering/Shaders/terrain_modern.frag b/src/AcDream.App/Rendering/Shaders/terrain_modern.frag index 2e527308..2d09c9f7 100644 --- a/src/AcDream.App/Rendering/Shaders/terrain_modern.frag +++ b/src/AcDream.App/Rendering/Shaders/terrain_modern.frag @@ -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);