From 935f4dc3d9fa381d61b772eaadf32d4288c3239a Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 28 Jul 2026 09:21:22 +0200 Subject: [PATCH] =?UTF-8?q?feat(render):=20V6e=20=E2=80=94=20move=20the=20?= =?UTF-8?q?world=20mesh's=20texture=20lookup=20to=20where=20Vulkan=20can?= =?UTF-8?q?=20express=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/AcDream.App/Rendering/RenderBootstrap.cs | 9 +++++- src/AcDream.App/Rendering/Shaders/common.glsl | 28 ++++++++++++++++ .../Rendering/Shaders/mesh_modern.frag | 8 +++-- .../Rendering/Shaders/mesh_modern.vert | 22 ++++++++----- .../Shaders/spv/mesh_modern.frag.spv | Bin 0 -> 3784 bytes .../Shaders/spv/mesh_modern.vert.spv | Bin 0 -> 9760 bytes .../Shaders/spv/shaders.manifest.json | 30 ++++++++---------- tools/ShaderCompiler/VulkanGlslPreamble.cs | 11 +++++++ 8 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 src/AcDream.App/Rendering/Shaders/spv/mesh_modern.frag.spv create mode 100644 src/AcDream.App/Rendering/Shaders/spv/mesh_modern.vert.spv 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 0000000000000000000000000000000000000000..04fa405e0828b3c25515f2a505117a08bd14c8c9 GIT binary patch literal 3784 zcmZ9NX=t5Q5Qe|xX0as3q-)Y>xUmS*ND$E=k=j&j5+Ptp)w@5!NghNtWOccJU}*VJ_xcTE-DjS4x;vY)Ql7xQCuGf8 z4N|OGwR&7fE4UO~4o1Nk&@lnFfgRugm;}w_a&J8t%xbflNY{djnC7e{*9;=AI&tL! zXJ^LQ=m44LAnQOMaNk^{tC(Ld@-vF*xX9mJ;39vyz}bIZ6K;U>n9se|cNQlD^M~D8 zu$`0sh}YKdGM6)K%JrID??AVnzAKThb5H6k3;Na5dQCom_f+ba^1Fz9WAOTF!5~?0 zM!G6??X+E-Rm6B-aTjvtH?9vjXF1O#cYc8@C2p|5#r%c}oISXAxWIXjau*di`;c>P zQTMpSIUi>ey{J#X#rf4ezd5{{ZOOJrZQrSWm$q+KyK+~h{QX9*$>g)gSwtAqNj-C^ z&qdQ$2J^^eO>O;iu+38j&QX60Qopn5K=ZqhE5d!V;L5<&m293w<_u26_Wiq8u7j$+ zO}Szo^)j*Zkd5 zA902=&|P8opjRc^{Jmi9%+0;}wU+`{*!{V+aj~|2UjXd0Et`?QANQEgb*yYB@?v=3 z*-7Y^Ahqpv1=1zwjI@1wF8!Aw&F5_NYx@n$>Awo;GN1Q6g0vUUuYVQN*b(4FE#9VJj|LDV^MbX;? zw!Im@CFzm>p=A5kjo0=Yi}`P8E7;!q-RkTXA`>1I{G#OIdiE;9l2s1>p3gm&wXG5_$Hi{cXB_FkJ)ccoV;hHb6kzS0Am+XsUB4^1r_p0iaL**pZ*G{ZzQt#O`;FJ%56-18 ze5AWxkI_AkGS2e}dYorIdYtD|bbaI8 zPZRPpAQ$)fdE&}QYki4yd7t{e0CLt-cb@j6E$`duLVg9TA8*II`5MT_9KK1MyesCg zInCi)IL~cdBlqNY@f|SkJfJ_$`#pM`xA-0X0Ix6Nek{ZlznP!l^;z3GKLa`Ud#7^0 z0BfHLdVw(!|0}w^8NW5<_zhj&eU03`IerJ`a7Ox=EZBJqUL&meg E|BACfwEzGB literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a983399931b3cbe4d26e260952af190c56dcd37d GIT binary patch literal 9760 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 literal 0 HcmV?d00001 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.");