diff --git a/src/AcDream.App/Rendering/RenderBootstrap.cs b/src/AcDream.App/Rendering/RenderBootstrap.cs index 10e76490..6636a4f5 100644 --- a/src/AcDream.App/Rendering/RenderBootstrap.cs +++ b/src/AcDream.App/Rendering/RenderBootstrap.cs @@ -174,9 +174,16 @@ public static class RenderBootstrap var lightingUbo = new SceneLightingUboBinding(gl); // --- Mesh shader (GameWindow ~1769-1771) --- + // Campaign V slice V6e: mesh_modern has needed common.glsl since V2 — + // ACDREAM_UBO_SET appears in a layout qualifier and ACDREAM_SAMPLE_ARRAY + // at the sample site, and without the preamble both are undeclared + // identifiers, so this program has failed to link on the Studio path + // since that slice. The world composition (WorldRenderComposition) has + // always passed true; this is the same pair loaded the same way. var meshShader = new Shader(gl, Path.Combine(shaderDir, "mesh_modern.vert"), - Path.Combine(shaderDir, "mesh_modern.frag")); + Path.Combine(shaderDir, "mesh_modern.frag"), + includeCommonPreamble: true); // --- TextureCache (GameWindow ~1774) --- var frameFlights = new GpuFrameFlightController(gl); diff --git a/src/AcDream.App/Rendering/Shaders/common.glsl b/src/AcDream.App/Rendering/Shaders/common.glsl index 21c0873a..ced0e599 100644 --- a/src/AcDream.App/Rendering/Shaders/common.glsl +++ b/src/AcDream.App/Rendering/Shaders/common.glsl @@ -38,8 +38,36 @@ layout(std430, binding = 9) readonly buffer TextureTableBuf { // explicit rather than folded into one sampler-returning macro) because every // existing call site already follows that exact pattern and a function cannot // return an opaque sampler type built from a runtime value in GLSL. +// +// Campaign V slice V6e: this macro is GL-shaped — under Vulkan it degenerates +// to the index itself, because there is no handle to look up. Every NEW call +// site should use ACDREAM_SAMPLE_ARRAY / ACDREAM_SAMPLE_2D below, which ask the +// dialect-neutral question ("sample table slot N") instead of the GL-only one +// ("what handle does slot N hold"). The remaining direct users are the shaders +// whose port slice has not run yet. #define ACDREAM_TEXTURE_HANDLE(idx) gTextureTable[idx] +// Campaign V slice V6e: samples a table slot that holds a 2-D ARRAY texture — +// the world/particle/terrain case, as opposed to the retained UI's plain 2-D +// entries that ACDREAM_SAMPLE_2D below covers. +// +// Expressed as a SAMPLING macro rather than a sampler-returning one on purpose. +// Under Vulkan the expansion carries a `nonuniformEXT` qualifier, and that +// qualifier belongs on the indexing expression at the point of use; binding the +// result to a local `sampler2DArray` variable first is where a driver is free to +// lose it. Both dialects therefore read the texture in one expression. +#define ACDREAM_SAMPLE_ARRAY(idx, uvw) texture(sampler2DArray(gTextureTable[idx]), uvw) + +// Campaign V slice V6e: the reserved "this draw has no texture" slot index. +// +// GL could ask the question directly — an unregistered slot holds the handle 0, +// so `gTextureTable[idx] == uvec2(0)` answered it. Vulkan cannot: set 2 is an +// opaque descriptor array with nothing to compare, and reading an unwritten +// element of a partially-bound array is undefined rather than zero. So the +// answer moves to the index itself, which both dialects can test identically, +// and the CPU writes this value instead of registering a null handle. +#define ACDREAM_TEXTURE_NONE 0xFFFFFFFFu + // Campaign V slice V6d: samples a table slot that holds a plain 2-D texture. // // The two backends disagree about what a 2-D table entry IS, and this macro is diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.frag b/src/AcDream.App/Rendering/Shaders/mesh_modern.frag index bd930397..447ce2e3 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh_modern.frag +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.frag @@ -5,7 +5,10 @@ in vec3 vNormal; in vec2 vTexCoord; in vec3 vWorldPos; in vec3 vLit; // A7: per-vertex Gouraud lighting (ambient + capped lights), from mesh_modern.vert -in flat uvec2 vTextureHandle; +// Campaign V slice V6e: the table slot, not the bindless handle — see +// mesh_modern.vert. The lookup moved here because a Vulkan varying cannot +// carry a descriptor. +in flat uint vTextureIndex; in flat uint vTextureLayer; in flat float vOpacityMultiplier; // #188 in flat vec2 vSelectionLighting; // x=luminosity, y=diffuse @@ -55,8 +58,7 @@ vec3 applyFog(vec3 lit, vec3 worldPos) { out vec4 FragColor; void main() { - sampler2DArray tex = sampler2DArray(vTextureHandle); - vec4 color = texture(tex, vec3(vTexCoord, float(vTextureLayer))); + vec4 color = ACDREAM_SAMPLE_ARRAY(vTextureIndex, vec3(vTexCoord, float(vTextureLayer))); // Two-pass alpha-test (N.5 Decision 2). // A.5 T20: opaque pass writes alpha as-sampled so GL_SAMPLE_ALPHA_TO_COVERAGE diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert index 58d81021..b2c58da2 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert @@ -12,9 +12,8 @@ struct InstanceData { // Campaign V slice V2 (2026-07-27): textureHandle (uvec2, a 64-bit // GL_ARB_bindless_texture handle) became textureIndex (uint) plus an explicit // pad word. textureIndex is a slot into the binding=9 handle table -// (ACDREAM_TEXTURE_HANDLE, common.glsl) that main() below looks up once per -// vertex to reconstruct the exact same uvec2 handle main() used to receive -// directly — one indirection, identical value. The pad word keeps +// (common.glsl) which main() below forwards to the fragment stage, where slice +// V6e moved the lookup so the same source compiles for Vulkan. The pad word keeps // textureLayer/flags at their original std430 offsets (8/12), so the struct // is still 16 bytes and every existing CPU writer's layout is unchanged // (GpuBindingModel.GpuBatchDataStrideBytes). @@ -289,7 +288,14 @@ out vec3 vNormal; out vec2 vTexCoord; out vec3 vWorldPos; out vec3 vLit; // A7: per-vertex Gouraud lighting (ambient + capped lights) -out flat uvec2 vTextureHandle; +// Campaign V slice V6e: was `flat uvec2 vTextureHandle` — a raw 64-bit +// GL_ARB_bindless_texture handle handed across the stage boundary. A varying +// cannot carry a Vulkan descriptor, so what travels is the table SLOT and the +// fragment stage does the lookup (see mesh_modern.frag). Under GL the value +// sampled is bit-for-bit the one the vertex stage used to forward; the SSBO +// read simply happens one stage later, and `flat` keeps it one scalar load per +// primitive rather than per fragment. +out flat uint vTextureIndex; out flat uint vTextureLayer; out flat float vOpacityMultiplier; // #188 out flat vec2 vSelectionLighting; @@ -323,9 +329,9 @@ void main() { vTexCoord = aTexCoord; BatchData b = Batches[uDrawIDOffset + gl_DrawIDARB]; - // Campaign V slice V2: reconstruct the SAME uvec2 handle the shader used - // to receive directly from BatchData, now via one binding=9 table lookup. - // vTextureHandle's type and every downstream frag-shader use are unchanged. - vTextureHandle = ACDREAM_TEXTURE_HANDLE(b.textureIndex); + // Campaign V slice V6e: forward the table SLOT untouched. V2 looked the + // handle up here and passed the handle; the lookup now lives at the sample + // site in mesh_modern.frag, which is the only form Vulkan can express. + vTextureIndex = b.textureIndex; vTextureLayer = b.textureLayer; } diff --git a/src/AcDream.App/Rendering/Shaders/spv/mesh_modern.frag.spv b/src/AcDream.App/Rendering/Shaders/spv/mesh_modern.frag.spv new file mode 100644 index 00000000..04fa405e Binary files /dev/null and b/src/AcDream.App/Rendering/Shaders/spv/mesh_modern.frag.spv differ diff --git a/src/AcDream.App/Rendering/Shaders/spv/mesh_modern.vert.spv b/src/AcDream.App/Rendering/Shaders/spv/mesh_modern.vert.spv new file mode 100644 index 00000000..a9833999 Binary files /dev/null and b/src/AcDream.App/Rendering/Shaders/spv/mesh_modern.vert.spv differ diff --git a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json index a57f56cb..3c6fb2c1 100644 --- a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json +++ b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json @@ -25,31 +25,29 @@ "stage": "vert", "sourceSha256": "c35f767ab07fa9df805f9e77f4851f517c153dd2ef2efa6d49d0c24b688e4f56", "compiled": false, - "message": "mesh.vert:71: error: \u0027uModel\u0027 : undeclared identifier" + "message": "mesh.vert:73: error: \u0027uModel\u0027 : undeclared identifier" }, { "stage": "frag", "sourceSha256": "4d6478543a9a903a3453581fa847e096aaecf01f38ebb2921572663bad8e24ea", "compiled": false, - "message": "mesh.frag:158: error: \u0027uDiffuse\u0027 : undeclared identifier" + "message": "mesh.frag:160: error: \u0027uDiffuse\u0027 : undeclared identifier" } ] }, { "name": "mesh_modern", - "vulkanReady": false, + "vulkanReady": true, "stages": [ { "stage": "vert", - "sourceSha256": "1ec2f4af83e73102d87997244a35b69ad5e9ece4b1ad78e2b5ece4d58fab5530", - "compiled": false, - "message": "mesh_modern.vert:380: error: \u0027assign\u0027 : cannot convert from \u0027 global highp uint\u0027 to \u0027layout( location=4) flat out highp 2-component vector of uint\u0027" + "sourceSha256": "770e6300e023bd2600e8fed52768c624d8fff2ff8d6f997f076980502d3f675b", + "compiled": true }, { "stage": "frag", - "sourceSha256": "3aea96cba6c2afc49caae7545f9e42603b6b17afa50b3254beca60f95af5d2f3", - "compiled": false, - "message": "mesh_modern.frag:106: error: \u0027sampler2DArray\u0027 : sampler-constructor requires the extension GL_ARB_bindless_texture enabled" + "sourceSha256": "12f2ceec9a8420bd273650e95251ca56c3bf3c3691fce1a539e4c5aea7abaabf", + "compiled": true } ] }, @@ -61,13 +59,13 @@ "stage": "vert", "sourceSha256": "6a6ebeaacba95e5e4e8a308ed7c4cd805b80f305650c1e9e03e2bdfc6c18f5e7", "compiled": false, - "message": "particle.vert:83: error: \u0027assign\u0027 : cannot convert from \u0027layout( location=6) in highp uint\u0027 to \u0027layout( location=2) flat out highp 2-component vector of uint\u0027" + "message": "particle.vert:85: error: \u0027assign\u0027 : cannot convert from \u0027layout( location=6) in highp uint\u0027 to \u0027layout( location=2) flat out highp 2-component vector of uint\u0027" }, { "stage": "frag", "sourceSha256": "3924ecbabf051349bc6a13e6cff370725a0832f8baf515decdb7e0394304006d", "compiled": false, - "message": "particle.frag:60: error: \u0027sampler2DArray\u0027 : sampler-constructor requires the extension GL_ARB_bindless_texture enabled" + "message": "particle.frag:62: error: \u0027sampler2DArray\u0027 : sampler-constructor requires the extension GL_ARB_bindless_texture enabled" } ] }, @@ -84,7 +82,7 @@ "stage": "frag", "sourceSha256": "0da368243e967388990f4f4b90e2304044af6187de45f70499a3e4ece8dfd5a8", "compiled": false, - "message": "particle_mesh.frag:62: error: \u0027uTextureIndex\u0027 : undeclared identifier" + "message": "particle_mesh.frag:64: error: \u0027uTextureIndex\u0027 : undeclared identifier" } ] }, @@ -96,13 +94,13 @@ "stage": "vert", "sourceSha256": "d338e9b03686b7baf79d5121c5c8d0f24037979cc58f203957d7bd97b02b1cc2", "compiled": false, - "message": "sky.vert:151: error: \u0027uUvScroll\u0027 : undeclared identifier" + "message": "sky.vert:153: error: \u0027uUvScroll\u0027 : undeclared identifier" }, { "stage": "frag", "sourceSha256": "8084af39f65ae399c73e3ca864376ef20ba8a1c495ee4774be6a82af3872c51c", "compiled": false, - "message": "sky.frag:76: error: \u0027uDiffuse\u0027 : undeclared identifier" + "message": "sky.frag:78: error: \u0027uDiffuse\u0027 : undeclared identifier" } ] }, @@ -114,13 +112,13 @@ "stage": "vert", "sourceSha256": "4de580ce11b8d755d3558dc49bf7ebccec54d307595d91c38b5c5d552d645c7e", "compiled": false, - "message": "terrain_modern.vert:219: error: \u0027uProjection\u0027 : undeclared identifier" + "message": "terrain_modern.vert:221: error: \u0027uProjection\u0027 : undeclared identifier" }, { "stage": "frag", "sourceSha256": "6003b81df6da6cbea7f00310bd956348bc7b2525345dd490b0b6a3b6428340d9", "compiled": false, - "message": "terrain_modern.frag:108: error: \u0027uTexTiling\u0027 : undeclared identifier" + "message": "terrain_modern.frag:110: error: \u0027uTexTiling\u0027 : undeclared identifier" } ] }, diff --git a/tools/ShaderCompiler/VulkanGlslPreamble.cs b/tools/ShaderCompiler/VulkanGlslPreamble.cs index 08f9fc9b..683e2161 100644 --- a/tools/ShaderCompiler/VulkanGlslPreamble.cs +++ b/tools/ShaderCompiler/VulkanGlslPreamble.cs @@ -101,6 +101,17 @@ internal static class VulkanGlslPreamble // textures stay plain GL_TEXTURE_2D objects. text.AppendLine( "#define ACDREAM_SAMPLE_2D(idx, uv) texture(ACDREAM_TEXTURE(idx), vec3((uv), 0.0))"); + // Slice V6e: the 2-D ARRAY read (world meshes, particles, terrain). GL + // reconstructs a sampler2DArray from the slot's bindless handle; Vulkan + // indexes set 2 directly. Sampling in one expression is what keeps the + // nonuniformEXT qualifier on the indexing operation itself. + text.AppendLine( + "#define ACDREAM_SAMPLE_ARRAY(idx, uvw) texture(ACDREAM_TEXTURE(idx), uvw)"); + // Slice V6e: the reserved "no texture" slot. Under GL an empty slot can + // be recognised by the null handle it holds; a Vulkan descriptor array + // has nothing to compare, so the sentinel lives in the index and both + // dialects test it the same way. See common.glsl for the GL half. + text.AppendLine("#define ACDREAM_TEXTURE_NONE 0xFFFFFFFFu"); text.AppendLine(); text.AppendLine("// §3.4 push constants: one shared 96-byte block, so switching pipelines"); text.AppendLine("// mid-pass invalidates neither descriptors nor constants.");