[Feature] (MG_Backend): log format caveat fallbacks

This commit is contained in:
2026-06-26 14:49:51 +08:00
parent 76acae9889
commit 943600edb2
2 changed files with 156 additions and 21 deletions
@@ -12,7 +12,10 @@
#include <MG_Backend/DirectGLES/Utils.h> #include <MG_Backend/DirectGLES/Utils.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h> #include <MG_Util/BackendLoaders/OpenGL/Loader.h>
#include <MG_Util/Classifiers/TextureEnumClassifier.h> #include <MG_Util/Classifiers/TextureEnumClassifier.h>
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h> #include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
#include <MG_Util/Converters/MGToStr/TextureEnumConverter.h>
#include <MG_Util/Texture/TextureFormatProcessor.h> #include <MG_Util/Texture/TextureFormatProcessor.h>
#include <format> #include <format>
@@ -140,6 +143,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLenum InternalFormat = GL_UNKNOWN_MGL; GLenum InternalFormat = GL_UNKNOWN_MGL;
GLenum ImageFormat = GL_RGBA; GLenum ImageFormat = GL_RGBA;
GLenum ImageType = GL_UNSIGNED_BYTE; GLenum ImageType = GL_UNSIGNED_BYTE;
String Reason;
}; };
constexpr FormatCapability kGLESProbeCapabilities[] = { constexpr FormatCapability kGLESProbeCapabilities[] = {
@@ -186,8 +190,66 @@ namespace MobileGL::MG_Backend::DirectGLES {
return options; return options;
} }
String BuildPixelFormatFallbackReason(Flags<PixelFormatNormalizeOptionBit> options, Bool forced) {
Vector<String> 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<TextureTarget>(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, Bool BuildFallbackProbeFormatInfo(GLenum requestedInternalFormat,
Flags<PixelFormatNormalizeOptionBit> options, Flags<PixelFormatNormalizeOptionBit> options,
Bool forced,
GLESProbeFormatInfo& outInfo) { GLESProbeFormatInfo& outInfo) {
const Flags<PixelFormatNormalizeOptionBit> applicableOptions = const Flags<PixelFormatNormalizeOptionBit> applicableOptions =
MG_Util::TextureFormatProcessor::GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, MG_Util::TextureFormatProcessor::GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
@@ -199,6 +261,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::TextureFormatProcessor::NormalizePixelFormat(requestedInternalFormat, applicableOptions, MG_Util::TextureFormatProcessor::NormalizePixelFormat(requestedInternalFormat, applicableOptions,
&outInfo.InternalFormat, &outInfo.ImageFormat, &outInfo.InternalFormat, &outInfo.ImageFormat,
&outInfo.ImageType); &outInfo.ImageType);
outInfo.Reason = BuildPixelFormatFallbackReason(applicableOptions, forced);
return outInfo.InternalFormat != GL_UNKNOWN_MGL; return outInfo.InternalFormat != GL_UNKNOWN_MGL;
} }
@@ -224,16 +287,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
cache.FullCaps[targetIndex][formatIndex] |= caps; cache.FullCaps[targetIndex][formatIndex] |= caps;
} }
void AddCaveatFormatCaps(FormatCapabilityCache& cache, Bool AddCaveatFormatCaps(FormatCapabilityCache& cache,
SizeT targetIndex, SizeT targetIndex,
SizeT formatIndex, SizeT formatIndex,
FormatCapabilityFlags caps) { FormatCapabilityFlags caps) {
Bool added = false;
for (FormatCapability capability : kGLESProbeCapabilities) { for (FormatCapability capability : kGLESProbeCapabilities) {
if (HasFormatCapability(caps, capability) && if (HasFormatCapability(caps, capability) &&
!HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) { !HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) {
cache.CaveatCaps[targetIndex][formatIndex] |= capability; cache.CaveatCaps[targetIndex][formatIndex] |= capability;
added = true;
} }
} }
return added;
} }
Int GetGLESFormatMaxSamples(const DynamicBackendParameters& dynamicParameters, Int GetGLESFormatMaxSamples(const DynamicBackendParameters& dynamicParameters,
@@ -464,9 +530,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const GLESProbeFormatInfo nativeInfo = BuildNativeProbeFormatInfo(requestedInternalFormat); const GLESProbeFormatInfo nativeInfo = BuildNativeProbeFormatInfo(requestedInternalFormat);
GLESProbeFormatInfo fallbackInfo; GLESProbeFormatInfo fallbackInfo;
const Bool hasForcedFallback = const Bool hasForcedFallback =
BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, fallbackInfo); BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, fallbackInfo);
if (!hasForcedFallback) { if (!hasForcedFallback) {
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, fallbackInfo); BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, fallbackInfo);
} }
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
@@ -493,8 +559,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
ProbeTexture(gl, target, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat, ProbeTexture(gl, target, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat,
fallbackInfo.ImageType, logicalFormat, &fallbackRenderable); fallbackInfo.ImageType, logicalFormat, &fallbackRenderable);
if (fallbackCreated) { if (fallbackCreated) {
AddCaveatFormatCaps(cache, targetIndex, formatIndex, if (AddCaveatFormatCaps(cache, targetIndex, formatIndex,
BuildTextureCapsFromProbe(logicalFormat, target, fallbackRenderable)); BuildTextureCapsFromProbe(logicalFormat, target,
fallbackRenderable))) {
LogGLESFormatCaveat(logicalFormat, targetIndex, fallbackInfo);
}
if (IsGLESProbeMultisampleTarget(target)) { if (IsGLESProbeMultisampleTarget(target)) {
cache.SampleCounts[targetIndex][formatIndex] = {1}; cache.SampleCounts[targetIndex][formatIndex] = {1};
} }
@@ -520,8 +589,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
if (shouldProbeFallbackRenderbuffer && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL && if (shouldProbeFallbackRenderbuffer && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL &&
ProbeRenderbuffer(gl, fallbackInfo.InternalFormat, logicalFormat, false, 1)) { ProbeRenderbuffer(gl, fallbackInfo.InternalFormat, logicalFormat, false, 1)) {
AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex, if (AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex,
GetRenderbufferFeatureCaps(logicalFormat)); GetRenderbufferFeatureCaps(logicalFormat))) {
LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo);
}
const Int maxSamples = const Int maxSamples =
GetGLESFormatMaxSamples(dynamicParameters, logicalFormat, fallbackInfo.ImageFormat); GetGLESFormatMaxSamples(dynamicParameters, logicalFormat, fallbackInfo.ImageFormat);
cache.SampleCounts[renderbufferTargetIndex][formatIndex] = cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
@@ -13,6 +13,7 @@
#include "MG_State/GLState/TextureState/TextureState.h" #include "MG_State/GLState/TextureState/TextureState.h"
#include "MG_Util/Classifiers/TextureEnumClassifier.h" #include "MG_Util/Classifiers/TextureEnumClassifier.h"
#include "MG_Util/Converters/MGToGL/TextureEnumConverter.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/Converters/MGToVk/TextureEnumConverter.h"
#include "MG_Util/Texture/TextureFormatProcessor.h" #include "MG_Util/Texture/TextureFormatProcessor.h"
@@ -125,40 +126,94 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return caps; return caps;
} }
Optional<VkFormat> ResolveVulkanFallbackFormat(TextureInternalFormat format) { Optional<TextureInternalFormat> ResolveVulkanFallbackLogicalFormat(TextureInternalFormat format) {
switch (format) { switch (format) {
case TextureInternalFormat::RGB: case TextureInternalFormat::RGB:
case TextureInternalFormat::RGB8: case TextureInternalFormat::RGB8:
return VK_FORMAT_R8G8B8A8_UNORM; return TextureInternalFormat::RGBA8;
case TextureInternalFormat::SRGB8: case TextureInternalFormat::SRGB8:
return VK_FORMAT_R8G8B8A8_SRGB; return TextureInternalFormat::SRGB8Alpha8;
case TextureInternalFormat::RGB8Snorm: case TextureInternalFormat::RGB8Snorm:
return VK_FORMAT_R8G8B8A8_SNORM; return TextureInternalFormat::RGBA8Snorm;
case TextureInternalFormat::RGB16: case TextureInternalFormat::RGB16:
return VK_FORMAT_R16G16B16A16_UNORM; return TextureInternalFormat::RGBA16;
case TextureInternalFormat::RGB16Snorm: case TextureInternalFormat::RGB16Snorm:
return VK_FORMAT_R16G16B16A16_SNORM; return TextureInternalFormat::RGBA16Snorm;
case TextureInternalFormat::RGB16F: case TextureInternalFormat::RGB16F:
return VK_FORMAT_R16G16B16A16_SFLOAT; return TextureInternalFormat::RGBA16F;
case TextureInternalFormat::RGB32F: case TextureInternalFormat::RGB32F:
return VK_FORMAT_R32G32B32A32_SFLOAT; return TextureInternalFormat::RGBA32F;
case TextureInternalFormat::RGB8I: case TextureInternalFormat::RGB8I:
return VK_FORMAT_R8G8B8A8_SINT; return TextureInternalFormat::RGBA8I;
case TextureInternalFormat::RGB8UI: case TextureInternalFormat::RGB8UI:
return VK_FORMAT_R8G8B8A8_UINT; return TextureInternalFormat::RGBA8UI;
case TextureInternalFormat::RGB16I: case TextureInternalFormat::RGB16I:
return VK_FORMAT_R16G16B16A16_SINT; return TextureInternalFormat::RGBA16I;
case TextureInternalFormat::RGB16UI: case TextureInternalFormat::RGB16UI:
return VK_FORMAT_R16G16B16A16_UINT; return TextureInternalFormat::RGBA16UI;
case TextureInternalFormat::RGB32I: case TextureInternalFormat::RGB32I:
return VK_FORMAT_R32G32B32A32_SINT; return TextureInternalFormat::RGBA32I;
case TextureInternalFormat::RGB32UI: case TextureInternalFormat::RGB32UI:
return VK_FORMAT_R32G32B32A32_UINT; return TextureInternalFormat::RGBA32UI;
default: default:
return Nullopt; return Nullopt;
} }
} }
Optional<VkFormat> ResolveVulkanFallbackFormat(TextureInternalFormat format) {
const Optional<TextureInternalFormat> 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<TextureTarget>(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<Int> BuildSampleCounts(Int maxSamples) { Vector<Int> BuildSampleCounts(Int maxSamples) {
Vector<Int> counts; Vector<Int> counts;
for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) { 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); VkFormat nativeFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(logicalFormat);
const Optional<TextureInternalFormat> fallbackLogicalFormat =
ResolveVulkanFallbackLogicalFormat(logicalFormat);
VkFormat fallbackFormat = ResolveVulkanFallbackFormat(logicalFormat).value_or(VK_FORMAT_UNDEFINED); VkFormat fallbackFormat = ResolveVulkanFallbackFormat(logicalFormat).value_or(VK_FORMAT_UNDEFINED);
VkFormatProperties nativeProperties{}; VkFormatProperties nativeProperties{};
@@ -209,6 +266,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures); FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures);
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) { if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps; cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps;
if (fallbackLogicalFormat && HasNewCaveatFormatCaps(nativeCaps, fallbackCaps)) {
LogVulkanFormatCaveat(logicalFormat, targetIndex, *fallbackLogicalFormat);
}
} }
if (HasFormatCapability(nativeCaps | fallbackCaps, FormatCapability::MultisampleTexture)) { if (HasFormatCapability(nativeCaps | fallbackCaps, FormatCapability::MultisampleTexture)) {
@@ -248,6 +308,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer; fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
} }
cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps; cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps;
if (fallbackLogicalFormat &&
HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat);
}
} }
const FormatCapabilityFlags rbCaps = cache.FullCaps[renderbufferTargetIndex][formatIndex] | const FormatCapabilityFlags rbCaps = cache.FullCaps[renderbufferTargetIndex][formatIndex] |