diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 366b7a67..11344c46 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -210,6 +210,9 @@ namespace MobileGL::MG_Backend::DirectGLES { if (options & PixelFormatNormalizeOptionBit::NoDepthComponent32) { reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES"); } + if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) { + reasons.push_back("no three-channel multisample storage format on OpenGL ES"); + } String reason; for (SizeT i = 0; i < reasons.size(); ++i) { @@ -556,15 +559,35 @@ namespace MobileGL::MG_Backend::DirectGLES { } const GLESProbeFormatInfo nativeInfo = BuildNativeProbeFormatInfo(requestedInternalFormat); - GLESProbeFormatInfo fallbackInfo; - const Bool hasForcedFallback = - BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, fallbackInfo); - if (!hasForcedFallback) { - BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, fallbackInfo); + GLESProbeFormatInfo outerFallbackInfo; + const Bool outerHasForcedFallback = + BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, outerFallbackInfo); + if (!outerHasForcedFallback) { + BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, outerFallbackInfo); } for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { const auto target = static_cast(targetIndex); + // A multisample texture can only ever be rendered into, so its storage format + // has to stay colour-renderable; the ordinary fallback for a three-channel + // format is a three-channel one, which ES accepts as a texture but rejects as + // multisample storage. Recompute the fallback per target so those formats get + // widened here and nowhere else. + Flags targetOptions; + if (IsGLESProbeMultisampleTarget(target)) { + targetOptions |= PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget; + } + GLESProbeFormatInfo fallbackInfo = outerFallbackInfo; + Bool hasForcedFallback = outerHasForcedFallback; + if (targetOptions) { + hasForcedFallback = BuildFallbackProbeFormatInfo( + requestedInternalFormat, forcedOptions | targetOptions, true, fallbackInfo); + if (!hasForcedFallback) { + BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions | targetOptions, + false, fallbackInfo); + } + } + Bool shouldProbeFallback = hasForcedFallback; if (!hasForcedFallback) { Bool nativeRenderable = false; @@ -600,8 +623,8 @@ namespace MobileGL::MG_Backend::DirectGLES { } const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex(); - Bool shouldProbeFallbackRenderbuffer = hasForcedFallback; - if (!hasForcedFallback) { + Bool shouldProbeFallbackRenderbuffer = outerHasForcedFallback; + if (!outerHasForcedFallback) { const Bool nativeRenderbufferComplete = ProbeRenderbuffer(gl, nativeInfo.InternalFormat, logicalFormat, false, 1); if (nativeRenderbufferComplete) { @@ -615,16 +638,16 @@ namespace MobileGL::MG_Backend::DirectGLES { shouldProbeFallbackRenderbuffer = true; } } - if (shouldProbeFallbackRenderbuffer && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL && - ProbeRenderbuffer(gl, fallbackInfo.InternalFormat, logicalFormat, false, 1)) { + if (shouldProbeFallbackRenderbuffer && outerFallbackInfo.InternalFormat != GL_UNKNOWN_MGL && + ProbeRenderbuffer(gl, outerFallbackInfo.InternalFormat, logicalFormat, false, 1)) { if (AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex, GetRenderbufferFeatureCaps(logicalFormat))) { - LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo); + LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, outerFallbackInfo); } const Int maxSamples = - GetGLESFormatMaxSamples(capabilities, logicalFormat, fallbackInfo.ImageFormat); + GetGLESFormatMaxSamples(capabilities, logicalFormat, outerFallbackInfo.ImageFormat); cache.SampleCounts[renderbufferTargetIndex][formatIndex] = - ProbeRenderbufferSampleCounts(gl, fallbackInfo.InternalFormat, logicalFormat, maxSamples); + ProbeRenderbufferSampleCounts(gl, outerFallbackInfo.InternalFormat, logicalFormat, maxSamples); } } } diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index bfd3d550..fdb16627 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -48,15 +48,31 @@ namespace MobileGL::MG_Backend::DirectGLES { return options; } - Flags GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat) { + Flags + GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat, Bool mustStayRenderable) { using namespace MG_Util::TextureFormatProcessor; - const Flags forcedOptions = - GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, GetForcedPixelFormatNormalizeOptions()); + Flags extraOptions; + if (mustStayRenderable) { + extraOptions |= PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget; + } + const Flags forcedOptions = GetApplicablePixelFormatNormalizeOptions( + requestedInternalFormat, GetForcedPixelFormatNormalizeOptions() | extraOptions); if (forcedOptions) { return forcedOptions; } - return GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, - GetDriverPixelFormatNormalizeOptions()); + return GetApplicablePixelFormatNormalizeOptions( + requestedInternalFormat, GetDriverPixelFormatNormalizeOptions() | extraOptions); + } + + // Multisample textures can only ever be rendered into, never uploaded to, so a fallback + // format for them has to stay colour-renderable - a three-channel float fallback is a legal + // ES texture format but not a legal multisample storage format. Widening to four channels + // is safe here precisely because there is no transfer path that would have to expand + // three-channel client data, and the alpha the draw writes for a three-channel source is + // already the 1.0 the frontend format implies. + Bool TargetRequiresRenderableFormat(SizeT targetIndex) { + return targetIndex == static_cast(TextureTarget::Texture2DMultisample) || + targetIndex == static_cast(TextureTarget::Texture2DMultisampleArray); } Bool HasCachedFormatCapability(TextureInternalFormat internalFormat, @@ -116,7 +132,8 @@ namespace MobileGL::MG_Backend::DirectGLES { const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat); Flags options; if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) { - options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat); + options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat, + TargetRequiresRenderableFormat(targetIndex)); } NormalizePixelFormat(requestedInternalFormat, options, outInternalFormat, outFormat, outType); } diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp index f58c5a06..dbd409cc 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp @@ -29,11 +29,13 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { case GL_RGB12: // stored as RGB16 (see NormalizePixelFormat) applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16; applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRgb16; + applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget; break; case GL_RGB16_SNORM: applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRGB16Snorm; applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16; applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16; + applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget; break; case GL_RGBA16_SNORM: case GL_RG16_SNORM: @@ -46,6 +48,9 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm; break; case GL_RGB8_SNORM: + applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8; + applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget; + break; case GL_RG8_SNORM: case GL_R8_SNORM: applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8; @@ -87,6 +92,13 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outInternalFormat = internalFormat; break; case GL_RGB16: + if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) { + // GL_RGB32F is a legal ES texture format but is not colour-renderable, so + // glTexStorage2DMultisample rejects it and the attachment ends up with no + // storage at all. + *outInternalFormat = GL_RGBA32F; + break; + } if ((options & PixelFormatNormalizeOptionBit::NoNorm16) || (options & PixelFormatNormalizeOptionBit::NoRgb16)) { *outInternalFormat = GL_RGB32F; @@ -117,6 +129,10 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outInternalFormat = internalFormat; break; case GL_RGB16_SNORM: + if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) { + *outInternalFormat = GL_RGBA16F; + break; + } if ((options & PixelFormatNormalizeOptionBit::NoNorm16) || (options & PixelFormatNormalizeOptionBit::NoRGB16Snorm) || (options & PixelFormatNormalizeOptionBit::NoSnorm16)) { @@ -150,6 +166,10 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outInternalFormat = internalFormat; break; case GL_RGB8_SNORM: + if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) { + *outInternalFormat = GL_RGBA16F; + break; + } if (options & PixelFormatNormalizeOptionBit::NoSnorm8) { *outInternalFormat = GL_RGB16F; break; diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.h b/MobileGL/MG_Util/Texture/TextureFormatProcessor.h index 4efbeee5..8190c558 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.h +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.h @@ -18,6 +18,12 @@ namespace MobileGL { NoDepthComponent32 = 1 << 4, NoRGBA8Snorm = 1 << 5, NoRGB16Snorm = 1 << 6, + // The target must be colour-renderable and ES has no renderable three-channel + // form of the requested format, so it has to be widened to the four-channel one. + // Only meaningful for multisample textures: those can never be uploaded to, only + // rendered into, so the extra alpha comes from the draw (1.0 for an RGB source) + // and no transfer path has to expand three-channel client data. + NoThreeChannelRenderTarget = 1 << 7, None = 0, }; namespace MG_Util::TextureFormatProcessor {