diff --git a/MobileGL/MG_Backend/BackendObject.cpp b/MobileGL/MG_Backend/BackendObject.cpp index 2ceb4611..f238dd3a 100644 --- a/MobileGL/MG_Backend/BackendObject.cpp +++ b/MobileGL/MG_Backend/BackendObject.cpp @@ -15,23 +15,6 @@ namespace MobileGL::MG_Backend { namespace { - constexpr FormatCapability kPrintedFormatCapabilities[] = { - 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 IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { (void)dpy; return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT; @@ -41,40 +24,6 @@ namespace MobileGL::MG_Backend { return std::this_thread::get_id(); } - const char* ConvertFormatCapabilityToString(FormatCapability capability) { - switch (capability) { - case FormatCapability::Creatable: - return "Creatable"; - case FormatCapability::Sampled: - return "Sampled"; - case FormatCapability::LinearFilter: - return "LinearFilter"; - case FormatCapability::GenerateMipmap: - return "GenerateMipmap"; - case FormatCapability::TextureGather: - return "TextureGather"; - case FormatCapability::TextureShadow: - return "TextureShadow"; - case FormatCapability::FramebufferRenderable: - return "FramebufferRenderable"; - case FormatCapability::FramebufferLayered: - return "FramebufferLayered"; - case FormatCapability::MultisampleTexture: - return "MultisampleTexture"; - case FormatCapability::MultisampleRenderbuffer: - return "MultisampleRenderbuffer"; - case FormatCapability::ColorAttachment: - return "ColorAttachment"; - case FormatCapability::DepthAttachment: - return "DepthAttachment"; - case FormatCapability::StencilAttachment: - return "StencilAttachment"; - case FormatCapability::TextureBuffer: - return "TextureBuffer"; - } - return "Unknown"; - } - const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex, @@ -94,22 +43,17 @@ namespace MobileGL::MG_Backend { } SizeT GetCapabilityColumnWidth(FormatCapability capability) { - SizeT width = std::strlen(ConvertFormatCapabilityToString(capability)); + SizeT width = std::strlen(GetFormatCapabilityName(capability)); width = std::max(width, std::strlen("Caveat")); return width; } - String GetFormatCapabilityTargetName(SizeT targetIndex) { - if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) return "Renderbuffer"; - return MG_Util::ConvertTextureTargetToString(static_cast(targetIndex)); - } - String BuildFormatCapabilityHeader(SizeT formatNameWidth) { std::ostringstream line; line << std::left << std::setw(static_cast(formatNameWidth)) << ""; - for (FormatCapability capability : kPrintedFormatCapabilities) { + for (FormatCapability capability : kReportedFormatCapabilities) { line << " | " << std::left << std::setw(static_cast(GetCapabilityColumnWidth(capability))) - << ConvertFormatCapabilityToString(capability); + << GetFormatCapabilityName(capability); } return line.str(); } @@ -122,7 +66,7 @@ namespace MobileGL::MG_Backend { std::ostringstream line; line << std::left << std::setw(static_cast(formatNameWidth)) << MG_Util::ConvertTextureInternalFormatToString(format); - for (FormatCapability capability : kPrintedFormatCapabilities) { + for (FormatCapability capability : kReportedFormatCapabilities) { line << " | " << std::left << std::setw(static_cast(GetCapabilityColumnWidth(capability))) << GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability); } @@ -160,6 +104,50 @@ namespace MobileGL::MG_Backend { return kFormatCapabilityRenderbufferTargetIndex; } + const char* GetFormatCapabilityName(FormatCapability capability) { + switch (capability) { + case FormatCapability::Creatable: + return "Creatable"; + case FormatCapability::Sampled: + return "Sampled"; + case FormatCapability::LinearFilter: + return "LinearFilter"; + case FormatCapability::GenerateMipmap: + return "GenerateMipmap"; + case FormatCapability::TextureGather: + return "TextureGather"; + case FormatCapability::TextureShadow: + return "TextureShadow"; + case FormatCapability::FramebufferRenderable: + return "FramebufferRenderable"; + case FormatCapability::FramebufferLayered: + return "FramebufferLayered"; + case FormatCapability::MultisampleTexture: + return "MultisampleTexture"; + case FormatCapability::MultisampleRenderbuffer: + return "MultisampleRenderbuffer"; + case FormatCapability::ColorAttachment: + return "ColorAttachment"; + case FormatCapability::DepthAttachment: + return "DepthAttachment"; + case FormatCapability::StencilAttachment: + return "StencilAttachment"; + case FormatCapability::TextureBuffer: + return "TextureBuffer"; + } + return "Unknown"; + } + + String GetFormatCapabilityTargetName(SizeT targetIndex) { + if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) { + return "Renderbuffer"; + } + if (targetIndex >= kFormatCapabilityTextureTargetCount) { + return "Unknown"; + } + return MG_Util::ConvertTextureTargetToString(static_cast(targetIndex)); + } + void PrintFormatCapabilities(const FormatCapabilityCache& cache) { const SizeT formatNameWidth = GetPrintedFormatNameWidth(); diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index ffc889a5..5032c5dc 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -47,6 +47,23 @@ namespace MobileGL { using FormatCapabilityFlags = Flags; + inline constexpr Array kReportedFormatCapabilities = { + 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, + }; + inline constexpr SizeT kFormatCapabilityTextureTargetCount = static_cast(TextureTarget::TextureTargetCount); inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount; @@ -70,6 +87,8 @@ namespace MobileGL { Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability); SizeT GetFormatCapabilityTargetIndex(TextureTarget target); SizeT GetRenderbufferFormatCapabilityTargetIndex(); + const char* GetFormatCapabilityName(FormatCapability capability); + String GetFormatCapabilityTargetName(SizeT targetIndex); void PrintFormatCapabilities(const FormatCapabilityCache& cache); // Opaque backend fence-sync handle, created by GLFunctionsTable::FenceSync diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index df2ae21b..de7dd358 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -150,23 +150,6 @@ namespace MobileGL::MG_Backend::DirectGLES { String Reason; }; - constexpr FormatCapability kGLESProbeCapabilities[] = { - 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, - }; - GLESProbeFormatInfo BuildNativeProbeFormatInfo(GLenum requestedInternalFormat) { GLESProbeFormatInfo info; info.InternalFormat = requestedInternalFormat; @@ -176,9 +159,10 @@ namespace MobileGL::MG_Backend::DirectGLES { return info; } - Flags GetForcedPixelFormatNormalizeOptions() { + Flags GetForcedPixelFormatNormalizeOptions( + const MG_External::GLESCapabilities& capabilities) { Flags options; - if (g_GLESCapabilities.IsAngleRenderer) { + if (capabilities.IsAngleRenderer) { options |= PixelFormatNormalizeOptionBit::NoRgb16; options |= PixelFormatNormalizeOptionBit::NoSnorm16; options |= PixelFormatNormalizeOptionBit::NoSnorm8; @@ -186,11 +170,12 @@ namespace MobileGL::MG_Backend::DirectGLES { return options; } - Flags GetDriverPixelFormatNormalizeOptions() { + Flags GetDriverPixelFormatNormalizeOptions( + const MG_External::GLESCapabilities& capabilities) { Flags options = PixelFormatNormalizeOptionBit::NoDepthComponent32; options |= PixelFormatNormalizeOptionBit::NoRGBA8Snorm; options |= PixelFormatNormalizeOptionBit::NoRGB16Snorm; - if (!g_GLESCapabilities.SupportsNorm16Texture) { + if (!capabilities.SupportsNorm16Texture) { options |= PixelFormatNormalizeOptionBit::NoNorm16; } return options; @@ -241,21 +226,11 @@ namespace MobileGL::MG_Backend::DirectGLES { 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_D("Caveat: %s %s not fully supported. Reason: %s. Fallback: %s", - GetFormatCapabilityTargetNameForLog(targetIndex).c_str(), + GetFormatCapabilityTargetName(targetIndex).c_str(), MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(), fallbackInfo.Reason.c_str(), ConvertFallbackInternalFormatToString(fallbackInfo.InternalFormat).c_str()); @@ -306,7 +281,7 @@ namespace MobileGL::MG_Backend::DirectGLES { SizeT formatIndex, FormatCapabilityFlags caps) { Bool added = false; - for (FormatCapability capability : kGLESProbeCapabilities) { + for (FormatCapability capability : kReportedFormatCapabilities) { if (HasFormatCapability(caps, capability) && !HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) { cache.CaveatCaps[targetIndex][formatIndex] |= capability; @@ -316,7 +291,7 @@ namespace MobileGL::MG_Backend::DirectGLES { return added; } - Int GetGLESFormatMaxSamples(const DynamicBackendParameters& dynamicParameters, + Int GetGLESFormatMaxSamples(const MG_External::GLESCapabilities& capabilities, TextureInternalFormat logicalFormat, GLenum imageFormat) { const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat); @@ -324,12 +299,12 @@ namespace MobileGL::MG_Backend::DirectGLES { const Bool isInteger = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER || imageFormat == GL_RGBA_INTEGER; if (isDepth || isStencil) { - return dynamicParameters.MaxDepthTextureSamples; + return capabilities.MaxDepthTextureSamples; } if (isInteger) { - return dynamicParameters.MaxIntegerSamples; + return capabilities.MaxIntegerSamples; } - return dynamicParameters.MaxColorTextureSamples; + return capabilities.MaxColorTextureSamples; } Bool ProbeFramebufferCompletenessForTexture(const MG_External::GLESFunctionsTable& gl, @@ -527,12 +502,14 @@ namespace MobileGL::MG_Backend::DirectGLES { return sampleCounts; } - void ProbeGLESFormatCapabilities(const MG_External::GLESFunctionsTable& gl, - FormatCapabilityCache& cache, - const DynamicBackendParameters& dynamicParameters) { + void PopulateFormatCapabilitiesImpl(const MG_External::GLESFunctionsTable& gl, + const MG_External::GLESCapabilities& capabilities, + FormatCapabilityCache& cache) { cache.Clear(); - const Flags forcedOptions = GetForcedPixelFormatNormalizeOptions(); - const Flags driverOptions = GetDriverPixelFormatNormalizeOptions(); + const Flags forcedOptions = + GetForcedPixelFormatNormalizeOptions(capabilities); + const Flags driverOptions = + GetDriverPixelFormatNormalizeOptions(capabilities); for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) { const auto logicalFormat = static_cast(formatIndex); @@ -594,7 +571,7 @@ namespace MobileGL::MG_Backend::DirectGLES { AddFullFormatCaps(cache, renderbufferTargetIndex, formatIndex, GetRenderbufferFeatureCaps(logicalFormat)); const Int maxSamples = - GetGLESFormatMaxSamples(dynamicParameters, logicalFormat, nativeInfo.ImageFormat); + GetGLESFormatMaxSamples(capabilities, logicalFormat, nativeInfo.ImageFormat); cache.SampleCounts[renderbufferTargetIndex][formatIndex] = ProbeRenderbufferSampleCounts(gl, nativeInfo.InternalFormat, logicalFormat, maxSamples); } else { @@ -608,7 +585,7 @@ namespace MobileGL::MG_Backend::DirectGLES { LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo); } const Int maxSamples = - GetGLESFormatMaxSamples(dynamicParameters, logicalFormat, fallbackInfo.ImageFormat); + GetGLESFormatMaxSamples(capabilities, logicalFormat, fallbackInfo.ImageFormat); cache.SampleCounts[renderbufferTargetIndex][formatIndex] = ProbeRenderbufferSampleCounts(gl, fallbackInfo.InternalFormat, logicalFormat, maxSamples); } @@ -655,6 +632,12 @@ namespace MobileGL::MG_Backend::DirectGLES { } } // namespace + void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl, + const MG_External::GLESCapabilities& capabilities, + FormatCapabilityCache& cache) { + PopulateFormatCapabilitiesImpl(gl, capabilities, cache); + } + BackendObject_DirectGLES::~BackendObject_DirectGLES() { DestroyEGLContext(); } @@ -695,7 +678,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // the extension list is first built). UpdateAdvertisedTimerQueryExtension(); UpdateDynamicBackendParameters(); - ProbeGLESFormatCapabilities(m_GLESFunctions, MutableFormatCapabilities(), m_dynamicParameters); + PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities()); return true; } diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h index 6d9d6935..b13f2344 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h @@ -12,6 +12,12 @@ #include namespace MobileGL::MG_Backend::DirectGLES { + // Populates the same format-capability cache used by backend startup. The caller + // must keep the supplied GLES context current for the duration of this call. + void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl, + const MG_External::GLESCapabilities& capabilities, + FormatCapabilityCache& cache); + class BackendObject_DirectGLES : public BackendObject { public: ~BackendObject_DirectGLES() override; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index c359cb59..44502133 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -182,25 +182,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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) { + for (FormatCapability capability : kReportedFormatCapabilities) { if (HasFormatCapability(fallbackCaps, capability) && !HasFormatCapability(nativeCaps, capability)) { return true; @@ -209,21 +192,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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_D("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s", - GetFormatCapabilityTargetNameForLog(targetIndex).c_str(), + GetFormatCapabilityTargetName(targetIndex).c_str(), MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(), MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str()); } @@ -237,11 +210,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { return counts; } - void FillVulkanFormatCapabilities(VkPhysicalDevice physicalDevice, - const DynamicBackendParameters& dynamicParameters, - FormatCapabilityCache& cache) { + void PopulateFormatCapabilitiesImpl(VkPhysicalDevice physicalDevice, + PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties, + const MG_External::VulkanCapabilities& capabilities, + FormatCapabilityCache& cache) { cache.Clear(); - if (physicalDevice == VK_NULL_HANDLE) { + if (physicalDevice == VK_NULL_HANDLE || getFormatProperties == nullptr) { return; } @@ -258,12 +232,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFormatProperties nativeProperties{}; if (nativeFormat != VK_FORMAT_UNDEFINED) { - vkGetPhysicalDeviceFormatProperties(physicalDevice, nativeFormat, &nativeProperties); + getFormatProperties(physicalDevice, nativeFormat, &nativeProperties); } VkFormatProperties fallbackProperties{}; if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) { - vkGetPhysicalDeviceFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties); + getFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties); } for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { @@ -289,11 +263,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat); const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat); const Bool isInteger = IsIntegerInternalFormat(logicalFormat); - Int maxSamples = dynamicParameters.MaxColorTextureSamples; + Int maxSamples = capabilities.MaxColorTextureSamples; if (isDepth || isStencil) { - maxSamples = dynamicParameters.MaxDepthTextureSamples; + maxSamples = capabilities.MaxDepthTextureSamples; } else if (isInteger) { - maxSamples = dynamicParameters.MaxIntegerSamples; + maxSamples = capabilities.MaxIntegerSamples; } cache.SampleCounts[targetIndex][formatIndex] = BuildSampleCounts(maxSamples); } @@ -332,12 +306,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { cache.CaveatCaps[renderbufferTargetIndex][formatIndex]; if (HasFormatCapability(rbCaps, FormatCapability::MultisampleRenderbuffer)) { cache.SampleCounts[renderbufferTargetIndex][formatIndex] = - BuildSampleCounts(dynamicParameters.MaxFramebufferSamples); + BuildSampleCounts(capabilities.MaxFramebufferSamples); } } } } // namespace + void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice, + PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties, + const MG_External::VulkanCapabilities& capabilities, + FormatCapabilityCache& cache) { + PopulateFormatCapabilitiesImpl(physicalDevice, getFormatProperties, capabilities, cache); + } + BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default; BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {} @@ -394,7 +375,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } UpdateDynamicBackendParameters(); UpdateAdvertisedExtensions(); - FillVulkanFormatCapabilities(physicalDevice.handle, m_dynamicParameters, MutableFormatCapabilities()); + PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps, + MutableFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities()); return true; } diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index e0c70203..f3a19816 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -12,6 +12,14 @@ #include namespace MobileGL::MG_Backend::DirectVulkan { + // Populates the same format-capability cache used by backend startup. Passing the + // instance-resolved function keeps standalone callers independent of global loader + // initialization; the physical device must remain valid for the duration of the call. + void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice, + PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties, + const MG_External::VulkanCapabilities& capabilities, + FormatCapabilityCache& cache); + class BackendObject_DirectVulkan : public BackendObject { public: BackendObject_DirectVulkan(); diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index fa84600c..6cb8c4ec 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -525,6 +525,9 @@ namespace MobileGL::MG_Util::SelfTest { summary.capsValid = true; const MG_External::GLESCapabilities& caps = summary.caps; builder.report.rendererInfo = format("{} ({})", caps.GLESRendererString, caps.GLESVersionString); + builder.report.formatCapabilities.emplace(); + MG_Backend::DirectGLES::PopulateFormatCapabilities( + glesFuncs, caps, builder.report.formatCapabilities.value()); EvaluateGlesChecklist(builder, caps, glesFuncs); ProbeGlesTimerQuery(builder, caps, glesFuncs); } while (false); @@ -980,6 +983,9 @@ namespace MobileGL::MG_Util::SelfTest { getInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2")); const auto vkGetPhysicalDeviceProperties2Fn = reinterpret_cast( getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2")); + const auto vkGetPhysicalDeviceFormatPropertiesFn = + reinterpret_cast( + getInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties")); // The instance is destroyed from a scope guard so it is released on every early-return // path and even if a String/format allocation throws while report rows are being built. @@ -1059,6 +1065,14 @@ namespace MobileGL::MG_Util::SelfTest { summary.deviceName = String(properties.deviceName); summary.apiVersionString = VkApiVersionToString(properties.apiVersion); summary.driverVersionString = driverVersionString; + if (vkGetPhysicalDeviceFormatPropertiesFn != nullptr) { + MG_External::VulkanCapabilities formatProbeCapabilities{}; + BackendLoader::FillInVulkanCapabilities(formatProbeCapabilities, properties); + builder.report.formatCapabilities.emplace(); + MG_Backend::DirectVulkan::PopulateFormatCapabilities( + physicalDevice, vkGetPhysicalDeviceFormatPropertiesFn, formatProbeCapabilities, + builder.report.formatCapabilities.value()); + } // The chosen-device facts (name, enumeration count, graphics queue) ride along // on both outcomes so the device API verdict never hides them. diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.h b/MobileGL/MG_Util/SelfTest/DriverPost.h index d81ca193..e698acf9 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.h +++ b/MobileGL/MG_Util/SelfTest/DriverPost.h @@ -8,6 +8,7 @@ #pragma once #include +#include namespace MobileGL::MG_Util::SelfTest { // One row of a backend power-on self-test (POST) report. @@ -33,6 +34,7 @@ namespace MobileGL::MG_Util::SelfTest { String verdict = "UNSUPPORTED"; // "OK" | "DEGRADED" | "UNSUPPORTED" String rendererInfo; Vector checks; + Optional formatCapabilities; }; // Probes the DEVICE GLES driver with self-contained EGL boilerplate (1x1 pbuffer diff --git a/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp b/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp index 01719159..c42f57b0 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp @@ -11,6 +11,7 @@ #ifdef __ANDROID__ #include "DriverPost.h" +#include #include @@ -19,6 +20,9 @@ namespace { using MobileGL::SizeT; using MobileGL::String; using MobileGL::StringStream; + using MobileGL::Uint64; + using MobileGL::MG_Backend::FormatCapabilityCache; + using MobileGL::MG_Backend::FormatCapabilityFlags; using MobileGL::MG_Util::SelfTest::BackendPostReport; using MobileGL::MG_Util::SelfTest::PostCheck; @@ -70,6 +74,55 @@ namespace { out << '"' << EscapeJsonString(value) << '"'; } + Uint64 BuildFormatCapabilityMask(FormatCapabilityFlags capabilities) { + Uint64 mask = 0; + for (SizeT capabilityIndex = 0; + capabilityIndex < MobileGL::MG_Backend::kReportedFormatCapabilities.size(); ++capabilityIndex) { + if (MobileGL::MG_Backend::HasFormatCapability( + capabilities, MobileGL::MG_Backend::kReportedFormatCapabilities[capabilityIndex])) { + mask |= 1ull << capabilityIndex; + } + } + return mask; + } + + void AppendFormatCapabilitiesJson(StringStream& out, const FormatCapabilityCache& cache) { + out << ",\"formatCapabilities\":{\"capabilities\":["; + for (SizeT capabilityIndex = 0; + capabilityIndex < MobileGL::MG_Backend::kReportedFormatCapabilities.size(); ++capabilityIndex) { + if (capabilityIndex != 0) { + out << ','; + } + AppendJsonString( + out, MobileGL::MG_Backend::GetFormatCapabilityName( + MobileGL::MG_Backend::kReportedFormatCapabilities[capabilityIndex])); + } + out << "],\"targets\":["; + for (SizeT targetIndex = 0; targetIndex < MobileGL::MG_Backend::kFormatCapabilityTargetCount; + ++targetIndex) { + if (targetIndex != 0) { + out << ','; + } + out << "{\"name\":"; + AppendJsonString(out, MobileGL::MG_Backend::GetFormatCapabilityTargetName(targetIndex)); + out << ",\"rows\":["; + for (SizeT formatIndex = 0; formatIndex < MobileGL::MG_Backend::kFormatCapabilityFormatCount; + ++formatIndex) { + if (formatIndex != 0) { + out << ','; + } + out << '['; + AppendJsonString( + out, MobileGL::MG_Util::ConvertTextureInternalFormatToString( + static_cast(formatIndex))); + out << ',' << BuildFormatCapabilityMask(cache.FullCaps[targetIndex][formatIndex]); + out << ',' << BuildFormatCapabilityMask(cache.CaveatCaps[targetIndex][formatIndex]) << ']'; + } + out << "]}"; + } + out << "]}"; + } + void AppendBackendReportJson(StringStream& out, const BackendPostReport& report) { out << "{\"available\":" << (report.available ? "true" : "false"); out << ",\"verdict\":"; @@ -90,7 +143,11 @@ namespace { AppendJsonString(out, check.detail); out << '}'; } - out << "]}"; + out << ']'; + if (report.formatCapabilities.has_value()) { + AppendFormatCapabilitiesJson(out, report.formatCapabilities.value()); + } + out << '}'; } } // namespace diff --git a/android-plugin/app/src/main/java/top/mobilegl/plugin/PostActivity.java b/android-plugin/app/src/main/java/top/mobilegl/plugin/PostActivity.java index 881b668c..24e95e6d 100644 --- a/android-plugin/app/src/main/java/top/mobilegl/plugin/PostActivity.java +++ b/android-plugin/app/src/main/java/top/mobilegl/plugin/PostActivity.java @@ -7,6 +7,7 @@ import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.View; +import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -30,6 +31,15 @@ public final class PostActivity extends Activity { private static final int COLOR_DETAIL = 0xFFBBBBBB; private static final int COLOR_ROW_EVEN = 0xFF1A1A1A; private static final int COLOR_ROW_ODD = 0xFF121212; + private static final int COLOR_TABLE_HEADER = 0xFF303030; + private static final int COLOR_FORMAT_CELL = 0xFF242424; + private static final int COLOR_CAPABILITY_FULL = 0xFF2E7D32; + private static final int COLOR_CAPABILITY_CAVEAT = 0xFFFBC02D; + private static final int COLOR_CAPABILITY_NONE = 0xFFC62828; + + private static final int FORMAT_COLUMN_WIDTH_DP = 152; + private static final int CAPABILITY_COLUMN_WIDTH_DP = 152; + private static final int CAPABILITY_ROW_HEIGHT_DP = 36; private static final String INDICATOR_COLLAPSED = "▸"; private static final String INDICATOR_EXPANDED = "▾"; @@ -244,25 +254,26 @@ public final class PostActivity extends Activity { } JSONArray checks = backend.optJSONArray("checks"); - if (checks == null) { - return; - } - LinearLayout table = new LinearLayout(this); - table.setOrientation(LinearLayout.VERTICAL); - LinearLayout.LayoutParams tableParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - tableParams.topMargin = dp(6); - contentLayout.addView(table, tableParams); - int rowIndex = 0; - for (int i = 0; i < checks.length(); ++i) { - JSONObject check = checks.optJSONObject(i); - if (check == null) { - continue; + if (checks != null) { + LinearLayout table = new LinearLayout(this); + table.setOrientation(LinearLayout.VERTICAL); + LinearLayout.LayoutParams tableParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + tableParams.topMargin = dp(6); + contentLayout.addView(table, tableParams); + int rowIndex = 0; + for (int i = 0; i < checks.length(); ++i) { + JSONObject check = checks.optJSONObject(i); + if (check == null) { + continue; + } + addCheckRow(table, check, rowIndex++); } - addCheckRow(table, check, rowIndex++); } + + renderFormatCapabilities(backend.optJSONObject("formatCapabilities")); } /** @@ -336,6 +347,178 @@ public final class PostActivity extends Activity { }); } + /** Adds one initially-collapsed capability matrix for every reported target. */ + private void renderFormatCapabilities(JSONObject formatCapabilities) { + addText("Format capabilities", 14, COLOR_TEXT, true, dp(16)); + if (formatCapabilities == null) { + addText("Format capability table unavailable.", 12, COLOR_INFO, false, dp(4)); + return; + } + + JSONArray capabilities = formatCapabilities.optJSONArray("capabilities"); + JSONArray targets = formatCapabilities.optJSONArray("targets"); + if (capabilities == null || capabilities.length() == 0 || targets == null || targets.length() == 0) { + addText("Format capability table is empty.", 12, COLOR_INFO, false, dp(4)); + return; + } + + for (int targetIndex = 0; targetIndex < targets.length(); ++targetIndex) { + JSONObject target = targets.optJSONObject(targetIndex); + if (target != null) { + addFormatTargetTable(target, capabilities); + } + } + } + + /** + * Adds a target header whose table is created only while expanded. Removing the + * table again on collapse avoids retaining all status cells for both backends. + */ + private void addFormatTargetTable(JSONObject target, JSONArray capabilities) { + String targetName = target.optString("name", "Unknown target"); + JSONArray rows = target.optJSONArray("rows"); + int formatCount = rows == null ? 0 : rows.length(); + + LinearLayout section = new LinearLayout(this); + section.setOrientation(LinearLayout.VERTICAL); + LinearLayout.LayoutParams sectionParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + sectionParams.topMargin = dp(6); + contentLayout.addView(section, sectionParams); + + TextView toggleView = makeText( + INDICATOR_COLLAPSED + " " + targetName + " (" + formatCount + " formats)", + 12, + COLOR_TEXT, + true + ); + toggleView.setBackgroundColor(COLOR_ROW_EVEN); + toggleView.setGravity(Gravity.CENTER_VERTICAL); + toggleView.setMinimumHeight(dp(44)); + toggleView.setPadding(dp(10), dp(6), dp(10), dp(6)); + section.addView(toggleView, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + toggleView.setOnClickListener(view -> { + boolean expanded = section.getChildCount() > 1; + if (expanded) { + section.removeViews(1, section.getChildCount() - 1); + toggleView.setText( + INDICATOR_COLLAPSED + " " + targetName + " (" + formatCount + " formats)" + ); + return; + } + + section.addView(buildFormatCapabilityTable(capabilities, rows), new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + toggleView.setText( + INDICATOR_EXPANDED + " " + targetName + " (" + formatCount + " formats)" + ); + }); + } + + /** Builds the horizontally-scrollable table for one target. */ + private HorizontalScrollView buildFormatCapabilityTable(JSONArray capabilities, JSONArray rows) { + HorizontalScrollView scrollView = new HorizontalScrollView(this); + scrollView.setFillViewport(false); + scrollView.setHorizontalScrollBarEnabled(true); + scrollView.setPadding(0, dp(2), 0, dp(4)); + + LinearLayout table = new LinearLayout(this); + table.setOrientation(LinearLayout.VERTICAL); + table.addView(buildFormatCapabilityHeader(capabilities)); + + if (rows != null) { + for (int rowIndex = 0; rowIndex < rows.length(); ++rowIndex) { + JSONArray row = rows.optJSONArray(rowIndex); + if (row != null) { + table.addView(buildFormatCapabilityRow(capabilities.length(), row)); + } + } + } + + scrollView.addView(table, new HorizontalScrollView.LayoutParams( + HorizontalScrollView.LayoutParams.WRAP_CONTENT, + HorizontalScrollView.LayoutParams.WRAP_CONTENT + )); + return scrollView; + } + + private LinearLayout buildFormatCapabilityHeader(JSONArray capabilities) { + LinearLayout header = new LinearLayout(this); + header.setOrientation(LinearLayout.HORIZONTAL); + addFormatTableCell(header, "Format", FORMAT_COLUMN_WIDTH_DP, COLOR_TABLE_HEADER, COLOR_TEXT, true); + for (int capabilityIndex = 0; capabilityIndex < capabilities.length(); ++capabilityIndex) { + addFormatTableCell( + header, + capabilities.optString(capabilityIndex, "Capability " + capabilityIndex), + CAPABILITY_COLUMN_WIDTH_DP, + COLOR_TABLE_HEADER, + COLOR_TEXT, + true + ); + } + return header; + } + + private LinearLayout buildFormatCapabilityRow(int capabilityCount, JSONArray row) { + LinearLayout line = new LinearLayout(this); + line.setOrientation(LinearLayout.HORIZONTAL); + + addFormatTableCell( + line, + row.optString(0, "Unknown"), + FORMAT_COLUMN_WIDTH_DP, + COLOR_FORMAT_CELL, + COLOR_TEXT, + false + ); + + long fullMask = row.optLong(1, 0L); + long caveatMask = row.optLong(2, 0L); + for (int capabilityIndex = 0; capabilityIndex < capabilityCount; ++capabilityIndex) { + long capabilityBit = capabilityIndex < Long.SIZE ? 1L << capabilityIndex : 0L; + boolean full = capabilityBit != 0L && (fullMask & capabilityBit) != 0L; + boolean caveat = !full && capabilityBit != 0L && (caveatMask & capabilityBit) != 0L; + addFormatTableCell( + line, + full ? "Full" : caveat ? "Caveat" : "None", + CAPABILITY_COLUMN_WIDTH_DP, + full ? COLOR_CAPABILITY_FULL : caveat ? COLOR_CAPABILITY_CAVEAT : COLOR_CAPABILITY_NONE, + caveat ? 0xFF000000 : 0xFFFFFFFF, + true + ); + } + return line; + } + + private void addFormatTableCell(LinearLayout row, + String text, + int widthDp, + int backgroundColor, + int textColor, + boolean bold) { + TextView cell = makeText(text, 10, textColor, bold); + cell.setBackgroundColor(backgroundColor); + cell.setGravity(Gravity.CENTER); + cell.setSingleLine(true); + cell.setPadding(dp(6), 0, dp(6), 0); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + dp(widthDp), + dp(CAPABILITY_ROW_HEIGHT_DP) + ); + params.rightMargin = dp(1); + params.bottomMargin = dp(1); + row.addView(cell, params); + } + /** Adds the collapsed "Raw report" toggle plus the (initially hidden) raw JSON dump. */ private void addRawJsonSection(String json) { TextView toggleView = addText(INDICATOR_COLLAPSED + " Raw report (tap to expand)", 12, COLOR_INFO, true, dp(24));