mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 13:18:31 +09:00
[Fix, Test] (TextureFormatProcessor, DirectGLES, MG_IntegrationTest): give every unrenderable signed-normalized colour attachment an exact float substitute
This commit is contained in:
@@ -558,8 +558,9 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
} 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");
|
||||
"render target is stored as a float (8-bit -> *16F, 16-bit -> *32F, which "
|
||||
"is the narrowest float that still holds a 16-bit SNORM channel exactly) "
|
||||
"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
|
||||
|
||||
@@ -31,32 +31,41 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRgb16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
break;
|
||||
// The two render-target bits reach EVERY signed-normalized format, one-, two- and
|
||||
// four-channel included. They used to be granted to GL_RGB16_SNORM alone, which left the
|
||||
// other seven with no colour-renderable fallback at all on a driver without
|
||||
// EXT_render_snorm: an R8_SNORM or R16_SNORM attachment (what KHR-GL4x.texture_swizzle
|
||||
// renders into for every SNORM source format) got no substitute, so the ES framebuffer was
|
||||
// incomplete, the draw landed nowhere and the readback fell through to the never-written
|
||||
// CPU shadow.
|
||||
case GL_RGB16_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRGB16Snorm;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
||||
}
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
||||
break;
|
||||
case GL_RGBA16_SNORM:
|
||||
case GL_RG16_SNORM:
|
||||
case GL_R16_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
||||
break;
|
||||
case GL_RGBA8_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget;
|
||||
break;
|
||||
case GL_RGB8_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget;
|
||||
break;
|
||||
case GL_RG8_SNORM:
|
||||
case GL_R8_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget;
|
||||
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
|
||||
@@ -113,9 +122,12 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
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.
|
||||
// signed-normalized encoding whenever the driver can render to it - and when it
|
||||
// cannot, widen to the 32-bit float, which is the only renderable storage that
|
||||
// still holds all 65535 channel values exactly. GL_RGBA16F here handed -23451/32767
|
||||
// back as -23457, six times the +/-1-step window KHR-GL4x.texture_swizzle allows.
|
||||
return (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget)
|
||||
? ThreeChannelWidening{GL_RGBA16F, GL_RGBA, GL_FLOAT}
|
||||
? ThreeChannelWidening{GL_RGBA32F, 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.
|
||||
@@ -203,7 +215,17 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
}
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
// NoSnorm16RenderTarget outranks the other two 16-bit fallbacks on purpose: it is the
|
||||
// only one whose substitute has to be EXACT, so it picks the 32-bit float rather than
|
||||
// the half the driver/ANGLE fallbacks settle for. The capability probe folds the
|
||||
// driver options and the render-target options into one set while the runtime storage
|
||||
// choice can see the render-target bit alone (GetRuntimeFallbackNormalizeOptions), so
|
||||
// the two would disagree on the storage format without a fixed precedence.
|
||||
case GL_RGBA16_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) {
|
||||
*outInternalFormat = GL_RGBA32F;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16)) {
|
||||
*outInternalFormat = GL_RGBA16F;
|
||||
@@ -212,6 +234,12 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB16_SNORM:
|
||||
// The three-channel widening below replaces this whenever the target has to stay
|
||||
// renderable; GL_RGB32F keeps the precision for the targets that do not.
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) {
|
||||
*outInternalFormat = GL_RGB32F;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGB16Snorm) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16)) {
|
||||
@@ -221,6 +249,10 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RG16_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) {
|
||||
*outInternalFormat = GL_RG32F;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16)) {
|
||||
*outInternalFormat = GL_RG16F;
|
||||
@@ -229,6 +261,10 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_R16_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) {
|
||||
*outInternalFormat = GL_R32F;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16)) {
|
||||
*outInternalFormat = GL_R16F;
|
||||
@@ -236,30 +272,36 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
}
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
// 8-bit SNORM: the half float already IS exact here, so the render-target bit lands on
|
||||
// the same storage the other two 8-bit fallbacks pick.
|
||||
case GL_RGBA8_SNORM:
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm)) {
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget)) {
|
||||
*outInternalFormat = GL_RGBA16F;
|
||||
break;
|
||||
}
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB8_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm8) {
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget)) {
|
||||
*outInternalFormat = GL_RGB16F;
|
||||
break;
|
||||
}
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RG8_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm8) {
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget)) {
|
||||
*outInternalFormat = GL_RG16F;
|
||||
break;
|
||||
}
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_R8_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm8) {
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget)) {
|
||||
*outInternalFormat = GL_R16F;
|
||||
break;
|
||||
}
|
||||
@@ -533,7 +575,8 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(internalFormat == GL_RGB16_SNORM &&
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGB16Snorm)) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16)) {
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget)) {
|
||||
*outType = GL_FLOAT;
|
||||
break;
|
||||
} else {
|
||||
@@ -543,7 +586,8 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
case GL_RGB8_SNORM:
|
||||
case GL_RG8_SNORM:
|
||||
case GL_R8_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm8) {
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget)) {
|
||||
*outType = GL_FLOAT;
|
||||
break;
|
||||
}
|
||||
@@ -551,7 +595,8 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
break;
|
||||
case GL_RGBA8_SNORM:
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoSnorm8) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm)) {
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm8RenderTarget)) {
|
||||
*outType = GL_FLOAT;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,21 @@ namespace MobileGL {
|
||||
// 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
|
||||
// EXT_render_snorm. Without them the only renderable widening left is a half float, whose
|
||||
// 11-bit mantissa cannot represent a 16-bit SNORM channel exactly.
|
||||
// A 16-bit signed-normalized image has to back a colour attachment, and the driver cannot
|
||||
// render to the signed-normalized encoding itself: that needs both EXT_texture_norm16 and
|
||||
// EXT_render_snorm, and without either one an R16_SNORM / RG16_SNORM / RGB16_SNORM /
|
||||
// RGBA16_SNORM attachment is texture-only, so the framebuffer is never complete and the
|
||||
// draw silently lands nowhere. The substitute is a 32-bit float, NOT the half float the
|
||||
// other SNORM fallbacks use: a half's 11-bit mantissa cannot represent a 16-bit SNORM
|
||||
// channel exactly - its spacing just below 1.0 is 2^-11, some 16 SNORM steps, so
|
||||
// -23451/32767 comes back as -23457 - while a 32-bit float round-trips every one of the
|
||||
// 65535 channel values bit for bit.
|
||||
NoSnorm16RenderTarget = 1 << 8,
|
||||
// The 8-bit twin of the bit above: without EXT_render_snorm an R8_SNORM / RG8_SNORM /
|
||||
// RGB8_SNORM / RGBA8_SNORM colour attachment is not renderable either. Here a half float
|
||||
// IS exact - every value in [-127, 127] divided by 127 round-trips through a half - so the
|
||||
// substitute matches what the always-on GL_RGBA8_SNORM fallback already picks.
|
||||
NoSnorm8RenderTarget = 1 << 9,
|
||||
None = 0,
|
||||
};
|
||||
namespace MG_Util::TextureFormatProcessor {
|
||||
|
||||
Reference in New Issue
Block a user