diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index f9fa4894..a78232e1 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -5865,6 +5865,16 @@ namespace MobileGL::MG_Backend::DirectGLES { spvcSession.SetAtomicCounterBlockBindings(atomicCounterEsslBindingTop, outAtomicCounterGlBindings); + // `layout(index = 0)` is the GL default spelled out loud, and GLSL ES has no such + // qualifier in core - a stage that prints it is refused with "index layout + // qualifier requires EXT_blend_func_extended" and the whole program then draws + // nothing. Drop the decoration when it carries the default; a REAL dual-source + // index (1) is left alone, because that one genuinely needs the extension and the + // driver has to see it. Fragment stage only: no other stage can carry it. + if (glShaderType == GL_FRAGMENT_SHADER) { + spvcSession.DropDefaultFragmentOutputColorIndex(); + } + const char* result = nullptr; spvcSession.Compile(&result); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp index a0f721d5..8489ceff 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp @@ -179,6 +179,13 @@ void main() in flat uint v_index; out vec4 o_color; void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } +)"; + + // The colour index spelled out at its default value. Says nothing that + // `layout(location = 0)` alone does not, and must therefore cost nothing. + constexpr const char* kExplicitColorIndexFS = R"(#version 420 core +layout(location = 0, index = 0) out vec4 o_color; +void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } )"; class Glsl420DeclarationScenario : public ScenarioTest { @@ -473,4 +480,24 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } EXPECT_EQ(centre.g, 255) << "the atomic-counter shader linked but painted nothing"; } + // `layout(location = 0, index = 0)` is the GL default written out loud, and an application + // is entitled to write it - KHR-GL43.shader_atomic_counters.basic-program-query does. It has + // to reach the driver as an ORDINARY single-source output: GLSL ES has no `index` qualifier + // in core, so a transpiler that prints the decoration back gets "index layout qualifier + // requires EXT_blend_func_extended", the stage never compiles, the program runs with a stage + // missing and the draw paints nothing at all. Black, not red - which is why the conformance + // case looked like the atomic counters had stopped counting. + TEST_F(Glsl420DeclarationScenario, AnExplicitDefaultColorIndexStillDraws) { + if (!Ready()) return; + + const GLuint program = Build(kQuadVS, kExplicitColorIndexFS); + if (program == 0) return; + + const Rgba8 centre = DrawAndRead(program); + EXPECT_EQ(FirstGLError(), 0u); + EXPECT_EQ(centre.g, 255) << "a fragment output declared layout(location = 0, index = 0) painted " + "nothing; its stage was almost certainly refused by the driver"; + EXPECT_EQ(centre.r, 0u); + } + } // namespace MGITest diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 29b32225..453c0c16 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -442,6 +442,23 @@ namespace MobileGL { SPVC_CHK_RETURN } + spvc_result SpvcSession::DropDefaultFragmentOutputColorIndex() { + if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT; + + SPVC_CHK_INIT + const spvc_reflected_resource* list = nullptr; + size_t count = 0; + SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type( + resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT, &list, &count)); + for (size_t i = 0; i < count; ++i) { + const spvc_reflected_resource& resource = list[i]; + if (!spvc_compiler_has_decoration(compiler, resource.id, SpvDecorationIndex)) continue; + if (spvc_compiler_get_decoration(compiler, resource.id, SpvDecorationIndex) != 0u) continue; + spvc_compiler_unset_decoration(compiler, resource.id, SpvDecorationIndex); + } + SPVC_CHK_RETURN + } + spvc_result SpvcSession::Compile(const char** result) { if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT; SPVC_CHK_INIT diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h index f602514a..357d63c6 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h @@ -120,6 +120,25 @@ namespace MobileGL { // `outGlBindings` is appended to, so one vector can collect a whole program's // stages; it may repeat a binding declared by several of them. spvc_result SetAtomicCounterBlockBindings(Int topBinding, Vector& outGlBindings); + // Drops the Index decoration from every fragment output that carries the DEFAULT + // colour index 0, so the emitted ESSL does not print `index = 0`. + // + // Index 0 is what every single-source fragment output already is, in GL and in + // ESSL alike, and SPIR-V carries the decoration only because the application + // spelled the qualifier out - `layout(location = 0, index = 0) out vec4 c;` is + // legal desktop GLSL and says nothing. Printing it back into ESSL is NOT + // harmless: GLSL ES has no `index` layout qualifier in core, so the driver + // answers "index layout qualifier requires EXT_blend_func_extended" and refuses + // the stage. The program then links nothing and every draw with it renders + // NOTHING - verified on Mesa 26.1.4 llvmpipe with no MobileGL in the process, + // and it is why KHR-GL43.shader_atomic_counters.basic-program-query read back a + // black render target. + // + // A NON-zero index is left exactly as it is: that one really does select the + // second dual-source input and cannot be expressed without the extension, so it + // must keep reaching the driver (the frontend's own glBindFragDataLocationIndexed + // path already emits only non-zero indices for the same reason). + spvc_result DropDefaultFragmentOutputColorIndex(); spvc_result Compile(const char** result); const SpvcMetadata& GetMetadata() const; const char* GetLastErrorString() const;