mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Feat] (MG_Backend, android-plugin): show format capability tables in POST
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user