[Feat] (MG_Backend, android-plugin): show format capability tables in POST

This commit is contained in:
2026-07-16 00:10:11 -04:00
parent 1b0be9a997
commit 4453f1910d
10 changed files with 405 additions and 163 deletions
+48 -60
View File
@@ -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<SizeT>(width, std::strlen("Caveat"));
return width;
}
String GetFormatCapabilityTargetName(SizeT targetIndex) {
if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) return "Renderbuffer";
return MG_Util::ConvertTextureTargetToString(static_cast<TextureTarget>(targetIndex));
}
String BuildFormatCapabilityHeader(SizeT formatNameWidth) {
std::ostringstream line;
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) << "";
for (FormatCapability capability : kPrintedFormatCapabilities) {
for (FormatCapability capability : kReportedFormatCapabilities) {
line << " | " << std::left << std::setw(static_cast<Int>(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<Int>(formatNameWidth))
<< MG_Util::ConvertTextureInternalFormatToString(format);
for (FormatCapability capability : kPrintedFormatCapabilities) {
for (FormatCapability capability : kReportedFormatCapabilities) {
line << " | " << std::left << std::setw(static_cast<Int>(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<TextureTarget>(targetIndex));
}
void PrintFormatCapabilities(const FormatCapabilityCache& cache) {
const SizeT formatNameWidth = GetPrintedFormatNameWidth();
+19
View File
@@ -47,6 +47,23 @@ namespace MobileGL {
using FormatCapabilityFlags = Flags<FormatCapability>;
inline constexpr Array<FormatCapability, 14> 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<SizeT>(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
@@ -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<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions() {
Flags<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions(
const MG_External::GLESCapabilities& capabilities) {
Flags<PixelFormatNormalizeOptionBit> 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<PixelFormatNormalizeOptionBit> GetDriverPixelFormatNormalizeOptions() {
Flags<PixelFormatNormalizeOptionBit> GetDriverPixelFormatNormalizeOptions(
const MG_External::GLESCapabilities& capabilities) {
Flags<PixelFormatNormalizeOptionBit> 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<TextureTarget>(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<PixelFormatNormalizeOptionBit> forcedOptions = GetForcedPixelFormatNormalizeOptions();
const Flags<PixelFormatNormalizeOptionBit> driverOptions = GetDriverPixelFormatNormalizeOptions();
const Flags<PixelFormatNormalizeOptionBit> forcedOptions =
GetForcedPixelFormatNormalizeOptions(capabilities);
const Flags<PixelFormatNormalizeOptionBit> driverOptions =
GetDriverPixelFormatNormalizeOptions(capabilities);
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
const auto logicalFormat = static_cast<TextureInternalFormat>(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;
}
@@ -12,6 +12,12 @@
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
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;
@@ -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<TextureTarget>(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;
}
@@ -12,6 +12,14 @@
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
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();
+14
View File
@@ -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<PFN_vkGetPhysicalDeviceProperties2>(
getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2"));
const auto vkGetPhysicalDeviceFormatPropertiesFn =
reinterpret_cast<PFN_vkGetPhysicalDeviceFormatProperties>(
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.
+2
View File
@@ -8,6 +8,7 @@
#pragma once
#include <Includes.h>
#include <MG_Backend/BackendObject.h>
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<PostCheck> checks;
Optional<MG_Backend::FormatCapabilityCache> formatCapabilities;
};
// Probes the DEVICE GLES driver with self-contained EGL boilerplate (1x1 pbuffer
+58 -1
View File
@@ -11,6 +11,7 @@
#ifdef __ANDROID__
#include "DriverPost.h"
#include <MG_Util/Converters/MGToStr/TextureEnumConverter.h>
#include <jni.h>
@@ -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<MobileGL::TextureInternalFormat>(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
@@ -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));