diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index f58372f8..68538a33 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -12,7 +12,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -140,6 +143,7 @@ namespace MobileGL::MG_Backend::DirectGLES { GLenum InternalFormat = GL_UNKNOWN_MGL; GLenum ImageFormat = GL_RGBA; GLenum ImageType = GL_UNSIGNED_BYTE; + String Reason; }; constexpr FormatCapability kGLESProbeCapabilities[] = { @@ -186,8 +190,66 @@ namespace MobileGL::MG_Backend::DirectGLES { return options; } + String BuildPixelFormatFallbackReason(Flags options, Bool forced) { + Vector reasons; + if (options & PixelFormatNormalizeOptionBit::NoNorm16) { + reasons.push_back("EXT_texture_norm16 not supported"); + } + if (options & PixelFormatNormalizeOptionBit::NoRgb16) { + reasons.push_back(forced ? "RGB16 fallback forced by backend policy" + : "RGB16 native path is not supported"); + } + if (options & PixelFormatNormalizeOptionBit::NoSnorm16) { + reasons.push_back(forced ? "SNORM16 fallback forced by backend policy" + : "SNORM16 native path is not supported"); + } + if (options & PixelFormatNormalizeOptionBit::NoSnorm8) { + reasons.push_back(forced ? "SNORM8 fallback forced by backend policy" + : "SNORM8 native path is not supported"); + } + if (options & PixelFormatNormalizeOptionBit::NoDepthComponent32) { + reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES"); + } + + String reason; + for (SizeT i = 0; i < reasons.size(); ++i) { + if (i != 0) reason += "; "; + reason += reasons[i]; + } + return reason.empty() ? "Native format probe failed" : reason; + } + + String ConvertFallbackInternalFormatToString(GLenum internalFormat) { + const TextureInternalFormat logicalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalFormat); + if (logicalFormat != TextureInternalFormat::Unknown) { + return MG_Util::ConvertTextureInternalFormatToString(logicalFormat); + } + return MG_Util::ConvertGLEnumToString(internalFormat); + } + + String GetFormatCapabilityTargetNameForLog(SizeT targetIndex) { + if (targetIndex == GetRenderbufferFormatCapabilityTargetIndex()) { + return "Renderbuffer"; + } + if (targetIndex < kFormatCapabilityTextureTargetCount) { + return MG_Util::ConvertTextureTargetToString(static_cast(targetIndex)); + } + return "Unknown"; + } + + void LogGLESFormatCaveat(TextureInternalFormat logicalFormat, + SizeT targetIndex, + const GLESProbeFormatInfo& fallbackInfo) { + MGLOG_I("Caveat: %s %s not fully supported. Reason: %s. Fallback: %s", + GetFormatCapabilityTargetNameForLog(targetIndex).c_str(), + MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(), + fallbackInfo.Reason.c_str(), + ConvertFallbackInternalFormatToString(fallbackInfo.InternalFormat).c_str()); + } + Bool BuildFallbackProbeFormatInfo(GLenum requestedInternalFormat, Flags options, + Bool forced, GLESProbeFormatInfo& outInfo) { const Flags applicableOptions = MG_Util::TextureFormatProcessor::GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, @@ -199,6 +261,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_Util::TextureFormatProcessor::NormalizePixelFormat(requestedInternalFormat, applicableOptions, &outInfo.InternalFormat, &outInfo.ImageFormat, &outInfo.ImageType); + outInfo.Reason = BuildPixelFormatFallbackReason(applicableOptions, forced); return outInfo.InternalFormat != GL_UNKNOWN_MGL; } @@ -224,16 +287,19 @@ namespace MobileGL::MG_Backend::DirectGLES { cache.FullCaps[targetIndex][formatIndex] |= caps; } - void AddCaveatFormatCaps(FormatCapabilityCache& cache, + Bool AddCaveatFormatCaps(FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex, FormatCapabilityFlags caps) { + Bool added = false; for (FormatCapability capability : kGLESProbeCapabilities) { if (HasFormatCapability(caps, capability) && !HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) { cache.CaveatCaps[targetIndex][formatIndex] |= capability; + added = true; } } + return added; } Int GetGLESFormatMaxSamples(const DynamicBackendParameters& dynamicParameters, @@ -464,9 +530,9 @@ namespace MobileGL::MG_Backend::DirectGLES { const GLESProbeFormatInfo nativeInfo = BuildNativeProbeFormatInfo(requestedInternalFormat); GLESProbeFormatInfo fallbackInfo; const Bool hasForcedFallback = - BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, fallbackInfo); + BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, fallbackInfo); if (!hasForcedFallback) { - BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, fallbackInfo); + BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, fallbackInfo); } for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { @@ -493,8 +559,11 @@ namespace MobileGL::MG_Backend::DirectGLES { ProbeTexture(gl, target, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat, fallbackInfo.ImageType, logicalFormat, &fallbackRenderable); if (fallbackCreated) { - AddCaveatFormatCaps(cache, targetIndex, formatIndex, - BuildTextureCapsFromProbe(logicalFormat, target, fallbackRenderable)); + if (AddCaveatFormatCaps(cache, targetIndex, formatIndex, + BuildTextureCapsFromProbe(logicalFormat, target, + fallbackRenderable))) { + LogGLESFormatCaveat(logicalFormat, targetIndex, fallbackInfo); + } if (IsGLESProbeMultisampleTarget(target)) { cache.SampleCounts[targetIndex][formatIndex] = {1}; } @@ -520,8 +589,10 @@ namespace MobileGL::MG_Backend::DirectGLES { } if (shouldProbeFallbackRenderbuffer && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL && ProbeRenderbuffer(gl, fallbackInfo.InternalFormat, logicalFormat, false, 1)) { - AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex, - GetRenderbufferFeatureCaps(logicalFormat)); + if (AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex, + GetRenderbufferFeatureCaps(logicalFormat))) { + LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo); + } const Int maxSamples = GetGLESFormatMaxSamples(dynamicParameters, logicalFormat, fallbackInfo.ImageFormat); cache.SampleCounts[renderbufferTargetIndex][formatIndex] = diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index e9266d5e..60ad0fef 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -13,6 +13,7 @@ #include "MG_State/GLState/TextureState/TextureState.h" #include "MG_Util/Classifiers/TextureEnumClassifier.h" #include "MG_Util/Converters/MGToGL/TextureEnumConverter.h" +#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" #include "MG_Util/Texture/TextureFormatProcessor.h" @@ -125,40 +126,94 @@ namespace MobileGL::MG_Backend::DirectVulkan { return caps; } - Optional ResolveVulkanFallbackFormat(TextureInternalFormat format) { + Optional ResolveVulkanFallbackLogicalFormat(TextureInternalFormat format) { switch (format) { case TextureInternalFormat::RGB: case TextureInternalFormat::RGB8: - return VK_FORMAT_R8G8B8A8_UNORM; + return TextureInternalFormat::RGBA8; case TextureInternalFormat::SRGB8: - return VK_FORMAT_R8G8B8A8_SRGB; + return TextureInternalFormat::SRGB8Alpha8; case TextureInternalFormat::RGB8Snorm: - return VK_FORMAT_R8G8B8A8_SNORM; + return TextureInternalFormat::RGBA8Snorm; case TextureInternalFormat::RGB16: - return VK_FORMAT_R16G16B16A16_UNORM; + return TextureInternalFormat::RGBA16; case TextureInternalFormat::RGB16Snorm: - return VK_FORMAT_R16G16B16A16_SNORM; + return TextureInternalFormat::RGBA16Snorm; case TextureInternalFormat::RGB16F: - return VK_FORMAT_R16G16B16A16_SFLOAT; + return TextureInternalFormat::RGBA16F; case TextureInternalFormat::RGB32F: - return VK_FORMAT_R32G32B32A32_SFLOAT; + return TextureInternalFormat::RGBA32F; case TextureInternalFormat::RGB8I: - return VK_FORMAT_R8G8B8A8_SINT; + return TextureInternalFormat::RGBA8I; case TextureInternalFormat::RGB8UI: - return VK_FORMAT_R8G8B8A8_UINT; + return TextureInternalFormat::RGBA8UI; case TextureInternalFormat::RGB16I: - return VK_FORMAT_R16G16B16A16_SINT; + return TextureInternalFormat::RGBA16I; case TextureInternalFormat::RGB16UI: - return VK_FORMAT_R16G16B16A16_UINT; + return TextureInternalFormat::RGBA16UI; case TextureInternalFormat::RGB32I: - return VK_FORMAT_R32G32B32A32_SINT; + return TextureInternalFormat::RGBA32I; case TextureInternalFormat::RGB32UI: - return VK_FORMAT_R32G32B32A32_UINT; + return TextureInternalFormat::RGBA32UI; default: return Nullopt; } } + Optional ResolveVulkanFallbackFormat(TextureInternalFormat format) { + const Optional fallbackLogicalFormat = ResolveVulkanFallbackLogicalFormat(format); + if (!fallbackLogicalFormat) { + return Nullopt; + } + return MG_Util::ConvertTextureInternalFormatToVkEnum(*fallbackLogicalFormat); + } + + constexpr FormatCapability kVulkanProbeCapabilities[] = { + FormatCapability::Creatable, + FormatCapability::Sampled, + FormatCapability::LinearFilter, + FormatCapability::GenerateMipmap, + FormatCapability::TextureGather, + FormatCapability::TextureShadow, + FormatCapability::FramebufferRenderable, + FormatCapability::FramebufferLayered, + FormatCapability::MultisampleTexture, + FormatCapability::MultisampleRenderbuffer, + FormatCapability::ColorAttachment, + FormatCapability::DepthAttachment, + FormatCapability::StencilAttachment, + FormatCapability::TextureBuffer, + }; + + Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) { + for (FormatCapability capability : kVulkanProbeCapabilities) { + if (HasFormatCapability(fallbackCaps, capability) && + !HasFormatCapability(nativeCaps, capability)) { + return true; + } + } + return false; + } + + String GetFormatCapabilityTargetNameForLog(SizeT targetIndex) { + if (targetIndex == GetRenderbufferFormatCapabilityTargetIndex()) { + return "Renderbuffer"; + } + if (targetIndex < kFormatCapabilityTextureTargetCount) { + return MG_Util::ConvertTextureTargetToString(static_cast(targetIndex)); + } + return "Unknown"; + } + + void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat, + SizeT targetIndex, + TextureInternalFormat fallbackFormat) { + MGLOG_I("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s", + GetFormatCapabilityTargetNameForLog(targetIndex).c_str(), + MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(), + MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str()); + } + Vector BuildSampleCounts(Int maxSamples) { Vector counts; for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) { @@ -183,6 +238,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkFormat nativeFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(logicalFormat); + const Optional fallbackLogicalFormat = + ResolveVulkanFallbackLogicalFormat(logicalFormat); VkFormat fallbackFormat = ResolveVulkanFallbackFormat(logicalFormat).value_or(VK_FORMAT_UNDEFINED); VkFormatProperties nativeProperties{}; @@ -209,6 +266,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures); if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) { cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps; + if (fallbackLogicalFormat && HasNewCaveatFormatCaps(nativeCaps, fallbackCaps)) { + LogVulkanFormatCaveat(logicalFormat, targetIndex, *fallbackLogicalFormat); + } } if (HasFormatCapability(nativeCaps | fallbackCaps, FormatCapability::MultisampleTexture)) { @@ -248,6 +308,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer; } cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps; + if (fallbackLogicalFormat && + HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) { + LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat); + } } const FormatCapabilityFlags rbCaps = cache.FullCaps[renderbufferTargetIndex][formatIndex] |