mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
[Fix, Test] (MG_Backend/DirectGLES, MG_Util, MG_Impl): widen three-channel render targets wherever the driver refuses them
Complementary Reimagined would not load through Espryt on Mali: Iris got GL_FRAMEBUFFER_UNSUPPORTED building its composite framebuffer, because colortex1 is RGB8_SNORM and colortex2 is RGB16F - three-channel formats that no real ES driver can render to (EXT_render_snorm covers R/RG/RGBA only, and the float extensions exclude the RGB forms). The frontend's probe cache diagnosed this correctly and then had nothing to offer: the NoThreeChannelRenderTarget widening machinery existed but was gated to multisample targets alone. llvmpipe turns out to refuse most of the same attachments - CI retrace stayed green only because a replay never branches on glCheckFramebufferStatus - so this was never a desktop-vs- device split, just an unlit path. The widening now applies to every color-attachable image, renderbuffers included, riding the driver-probe branch so the native format is still tried first and substituted only on refusal. One ThreeChannelWidening table owns the widened (internalformat, format, type) triple per source format - the previous per-case branches disagreed with each other and could emit an unuploadable (RGBA16F, GL_RGB, GL_BYTE) combination or widen into another three-channel format the driver refuses just the same. Uploads repack three-component client data to four with the format's own one in the alpha channel (127 is not 1 for RGB8I - the integer arms carry integer ones); readback drops the synthetic alpha, derived from the actual image being read, not the bound framebuffer, so glGetTexImage through a scratch FBO cannot be confused by an unrelated widened attachment. Stored alpha on a widened attachment is now an invariant 1.0 rather than an accident: the color-mask sync clears the alpha bit per draw buffer (glColorMaski for MRT mixes), and clears route through glClearBufferfv with alpha substituted on widened slots only - scissored clears inherit the discipline for free, integer color buffers keep their explicit integer-clear path, and glGet still answers the application's own mask. GL_DST_ALPHA blending, blits and readback therefore all see 1.0 without further interception. DriverPost grows the rows this bug earned: EXT_color_buffer_float detection (previously unreferenced anywhere) with a FAIL row when absent, the missing EXT_render_snorm row, and a three-channel- attachment row that reports one representative per widening class - graded so a half-float-only driver warns about the 32-bit float gap instead of being declared unsupported. Gates: 606/606 unit at default and with the async kill switch; full retrace, both backends - the complementary fixtures now run with the widening ACTIVE on llvmpipe and pass with a slightly better SSIM than before; ext caselist DirectGLES holds 3914/4867 with zero set drift while 54 cases move from NotSupported to genuinely passing; on the Mali-G77 device, Complementary Reimagined builds its pipeline and renders in-world through Espryt (md5-verified build), BSL still green. A new ThreeChannelAttachmentScenario pins the frontend answer - COMPLETE where it used to say UNSUPPORTED - on the real driver.
This commit is contained in:
@@ -838,6 +838,12 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_EXT_render_snorm") == 0) {
|
||||
caps.SupportsRenderSnorm = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_color_buffer_float") == 0) {
|
||||
caps.SupportsColorBufferFloat = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_color_buffer_half_float") == 0) {
|
||||
caps.SupportsColorBufferHalfFloat = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_sRGB_write_control") == 0) {
|
||||
caps.SupportsSrgbWriteControl = true;
|
||||
}
|
||||
|
||||
@@ -1034,6 +1034,16 @@ namespace MobileGL {
|
||||
// GL_EXT_render_snorm is present, so the signed-normalized formats are colour-renderable
|
||||
// (and usable as multisample texture storage) rather than texture-only.
|
||||
Bool SupportsRenderSnorm = false;
|
||||
// GL_EXT_color_buffer_float is present, so GL_RGBA16F / GL_RGBA32F / GL_R11F_G11F_B10F
|
||||
// (and the R/RG float formats) are colour-renderable. ES 3.x core makes them
|
||||
// texture-only, and every Iris shaderpack renders into at least R11F_G11F_B10F, so
|
||||
// without this no shaderpack can work at all.
|
||||
Bool SupportsColorBufferFloat = false;
|
||||
// GL_EXT_color_buffer_half_float is present: the half-float subset of the above, for
|
||||
// drivers that ship only the smaller extension. Note it does NOT rescue GL_RGB16F -
|
||||
// the extension nominally lists it but disclaims it under ES 3.x, and real drivers
|
||||
// reject it, which is why three-channel float attachments are widened instead.
|
||||
Bool SupportsColorBufferHalfFloat = false;
|
||||
// GL_EXT_sRGB_write_control is present, so GL_FRAMEBUFFER_SRGB can be turned off.
|
||||
// GLES has no such switch in core: writes into an sRGB attachment are ALWAYS encoded,
|
||||
// while desktop GL leaves GL_FRAMEBUFFER_SRGB disabled by default and writes raw.
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
// Only for the compile-time MAX_VERTEX_ATTRIBS constant asserted below. The POST still executes no
|
||||
// MG_State code: it runs standalone, before MG_State::Init().
|
||||
#include <MG_State/GLState/VertexArrayState/VertexArrayObject.h>
|
||||
#include <MG_Backend/DirectGLES/Utils.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToStr/GLExtensionConverter.h>
|
||||
#include <MG_Util/Converters/MGToStr/TextureEnumConverter.h>
|
||||
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
||||
#include <MG_Util/Async/ShaderCompilePool.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
@@ -450,6 +455,35 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
builder.Warn("GL_EXT_texture_norm16",
|
||||
"not supported; 16-bit normalized texture formats need emulation");
|
||||
}
|
||||
if (caps.SupportsRenderSnorm) {
|
||||
builder.Pass("GL_EXT_render_snorm",
|
||||
"supported (signed-normalized formats are colour-renderable, so an "
|
||||
"SNORM render target keeps its own encoding instead of a float substitute)");
|
||||
} else {
|
||||
builder.Warn("GL_EXT_render_snorm",
|
||||
"not supported; signed-normalized formats are texture-only, so every SNORM "
|
||||
"render target is stored as a float (GL_RGBA8_SNORM/GL_RGB8_SNORM -> "
|
||||
"GL_RGBA16F) and its fragment outputs are clamped to [-1,1] in software");
|
||||
}
|
||||
// FAIL, not WARN: ES 3.x core makes every float format texture-only, and every Iris
|
||||
// shaderpack renders into at least GL_R11F_G11F_B10F (Complementary's colortex0, BSL's
|
||||
// colortex0). Without this extension there is no substitute format left - a half float
|
||||
// is not renderable either - so shaderpacks cannot work at all on such a driver.
|
||||
if (caps.SupportsColorBufferFloat) {
|
||||
builder.Pass("GL_EXT_color_buffer_float",
|
||||
"supported (GL_R11F_G11F_B10F / GL_RGBA16F / GL_RGBA32F are "
|
||||
"colour-renderable, which is what every shaderpack renders into)");
|
||||
} else if (caps.SupportsColorBufferHalfFloat) {
|
||||
builder.Warn("GL_EXT_color_buffer_float",
|
||||
"not supported, but GL_EXT_color_buffer_half_float is; 16-bit float render "
|
||||
"targets work, 32-bit float ones (GL_RGBA32F, and the GL_RGBA16 fallback "
|
||||
"that lands on it) do not");
|
||||
} else {
|
||||
builder.Fail("GL_EXT_color_buffer_float",
|
||||
"not supported, and neither is GL_EXT_color_buffer_half_float; no floating-point "
|
||||
"format is colour-renderable on this driver, so no shaderpack can create its "
|
||||
"render targets (Iris reports GL_FRAMEBUFFER_UNSUPPORTED and refuses to load)");
|
||||
}
|
||||
|
||||
// INFO, never WARN: this is the HOST driver's ability to compile its own ESSL on
|
||||
// its own threads, and MobileGL's asynchronous compilation does not depend on it
|
||||
@@ -783,6 +817,117 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
}
|
||||
}
|
||||
|
||||
// No real ES driver renders to a three-channel image, but desktop GL applications ask for
|
||||
// one constantly - Complementary Reimagined's colortex1 is GL_RGB8_SNORM and its colortex2
|
||||
// is GL_RGB16F, and Iris refuses to load when a framebuffer built from them is not
|
||||
// COMPLETE. DirectGLES substitutes the four-channel sibling, and this row names the
|
||||
// outcome per format so the failure mode is a five-second read instead of an
|
||||
// investigation. Answered from the capability cache that was just probed on this very
|
||||
// driver, so it costs no extra GL work.
|
||||
void ReportThreeChannelColorAttachments(ReportBuilder& builder, const MG_External::GLESCapabilities& caps,
|
||||
const MG_Backend::FormatCapabilityCache& cache) {
|
||||
// GL_RGB8 is the control: it is ES-core renderable, and it is exactly why BSL loads on
|
||||
// the same driver where Complementary does not. The rest are one representative of
|
||||
// each widening class - signed-normalized, half float, 32-bit float, sRGB, integer -
|
||||
// so the row says which CLASS of shaderpack target a device cannot serve rather than
|
||||
// just "three-channel formats".
|
||||
constexpr TextureInternalFormat kProbedFormats[] = {
|
||||
TextureInternalFormat::RGB8, TextureInternalFormat::RGB8Snorm, TextureInternalFormat::RGB16F,
|
||||
TextureInternalFormat::RGB32F, TextureInternalFormat::SRGB8, TextureInternalFormat::RGB8UI};
|
||||
const SizeT targetIndex = MG_Backend::GetFormatCapabilityTargetIndex(TextureTarget::Texture2D);
|
||||
const Flags<PixelFormatNormalizeOptionBit> renderTargetOptions =
|
||||
MG_Backend::DirectGLES::TextureImpl::GetRenderTargetNormalizeOptions(caps, targetIndex);
|
||||
|
||||
String nativeList;
|
||||
String widenedList;
|
||||
String unusableList;
|
||||
// GL_RGB8 is colour-renderable in ES 3.0 CORE. A driver that answers no to it is
|
||||
// broken (or the probe itself is), and that is the ONLY three-channel verdict that
|
||||
// deserves a FAIL on its own - see the verdict block below.
|
||||
Bool controlFormatBroken = false;
|
||||
const auto append = [](String& list, const String& entry) {
|
||||
if (!list.empty()) list += ", ";
|
||||
list += entry;
|
||||
};
|
||||
|
||||
for (const TextureInternalFormat probedFormat : kProbedFormats) {
|
||||
const SizeT formatIndex = static_cast<SizeT>(probedFormat);
|
||||
const String name = MG_Util::ConvertTextureInternalFormatToString(probedFormat);
|
||||
if (MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][formatIndex],
|
||||
MG_Backend::FormatCapability::FramebufferRenderable)) {
|
||||
append(nativeList, name);
|
||||
continue;
|
||||
}
|
||||
if (probedFormat == TextureInternalFormat::RGB8) {
|
||||
controlFormatBroken = true;
|
||||
}
|
||||
if (MG_Backend::HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex],
|
||||
MG_Backend::FormatCapability::FramebufferRenderable)) {
|
||||
GLenum widenedInternalFormat = GL_UNKNOWN_MGL;
|
||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||
MG_Util::ConvertTextureInternalFormatToGLEnum(probedFormat), renderTargetOptions,
|
||||
&widenedInternalFormat, nullptr, nullptr);
|
||||
append(widenedList, name + " -> " + MG_Util::ConvertGLEnumToString(widenedInternalFormat));
|
||||
continue;
|
||||
}
|
||||
append(unusableList, name);
|
||||
}
|
||||
|
||||
String detail;
|
||||
if (!nativeList.empty()) detail += "renderable natively: " + nativeList;
|
||||
if (!widenedList.empty()) {
|
||||
if (!detail.empty()) detail += "; ";
|
||||
detail += "widened to stay renderable: " + widenedList;
|
||||
}
|
||||
if (!unusableList.empty()) {
|
||||
if (!detail.empty()) detail += "; ";
|
||||
detail += "NOT renderable and not substitutable: " + unusableList;
|
||||
}
|
||||
|
||||
// The verdict deliberately does NOT track "every probed format came out usable".
|
||||
//
|
||||
// GL_RGB32F widens to GL_RGBA32F, and GL_RGBA32F is colour-renderable only under
|
||||
// GL_EXT_color_buffer_float. A perfectly healthy half-float-only driver (the common
|
||||
// mobile shape: EXT_color_buffer_half_float and nothing more) therefore reports
|
||||
// GL_RGB32F as unusable while every format a shaderpack actually renders into works.
|
||||
// FAILing that device would make the POST's hardest verdict fire on a configuration
|
||||
// MobileGL runs fine on, which is exactly how a report stops being read.
|
||||
//
|
||||
// So FAIL is reserved for the two answers that really are broken:
|
||||
// * the ES-core control (GL_RGB8) is not renderable - the probe or the driver is
|
||||
// wrong about something much more basic than three-channel widening; and
|
||||
// * a widenable format has no usable fallback ON A DRIVER THAT ADVERTISES
|
||||
// GL_EXT_color_buffer_float - the extension promises the widened float targets
|
||||
// are renderable, so a gap here is a real, unexplained refusal.
|
||||
// Everything else is a WARN carrying the exact per-format status, which is what the
|
||||
// row is for. The "no float render targets at all" case is already a FAIL of its own
|
||||
// on the GL_EXT_color_buffer_float row above; repeating it here would only double-count.
|
||||
if (controlFormatBroken) {
|
||||
builder.Fail("Three-channel colour attachments",
|
||||
detail + " - GL_RGB8 is colour-renderable in OpenGL ES 3.0 core, so a driver "
|
||||
"that refuses it cannot render to ANY three-channel attachment and the "
|
||||
"capability probe itself is suspect");
|
||||
} else if (!unusableList.empty() && caps.SupportsColorBufferFloat) {
|
||||
builder.Fail("Three-channel colour attachments",
|
||||
detail + " - GL_EXT_color_buffer_float is supported, so the widened "
|
||||
"four-channel float targets are required to be renderable; a framebuffer "
|
||||
"using one of the formats above still reports GL_FRAMEBUFFER_UNSUPPORTED, "
|
||||
"which Iris turns into a hard load failure");
|
||||
} else if (!unusableList.empty()) {
|
||||
builder.Warn("Three-channel colour attachments",
|
||||
detail + " - without GL_EXT_color_buffer_float the 32-bit float widening has no "
|
||||
"renderable target left, so a shaderpack asking for one of the formats "
|
||||
"above gets GL_FRAMEBUFFER_UNSUPPORTED; the half-float and fixed-point "
|
||||
"ones above still work");
|
||||
} else if (!widenedList.empty()) {
|
||||
builder.Warn("Three-channel colour attachments",
|
||||
detail + " - the substitution costs the extra alpha channel's memory and is "
|
||||
"hidden from the application by an ALPHA->ONE swizzle");
|
||||
} else {
|
||||
builder.Pass("Three-channel colour attachments", detail);
|
||||
}
|
||||
}
|
||||
|
||||
// Everything the "MobileGL reported ..." rows need from the GLES device probe.
|
||||
struct GlesProbeSummary {
|
||||
Bool capsValid = false;
|
||||
@@ -913,6 +1058,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
builder.report.formatCapabilities.emplace();
|
||||
MG_Backend::DirectGLES::PopulateFormatCapabilities(
|
||||
glesFuncs, caps, builder.report.formatCapabilities.value());
|
||||
ReportThreeChannelColorAttachments(builder, caps, builder.report.formatCapabilities.value());
|
||||
} while (false);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,12 +58,99 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
case GL_R8_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
break;
|
||||
// The rest of the three-channel formats no real ES driver renders to. They have no
|
||||
// other fallback: none of the driver/forced option bits names them, so before the
|
||||
// render-target widening existed for ordinary targets an FBO attachment in one of
|
||||
// them could only ever be answered GL_FRAMEBUFFER_UNSUPPORTED (Complementary
|
||||
// Reimagined's colortex2 = RGB16F).
|
||||
//
|
||||
// GL_RGB9_E5 is deliberately absent: its four-channel sibling would have to be a
|
||||
// half float, which means unpacking the shared exponent on every transfer, and
|
||||
// nothing renders to a shared-exponent format on desktop GL either.
|
||||
case GL_RGB16F:
|
||||
case GL_RGB32F:
|
||||
case GL_SRGB8:
|
||||
case GL_RGB8I:
|
||||
case GL_RGB8UI:
|
||||
case GL_RGB16I:
|
||||
case GL_RGB16UI:
|
||||
case GL_RGB32I:
|
||||
case GL_RGB32UI:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return applicableOptions;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// The four-channel sibling a three-channel format is widened to when the target has to
|
||||
// stay colour-renderable, together with the transfer pair that describes client data for
|
||||
// it. Kept in one place because all three of NormalizePixelFormat's switches have to agree:
|
||||
// reporting the widened storage but the original three-channel base format emitted
|
||||
// inconsistent triples such as (GL_RGBA16F, GL_RGB, GL_BYTE), which is
|
||||
// GL_INVALID_OPERATION for glTexImage2D on ES. That only ever went unnoticed because the
|
||||
// bit was reachable for multisample storage alone, and glTexStorage*Multisample takes no
|
||||
// transfer pair at all.
|
||||
struct ThreeChannelWidening {
|
||||
GLenum InternalFormat = GL_UNKNOWN_MGL;
|
||||
GLenum Format = GL_UNKNOWN_MGL;
|
||||
GLenum Type = GL_UNKNOWN_MGL;
|
||||
|
||||
explicit operator Bool() const { return InternalFormat != GL_UNKNOWN_MGL; }
|
||||
};
|
||||
|
||||
ThreeChannelWidening GetThreeChannelRenderTargetWidening(GLenum internalFormat,
|
||||
Flags<PixelFormatNormalizeOptionBit> options) {
|
||||
if (!(options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget)) {
|
||||
return {};
|
||||
}
|
||||
switch (internalFormat) {
|
||||
// Signed-normalized: matches what the always-on NoRGBA8Snorm fallback already does to
|
||||
// GL_RGBA8_SNORM, so the two SNORM8 formats land on the same storage.
|
||||
case GL_RGB8_SNORM:
|
||||
return {GL_RGBA16F, GL_RGBA, GL_FLOAT};
|
||||
case GL_RGB16_SNORM:
|
||||
// A half float loses the low bits of a 16-bit SNORM channel, so keep the
|
||||
// signed-normalized encoding whenever the driver can render to it.
|
||||
return (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget)
|
||||
? ThreeChannelWidening{GL_RGBA16F, GL_RGBA, GL_FLOAT}
|
||||
: ThreeChannelWidening{GL_RGBA16_SNORM, GL_RGBA, GL_SHORT};
|
||||
// Unsigned-normalized 16-bit (and the legacy 10/12-bit formats stored as RGB16):
|
||||
// GL_RGB32F is a legal ES texture format but is not colour-renderable either.
|
||||
case GL_RGB16:
|
||||
case GL_RGB10:
|
||||
case GL_RGB12:
|
||||
return {GL_RGBA32F, GL_RGBA, GL_FLOAT};
|
||||
// Floating point.
|
||||
case GL_RGB16F:
|
||||
return {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT};
|
||||
case GL_RGB32F:
|
||||
return {GL_RGBA32F, GL_RGBA, GL_FLOAT};
|
||||
// sRGB: GL_SRGB8_ALPHA8 keeps the sRGB encoding of the colour channels and stores
|
||||
// the added alpha linearly, which is exactly the three-channel format's semantics.
|
||||
case GL_SRGB8:
|
||||
return {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||
// Integer.
|
||||
case GL_RGB8I:
|
||||
return {GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE};
|
||||
case GL_RGB8UI:
|
||||
return {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE};
|
||||
case GL_RGB16I:
|
||||
return {GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT};
|
||||
case GL_RGB16UI:
|
||||
return {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT};
|
||||
case GL_RGB32I:
|
||||
return {GL_RGBA32I, GL_RGBA_INTEGER, GL_INT};
|
||||
case GL_RGB32UI:
|
||||
return {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void NormalizePixelFormat(GLenum internalFormat, Flags<PixelFormatNormalizeOptionBit> options,
|
||||
GLenum* outInternalFormat, GLenum* outFormat, GLenum* outType) {
|
||||
#ifdef TRACY_ENABLE
|
||||
@@ -95,13 +182,6 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB16:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
// GL_RGB32F is a legal ES texture format but is not colour-renderable, so
|
||||
// glTexStorage2DMultisample rejects it and the attachment ends up with no
|
||||
// storage at all.
|
||||
*outInternalFormat = GL_RGBA32F;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoRgb16)) {
|
||||
*outInternalFormat = GL_RGB32F;
|
||||
@@ -132,14 +212,6 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB16_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
// A half float loses the low bits of a 16-bit SNORM channel, so keep the
|
||||
// signed-normalized encoding whenever the driver can render to it.
|
||||
*outInternalFormat = (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget)
|
||||
? GL_RGBA16F
|
||||
: GL_RGBA16_SNORM;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGB16Snorm) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16)) {
|
||||
@@ -173,10 +245,6 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB8_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
*outInternalFormat = GL_RGBA16F;
|
||||
break;
|
||||
}
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm8) {
|
||||
*outInternalFormat = GL_RGB16F;
|
||||
break;
|
||||
@@ -615,5 +683,18 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Applied last, over whatever the three switches above chose: widening a three-channel
|
||||
// format to keep a colour attachment renderable outranks every other fallback, because
|
||||
// the others all pick a three-channel storage the driver still refuses to render to
|
||||
// (GL_RGB8_SNORM -> GL_RGB16F under NoSnorm8, GL_RGB16 -> GL_RGB32F under NoNorm16).
|
||||
// All three outputs move together: reporting the widened storage while leaving the
|
||||
// three-channel base format and its component type in place produced triples like
|
||||
// (GL_RGBA16F, GL_RGB, GL_BYTE), which ES rejects for glTexImage2D outright.
|
||||
if (const ThreeChannelWidening widening = GetThreeChannelRenderTargetWidening(internalFormat, options)) {
|
||||
if (outInternalFormat) *outInternalFormat = widening.InternalFormat;
|
||||
if (outFormat) *outFormat = widening.Format;
|
||||
if (outType) *outType = widening.Type;
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Util::TextureFormatProcessor
|
||||
|
||||
@@ -20,9 +20,14 @@ namespace MobileGL {
|
||||
NoRGB16Snorm = 1 << 6,
|
||||
// The target must be colour-renderable and ES has no renderable three-channel
|
||||
// form of the requested format, so it has to be widened to the four-channel one.
|
||||
// Only meaningful for multisample textures: those can never be uploaded to, only
|
||||
// rendered into, so the extra alpha comes from the draw (1.0 for an RGB source)
|
||||
// and no transfer path has to expand three-channel client data.
|
||||
// Set for any colour-attachable target whose native three-channel form the driver
|
||||
// refused to render to (multisample storage always, since ES has no three-channel
|
||||
// multisample format at all; every other target only after its native probe failed).
|
||||
// The widening is visible to every transfer path, so it also retargets the (format,
|
||||
// type) pair NormalizePixelFormat reports: the upload has to describe four
|
||||
// components in the widened storage's component type, the backend has to expand
|
||||
// three-channel client data with an alpha of 1.0, and sampling/readback has to hide
|
||||
// the added alpha again (BackendTextureFormatAddsAlpha).
|
||||
NoThreeChannelRenderTarget = 1 << 7,
|
||||
// Pairs with the bit above: the widened four-channel format has to stay renderable AND
|
||||
// keep 16-bit signed-normalized precision, which needs both EXT_texture_norm16 and
|
||||
|
||||
Reference in New Issue
Block a user