diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 68538a33..fda40db2 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -184,6 +184,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Flags GetDriverPixelFormatNormalizeOptions() { Flags options = PixelFormatNormalizeOptionBit::NoDepthComponent32; + options |= PixelFormatNormalizeOptionBit::NoRGBA8Snorm; if (!g_GLESCapabilities.SupportsNorm16Texture) { options |= PixelFormatNormalizeOptionBit::NoNorm16; } @@ -207,6 +208,10 @@ namespace MobileGL::MG_Backend::DirectGLES { reasons.push_back(forced ? "SNORM8 fallback forced by backend policy" : "SNORM8 native path is not supported"); } + if (options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm) { + reasons.push_back(forced ? "RGBA8_SNORM fallback forced by backend policy" + : "RGBA8_SNORM render target path is not supported"); + } if (options & PixelFormatNormalizeOptionBit::NoDepthComponent32) { reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES"); } diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index fd1c2d08..7f851f24 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -711,7 +711,8 @@ namespace MobileGL::MG_Backend::DirectGLES { backendObj = MakeShared(); backendObj->SyncToBackend(currentProgram); } else { - if (!backendObj->GetBackendProgramId()) { + if (!backendObj->GetBackendProgramId() || + backendObj->GetRGBA8SnormClampOutputMask() != g_rgba8SnormClampOutputMask) { backendObj->SyncToBackend(currentProgram); } } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 77a8fc37..5e0d4e00 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -1316,6 +1316,22 @@ namespace MobileGL::MG_Backend::DirectGLES { return true; } + static Bool IsRGBA8SnormFallbackAttachment( + const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) { + if (attachmentObject.IsTexture()) { + const auto& textureObject = attachmentObject.GetTexture(); + return textureObject && textureObject->GetFormat() == TextureInternalFormat::RGBA8Snorm && + TextureImpl::ShouldUseCaveatTextureFormat(textureObject->GetFormat(), textureObject->GetTarget()); + } + if (attachmentObject.IsRenderbuffer()) { + const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); + return renderbufferObject && + renderbufferObject->GetInternalFormat() == TextureInternalFormat::RGBA8Snorm && + TextureImpl::ShouldUseCaveatRenderbufferFormat(renderbufferObject->GetInternalFormat()); + } + return false; + } + void BackendFramebufferObject::SyncToBackend( const SharedPtr& stateFBOObject, FramebufferTarget asTarget) { #ifdef TRACY_ENABLE @@ -1366,6 +1382,22 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers); } + if (asTarget == FramebufferTarget::Draw) { + Uint32 clampOutputMask = 0; + for (Uint i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS && i < 32; ++i) { + const auto frontendBuf = stateDrawBuffers[i]; + if (frontendBuf < FramebufferAttachmentType::Color0 || + frontendBuf > FramebufferAttachmentType::Color31) { + continue; + } + const auto& attachmentObject = stateFBOObject->GetAttachment(frontendBuf); + if (IsRGBA8SnormFallbackAttachment(attachmentObject)) { + clampOutputMask |= (1u << i); + } + } + PrgramImpl::g_rgba8SnormClampOutputMask = clampOutputMask; + } + // 2. Remap read buffer auto frontendReadBuf = stateFBOObject->GetReadBuffer(); if (frontendReadBuf != m_frontendReadBuffer) { @@ -1478,6 +1510,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } // namespace FramebufferImpl namespace PrgramImpl { + Uint32 g_rgba8SnormClampOutputMask = 0; StateBackendObjectRegistry g_backendProgramObjects; BackendProgramObjectImpl::BackendProgramObjectImpl() { @@ -1522,6 +1555,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u", stateProgramObject->GetExternalIndex(), m_backendProgramId); + m_rgba8SnormClampOutputMask = g_rgba8SnormClampOutputMask; // Detach all existing shaders GLint attachedCount = 0; @@ -1597,6 +1631,8 @@ namespace MobileGL::MG_Backend::DirectGLES { source = ForceFlatIntegerVaryings(source, glShaderType); source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType); source = ForceSupporterOutput(source); + source = ClampRGBA8SnormFallbackOutputs(std::move(source), glShaderType, + m_rgba8SnormClampOutputMask); // Patch for Photon compiler precision issue String findStr = "1000000.0"; diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 06ab7aa1..080fe1b1 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -284,14 +284,17 @@ namespace MobileGL::MG_Backend::DirectGLES { void SetBaseInstance(Uint32 baseInstance) const; Uint GetBackendProgramId() const { return m_backendProgramId; } Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; } + Uint32 GetRGBA8SnormClampOutputMask() const { return m_rgba8SnormClampOutputMask; } private: Uint m_backendProgramId = 0; Uint m_backendGlobalUBOId = 0; Int m_baseInstanceUniformLocation = -1; + Uint32 m_rgba8SnormClampOutputMask = 0; Bool m_isInitialized = false; }; + extern Uint32 g_rgba8SnormClampOutputMask; extern StateBackendObjectRegistry g_backendProgramObjects; } // namespace PrgramImpl diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 22f321b7..efd84450 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -33,6 +33,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Flags GetDriverPixelFormatNormalizeOptions() { Flags options = PixelFormatNormalizeOptionBit::NoDepthComponent32; + options |= PixelFormatNormalizeOptionBit::NoRGBA8Snorm; if (!g_GLESCapabilities.SupportsNorm16Texture) { options |= PixelFormatNormalizeOptionBit::NoNorm16; } @@ -81,10 +82,15 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool ShouldUseCaveatFormat(TextureInternalFormat internalFormat, SizeT targetIndex) { if (targetIndex < kFormatCapabilityTargetCount) { - if (HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::Creatable)) { - return false; - } - return HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::Creatable); + const Bool fullCreatable = + HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::Creatable); + const Bool caveatCreatable = + HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::Creatable); + const Bool fullRenderable = + HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::FramebufferRenderable); + const Bool caveatRenderable = + HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::FramebufferRenderable); + return (!fullCreatable && caveatCreatable) || (!fullRenderable && caveatRenderable); } if (HasAnyCachedFormatCapability(internalFormat, false, FormatCapability::Creatable)) { @@ -127,6 +133,16 @@ namespace MobileGL::MG_Backend::DirectGLES { GenerateFormatInfo(internalFormat, GetRenderbufferFormatCapabilityTargetIndex(), outInternalFormat, outFormat, outType); } + + Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target) { + const SizeT targetIndex = + target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target); + return ShouldUseCaveatFormat(internalFormat, targetIndex); + } + + Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat) { + return ShouldUseCaveatFormat(internalFormat, GetRenderbufferFormatCapabilityTargetIndex()); + } } // namespace TextureImpl namespace PrgramImpl { String ProcessOutColorLocations(const String& glslCode) { @@ -197,6 +213,57 @@ namespace MobileGL::MG_Backend::DirectGLES { return result; } + String ClampRGBA8SnormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 outputMask) { +#ifdef TRACY_ENABLE + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); +#endif + if (shaderType != GL_FRAGMENT_SHADER || outputMask == 0) { + return glslCode; + } + + const std::regex outputPattern( + R"(layout\s*\(\s*location\s*=\s*([0-9]+)\s*\)\s*out\s+(?:(?:lowp|mediump|highp)\s+)?vec4\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)"); + std::sregex_iterator outputIt(glslCode.begin(), glslCode.end(), outputPattern); + std::sregex_iterator outputEnd; + Vector outputNames; + for (; outputIt != outputEnd; ++outputIt) { + const Uint location = static_cast(std::stoul((*outputIt)[1].str())); + if (location < 32 && (outputMask & (1u << location))) { + outputNames.push_back((*outputIt)[2].str()); + } + } + if (outputNames.empty()) { + return glslCode; + } + + const std::regex mainPattern(R"(void\s+main\s*\([^)]*\)\s*\{)"); + std::smatch mainMatch; + if (!std::regex_search(glslCode, mainMatch, mainPattern)) { + 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) { + String clampLine; + for (const String& outputName : outputNames) { + clampLine += "\n " + outputName + " = clamp(" + outputName + + ", vec4(-1.0), vec4(1.0));"; + } + clampLine += "\n"; + glslCode.insert(pos, clampLine); + return glslCode; + } + } + } + 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 288e185b..f5f45a7a 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.h +++ b/MobileGL/MG_Backend/DirectGLES/Utils.h @@ -39,6 +39,8 @@ namespace MobileGL::MG_Backend::DirectGLES { TextureTarget target = TextureTarget::Unknown); void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat, GLenum* outFormat, GLenum* outType); + Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target); + Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat); } // namespace TextureImpl namespace FramebufferImpl {} // namespace FramebufferImpl @@ -46,6 +48,7 @@ namespace MobileGL::MG_Backend::DirectGLES { namespace PrgramImpl { String ProcessOutColorLocations(const String& glslCode); String ForceSupporterOutput(const String& glslCode); + String ClampRGBA8SnormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 outputMask); String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType); String RemoveLayoutBinding(const String& glslCode); } // namespace PrgramImpl diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp index 1bfbc810..06bebcce 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp @@ -35,6 +35,9 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16; break; case GL_RGBA8_SNORM: + applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8; + applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm; + break; case GL_RGB8_SNORM: case GL_RG8_SNORM: case GL_R8_SNORM: @@ -123,7 +126,8 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outInternalFormat = internalFormat; break; case GL_RGBA8_SNORM: - if (options & PixelFormatNormalizeOptionBit::NoSnorm8) { + if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) || + (options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm)) { *outInternalFormat = GL_RGBA16F; break; } @@ -360,7 +364,6 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outType = GL_SHORT; break; } - case GL_RGBA8_SNORM: case GL_RGB8_SNORM: case GL_RG8_SNORM: case GL_R8_SNORM: @@ -370,6 +373,14 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { } *outType = GL_BYTE; break; + case GL_RGBA8_SNORM: + if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) || + (options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm)) { + *outType = GL_FLOAT; + break; + } + *outType = GL_BYTE; + break; // Color Unsigned Integer case GL_RGBA32UI: diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.h b/MobileGL/MG_Util/Texture/TextureFormatProcessor.h index d664a4fb..55df20f0 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.h +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.h @@ -16,6 +16,7 @@ namespace MobileGL { NoRgb16 = 1 << 2, NoSnorm8 = 1 << 3, NoDepthComponent32 = 1 << 4, + NoRGBA8Snorm = 1 << 5, None = 0, }; namespace MG_Util::TextureFormatProcessor {