From 0f99d93300681a49cf4978041d70d6c60f1881cc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 11 Jul 2026 01:32:07 -0400 Subject: [PATCH] [Feat] (MobileGL): full dual-source blending across state, transpiler, and both backends Wire GL_SRC1_* dual-source blend factors (glBlendFunc) end to end with the glBindFragDataLocationIndexed color index, so a fragment shader can drive both dual-source blend inputs. State + converters: - RenderState BlendFactor gains Src1Color/OneMinusSrc1Color/Src1Alpha/ OneMinusSrc1Alpha; GLToMG/MGToGL/MGToVk/MGToStr converters map them to GL_SRC1_*, VK_BLEND_FACTOR_SRC1_*, and readable names. Transpiler layout(index = N): - ProgramAttrib carries explicitFragmentOutIndices; ProgramObject threads m_explicitFragDataIndex into it at both link sites. - TMglGlslIoResolver applies the color index as TQualifier.layoutIndex on the fragment output, emitting layout(index = 1) via the glslang Index decoration -> SPIRV-Cross path. Only the non-zero (dual-source) index is emitted: index 0 is the GL default and an explicit "index = 0" would demand GL_EXT_blend_func_extended on GLES for ordinary single-source outputs. Feature detection, POST, and hard-fail at use time (no silent fallback): - Vulkan: dualSrcBlend is detected at device creation and cached; a draw whose enabled blend state uses a SRC1 factor without the feature throws at pipeline build with the reason and a pointer to the POST row. - GLES: GL_EXT_blend_func_extended detected at load into GLESCapabilities.SupportsDualSourceBlend; a draw enabling blend with a SRC1 factor without it throws in the blend-state sync with the same guidance. - DriverPost adds a dual-source-blend row for both backends (Pass/Warn). Tests: - ProgramTest.CompileAndLinkWithExplicitFragmentOut now asserts the transpiled fragment shader carries layout(location = 0, index = 1) after a re-link with glBindFragDataLocationIndexed(index 1), and still omits any index qualifier for the plain index-0 output. --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 33 +++++++++++++++++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 26 +++++++++++++++ .../GLState/ProgramState/ProgramObject.cpp | 2 ++ .../GLState/RenderState/RenderState.h | 6 ++++ MobileGL/MG_Test/Program/ProgramTest.cpp | 19 +++++++++++ .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 5 +++ .../MG_Util/BackendLoaders/OpenGL/Loader.h | 4 +++ .../GLToMG/RenderStateEnumConverter.cpp | 8 +++++ .../MGToGL/RenderStateEnumConverter.cpp | 8 +++++ .../MGToStr/RenderStateEnumConverter.cpp | 8 +++++ .../MGToVk/RenderStateEnumConverter.cpp | 8 +++++ MobileGL/MG_Util/SelfTest/DriverPost.cpp | 7 ++++ .../ShaderTranspiler/ShaderCompiler.cpp | 1 + MobileGL/MG_Util/ShaderTranspiler/Types.h | 3 ++ .../glslang/TMglGlslIoResolver.cpp | 9 +++++ .../glslang/TMglGlslIoResolver.h | 11 ++++--- 16 files changed, 154 insertions(+), 4 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index d914ecae..5ce0daa8 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -49,6 +49,18 @@ namespace MobileGL::MG_Backend::DirectGLES { static SharedPtr g_rawDepthFetchSamplerState; static SharedPtr g_rawDepthFetchSamplerBackend; + static Bool IsDualSourceBlendFactor(BlendFactor v) { + switch (v) { + case BlendFactor::Src1Color: + case BlendFactor::OneMinusSrc1Color: + case BlendFactor::Src1Alpha: + case BlendFactor::OneMinusSrc1Alpha: + return true; + default: + return false; + } + } + enum class DrawSyncBit : Uint32 { None = 0, IndexBuffer = 1 << 0, @@ -583,6 +595,27 @@ namespace MobileGL::MG_Backend::DirectGLES { const auto& targetStates = parameters.BlendStates; auto& syncedStates = g_syncedRenderStateParameters.BlendStates; + // Dual-source blending (GL_SRC1_* factors from glBlendFunc paired with + // glBindFragDataLocationIndexed) needs GL_EXT_blend_func_extended; GLES core has none. + // Detected at load and surfaced in the POST. There is no fallback, so if a draw actually + // enables blending with a SRC1 factor on a driver that lacks it, hard-fail here at use + // time rather than let the driver reject glBlendFuncSeparate and silently mis-blend. + if (!g_GLESCapabilities.SupportsDualSourceBlend) { + for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) { + const auto& s = targetStates[i]; + if (s.Enabled && + (IsDualSourceBlendFactor(s.SrcFactorRGB) || IsDualSourceBlendFactor(s.DstFactorRGB) || + IsDualSourceBlendFactor(s.SrcFactorAlpha) || IsDualSourceBlendFactor(s.DstFactorAlpha))) { + THROW_EXCEPTION( + "Dual-source blending (GL_SRC1_* blend factor) was used on draw buffer " + + std::to_string(i) + + ", but the GLES driver does not expose GL_EXT_blend_func_extended (see the " + "dual-source blend row in the driver POST). No fallback exists; the draw " + "cannot proceed."); + } + } + } + Bool allEnabled = true; Bool allDisabled = true; Bool anyCapDirty = false; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6201b64e..1baae4d0 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -135,6 +135,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { return attachment; } + static Bool IsDualSourceBlendFactor(BlendFactor v) { + switch (v) { + case BlendFactor::Src1Color: + case BlendFactor::OneMinusSrc1Color: + case BlendFactor::Src1Alpha: + case BlendFactor::OneMinusSrc1Alpha: + return true; + default: + return false; + } + } + static Bool ShouldUseTransientVertexIndexBuffer(const MG_State::GLState::BufferObject& bufferObject) { switch (bufferObject.GetUsage()) { case BufferUsage::StreamDraw: @@ -3245,6 +3257,20 @@ void main() { program.GetExternalIndex()); #endif } + // Dual-source blending (GL_SRC1_* factors from glBlendFunc paired with + // glBindFragDataLocationIndexed) requires the dualSrcBlend device feature. It is detected at + // device creation and surfaced in the POST; if a shader actually issues a draw with a SRC1 + // factor on a device that lacks it, there is no fallback, so hard-fail here at use time + // rather than silently mistranslating the blend equation. + if (effectiveBlendEnabled && !m_dualSrcBlendFeatureEnabled && + (IsDualSourceBlendFactor(srcRGB) || IsDualSourceBlendFactor(dstRGB) || + IsDualSourceBlendFactor(srcAlpha) || IsDualSourceBlendFactor(dstAlpha))) { + THROW_EXCEPTION( + "Dual-source blending (GL_SRC1_* blend factor) was used on color attachment " + + std::to_string(i) + + ", but the Vulkan device does not support the dualSrcBlend feature (see the " + "dualSrcBlend row in the driver POST). No fallback exists; the draw cannot proceed."); + } payload.colorBlendAttachments[i] = MakeColorBlendAttachmentState( effectiveBlendEnabled, MG_Util::ConvertBlendFactorToVkEnum(srcRGB), diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index c6740699..3ee65d94 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -282,6 +282,7 @@ namespace MobileGL::MG_State::GLState { MG_Util::ShaderTranspiler::ProgramAttrib attrib{.shaders = Move(shaders), .explicitVertexInLocations = m_explicitAttribLocations, .explicitFragmentOutLocations = m_explicitFragDataLocation, + .explicitFragmentOutIndices = m_explicitFragDataIndex, .explicitOpaqueUniformBindings = &m_explicitOpaqueUniformBindings}; @@ -579,6 +580,7 @@ namespace MobileGL::MG_State::GLState { ProgramAttrib attrib{.shaders = Move(shaders), .explicitVertexInLocations = m_explicitAttribLocations, .explicitFragmentOutLocations = m_explicitFragDataLocation, + .explicitFragmentOutIndices = m_explicitFragDataIndex, .explicitOpaqueUniformBindings = &m_explicitOpaqueUniformBindings}; MGLOG_D("ProgramObject %u: GenerateBinary - linking program for binary", m_externalIndex); auto programResult = ShaderCompiler::LinkProgram(attrib); diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.h b/MobileGL/MG_State/GLState/RenderState/RenderState.h index c0a600b0..44b43a2d 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.h +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.h @@ -27,6 +27,12 @@ namespace MobileGL { OneMinusConstantColor, ConstantAlpha, OneMinusConstantAlpha, + // Dual-source blend factors (GL_SRC1_*, glBindFragDataLocationIndexed); require the + // dualSrcBlend device feature. + Src1Color, + OneMinusSrc1Color, + Src1Alpha, + OneMinusSrc1Alpha, BlendFactorCount, Unknown = -1 }; diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index 3447e83f..acad1095 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -1399,6 +1399,25 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) { EXPECT_EQ(GetFragDataIndex(program, "fragColor"), 1); EXPECT_EQ(GetFragDataIndex(program, "notAnActiveOutput"), -1); + // The color index must reach the transpiled shader as layout(location = 0, index = 1) so the + // driver binds fragColor as the second dual-source input; the SPIR-V Index decoration set from + // the glslang layoutIndex round-trips through SPIRV-Cross. + auto& spirvsIndexed = programObject->GetGeneratedSpirv(); + auto& fragSpirvIndexed = spirvsIndexed[programObject->GetShaderIndexByStage(ShaderStage::Fragment)]; + MG_Util::ShaderTranspiler::SpvcSession spvcSessionIndexed(fragSpirvIndexed, + MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); + spvc_compiler_options optionsIndexed; + spvcSessionIndexed.CreateOptions(&optionsIndexed); + spvc_compiler_options_set_uint(optionsIndexed, SPVC_COMPILER_OPTION_GLSL_VERSION, 460); + spvc_compiler_options_set_bool(optionsIndexed, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_FALSE); + spvcSessionIndexed.SetOptions(optionsIndexed); + const char* resultIndexed = nullptr; + spvcSessionIndexed.Compile(&resultIndexed); + printf("%s\n\n", resultIndexed); + const char* indexNeedle = "index = 1"; + ASSERT_TRUE(strstr(resultIndexed, indexNeedle) != nullptr) + << "Expected dual-source color index in generated shader.\n(Searching for \"" << indexNeedle << "\")"; + // glBindFragDataLocation is equivalent to index 0 and resets it. BindFragDataLocation(program, 0, "fragColor"); LinkProgram(program); diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index e7c2197f..ef9e2a06 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -742,6 +742,9 @@ namespace MobileGL::MG_Util::BackendLoader { if (std::strcmp(extension, "GL_EXT_disjoint_timer_query") == 0) { caps.SupportsDisjointTimerQuery = true; } + if (std::strcmp(extension, "GL_EXT_blend_func_extended") == 0) { + caps.SupportsDualSourceBlend = true; + } } } @@ -754,6 +757,8 @@ namespace MobileGL::MG_Util::BackendLoader { glesFuncs.glColorMaskiOES != nullptr; MGLOG_I(" glPolygonMode (NV/ANGLE): %s", caps.SupportsPolygonMode ? "yes" : "no"); MGLOG_I(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no"); + MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s", + caps.SupportsDualSourceBlend ? "yes" : "no"); MGLOG_I("OpenGL ES capabilities:"); glesFuncs.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &caps.UniformBufferOffsetAlignment); diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index cc173951..ff556d8e 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -1040,6 +1040,10 @@ namespace MobileGL { // An indexed glColorMaski entry point loaded (GLES 3.2 core, or GL_OES/EXT_draw_buffers_indexed). // Without it, only the non-indexed glColorMask (draw buffer 0 broadcast) is available. Bool SupportsIndexedColorMask = false; + // GL_EXT_blend_func_extended is present: the driver accepts GL_SRC1_* dual-source blend + // factors and layout(index = 1) fragment outputs. GLES core has no dual-source blending, + // so without this a draw using a SRC1 factor cannot proceed. + Bool SupportsDualSourceBlend = false; // GL_RENDERER contains "ANGLE". Bool IsAngleRenderer = false; // GL_RENDERER contains both "ANGLE" and "llvmpipe". diff --git a/MobileGL/MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp index 45bf157b..51218e6f 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp @@ -40,6 +40,14 @@ namespace MobileGL { return BlendFactor::ConstantAlpha; case GL_ONE_MINUS_CONSTANT_ALPHA: return BlendFactor::OneMinusConstantAlpha; + case GL_SRC1_COLOR: + return BlendFactor::Src1Color; + case GL_ONE_MINUS_SRC1_COLOR: + return BlendFactor::OneMinusSrc1Color; + case GL_SRC1_ALPHA: + return BlendFactor::Src1Alpha; + case GL_ONE_MINUS_SRC1_ALPHA: + return BlendFactor::OneMinusSrc1Alpha; default: return BlendFactor::Unknown; } diff --git a/MobileGL/MG_Util/Converters/MGToGL/RenderStateEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToGL/RenderStateEnumConverter.cpp index 49c9f3ae..5004bc30 100644 --- a/MobileGL/MG_Util/Converters/MGToGL/RenderStateEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToGL/RenderStateEnumConverter.cpp @@ -41,6 +41,14 @@ namespace MobileGL { return GL_CONSTANT_ALPHA; case BlendFactor::OneMinusConstantAlpha: return GL_ONE_MINUS_CONSTANT_ALPHA; + case BlendFactor::Src1Color: + return GL_SRC1_COLOR; + case BlendFactor::OneMinusSrc1Color: + return GL_ONE_MINUS_SRC1_COLOR; + case BlendFactor::Src1Alpha: + return GL_SRC1_ALPHA; + case BlendFactor::OneMinusSrc1Alpha: + return GL_ONE_MINUS_SRC1_ALPHA; default: return GL_UNKNOWN_MGL; } diff --git a/MobileGL/MG_Util/Converters/MGToStr/RenderStateEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToStr/RenderStateEnumConverter.cpp index 24258fb9..8952a039 100644 --- a/MobileGL/MG_Util/Converters/MGToStr/RenderStateEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToStr/RenderStateEnumConverter.cpp @@ -40,6 +40,14 @@ namespace MobileGL { return "ConstantAlpha"; case BlendFactor::OneMinusConstantAlpha: return "OneMinusConstantAlpha"; + case BlendFactor::Src1Color: + return "Src1Color"; + case BlendFactor::OneMinusSrc1Color: + return "OneMinusSrc1Color"; + case BlendFactor::Src1Alpha: + return "Src1Alpha"; + case BlendFactor::OneMinusSrc1Alpha: + return "OneMinusSrc1Alpha"; default: return "Unknown"; } diff --git a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp index 9063b86e..61da4390 100644 --- a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp @@ -175,6 +175,14 @@ namespace MobileGL { return VK_BLEND_FACTOR_CONSTANT_ALPHA; case BlendFactor::OneMinusConstantAlpha: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; + case BlendFactor::Src1Color: + return VK_BLEND_FACTOR_SRC1_COLOR; + case BlendFactor::OneMinusSrc1Color: + return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; + case BlendFactor::Src1Alpha: + return VK_BLEND_FACTOR_SRC1_ALPHA; + case BlendFactor::OneMinusSrc1Alpha: + return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; default: return VK_BLEND_FACTOR_ONE; } diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index 87fec742..fa84600c 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -248,6 +248,13 @@ namespace MobileGL::MG_Util::SelfTest { builder.Warn("Indexed color mask", "no indexed glColorMaski; per-draw-buffer color masks fall back to draw buffer 0"); } + if (caps.SupportsDualSourceBlend) { + builder.Pass("Dual-source blend", + "GL_SRC1_* dual-source blend factors available via GL_EXT_blend_func_extended"); + } else { + builder.Warn("Dual-source blend", + "no GL_EXT_blend_func_extended; GL_SRC1_* dual-source blend factors hard-fail at draw"); + } if (es31) { GLint maxVertexSsboBlocks = 0; diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 6bf96b0d..b9e6f100 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -205,6 +205,7 @@ namespace MobileGL { resolver = MakeUnique(*program, (EShLanguage)stage, attrib.explicitVertexInLocations, attrib.explicitFragmentOutLocations, + attrib.explicitFragmentOutIndices, attrib.explicitOpaqueUniformBindings); break; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/Types.h b/MobileGL/MG_Util/ShaderTranspiler/Types.h index ac45b7b5..443b2102 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/Types.h +++ b/MobileGL/MG_Util/ShaderTranspiler/Types.h @@ -31,6 +31,9 @@ namespace MobileGL { Vector> shaders; UnorderedMap explicitVertexInLocations; UnorderedMap explicitFragmentOutLocations; + // Dual-source blend color index per fragment output (glBindFragDataLocationIndexed) -> + // emitted as layout(index = N). + UnorderedMap explicitFragmentOutIndices; UnorderedMap* explicitOpaqueUniformBindings = nullptr; }; diff --git a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp index 135e81c0..c891106f 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp @@ -76,6 +76,15 @@ namespace MobileGL { auto& writableType = ent.symbol->getWritableType(); writableType.getQualifier().layoutLocation = it->second; } + // Dual-source blend color index (glBindFragDataLocationIndexed) -> layout(index = N). + // Only the non-zero (dual-source) index is emitted: index 0 is the GL default, and + // emitting an explicit "index = 0" qualifier would demand GL_EXT_blend_func_extended on + // GLES even for ordinary single-source fragment outputs. + auto idxIt = m_explicitFragOutIndices.find(name.c_str()); + if (idxIt != m_explicitFragOutIndices.end() && idxIt->second != 0) { + auto& writableType = ent.symbol->getWritableType(); + writableType.getQualifier().layoutIndex = idxIt->second; + } } if (ShouldAssignPlainUniformLocation(type)) { const int size = glslang::TIntermediate::computeTypeUniformLocationSize(type); diff --git a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h index 4d6281e0..bed30d1b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h +++ b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h @@ -26,13 +26,15 @@ namespace MobileGL { public: using ExplicitVarSlotMap = UnorderedMap; TMglGlslIoResolver(const glslang::TIntermediate& intermediate, const ExplicitVarSlotMap& vertexIns, - const ExplicitVarSlotMap& fragOuts, ExplicitVarSlotMap* opaqueUniformBindings) + const ExplicitVarSlotMap& fragOuts, const ExplicitVarSlotMap& fragOutIndices, + ExplicitVarSlotMap* opaqueUniformBindings) : TDefaultGlslIoResolver(intermediate), m_explicitVertexIns(vertexIns), m_explicitFragOuts(fragOuts), - m_explicitOpaqueUniformBindings(opaqueUniformBindings) {} + m_explicitFragOutIndices(fragOutIndices), m_explicitOpaqueUniformBindings(opaqueUniformBindings) {} TMglGlslIoResolver(const glslang::TProgram& program, const EShLanguage stage, const ExplicitVarSlotMap& vertexIns, const ExplicitVarSlotMap& fragOuts, - ExplicitVarSlotMap* opaqueUniformBindings) - : TMglGlslIoResolver(*program.getIntermediate(stage), vertexIns, fragOuts, opaqueUniformBindings) {} + const ExplicitVarSlotMap& fragOutIndices, ExplicitVarSlotMap* opaqueUniformBindings) + : TMglGlslIoResolver(*program.getIntermediate(stage), vertexIns, fragOuts, fragOutIndices, + opaqueUniformBindings) {} void reserverStorageSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) override; void reserverResourceSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) override; int resolveUniformLocation(EShLanguage stage, glslang::TVarEntryInfo& ent) override; @@ -43,6 +45,7 @@ namespace MobileGL { const ExplicitVarSlotMap& m_explicitVertexIns; const ExplicitVarSlotMap& m_explicitFragOuts; + const ExplicitVarSlotMap& m_explicitFragOutIndices; ExplicitVarSlotMap* m_explicitOpaqueUniformBindings = nullptr; std::map m_plainUniformLocationSizeByName; std::map m_plainUniformLocationByName;