mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Fix] (Espryt): store RGB565/RGB5_A1/RGBA4 as 8-bit channels where a POST probe measures the Mali packed16 array-mip field-order mirror
This commit is contained in:
@@ -127,6 +127,21 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
GLfloat clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GLint packAlignment = 4;
|
||||
GLint packRowLength = 0;
|
||||
// The rest of the pixel-transfer scope. The probes that upload or read back texels
|
||||
// run under whatever scope their caller left - the lazy ones run from live paths,
|
||||
// not just the POST screen - and a caller's skip/row-length/PBO would silently
|
||||
// shear a probe's own data. Saved so a probe can zero them and the caller gets
|
||||
// them back.
|
||||
GLint packSkipPixels = 0;
|
||||
GLint packSkipRows = 0;
|
||||
GLint unpackAlignment = 4;
|
||||
GLint unpackRowLength = 0;
|
||||
GLint unpackImageHeight = 0;
|
||||
GLint unpackSkipPixels = 0;
|
||||
GLint unpackSkipRows = 0;
|
||||
GLint unpackSkipImages = 0;
|
||||
GLint pixelPackBuffer = 0;
|
||||
GLint pixelUnpackBuffer = 0;
|
||||
GLint imageName = 0;
|
||||
GLint imageLevel = 0;
|
||||
GLint imageLayered = 0;
|
||||
@@ -166,6 +181,16 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
gl.glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &state.texture2DArray);
|
||||
gl.glGetIntegerv(GL_PACK_ALIGNMENT, &state.packAlignment);
|
||||
gl.glGetIntegerv(GL_PACK_ROW_LENGTH, &state.packRowLength);
|
||||
gl.glGetIntegerv(GL_PACK_SKIP_PIXELS, &state.packSkipPixels);
|
||||
gl.glGetIntegerv(GL_PACK_SKIP_ROWS, &state.packSkipRows);
|
||||
gl.glGetIntegerv(GL_UNPACK_ALIGNMENT, &state.unpackAlignment);
|
||||
gl.glGetIntegerv(GL_UNPACK_ROW_LENGTH, &state.unpackRowLength);
|
||||
gl.glGetIntegerv(GL_UNPACK_IMAGE_HEIGHT, &state.unpackImageHeight);
|
||||
gl.glGetIntegerv(GL_UNPACK_SKIP_PIXELS, &state.unpackSkipPixels);
|
||||
gl.glGetIntegerv(GL_UNPACK_SKIP_ROWS, &state.unpackSkipRows);
|
||||
gl.glGetIntegerv(GL_UNPACK_SKIP_IMAGES, &state.unpackSkipImages);
|
||||
gl.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &state.pixelPackBuffer);
|
||||
gl.glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &state.pixelUnpackBuffer);
|
||||
if (gl.glGetFloatv != nullptr) {
|
||||
gl.glGetFloatv(GL_COLOR_CLEAR_VALUE, state.clearColor);
|
||||
}
|
||||
@@ -221,6 +246,18 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
if (gl.glPixelStorei != nullptr) {
|
||||
gl.glPixelStorei(GL_PACK_ALIGNMENT, state.packAlignment);
|
||||
gl.glPixelStorei(GL_PACK_ROW_LENGTH, state.packRowLength);
|
||||
gl.glPixelStorei(GL_PACK_SKIP_PIXELS, state.packSkipPixels);
|
||||
gl.glPixelStorei(GL_PACK_SKIP_ROWS, state.packSkipRows);
|
||||
gl.glPixelStorei(GL_UNPACK_ALIGNMENT, state.unpackAlignment);
|
||||
gl.glPixelStorei(GL_UNPACK_ROW_LENGTH, state.unpackRowLength);
|
||||
gl.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, state.unpackImageHeight);
|
||||
gl.glPixelStorei(GL_UNPACK_SKIP_PIXELS, state.unpackSkipPixels);
|
||||
gl.glPixelStorei(GL_UNPACK_SKIP_ROWS, state.unpackSkipRows);
|
||||
gl.glPixelStorei(GL_UNPACK_SKIP_IMAGES, state.unpackSkipImages);
|
||||
}
|
||||
if (gl.glBindBuffer != nullptr) {
|
||||
gl.glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast<GLuint>(state.pixelPackBuffer));
|
||||
gl.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(state.pixelUnpackBuffer));
|
||||
}
|
||||
if (gl.glClearColor != nullptr) {
|
||||
gl.glClearColor(state.clearColor[0], state.clearColor[1], state.clearColor[2],
|
||||
@@ -1886,6 +1923,199 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
return measurement;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// ===================== PACKED16 COPY-IMAGE FIELD ORDER =====================
|
||||
|
||||
constexpr const char* kPacked16CopyProbeName = "packed16 copy-image field order";
|
||||
|
||||
// The shape the KHR-GL4x.copy_image failures pin, verbatim: on the affected Mali only a
|
||||
// 2D array whose base level is 30x30x12 showed the divergence at LEVEL 1 (the same
|
||||
// suite's level-0 copies and a 14x14 base's level 1 round-trip clean), so the probe
|
||||
// reproduces those dimensions rather than a minimal shape that might sit on the clean
|
||||
// side of whatever allocation threshold picks the driver's layout.
|
||||
constexpr GLsizei kPacked16BaseSize = 30;
|
||||
constexpr GLsizei kPacked16Layers = 12;
|
||||
constexpr GLsizei kPacked16DstSize = 7;
|
||||
|
||||
// One GL_RGB5_A1 texel, as the client word the probe uploads everywhere:
|
||||
// (R, G, B, A) = (0, 1, 3, 1) under GL_UNSIGNED_SHORT_5_5_5_1. Chosen because 5551 is
|
||||
// the one 16-bit packed layout whose field widths are not a palindrome - its mirror
|
||||
// fixes the DIRECTION of the swap - and because this word's mirror differs in every
|
||||
// channel including alpha, so no expansion rounding can confuse the two predictions.
|
||||
constexpr Uint16 kPacked16Word = 0x0047;
|
||||
// What an FBO readback answers for the word, as UNorm8: (0, 1, 3) / 31 and alpha 1.
|
||||
constexpr GLubyte kPacked16Expected[4] = {0, 8, 25, 255};
|
||||
// The same readback when the stored bits are the mirrored re-encoding: 0x0047 decoded
|
||||
// as 5_5_5_1 and re-encoded as 1_5_5_5_REV is 0x8C20, which the destination's non-REV
|
||||
// layout then decodes as (17, 16, 16) / 31 with alpha 0. This is byte-for-byte the
|
||||
// arithmetic behind every failing CTS body (src 0x0047 -> got 0x8C20).
|
||||
constexpr GLubyte kPacked16Mirrored[4] = {140, 132, 132, 0};
|
||||
// A 5-bit step is 255/31 ~ 8.2 UNorm8 codes; half a step accepts every 5-bit-to-8-bit
|
||||
// expansion a driver uses (floor, round, bit replication) while still telling two
|
||||
// adjacent 5-bit values apart.
|
||||
constexpr Int kPacked16Tolerance = 4;
|
||||
|
||||
// A 2-level GL_RGB5_A1 2D array allocated the way MobileGL's own mutable-texture path
|
||||
// allocates one (glTexImage3D per level), with every texel of both levels holding
|
||||
// kPacked16Word. MAX_LEVEL is clamped so the two-level chain is complete - some
|
||||
// drivers refuse glCopyImageSubData on an incomplete texture.
|
||||
GLuint MakePacked16ArrayTexture(const GLESFunctionsTable& gl) {
|
||||
GLuint texture = 0;
|
||||
gl.glGenTextures(1, &texture);
|
||||
if (texture == 0) return 0;
|
||||
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
for (GLint level = 0; level < 2; ++level) {
|
||||
const GLsizei size = kPacked16BaseSize >> level;
|
||||
const Vector<Uint16> words(
|
||||
static_cast<SizeT>(size) * static_cast<SizeT>(size) * kPacked16Layers, kPacked16Word);
|
||||
gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, level, GL_RGB5_A1, size, size, kPacked16Layers, 0,
|
||||
GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, words.data());
|
||||
}
|
||||
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
return texture;
|
||||
}
|
||||
|
||||
// A one-level GL_RGB5_A1 2D destination filled with 0xFFFF - the CTS's own (1,1,1,1)
|
||||
// destination fill - so a copy that silently did nothing reads as "no verdict" rather
|
||||
// than as either prediction.
|
||||
GLuint MakePacked16DstTexture(const GLESFunctionsTable& gl) {
|
||||
GLuint texture = 0;
|
||||
gl.glGenTextures(1, &texture);
|
||||
if (texture == 0) return 0;
|
||||
gl.glBindTexture(GL_TEXTURE_2D, texture);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
const Vector<Uint16> fill(static_cast<SizeT>(kPacked16DstSize) * kPacked16DstSize, Uint16{0xFFFF});
|
||||
gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, kPacked16DstSize, kPacked16DstSize, 0, GL_RGBA,
|
||||
GL_UNSIGNED_SHORT_5_5_5_1, fill.data());
|
||||
gl.glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return texture;
|
||||
}
|
||||
|
||||
// The destination's texel (0, 0), through a framebuffer of its own. False when the
|
||||
// attachment is incomplete or the read errors - both are declines, not verdicts.
|
||||
Bool ReadPacked16DstTexel(const GLESFunctionsTable& gl, GLuint texture, GLubyte out[4]) {
|
||||
GLuint framebuffer = 0;
|
||||
gl.glGenFramebuffers(1, &framebuffer);
|
||||
if (framebuffer == 0) return false;
|
||||
gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||
gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||
Bool read = false;
|
||||
if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
||||
gl.glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
Drain(gl);
|
||||
gl.glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, out);
|
||||
read = gl.glGetError() == GL_NO_ERROR;
|
||||
}
|
||||
gl.glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
gl.glDeleteFramebuffers(1, &framebuffer);
|
||||
Drain(gl);
|
||||
return read;
|
||||
}
|
||||
|
||||
// Copies a kPacked16DstSize-square region out of layer 0 of the array's `sourceLevel`
|
||||
// onto a freshly filled 2D destination and hands back the destination's texel (0, 0).
|
||||
// False when the copy raised an error or the readback could not run.
|
||||
Bool Packed16CopyLandsTexel(const GLESFunctionsTable& gl, GLuint array, GLint sourceLevel,
|
||||
GLubyte out[4]) {
|
||||
const GLuint destination = MakePacked16DstTexture(gl);
|
||||
if (destination == 0) return false;
|
||||
Drain(gl);
|
||||
gl.glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, sourceLevel, 0, 0, 0, destination,
|
||||
GL_TEXTURE_2D, 0, 0, 0, 0, kPacked16DstSize, kPacked16DstSize, 1);
|
||||
const Bool copied = gl.glGetError() == GL_NO_ERROR;
|
||||
const Bool read = copied && ReadPacked16DstTexel(gl, destination, out);
|
||||
gl.glDeleteTextures(1, &destination);
|
||||
Drain(gl);
|
||||
return read;
|
||||
}
|
||||
|
||||
Bool Packed16TexelNear(const GLubyte got[4], const GLubyte want[4]) {
|
||||
for (Int i = 0; i < 4; ++i) {
|
||||
const Int delta = static_cast<Int>(got[i]) - static_cast<Int>(want[i]);
|
||||
if (delta > kPacked16Tolerance || delta < -kPacked16Tolerance) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool ProbeCopyImageMirrorsPacked16FieldOrder(const GLESFunctionsTable& gl) {
|
||||
if (!gl.glGenTextures || !gl.glBindTexture || !gl.glTexParameteri || !gl.glTexImage2D ||
|
||||
!gl.glTexImage3D || !gl.glDeleteTextures || !gl.glCopyImageSubData || !gl.glGenFramebuffers ||
|
||||
!gl.glBindFramebuffer || !gl.glFramebufferTexture2D || !gl.glCheckFramebufferStatus ||
|
||||
!gl.glDeleteFramebuffers || !gl.glReadBuffer || !gl.glReadPixels || !gl.glPixelStorei ||
|
||||
!gl.glGetError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SavedState saved;
|
||||
Save(gl, saved);
|
||||
// The uploads and readbacks below run under the probe's own tight pixel-transfer
|
||||
// scope - a caller's skip/row-length/PBO would shear the probe's data into a false
|
||||
// verdict either way. Restore puts the caller's scope back with the rest.
|
||||
gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
gl.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
gl.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
|
||||
gl.glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
|
||||
gl.glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
|
||||
gl.glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
|
||||
gl.glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
gl.glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
gl.glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
|
||||
gl.glPixelStorei(GL_PACK_SKIP_ROWS, 0);
|
||||
if (gl.glBindBuffer != nullptr) {
|
||||
gl.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
gl.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
Drain(gl);
|
||||
|
||||
Bool detected = false;
|
||||
const GLuint array = MakePacked16ArrayTexture(gl);
|
||||
GLubyte control[4] = {0, 0, 0, 0};
|
||||
GLubyte subject[4] = {0, 0, 0, 0};
|
||||
// THE CONTROL: the identical copy out of the array's LEVEL 0, which is clean on the
|
||||
// affected driver too. It proves glCopyImageSubData works between a 5551 array and a
|
||||
// 5551 2D image at all, that the upload and the FBO readback round-trip the word, and
|
||||
// that only the mip level moves the answer - so a driver with no copy_image, or none
|
||||
// for these formats, reaches no verdict instead of being reported as this.
|
||||
if (array == 0 || !Packed16CopyLandsTexel(gl, array, 0, control)) {
|
||||
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-0 control copy could not run)",
|
||||
kPacked16CopyProbeName);
|
||||
} else if (!Packed16TexelNear(control, kPacked16Expected)) {
|
||||
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-0 control read back "
|
||||
"(%d, %d, %d, %d) instead of the uploaded word's (%d, %d, %d, %d))",
|
||||
kPacked16CopyProbeName, control[0], control[1], control[2], control[3],
|
||||
kPacked16Expected[0], kPacked16Expected[1], kPacked16Expected[2], kPacked16Expected[3]);
|
||||
} else if (!Packed16CopyLandsTexel(gl, array, 1, subject)) {
|
||||
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-1 subject copy could not run)",
|
||||
kPacked16CopyProbeName);
|
||||
} else if (Packed16TexelNear(subject, kPacked16Mirrored)) {
|
||||
detected = true;
|
||||
MGLOG_I("[driver-bug] %s probe: a copy out of the array's level 1 delivered "
|
||||
"(%d, %d, %d, %d), the 1_5_5_5_REV re-encoding of the word - THE FIELD ORDER "
|
||||
"OF A NON-ZERO ARRAY MIP LEVEL IS MIRRORED",
|
||||
kPacked16CopyProbeName, subject[0], subject[1], subject[2], subject[3]);
|
||||
} else if (!Packed16TexelNear(subject, kPacked16Expected)) {
|
||||
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-1 copy read back "
|
||||
"(%d, %d, %d, %d), which is neither the word nor its mirror)",
|
||||
kPacked16CopyProbeName, subject[0], subject[1], subject[2], subject[3]);
|
||||
}
|
||||
if (array != 0) gl.glDeleteTextures(1, &array);
|
||||
Restore(gl, saved);
|
||||
return detected;
|
||||
}
|
||||
|
||||
Bool CopyImageMirrorsPacked16FieldOrder(const GLESFunctionsTable& gl) {
|
||||
// One driver per process, and the answer is structural (the driver's storage layout
|
||||
// for a shape), not sampled.
|
||||
static const Bool mirrored = ProbeCopyImageMirrorsPacked16FieldOrder(gl);
|
||||
return mirrored;
|
||||
}
|
||||
|
||||
namespace {
|
||||
Optional<DriverBugFinding> ProbeExplicitVertexInputLocationCeilingBug(const GLESFunctionsTable& gl) {
|
||||
const VertexInputLocationCeilingMeasurement& measurement = ExplicitVertexInputLocationCeiling(gl);
|
||||
@@ -2053,6 +2283,25 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
Move(detail)};
|
||||
}
|
||||
|
||||
Optional<DriverBugFinding> ProbeCopyImagePacked16FieldOrderBug(const GLESFunctionsTable& gl) {
|
||||
if (!CopyImageMirrorsPacked16FieldOrder(gl)) return std::nullopt;
|
||||
return DriverBugFinding{
|
||||
"glCopyImageSubData mirrors 16-bit packed texels at a non-zero array mip level",
|
||||
DriverBugVerdict::Fixed,
|
||||
"the driver's physical field order for a 16-bit packed texel (RGB565 / RGB5_A1 / "
|
||||
"RGBA4) at a non-zero mip level of a GL_TEXTURE_2D_ARRAY is the *_REV mirror of "
|
||||
"the order every other image uses, so a glCopyImageSubData - a raw texel-block "
|
||||
"move - between such a level and any other image lands the R/G/B/A fields "
|
||||
"reversed (a 5551 word 0x0047 arrives as 0x8C20). Uploads and readbacks of the "
|
||||
"same level are clean - the driver decodes its own layout consistently, which is "
|
||||
"this probe's control - so only the raw-copy path ever crosses the two layouts. "
|
||||
"MobileGL stores these three formats as 8-bit-per-channel ES storage on this "
|
||||
"driver instead (GL_RGB8 / GL_RGBA8, the storage their canonical shadow already "
|
||||
"holds and the client word round-trips through exactly), so no 16-bit packed "
|
||||
"image is left for a copy to disagree about, at twice the memory for images of "
|
||||
"those formats"};
|
||||
}
|
||||
|
||||
// The table. One row per known driver bug; see the header for how to add a sibling.
|
||||
using DriverBugProbeFn = Optional<DriverBugFinding> (*)(const GLESFunctionsTable&);
|
||||
constexpr DriverBugProbeFn kGlesDriverBugProbes[] = {
|
||||
@@ -2064,6 +2313,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
&ProbeExplicitVertexInputLocationCeilingBug,
|
||||
&ProbeLayeredBlitDestinationBug,
|
||||
&ProbeLocatedIoBlockPayloadBug,
|
||||
&ProbeCopyImagePacked16FieldOrderBug,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -299,6 +299,35 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual(
|
||||
const MG_External::GLESFunctionsTable& gl);
|
||||
|
||||
// Copies one known GL_UNSIGNED_SHORT_5_5_5_1 word out of a GL_RGB5_A1 2D array's mip
|
||||
// level 1 into a plain 2D image with glCopyImageSubData and reads the landed texel back.
|
||||
// Returns true only when the level-1 copy delivers the word's 5_5_5_1 <-> 1_5_5_5_REV
|
||||
// field-order mirror while the identical level-0 copy delivers the word itself.
|
||||
//
|
||||
// The affected Mali stores 16-bit packed texels (RGB565 / RGB5_A1 / RGBA4) at a non-zero
|
||||
// mip level of a 2D array in the *_REV field order every other image does NOT use.
|
||||
// Uploads and readbacks decode that layout consistently, so nothing but a raw texel-block
|
||||
// move can see it - which is exactly what glCopyImageSubData is defined to be, and why
|
||||
// the whole KHR-GL4x.copy_image rgb5/rgb5_a1/rgba4 x *2d_array* matrix fails there while
|
||||
// every other suite touching these formats passes. The texture reproduces the failing
|
||||
// shape verbatim (a 30x30x12 two-level array; a 14x14 base's level 1 measured clean on
|
||||
// the same driver, so a minimal shape might not manifest the layout).
|
||||
//
|
||||
// THE CONTROL is the identical copy out of mip level 0, which is clean on the affected
|
||||
// driver too: it proves copy_image works between these images at all and that the
|
||||
// upload/readback round trip is exact, so a driver that cannot host the shape reaches no
|
||||
// verdict instead of being reported as this. The subject must also match the mirror
|
||||
// PREDICTION, not merely differ from the expectation - a copy that delivered anything
|
||||
// else is a different defect and reaches no verdict either. Restores every piece of GL
|
||||
// state it touches.
|
||||
Bool ProbeCopyImageMirrorsPacked16FieldOrder(const MG_External::GLESFunctionsTable& gl);
|
||||
|
||||
// ProbeCopyImageMirrorsPacked16FieldOrder(), evaluated at most once per process. The
|
||||
// DirectGLES format normalization consults this to decide whether the three 16-bit packed
|
||||
// normalized formats must be stored as 8-bit-per-channel ES storage (see
|
||||
// PixelFormatNormalizeOptionBit::WidenPacked16Norm).
|
||||
Bool CopyImageMirrorsPacked16FieldOrder(const MG_External::GLESFunctionsTable& gl);
|
||||
|
||||
// Every known driver bug this GLES driver actually has. Bugs it does not have are absent,
|
||||
// so an unaffected device renders an empty section rather than a wall of "not affected".
|
||||
Vector<DriverBugFinding> CollectGlesKnownDriverBugs(const MG_External::GLESFunctionsTable& gl);
|
||||
|
||||
@@ -340,17 +340,38 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
// (VkTextureManager::ResolveTextureFormatInfo resolves all six legacy low-bit formats
|
||||
// to R8G8B8A8_UNORM), so the two backends now agree here.
|
||||
//
|
||||
// Only the DESKTOP-ONLY formats move. GL_RGBA4 and GL_RGB5_A1 are ES formats an
|
||||
// application can legitimately ask for - the same normalization picks the storage for
|
||||
// glRenderbufferStorage - so widening them would be a memory decision, not a
|
||||
// correctness one. Nothing about the REPORTED precision moves either way:
|
||||
// GL_TEXTURE_*_SIZE and glGetInternalformativ answer from TextureMetrics, keyed on the
|
||||
// requested format, not on the ES storage.
|
||||
// Only the DESKTOP-ONLY formats move UNCONDITIONALLY. GL_RGBA4, GL_RGB5_A1 and
|
||||
// GL_RGB565 are ES formats an application can legitimately ask for - the same
|
||||
// normalization picks the storage for glRenderbufferStorage - so widening them
|
||||
// used to be declined as "a memory decision, not a correctness one". The 18
|
||||
// KHR-GL4x.copy_image.functional bodies on Mali falsified that: the driver's own
|
||||
// 16-bit packed storage keeps a MIRRORED field order at a non-zero mip level of a
|
||||
// 2D array, so a raw glCopyImageSubData between such a level and any other image
|
||||
// delivers the channels reversed (0x0007 -> 0x3800 for a 5551 word: the 1_5_5_5_REV
|
||||
// re-encoding of the same fields). Where that is measured -
|
||||
// WidenPacked16Norm, set from the POST probe or its ForceOn override - the three
|
||||
// formats take the same 8-bit widening; everywhere else they stay narrow and the
|
||||
// memory argument stands. Nothing about the REPORTED precision moves either way:
|
||||
// GL_TEXTURE_*_SIZE and glGetInternalformativ answer from TextureMetrics, keyed on
|
||||
// the requested format, not on the ES storage.
|
||||
case GL_R3_G3_B2:
|
||||
case GL_RGB4:
|
||||
case GL_RGB5:
|
||||
*outInternalFormat = GL_RGB8;
|
||||
break;
|
||||
// GL_RGB5 above is nominally the same resolution, but a TEXTURE never arrives here
|
||||
// as GL_RGB5: ConvertGLEnumToTextureInternalFormat folds GL_RGB5 and GL_RGB565 onto
|
||||
// one logical format whose GL spelling is GL_RGB565, so this case is the one the
|
||||
// allocation path actually reaches for both spellings.
|
||||
case GL_RGB565:
|
||||
*outInternalFormat =
|
||||
(options & PixelFormatNormalizeOptionBit::WidenPacked16Norm) ? GL_RGB8 : internalFormat;
|
||||
break;
|
||||
case GL_RGB5_A1:
|
||||
case GL_RGBA4:
|
||||
*outInternalFormat =
|
||||
(options & PixelFormatNormalizeOptionBit::WidenPacked16Norm) ? GL_RGBA8 : internalFormat;
|
||||
break;
|
||||
case GL_RGB10:
|
||||
case GL_RGB12:
|
||||
*outInternalFormat = (options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
|
||||
@@ -44,6 +44,17 @@ namespace MobileGL {
|
||||
// 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,
|
||||
// Store the three 16-bit packed normalized formats (GL_RGB565, GL_RGB5_A1, GL_RGBA4)
|
||||
// as 8-bit-per-channel ES storage (GL_RGB8 / GL_RGBA8), the way the desktop-only
|
||||
// narrow formats already are. Set by DirectGLES when the driver's 16-bit packed
|
||||
// storage cannot be trusted as a raw-copy endpoint: some Mali drivers keep a
|
||||
// MIRRORED field order for these texels at a non-zero mip level of a 2D array, so
|
||||
// glCopyImageSubData (a raw texel-block move) delivers the channels reversed. The
|
||||
// (format, type) transfer pair does not move with the bit - it is already the
|
||||
// UNorm8 component layout the canonical shadow holds for all three formats.
|
||||
// Reported precision does not move either: GL_TEXTURE_*_SIZE and
|
||||
// glGetInternalformativ answer from TextureMetrics, keyed on the requested format.
|
||||
WidenPacked16Norm = 1 << 10,
|
||||
None = 0,
|
||||
};
|
||||
namespace MG_Util::TextureFormatProcessor {
|
||||
|
||||
Reference in New Issue
Block a user