diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index f77f001c..984cbbe8 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -832,6 +832,12 @@ namespace MobileGL::MG_Backend::DirectGLES { E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding, + // Both are core from GL 3.2/3.3 on and implemented here for + // every advertised version, but an app targeting 3.0/3.1 + // only reaches them through the extension string - the CTS + // picks a whole different shader for draw_buffers without + // explicit_attrib_location. DirectVulkan advertises both. + E_GL_ARB_explicit_attrib_location, E_GL_ARB_texture_multisample, E_GL_ARB_shader_image_size}; // Only advertised when the device driver actually has usable timer queries // (GL_EXT_disjoint_timer_query plus its entry points) and the diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 5e909589..72406e0b 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1120,6 +1120,23 @@ namespace MobileGL::MG_Backend::DirectGLES { g_lastUsedBackendProgramId = 0; return; } + // Read from the frontend rather than from the backend framebuffer sync, which + // only runs later in PrepareForDraw: a program compiled against a stale count + // would not be relinked until the draw after the one that needed it. + { + Uint enabledDrawBuffers = 0; + if (const auto& drawFBO = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject()) { + const auto& drawBuffers = drawFBO->GetDrawBuffers(); + for (Uint i = 0; i < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; ++i) { + if (drawBuffers[i] != FramebufferAttachmentType::None) { + enabledDrawBuffers = i + 1; + } + } + } + g_fragColorBroadcastCount = std::max(enabledDrawBuffers, 1); + } + const auto& backendProgramIt = g_backendProgramObjects.find(currentProgram.get()); Bool exist = (backendProgramIt != g_backendProgramObjects.end()); auto& backendObj = exist ? backendProgramIt->second : g_backendProgramObjects.GetOrCreate(currentProgram); @@ -1133,7 +1150,8 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!backendObj->GetBackendProgramId() || backendObj->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() || backendObj->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask || - backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask) { + backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask || + backendObj->GetFragColorBroadcastCount() != g_fragColorBroadcastCount) { backendObj->SyncToBackend(currentProgram); } } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 2c7b030e..c80bfe00 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3181,6 +3181,7 @@ namespace MobileGL::MG_Backend::DirectGLES { namespace PrgramImpl { Uint32 g_snormFallbackClampOutputMask = 0; + Uint g_fragColorBroadcastCount = 1; Uint32 g_unormFallbackClampOutputMask = 0; Uint g_lastUsedBackendProgramId = 0; StateBackendObjectRegistry g_backendProgramObjects; @@ -3234,6 +3235,7 @@ namespace MobileGL::MG_Backend::DirectGLES { stateProgramObject->GetExternalIndex(), m_backendProgramId); m_snormFallbackClampOutputMask = g_snormFallbackClampOutputMask; m_unormFallbackClampOutputMask = g_unormFallbackClampOutputMask; + m_fragColorBroadcastCount = g_fragColorBroadcastCount; // Detach all existing shaders GLint attachedCount = 0; @@ -3347,6 +3349,7 @@ namespace MobileGL::MG_Backend::DirectGLES { source = RemoveLayoutBinding(source); source = ProcessOutColorLocations(source); source = ForceFlatIntegerVaryings(source, glShaderType); + source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount); source = EmulateTextureLodBias(source); source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType); source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index e4dd64b1..ff68d78c 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -596,6 +596,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; } Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; } Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; } + Uint GetFragColorBroadcastCount() const { return m_fragColorBroadcastCount; } Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; } const Vector& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; } @@ -622,6 +623,9 @@ namespace MobileGL::MG_Backend::DirectGLES { Int m_indirectParamsBinding = -1; Uint32 m_snormFallbackClampOutputMask = 0; Uint32 m_unormFallbackClampOutputMask = 0; + // Draw buffers a legacy gl_FragColor write has to reach (see + // PrgramImpl::BroadcastLegacyFragColor); 1 keeps the plain single-output shader. + Uint m_fragColorBroadcastCount = 1; Bool m_isInitialized = false; Int m_globalUboBackendBlockIndex = -1; @@ -635,6 +639,10 @@ namespace MobileGL::MG_Backend::DirectGLES { extern Uint32 g_snormFallbackClampOutputMask; extern Uint32 g_unormFallbackClampOutputMask; + // Draw buffers the current draw framebuffer enables. Like the clamp masks above it + // is framebuffer state that the shader has to be compiled against, so a program + // whose snapshot no longer matches is relinked. + extern Uint g_fragColorBroadcastCount; // Backend id of the last glUseProgram issued through this backend; lets Use() // skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the // ES context is recreated. diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 58a00536..bfd3d550 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -279,6 +279,55 @@ namespace MobileGL::MG_Backend::DirectGLES { return glslCode; } + String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount) { +#ifdef TRACY_ENABLE + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); +#endif + // The name is the marker: ShaderSourceProcessor only emits it when the source + // wrote gl_FragColor, and such a shader can have no other output. + static const char* const kLoweredName = "mg_FragColor"; + if (shaderType != GL_FRAGMENT_SHADER || drawBufferCount <= 1) { + return glslCode; + } + static const std::regex declRegex( + R"(layout\s*\(\s*location\s*=\s*0\s*\)\s*out\s+((?:lowp|mediump|highp)\s+)?vec4\s+mg_FragColor\s*;)"); + std::smatch declMatch; + if (!std::regex_search(glslCode, declMatch, declRegex)) { + return glslCode; + } + const String precision = declMatch[1].matched ? declMatch[1].str() : String(); + + String replicaDecls; + String replicaCopies; + for (Uint location = 1; location < drawBufferCount; ++location) { + const String name = String(kLoweredName) + "_" + std::to_string(location); + replicaDecls += "\nlayout(location = " + std::to_string(location) + ") out " + precision + "vec4 " + + name + ";"; + replicaCopies += "\n " + name + " = " + kLoweredName + ";"; + } + + static const std::regex mainRegex(R"(void\s+main\s*\([^)]*\)\s*\{)"); + std::smatch mainMatch; + if (!std::regex_search(glslCode, mainMatch, mainRegex)) { + return glslCode; + } + SizeT bracePos = static_cast(mainMatch.position(0) + mainMatch.length(0) - 1); + Int depth = 0; + for (SizeT pos = bracePos; pos < glslCode.size(); ++pos) { + if (glslCode[pos] == '{') { + ++depth; + } else if (glslCode[pos] == '}') { + --depth; + if (depth == 0) { + glslCode.insert(pos, replicaCopies + "\n"); + break; + } + } + } + glslCode.insert(static_cast(declMatch.position(0)) + declMatch[0].str().size(), replicaDecls); + return glslCode; + } + String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.h b/MobileGL/MG_Backend/DirectGLES/Utils.h index 761a23bc..fd896892 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.h +++ b/MobileGL/MG_Backend/DirectGLES/Utils.h @@ -104,6 +104,13 @@ namespace MobileGL::MG_Backend::DirectGLES { String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask, Uint32 unormOutputMask); String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType); + // Legacy GLSL's gl_FragColor is broadcast to every enabled draw buffer (GL 4.6 + // 15.2.3), but ShaderSourceProcessor lowers it to the single output mg_FragColor, + // which only ever reaches draw buffer 0. Replicates it across `drawBufferCount` + // outputs and copies the value into them at the end of main. A no-op for + // drawBufferCount <= 1, i.e. for everything but a framebuffer that actually + // enables several draw buffers, so the ordinary single-target shader is untouched. + String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount); String RemoveLayoutBinding(const String& glslCode); // Prefix of the per-sampler float uniform that carries GL_TEXTURE_LOD_BIAS into // the shader (see EmulateTextureLodBias); the suffix is the sampler's own name.