[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 MobileGL::MG_Backend {
namespace { 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) { Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
(void)dpy; (void)dpy;
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT; 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(); 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, const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache,
SizeT targetIndex, SizeT targetIndex,
SizeT formatIndex, SizeT formatIndex,
@@ -94,22 +43,17 @@ namespace MobileGL::MG_Backend {
} }
SizeT GetCapabilityColumnWidth(FormatCapability capability) { SizeT GetCapabilityColumnWidth(FormatCapability capability) {
SizeT width = std::strlen(ConvertFormatCapabilityToString(capability)); SizeT width = std::strlen(GetFormatCapabilityName(capability));
width = std::max<SizeT>(width, std::strlen("Caveat")); width = std::max<SizeT>(width, std::strlen("Caveat"));
return width; return width;
} }
String GetFormatCapabilityTargetName(SizeT targetIndex) {
if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) return "Renderbuffer";
return MG_Util::ConvertTextureTargetToString(static_cast<TextureTarget>(targetIndex));
}
String BuildFormatCapabilityHeader(SizeT formatNameWidth) { String BuildFormatCapabilityHeader(SizeT formatNameWidth) {
std::ostringstream line; std::ostringstream line;
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) << ""; 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))) line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
<< ConvertFormatCapabilityToString(capability); << GetFormatCapabilityName(capability);
} }
return line.str(); return line.str();
} }
@@ -122,7 +66,7 @@ namespace MobileGL::MG_Backend {
std::ostringstream line; std::ostringstream line;
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) line << std::left << std::setw(static_cast<Int>(formatNameWidth))
<< MG_Util::ConvertTextureInternalFormatToString(format); << MG_Util::ConvertTextureInternalFormatToString(format);
for (FormatCapability capability : kPrintedFormatCapabilities) { for (FormatCapability capability : kReportedFormatCapabilities) {
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability))) line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
<< GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability); << GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability);
} }
@@ -160,6 +104,50 @@ namespace MobileGL::MG_Backend {
return kFormatCapabilityRenderbufferTargetIndex; 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) { void PrintFormatCapabilities(const FormatCapabilityCache& cache) {
const SizeT formatNameWidth = GetPrintedFormatNameWidth(); const SizeT formatNameWidth = GetPrintedFormatNameWidth();
+19
View File
@@ -47,6 +47,23 @@ namespace MobileGL {
using FormatCapabilityFlags = Flags<FormatCapability>; 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 = inline constexpr SizeT kFormatCapabilityTextureTargetCount =
static_cast<SizeT>(TextureTarget::TextureTargetCount); static_cast<SizeT>(TextureTarget::TextureTargetCount);
inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount; inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount;
@@ -70,6 +87,8 @@ namespace MobileGL {
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability); Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability);
SizeT GetFormatCapabilityTargetIndex(TextureTarget target); SizeT GetFormatCapabilityTargetIndex(TextureTarget target);
SizeT GetRenderbufferFormatCapabilityTargetIndex(); SizeT GetRenderbufferFormatCapabilityTargetIndex();
const char* GetFormatCapabilityName(FormatCapability capability);
String GetFormatCapabilityTargetName(SizeT targetIndex);
void PrintFormatCapabilities(const FormatCapabilityCache& cache); void PrintFormatCapabilities(const FormatCapabilityCache& cache);
// Opaque backend fence-sync handle, created by GLFunctionsTable::FenceSync // Opaque backend fence-sync handle, created by GLFunctionsTable::FenceSync
@@ -150,23 +150,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
String Reason; 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 BuildNativeProbeFormatInfo(GLenum requestedInternalFormat) {
GLESProbeFormatInfo info; GLESProbeFormatInfo info;
info.InternalFormat = requestedInternalFormat; info.InternalFormat = requestedInternalFormat;
@@ -176,9 +159,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
return info; return info;
} }
Flags<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions() { Flags<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions(
const MG_External::GLESCapabilities& capabilities) {
Flags<PixelFormatNormalizeOptionBit> options; Flags<PixelFormatNormalizeOptionBit> options;
if (g_GLESCapabilities.IsAngleRenderer) { if (capabilities.IsAngleRenderer) {
options |= PixelFormatNormalizeOptionBit::NoRgb16; options |= PixelFormatNormalizeOptionBit::NoRgb16;
options |= PixelFormatNormalizeOptionBit::NoSnorm16; options |= PixelFormatNormalizeOptionBit::NoSnorm16;
options |= PixelFormatNormalizeOptionBit::NoSnorm8; options |= PixelFormatNormalizeOptionBit::NoSnorm8;
@@ -186,11 +170,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
return options; return options;
} }
Flags<PixelFormatNormalizeOptionBit> GetDriverPixelFormatNormalizeOptions() { Flags<PixelFormatNormalizeOptionBit> GetDriverPixelFormatNormalizeOptions(
const MG_External::GLESCapabilities& capabilities) {
Flags<PixelFormatNormalizeOptionBit> options = PixelFormatNormalizeOptionBit::NoDepthComponent32; Flags<PixelFormatNormalizeOptionBit> options = PixelFormatNormalizeOptionBit::NoDepthComponent32;
options |= PixelFormatNormalizeOptionBit::NoRGBA8Snorm; options |= PixelFormatNormalizeOptionBit::NoRGBA8Snorm;
options |= PixelFormatNormalizeOptionBit::NoRGB16Snorm; options |= PixelFormatNormalizeOptionBit::NoRGB16Snorm;
if (!g_GLESCapabilities.SupportsNorm16Texture) { if (!capabilities.SupportsNorm16Texture) {
options |= PixelFormatNormalizeOptionBit::NoNorm16; options |= PixelFormatNormalizeOptionBit::NoNorm16;
} }
return options; return options;
@@ -241,21 +226,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
return MG_Util::ConvertGLEnumToString(internalFormat); 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, void LogGLESFormatCaveat(TextureInternalFormat logicalFormat,
SizeT targetIndex, SizeT targetIndex,
const GLESProbeFormatInfo& fallbackInfo) { const GLESProbeFormatInfo& fallbackInfo) {
MGLOG_D("Caveat: %s %s not fully supported. Reason: %s. Fallback: %s", 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(), MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
fallbackInfo.Reason.c_str(), fallbackInfo.Reason.c_str(),
ConvertFallbackInternalFormatToString(fallbackInfo.InternalFormat).c_str()); ConvertFallbackInternalFormatToString(fallbackInfo.InternalFormat).c_str());
@@ -306,7 +281,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
SizeT formatIndex, SizeT formatIndex,
FormatCapabilityFlags caps) { FormatCapabilityFlags caps) {
Bool added = false; Bool added = false;
for (FormatCapability capability : kGLESProbeCapabilities) { for (FormatCapability capability : kReportedFormatCapabilities) {
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;
@@ -316,7 +291,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return added; return added;
} }
Int GetGLESFormatMaxSamples(const DynamicBackendParameters& dynamicParameters, Int GetGLESFormatMaxSamples(const MG_External::GLESCapabilities& capabilities,
TextureInternalFormat logicalFormat, TextureInternalFormat logicalFormat,
GLenum imageFormat) { GLenum imageFormat) {
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat); 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 || const Bool isInteger = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER ||
imageFormat == GL_RGB_INTEGER || imageFormat == GL_RGBA_INTEGER; imageFormat == GL_RGB_INTEGER || imageFormat == GL_RGBA_INTEGER;
if (isDepth || isStencil) { if (isDepth || isStencil) {
return dynamicParameters.MaxDepthTextureSamples; return capabilities.MaxDepthTextureSamples;
} }
if (isInteger) { if (isInteger) {
return dynamicParameters.MaxIntegerSamples; return capabilities.MaxIntegerSamples;
} }
return dynamicParameters.MaxColorTextureSamples; return capabilities.MaxColorTextureSamples;
} }
Bool ProbeFramebufferCompletenessForTexture(const MG_External::GLESFunctionsTable& gl, Bool ProbeFramebufferCompletenessForTexture(const MG_External::GLESFunctionsTable& gl,
@@ -527,12 +502,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
return sampleCounts; return sampleCounts;
} }
void ProbeGLESFormatCapabilities(const MG_External::GLESFunctionsTable& gl, void PopulateFormatCapabilitiesImpl(const MG_External::GLESFunctionsTable& gl,
FormatCapabilityCache& cache, const MG_External::GLESCapabilities& capabilities,
const DynamicBackendParameters& dynamicParameters) { FormatCapabilityCache& cache) {
cache.Clear(); cache.Clear();
const Flags<PixelFormatNormalizeOptionBit> forcedOptions = GetForcedPixelFormatNormalizeOptions(); const Flags<PixelFormatNormalizeOptionBit> forcedOptions =
const Flags<PixelFormatNormalizeOptionBit> driverOptions = GetDriverPixelFormatNormalizeOptions(); GetForcedPixelFormatNormalizeOptions(capabilities);
const Flags<PixelFormatNormalizeOptionBit> driverOptions =
GetDriverPixelFormatNormalizeOptions(capabilities);
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) { for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
const auto logicalFormat = static_cast<TextureInternalFormat>(formatIndex); const auto logicalFormat = static_cast<TextureInternalFormat>(formatIndex);
@@ -594,7 +571,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
AddFullFormatCaps(cache, renderbufferTargetIndex, formatIndex, AddFullFormatCaps(cache, renderbufferTargetIndex, formatIndex,
GetRenderbufferFeatureCaps(logicalFormat)); GetRenderbufferFeatureCaps(logicalFormat));
const Int maxSamples = const Int maxSamples =
GetGLESFormatMaxSamples(dynamicParameters, logicalFormat, nativeInfo.ImageFormat); GetGLESFormatMaxSamples(capabilities, logicalFormat, nativeInfo.ImageFormat);
cache.SampleCounts[renderbufferTargetIndex][formatIndex] = cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
ProbeRenderbufferSampleCounts(gl, nativeInfo.InternalFormat, logicalFormat, maxSamples); ProbeRenderbufferSampleCounts(gl, nativeInfo.InternalFormat, logicalFormat, maxSamples);
} else { } else {
@@ -608,7 +585,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo); LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo);
} }
const Int maxSamples = const Int maxSamples =
GetGLESFormatMaxSamples(dynamicParameters, logicalFormat, fallbackInfo.ImageFormat); GetGLESFormatMaxSamples(capabilities, logicalFormat, fallbackInfo.ImageFormat);
cache.SampleCounts[renderbufferTargetIndex][formatIndex] = cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
ProbeRenderbufferSampleCounts(gl, fallbackInfo.InternalFormat, logicalFormat, maxSamples); ProbeRenderbufferSampleCounts(gl, fallbackInfo.InternalFormat, logicalFormat, maxSamples);
} }
@@ -655,6 +632,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
} // namespace } // namespace
void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl,
const MG_External::GLESCapabilities& capabilities,
FormatCapabilityCache& cache) {
PopulateFormatCapabilitiesImpl(gl, capabilities, cache);
}
BackendObject_DirectGLES::~BackendObject_DirectGLES() { BackendObject_DirectGLES::~BackendObject_DirectGLES() {
DestroyEGLContext(); DestroyEGLContext();
} }
@@ -695,7 +678,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the extension list is first built). // the extension list is first built).
UpdateAdvertisedTimerQueryExtension(); UpdateAdvertisedTimerQueryExtension();
UpdateDynamicBackendParameters(); UpdateDynamicBackendParameters();
ProbeGLESFormatCapabilities(m_GLESFunctions, MutableFormatCapabilities(), m_dynamicParameters); PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
PrintFormatCapabilities(GetFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities());
return true; return true;
} }
@@ -12,6 +12,12 @@
#include <MG_Util/BackendLoaders/OpenGL/Loader.h> #include <MG_Util/BackendLoaders/OpenGL/Loader.h>
namespace MobileGL::MG_Backend::DirectGLES { 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 { class BackendObject_DirectGLES : public BackendObject {
public: public:
~BackendObject_DirectGLES() override; ~BackendObject_DirectGLES() override;
@@ -182,25 +182,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return MG_Util::ConvertTextureInternalFormatToVkEnum(*fallbackLogicalFormat); 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) { Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) {
for (FormatCapability capability : kVulkanProbeCapabilities) { for (FormatCapability capability : kReportedFormatCapabilities) {
if (HasFormatCapability(fallbackCaps, capability) && if (HasFormatCapability(fallbackCaps, capability) &&
!HasFormatCapability(nativeCaps, capability)) { !HasFormatCapability(nativeCaps, capability)) {
return true; return true;
@@ -209,21 +192,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false; 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, void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat,
SizeT targetIndex, SizeT targetIndex,
TextureInternalFormat fallbackFormat) { TextureInternalFormat fallbackFormat) {
MGLOG_D("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s", 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(logicalFormat).c_str(),
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str()); MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
} }
@@ -237,11 +210,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return counts; return counts;
} }
void FillVulkanFormatCapabilities(VkPhysicalDevice physicalDevice, void PopulateFormatCapabilitiesImpl(VkPhysicalDevice physicalDevice,
const DynamicBackendParameters& dynamicParameters, PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
const MG_External::VulkanCapabilities& capabilities,
FormatCapabilityCache& cache) { FormatCapabilityCache& cache) {
cache.Clear(); cache.Clear();
if (physicalDevice == VK_NULL_HANDLE) { if (physicalDevice == VK_NULL_HANDLE || getFormatProperties == nullptr) {
return; return;
} }
@@ -258,12 +232,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkFormatProperties nativeProperties{}; VkFormatProperties nativeProperties{};
if (nativeFormat != VK_FORMAT_UNDEFINED) { if (nativeFormat != VK_FORMAT_UNDEFINED) {
vkGetPhysicalDeviceFormatProperties(physicalDevice, nativeFormat, &nativeProperties); getFormatProperties(physicalDevice, nativeFormat, &nativeProperties);
} }
VkFormatProperties fallbackProperties{}; VkFormatProperties fallbackProperties{};
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) { if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
vkGetPhysicalDeviceFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties); getFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties);
} }
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { 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 isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat); const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
const Bool isInteger = IsIntegerInternalFormat(logicalFormat); const Bool isInteger = IsIntegerInternalFormat(logicalFormat);
Int maxSamples = dynamicParameters.MaxColorTextureSamples; Int maxSamples = capabilities.MaxColorTextureSamples;
if (isDepth || isStencil) { if (isDepth || isStencil) {
maxSamples = dynamicParameters.MaxDepthTextureSamples; maxSamples = capabilities.MaxDepthTextureSamples;
} else if (isInteger) { } else if (isInteger) {
maxSamples = dynamicParameters.MaxIntegerSamples; maxSamples = capabilities.MaxIntegerSamples;
} }
cache.SampleCounts[targetIndex][formatIndex] = BuildSampleCounts(maxSamples); cache.SampleCounts[targetIndex][formatIndex] = BuildSampleCounts(maxSamples);
} }
@@ -332,12 +306,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
cache.CaveatCaps[renderbufferTargetIndex][formatIndex]; cache.CaveatCaps[renderbufferTargetIndex][formatIndex];
if (HasFormatCapability(rbCaps, FormatCapability::MultisampleRenderbuffer)) { if (HasFormatCapability(rbCaps, FormatCapability::MultisampleRenderbuffer)) {
cache.SampleCounts[renderbufferTargetIndex][formatIndex] = cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
BuildSampleCounts(dynamicParameters.MaxFramebufferSamples); BuildSampleCounts(capabilities.MaxFramebufferSamples);
} }
} }
} }
} // namespace } // 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() = default;
BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {} BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {}
@@ -394,7 +375,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
UpdateDynamicBackendParameters(); UpdateDynamicBackendParameters();
UpdateAdvertisedExtensions(); UpdateAdvertisedExtensions();
FillVulkanFormatCapabilities(physicalDevice.handle, m_dynamicParameters, MutableFormatCapabilities()); PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
MutableFormatCapabilities());
PrintFormatCapabilities(GetFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities());
return true; return true;
} }
@@ -12,6 +12,14 @@
#include <MG_Util/BackendLoaders/Vulkan/Loader.h> #include <MG_Util/BackendLoaders/Vulkan/Loader.h>
namespace MobileGL::MG_Backend::DirectVulkan { 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 { class BackendObject_DirectVulkan : public BackendObject {
public: public:
BackendObject_DirectVulkan(); BackendObject_DirectVulkan();
+14
View File
@@ -525,6 +525,9 @@ namespace MobileGL::MG_Util::SelfTest {
summary.capsValid = true; summary.capsValid = true;
const MG_External::GLESCapabilities& caps = summary.caps; const MG_External::GLESCapabilities& caps = summary.caps;
builder.report.rendererInfo = format("{} ({})", caps.GLESRendererString, caps.GLESVersionString); 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); EvaluateGlesChecklist(builder, caps, glesFuncs);
ProbeGlesTimerQuery(builder, caps, glesFuncs); ProbeGlesTimerQuery(builder, caps, glesFuncs);
} while (false); } while (false);
@@ -980,6 +983,9 @@ namespace MobileGL::MG_Util::SelfTest {
getInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2")); getInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2"));
const auto vkGetPhysicalDeviceProperties2Fn = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>( const auto vkGetPhysicalDeviceProperties2Fn = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
getInstanceProcAddr(instance, "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 // 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. // 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.deviceName = String(properties.deviceName);
summary.apiVersionString = VkApiVersionToString(properties.apiVersion); summary.apiVersionString = VkApiVersionToString(properties.apiVersion);
summary.driverVersionString = driverVersionString; 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 // The chosen-device facts (name, enumeration count, graphics queue) ride along
// on both outcomes so the device API verdict never hides them. // on both outcomes so the device API verdict never hides them.
+2
View File
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include <Includes.h> #include <Includes.h>
#include <MG_Backend/BackendObject.h>
namespace MobileGL::MG_Util::SelfTest { namespace MobileGL::MG_Util::SelfTest {
// One row of a backend power-on self-test (POST) report. // 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 verdict = "UNSUPPORTED"; // "OK" | "DEGRADED" | "UNSUPPORTED"
String rendererInfo; String rendererInfo;
Vector<PostCheck> checks; Vector<PostCheck> checks;
Optional<MG_Backend::FormatCapabilityCache> formatCapabilities;
}; };
// Probes the DEVICE GLES driver with self-contained EGL boilerplate (1x1 pbuffer // Probes the DEVICE GLES driver with self-contained EGL boilerplate (1x1 pbuffer
+58 -1
View File
@@ -11,6 +11,7 @@
#ifdef __ANDROID__ #ifdef __ANDROID__
#include "DriverPost.h" #include "DriverPost.h"
#include <MG_Util/Converters/MGToStr/TextureEnumConverter.h>
#include <jni.h> #include <jni.h>
@@ -19,6 +20,9 @@ namespace {
using MobileGL::SizeT; using MobileGL::SizeT;
using MobileGL::String; using MobileGL::String;
using MobileGL::StringStream; 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::BackendPostReport;
using MobileGL::MG_Util::SelfTest::PostCheck; using MobileGL::MG_Util::SelfTest::PostCheck;
@@ -70,6 +74,55 @@ namespace {
out << '"' << EscapeJsonString(value) << '"'; 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) { void AppendBackendReportJson(StringStream& out, const BackendPostReport& report) {
out << "{\"available\":" << (report.available ? "true" : "false"); out << "{\"available\":" << (report.available ? "true" : "false");
out << ",\"verdict\":"; out << ",\"verdict\":";
@@ -90,7 +143,11 @@ namespace {
AppendJsonString(out, check.detail); AppendJsonString(out, check.detail);
out << '}'; out << '}';
} }
out << "]}"; out << ']';
if (report.formatCapabilities.has_value()) {
AppendFormatCapabilitiesJson(out, report.formatCapabilities.value());
}
out << '}';
} }
} // namespace } // namespace
@@ -7,6 +7,7 @@ import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.TextView; 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_DETAIL = 0xFFBBBBBB;
private static final int COLOR_ROW_EVEN = 0xFF1A1A1A; private static final int COLOR_ROW_EVEN = 0xFF1A1A1A;
private static final int COLOR_ROW_ODD = 0xFF121212; 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_COLLAPSED = "";
private static final String INDICATOR_EXPANDED = ""; private static final String INDICATOR_EXPANDED = "";
@@ -244,9 +254,7 @@ public final class PostActivity extends Activity {
} }
JSONArray checks = backend.optJSONArray("checks"); JSONArray checks = backend.optJSONArray("checks");
if (checks == null) { if (checks != null) {
return;
}
LinearLayout table = new LinearLayout(this); LinearLayout table = new LinearLayout(this);
table.setOrientation(LinearLayout.VERTICAL); table.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams tableParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams tableParams = new LinearLayout.LayoutParams(
@@ -265,6 +273,9 @@ public final class PostActivity extends Activity {
} }
} }
renderFormatCapabilities(backend.optJSONObject("formatCapabilities"));
}
/** /**
* Adds one two-column check row (name | status chip) to the table. Rows with * Adds one two-column check row (name | status chip) to the table. Rows with
* a non-empty detail get a collapse indicator and toggle the detail text on tap; * a non-empty detail get a collapse indicator and toggle the detail text on tap;
@@ -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. */ /** Adds the collapsed "Raw report" toggle plus the (initially hidden) raw JSON dump. */
private void addRawJsonSection(String json) { private void addRawJsonSection(String json) {
TextView toggleView = addText(INDICATOR_COLLAPSED + " Raw report (tap to expand)", 12, COLOR_INFO, true, dp(24)); TextView toggleView = addText(INDICATOR_COLLAPSED + " Raw report (tap to expand)", 12, COLOR_INFO, true, dp(24));