[Fix] (Espryt): re-arm the packed16 probe on the allocation-scoped mirror the device actually has and let any mirrored level trigger the widening

This commit is contained in:
2026-08-28 00:22:15 -04:00
parent bf376b230f
commit 5d140a41ce
9 changed files with 239 additions and 150 deletions
+5 -4
View File
@@ -264,10 +264,11 @@ namespace MobileGL::MG_Config {
// MOBILEGL_WIDEN_PACKED16_STORAGE: DirectGLES stores GL_RGB565/GL_RGB5(A1)/GL_RGBA4 // MOBILEGL_WIDEN_PACKED16_STORAGE: DirectGLES stores GL_RGB565/GL_RGB5(A1)/GL_RGBA4
// images as 8-bit-per-channel ES storage (GL_RGB8/GL_RGBA8) instead of the driver's // images as 8-bit-per-channel ES storage (GL_RGB8/GL_RGBA8) instead of the driver's
// native 16-bit packed formats. Auto defers to a POST driver-bug probe // native 16-bit packed formats. Auto defers to a POST driver-bug probe
// (SelfTest::CopyImageMirrorsPacked16FieldOrder): some Mali drivers keep a MIRRORED // (SelfTest::CopyImageMirrorsPacked16FieldOrder): some Mali drivers store SOME
// field order for the 16-bit packed texels of a non-zero mip level of a // packed16 allocations with a MIRRORED field order (allocation-scoped and
// GL_TEXTURE_2D_ARRAY, so glCopyImageSubData - a raw texel-block move - lands // shape/context dependent - the failing 30x30x12 GL_TEXTURE_2D_ARRAYs are mirrored
// R/G/B/A reversed whenever exactly one endpoint is such a level // at every level), so glCopyImageSubData - a raw texel-block move - lands R/G/B/A
// reversed whenever exactly one endpoint sits in a mirrored allocation
// (KHR-GL4x.copy_image.functional rgb5/rgb5_a1/rgba4 x every *2d_array* pair). // (KHR-GL4x.copy_image.functional rgb5/rgb5_a1/rgba4 x every *2d_array* pair).
// With no 16-bit packed ES image left there is no field order to disagree about; the // With no 16-bit packed ES image left there is no field order to disagree about; the
// client word still round-trips exactly, because the canonical shadow is already // client word still round-trips exactly, because the canonical shadow is already
+2 -2
View File
@@ -47,8 +47,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const MG_External::GLESCapabilities& capabilities, SizeT targetIndex); const MG_External::GLESCapabilities& capabilities, SizeT targetIndex);
// Whether this format's ES storage is widened to 8-bit-per-channel because the // Whether this format's ES storage is widened to 8-bit-per-channel because the
// driver's 16-bit packed storage mirrors its field order at a non-zero array mip // driver stores some packed16 allocations with a mirrored field order
// level (PixelFormatNormalizeOptionBit::WidenPacked16Norm). True only for // (PixelFormatNormalizeOptionBit::WidenPacked16Norm). True only for
// GL_RGB565/GL_RGB5(_A1)/GL_RGBA4, and only where the POST probe measured the // GL_RGB565/GL_RGB5(_A1)/GL_RGBA4, and only where the POST probe measured the
// divergence (or MOBILEGL_WIDEN_PACKED16_STORAGE forces it). The transfer paths // divergence (or MOBILEGL_WIDEN_PACKED16_STORAGE forces it). The transfer paths
// consult it too: the packed-norm re-upload leg must stand down when the ES storage // consult it too: the packed-norm re-upload leg must stand down when the ES storage
@@ -12,20 +12,21 @@
// that survived every earlier wave: the three internal formats MobileGL can keep as 16-bit // that survived every earlier wave: the three internal formats MobileGL can keep as 16-bit
// packed ES storage - GL_RGB5 (stored GL_RGB565), GL_RGB5_A1, GL_RGBA4 - crossed with the // packed ES storage - GL_RGB5 (stored GL_RGB565), GL_RGB5_A1, GL_RGBA4 - crossed with the
// target pairs that put a GL_TEXTURE_2D_ARRAY's MIP LEVEL 1 on one side of the copy. On the // target pairs that put a GL_TEXTURE_2D_ARRAY's MIP LEVEL 1 on one side of the copy. On the
// affected Mali the driver's physical field order for such a level is the *_REV mirror of // affected Mali the mirrored *_REV field order is a property of WHOLE ALLOCATIONS (shape-
// every other image's, and glCopyImageSubData - a raw texel-block move - lands the fields // and context-dependent; the failing 30x30x12 arrays carry it at every level, the small
// arrays of the suite's passing iterations do not), and glCopyImageSubData - a raw
// texel-block move - between a mirrored allocation and a plain one lands the fields
// reversed: src word 0x0047 arrives as 0x8C20 (its 5_5_5_1 -> 1_5_5_5_REV re-encoding), // reversed: src word 0x0047 arrives as 0x8C20 (its 5_5_5_1 -> 1_5_5_5_REV re-encoding),
// 0x0007 as 0x3800, byte-exact on every failing body. Uploads and readbacks of the same // 0x0007 as 0x3800, byte-exact on every failing body. Uploads and readbacks of the same
// level are clean (the driver decodes its own layout consistently), which is why only the // image are clean (the driver decodes its own layout consistently), which is why only the
// copy path ever crossed the two layouts and why the CTS's "source image was not modified" // copy path ever crossed the two layouts and why the CTS's "source image was not modified"
// checks always passed. // checks always passed.
// //
// The array is 30x30x12 with THREE levels and the flat endpoint is 7x7 with three levels // The array is 30x30x12 with THREE levels and the flat endpoint is 7x7 with three levels
// (7/3/1) because that is the allocation the failures pin - the CTS builds every functional // (7/3/1) because that is the allocation the failures pin - the CTS builds every functional
// texture with FUNCTIONAL_TEST_N_LEVELS = 3 (makeTextureComplete(0, 2)) - and the same // texture with FUNCTIONAL_TEST_N_LEVELS = 3 (makeTextureComplete(0, 2)) - and any deviation
// suite's level-0 copies and a 14x14 base's level 1 measured clean on the same driver, so // from the measured shape might sit on the clean side of whatever allocation heuristic picks
// any deviation from the measured shape (a smaller array, a shorter chain) might sit on the // the driver's layout.
// clean side of whatever allocation threshold picks the driver's layout.
// //
// The repair under test is the packed16 storage widening // The repair under test is the packed16 storage widening
// (PixelFormatNormalizeOptionBit::WidenPacked16Norm): where the POST probe // (PixelFormatNormalizeOptionBit::WidenPacked16Norm): where the POST probe
@@ -108,23 +108,26 @@ namespace {
bool blitIgnoresDestinationLayer = false; bool blitIgnoresDestinationLayer = false;
bool blitIgnoresSourceLayer = false; bool blitIgnoresSourceLayer = false;
// Probe 7: the driver's PHYSICAL field order for a 16-bit packed texel at a non-zero // Probe 7: the driver stores a WHOLE 16-bit packed ALLOCATION with its fields packed
// mip level of a 2D array is the *_REV mirror of the plain-image order. Modelled at // from the other end of the word - on the measured device, every level of the probe's
// the raw copy, which is the only path that can observe it (uploads and readbacks of // 30x30x12 three-level array, while same-shape plain-2D images stay in the canonical
// the same image decode the driver's own layout consistently): a copy whose SOURCE is // order. Modelled at the raw copy, which is the only path that can observe it (uploads
// such a level delivers the mirrored re-encoding, which the plain 2D readback then // and readbacks of the same image decode the driver's own layout consistently): a copy
// decodes with the non-REV order - exactly the 0x0047 -> 0x8C20 arithmetic the // whose SOURCE is any level of the mirrored allocation delivers the mirrored
// affected Mali hands back. The mirror only engages for the allocation the CTS // re-encoding, which the plain-2D readback then decodes with the non-REV order -
// failures were measured on - a THREE-level 30x30x12 array - so a probe that stopped // exactly the 0x0047 -> 0x8C20 arithmetic the affected Mali hands back. The mirror
// building the triggering shape (fewer levels, other dimensions) stops detecting, // only engages for the allocation the failures were measured on - a THREE-level
// which is exactly what these tests are for: on the real driver a 14x14 base's level 1 // 30x30x12 array - so a probe that stopped building the triggering shape (fewer
// measured clean, and nobody has measured a two-level chain at all. // levels, other dimensions) stops detecting, which is exactly what these tests are
bool packed16ArrayMipFieldOrderMirrored = false; // for.
// The inconclusive path: EVERY array level stores mirrored words, level 0 included, so bool packed16ArrayAllocationMirrored = false;
// the probe's level-0 control copy is dirty too and no verdict may be reached. // "Not this bug": the UPLOAD corrupts, so the array's own direct readback is already
bool packed16EveryArrayLevelMirrored = false; // wrong. The probe's round-trip control must veto the verdict - the widening's
// Another inconclusive path: the copy silently lands nothing, so the destination keeps // raw-copy reasoning says nothing about an upload defect.
// its 0xFFFF fill - a value that is neither the word nor its mirror. bool packed16UploadCorrupted = false;
// The inconclusive path: the copy silently lands nothing, so every destination keeps
// its 0xFFFF fill - a value that is neither the word nor its mirror - and the 2D-to-2D
// machinery control fails first.
bool packed16CopyDoesNothing = false; bool packed16CopyDoesNothing = false;
// ---- object bookkeeping --------------------------------------------- // ---- object bookkeeping ---------------------------------------------
@@ -156,6 +159,9 @@ namespace {
std::map<GLuint, std::array<GLubyte, 2>> arrayLayerFill; std::map<GLuint, std::array<GLubyte, 2>> arrayLayerFill;
// framebuffer id -> the (2D array texture, layer) glFramebufferTextureLayer attached. // framebuffer id -> the (2D array texture, layer) glFramebufferTextureLayer attached.
std::map<GLuint, std::pair<GLuint, GLint>> framebufferLayerAttachment; std::map<GLuint, std::pair<GLuint, GLint>> framebufferLayerAttachment;
// framebuffer id -> the LEVEL that same call named. Kept apart so the layered-blit
// bookkeeping above keeps its shape; the packed16 probe reads array LEVELS directly.
std::map<GLuint, GLint> framebufferLayerLevel;
// (texture, level) -> the PHYSICAL 16-bit word every texel of that 5551 image holds. // (texture, level) -> the PHYSICAL 16-bit word every texel of that 5551 image holds.
// One word per level is all the packed16 probe distinguishes: it uploads a uniform // One word per level is all the packed16 probe distinguishes: it uploads a uniform
// fill and reads one texel. // fill and reads one texel.
@@ -495,12 +501,15 @@ namespace {
g_fake.packedTexelWords[{g_fake.boundTexture2D, level}] = word; g_fake.packedTexelWords[{g_fake.boundTexture2D, level}] = word;
}; };
// Records the allocation shape the mirror below is gated on, and the uploaded word. // Records the allocation shape the mirror below is gated on, and the uploaded word.
// Under the upload-corruption knob the STORED word is already wrong - the "not this
// bug" shape the probe's round-trip control must catch.
funcs.glTexImage3D = [](GLenum target, GLint level, GLint, GLsizei width, GLsizei height, funcs.glTexImage3D = [](GLenum target, GLint level, GLint, GLsizei width, GLsizei height,
GLsizei depth, GLint, GLenum, GLenum type, const void* pixels) { GLsizei depth, GLint, GLenum, GLenum type, const void* pixels) {
if (target != GL_TEXTURE_2D_ARRAY || type != GL_UNSIGNED_SHORT_5_5_5_1 || pixels == nullptr) return; if (target != GL_TEXTURE_2D_ARRAY || type != GL_UNSIGNED_SHORT_5_5_5_1 || pixels == nullptr) return;
GLushort word = 0; GLushort word = 0;
std::memcpy(&word, pixels, sizeof(word)); std::memcpy(&word, pixels, sizeof(word));
g_fake.packedTexelWords[{g_fake.boundArrayTexture, level}] = word; g_fake.packedTexelWords[{g_fake.boundArrayTexture, level}] =
g_fake.packed16UploadCorrupted ? MirrorPacked5551(word) : word;
auto& allocation = g_fake.packedArrayAllocations[g_fake.boundArrayTexture]; auto& allocation = g_fake.packedArrayAllocations[g_fake.boundArrayTexture];
if (level == 0) { if (level == 0) {
allocation.width = width; allocation.width = width;
@@ -510,7 +519,7 @@ namespace {
if (level >= 0 && level < 8) allocation.levelMask |= 1u << level; if (level >= 0 && level < 8) allocation.levelMask |= 1u << level;
}; };
// A raw texel-block move: the PHYSICAL word travels. The defect lives here - a source // A raw texel-block move: the PHYSICAL word travels. The defect lives here - a source
// level whose storage keeps the mirrored layout delivers the re-encoded word - and it // in the mirrored ALLOCATION delivers the re-encoded word from EVERY level - and it
// only exists for the allocation it was measured on: three levels of a 30x30x12 array. // only exists for the allocation it was measured on: three levels of a 30x30x12 array.
funcs.glCopyImageSubData = [](GLuint srcName, GLenum, GLint srcLevel, GLint, GLint, GLint, funcs.glCopyImageSubData = [](GLuint srcName, GLenum, GLint srcLevel, GLint, GLint, GLint,
GLuint dstName, GLenum, GLint dstLevel, GLint, GLint, GLint, GLuint dstName, GLenum, GLint dstLevel, GLint, GLint, GLint,
@@ -524,8 +533,7 @@ namespace {
allocation->second.width == 30 && allocation->second.height == 30 && allocation->second.width == 30 && allocation->second.height == 30 &&
allocation->second.layers == 12 && allocation->second.layers == 12 &&
allocation->second.levelMask == 0b111u; allocation->second.levelMask == 0b111u;
if (measuredShape && (g_fake.packed16EveryArrayLevelMirrored || if (measuredShape && g_fake.packed16ArrayAllocationMirrored) {
(g_fake.packed16ArrayMipFieldOrderMirrored && srcLevel >= 1))) {
word = MirrorPacked5551(word); word = MirrorPacked5551(word);
} }
g_fake.packedTexelWords[{dstName, dstLevel}] = word; g_fake.packedTexelWords[{dstName, dstLevel}] = word;
@@ -553,10 +561,11 @@ namespace {
: g_fake.boundDrawFramebuffer; : g_fake.boundDrawFramebuffer;
g_fake.framebuffer2DAttachment[framebuffer] = texture; g_fake.framebuffer2DAttachment[framebuffer] = texture;
}; };
funcs.glFramebufferTextureLayer = [](GLenum target, GLenum, GLuint texture, GLint, GLint layer) { funcs.glFramebufferTextureLayer = [](GLenum target, GLenum, GLuint texture, GLint level, GLint layer) {
const GLuint framebuffer = (target == GL_READ_FRAMEBUFFER) ? g_fake.boundReadFramebuffer const GLuint framebuffer = (target == GL_READ_FRAMEBUFFER) ? g_fake.boundReadFramebuffer
: g_fake.boundDrawFramebuffer; : g_fake.boundDrawFramebuffer;
g_fake.framebufferLayerAttachment[framebuffer] = {texture, layer}; g_fake.framebufferLayerAttachment[framebuffer] = {texture, layer};
g_fake.framebufferLayerLevel[framebuffer] = level;
}; };
funcs.glReadBuffer = [](GLenum) {}; funcs.glReadBuffer = [](GLenum) {};
// The defect itself: the source layer is read from where the READ framebuffer says (unless // The defect itself: the source layer is read from where the READ framebuffer says (unless
@@ -588,6 +597,7 @@ namespace {
for (GLsizei i = 0; i < n; ++i) { for (GLsizei i = 0; i < n; ++i) {
if (framebuffers[i] != 0) --g_fake.aliveFramebuffers; if (framebuffers[i] != 0) --g_fake.aliveFramebuffers;
g_fake.framebufferLayerAttachment.erase(framebuffers[i]); g_fake.framebufferLayerAttachment.erase(framebuffers[i]);
g_fake.framebufferLayerLevel.erase(framebuffers[i]);
g_fake.framebuffer2DAttachment.erase(framebuffers[i]); g_fake.framebuffer2DAttachment.erase(framebuffers[i]);
} }
}; };
@@ -690,11 +700,32 @@ namespace {
} }
return; return;
} }
// Answered before anything else below: a read framebuffer that names an array LAYER // A read framebuffer naming an array LEVEL that holds a 5551 word is the packed16
// is the layered-blit probe asking what that layer holds, and its bytes have nothing // probe's round-trip control: the driver decodes its OWN storage, so whatever the
// to do with the pass/fail texel encoding the image probes below share. // physical word is - mirrored at upload under that knob included - its own decode
// is handed back with the canonical field meaning.
if (const auto layered = g_fake.framebufferLayerAttachment.find(g_fake.boundReadFramebuffer); if (const auto layered = g_fake.framebufferLayerAttachment.find(g_fake.boundReadFramebuffer);
layered != g_fake.framebufferLayerAttachment.end()) { layered != g_fake.framebufferLayerAttachment.end()) {
const auto levelIt = g_fake.framebufferLayerLevel.find(g_fake.boundReadFramebuffer);
const GLint attachedLevel = levelIt == g_fake.framebufferLayerLevel.end() ? 0 : levelIt->second;
if (const auto word = g_fake.packedTexelWords.find({layered->second.first, attachedLevel});
word != g_fake.packedTexelWords.end()) {
const GLushort w = word->second;
const auto expand5 = [](GLushort v) {
return static_cast<GLubyte>((v << 3) | (v >> 2));
};
GLubyte* out = static_cast<GLubyte*>(pixels);
for (std::size_t i = 0; i < texels; ++i) {
out[i * 4 + 0] = expand5((w >> 11) & 0x1F);
out[i * 4 + 1] = expand5((w >> 6) & 0x1F);
out[i * 4 + 2] = expand5((w >> 1) & 0x1F);
out[i * 4 + 3] = (w & 0x1) ? 255 : 0;
}
return;
}
// Otherwise it is the layered-blit probe asking what a layer holds, and its
// bytes have nothing to do with the pass/fail texel encoding the image probes
// below share.
const auto& fill = g_fake.arrayLayerFill[layered->second.first]; const auto& fill = g_fake.arrayLayerFill[layered->second.first];
const GLint layer = layered->second.second; const GLint layer = layered->second.second;
const GLubyte value = const GLubyte value =
@@ -1084,31 +1115,32 @@ TEST(DriverBugProbes, Packed16FieldOrderIsCleanOnAConformingDriver) {
ExpectProbeReleasedEverything(); ExpectProbeReleasedEverything();
} }
TEST(DriverBugProbes, Packed16FieldOrderIsDetectedWhenTheArrayMipLevelIsMirrored) { // The measured device shape: EVERY level of the mirrored allocation delivers the
// re-encoding, and the machinery/round-trip controls stay clean, so the probe must detect.
TEST(DriverBugProbes, Packed16FieldOrderIsDetectedWhenTheArrayAllocationIsMirrored) {
ResetFakeDriver(); ResetFakeDriver();
g_fake.packed16ArrayMipFieldOrderMirrored = true; g_fake.packed16ArrayAllocationMirrored = true;
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions(); const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
EXPECT_TRUE(ProbeCopyImageMirrorsPacked16FieldOrder(gl)); EXPECT_TRUE(ProbeCopyImageMirrorsPacked16FieldOrder(gl));
ExpectProbeReleasedEverything(); ExpectProbeReleasedEverything();
} }
// THE CONTROL. A driver whose EVERY array level stores mirrored words fails the level-0 // THE ROUND-TRIP CONTROL. A driver that corrupts the UPLOAD hands the mirror back from the
// control copy too - a different (and larger) defect than the one this probe is entitled to // array's own direct readback too - a different defect, and one the widening's raw-copy
// report, so it must reach no verdict rather than pin the mip-level shape. // reasoning says nothing about - so the probe must reach no verdict rather than claim it.
TEST(DriverBugProbes, Packed16FieldOrderReportsNothingWhenTheControlIsMirroredToo) { TEST(DriverBugProbes, Packed16FieldOrderReportsNothingWhenTheUploadItselfCorrupts) {
ResetFakeDriver(); ResetFakeDriver();
g_fake.packed16ArrayMipFieldOrderMirrored = true; g_fake.packed16UploadCorrupted = true;
g_fake.packed16EveryArrayLevelMirrored = true;
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions(); const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
EXPECT_FALSE(ProbeCopyImageMirrorsPacked16FieldOrder(gl)); EXPECT_FALSE(ProbeCopyImageMirrorsPacked16FieldOrder(gl));
ExpectProbeReleasedEverything(); ExpectProbeReleasedEverything();
} }
// And the shape that is not this bug: a copy that lands nothing leaves the destination's // And the shape that is not this bug: a copy that lands nothing leaves every destination's
// 0xFFFF fill, which matches neither the word nor its mirror - "reached no verdict". // 0xFFFF fill, so the 2D-to-2D machinery control fails first - "reached no verdict".
TEST(DriverBugProbes, Packed16FieldOrderReportsNothingWhenTheCopyLandsNothing) { TEST(DriverBugProbes, Packed16FieldOrderReportsNothingWhenTheCopyLandsNothing) {
ResetFakeDriver(); ResetFakeDriver();
g_fake.packed16ArrayMipFieldOrderMirrored = true; g_fake.packed16ArrayAllocationMirrored = true;
g_fake.packed16CopyDoesNothing = true; g_fake.packed16CopyDoesNothing = true;
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions(); const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
EXPECT_FALSE(ProbeCopyImageMirrorsPacked16FieldOrder(gl)); EXPECT_FALSE(ProbeCopyImageMirrorsPacked16FieldOrder(gl));
+2 -2
View File
@@ -3389,8 +3389,8 @@ TEST_F(TextureTest, NormalizePixelFormatKeepsPackedTransferTypesForPackedSizedFo
} }
// The packed16 field-order quirk (PixelFormatNormalizeOptionBit::WidenPacked16Norm): where the // The packed16 field-order quirk (PixelFormatNormalizeOptionBit::WidenPacked16Norm): where the
// driver's 16-bit packed storage mirrors its field order at a non-zero array mip level (the // driver stores some packed16 allocations with a mirrored field order (the Mali defect
// Mali defect behind the KHR-GL4x.copy_image rgb5/rgb5_a1/rgba4 x *2d_array* failures), the // behind the KHR-GL4x.copy_image rgb5/rgb5_a1/rgba4 x *2d_array* failures), the
// three ES narrow formats move to 8-bit-per-channel storage. The transfer pair must NOT move // three ES narrow formats move to 8-bit-per-channel storage. The transfer pair must NOT move
// with the bit - it is already the UNorm8 component layout the canonical shadow holds - and // with the bit - it is already the UNorm8 component layout the canonical shadow holds - and
// no other format may move with it either. // no other format may move with it either.
+117 -68
View File
@@ -1929,17 +1929,22 @@ namespace MobileGL::MG_Util::SelfTest {
constexpr const char* kPacked16CopyProbeName = "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 // The shape the KHR-GL4x.copy_image failures pin: a 30x30x12 GL_RGB5_A1 2D array with
// 2D array whose base level is 30x30x12 showed the divergence at LEVEL 1 (the same // the CTS's three-level chain (FUNCTIONAL_TEST_N_LEVELS = 3, makeTextureComplete(0, 2):
// suite's level-0 copies and a 14x14 base's level 1 round-trip clean), so the probe // 30/15/7 x12; the plain endpoints are 7/3/1), against plain-2D endpoints.
// reproduces those dimensions rather than a minimal shape that might sit on the clean //
// side of whatever allocation threshold picks the driver's layout. VERBATIM INCLUDES // WHAT THE DEVICE MEASUREMENTS ACTUALLY SHOWED (round 2): the mirrored field order is a
// THE LEVEL COUNT: the CTS allocates THREE-level chains on BOTH endpoints // property of the WHOLE ALLOCATION, not of a mip level. In MobileGL's live context this
// (FUNCTIONAL_TEST_N_LEVELS = 3, makeTextureComplete(0, 2): 30/15/7 x12 for the array, // array shape lands in the *_REV layout at EVERY level - the first deployment's control
// 7/3/1 for the plain image), and every device data point above came from those // copy out of LEVEL 0 delivered the mirror too, which is what vetoed its own verdict -
// allocations - a chain one level shorter has never been measured on the affected // while the small arrays the CTS's passing iterations used (7- and 15-texel bases; its
// driver, and a probe miss here is not a red anything, it is the widening silently // src/dst dim loop is {7, 15}, so a base-30 array only ever appears at level 1) land in
// staying inert with all 18 bodies red. // the plain layout. A raw standalone context on the same driver additionally showed one-
// and two-level 30x30x12 arrays mirrored where a fresh three-level one is not, so the
// driver's layout choice depends on allocation shape AND context history. The probe
// therefore measures IN SITU - this very context is the one the application's copies run
// in - and treats a mirror delivered from ANY level as the finding, with the machinery
// controls below instead of a per-level "clean" assumption that does not exist.
constexpr GLsizei kPacked16BaseSize = 30; constexpr GLsizei kPacked16BaseSize = 30;
constexpr GLsizei kPacked16Layers = 12; constexpr GLsizei kPacked16Layers = 12;
constexpr GLsizei kPacked16DstSize = 7; constexpr GLsizei kPacked16DstSize = 7;
@@ -1990,7 +1995,11 @@ namespace MobileGL::MG_Util::SelfTest {
// The plain-2D destination, three levels (7/3/1) like the CTS's, every level filled // The plain-2D destination, three levels (7/3/1) like the CTS's, every level filled
// with 0xFFFF - the CTS's own (1,1,1,1) destination fill - so a copy that silently // 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. // did nothing reads as "no verdict" rather than as either prediction.
GLuint MakePacked16DstTexture(const GLESFunctionsTable& gl) { // A plain-2D endpoint with the CTS's three-level 7/3/1 chain, every texel of every
// level holding `fill`: 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),
// or kPacked16Word for the machinery control's source.
GLuint MakePacked16FlatTexture(const GLESFunctionsTable& gl, Uint16 fill) {
GLuint texture = 0; GLuint texture = 0;
gl.glGenTextures(1, &texture); gl.glGenTextures(1, &texture);
if (texture == 0) return 0; if (texture == 0) return 0;
@@ -2000,22 +2009,28 @@ namespace MobileGL::MG_Util::SelfTest {
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, kPacked16Levels - 1); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, kPacked16Levels - 1);
for (GLint level = 0; level < kPacked16Levels; ++level) { for (GLint level = 0; level < kPacked16Levels; ++level) {
const GLsizei size = std::max<GLsizei>(kPacked16DstSize >> level, 1); const GLsizei size = std::max<GLsizei>(kPacked16DstSize >> level, 1);
const Vector<Uint16> fill(static_cast<SizeT>(size) * size, Uint16{0xFFFF}); const Vector<Uint16> texels(static_cast<SizeT>(size) * size, fill);
gl.glTexImage2D(GL_TEXTURE_2D, level, GL_RGB5_A1, size, size, 0, GL_RGBA, gl.glTexImage2D(GL_TEXTURE_2D, level, GL_RGB5_A1, size, size, 0, GL_RGBA,
GL_UNSIGNED_SHORT_5_5_5_1, fill.data()); GL_UNSIGNED_SHORT_5_5_5_1, texels.data());
} }
gl.glBindTexture(GL_TEXTURE_2D, 0); gl.glBindTexture(GL_TEXTURE_2D, 0);
return texture; return texture;
} }
// The destination's texel (0, 0), through a framebuffer of its own. False when the // Texel (0, 0) of a 2D level 0, or of layer 0 of an array's `level`, through a
// attachment is incomplete or the read errors - both are declines, not verdicts. // framebuffer of its own. False when the attachment is incomplete or the read errors -
Bool ReadPacked16DstTexel(const GLESFunctionsTable& gl, GLuint texture, GLubyte out[4]) { // both are declines, not verdicts.
Bool ReadPacked16Texel(const GLESFunctionsTable& gl, GLuint texture, Bool isArray, GLint level,
GLubyte out[4]) {
GLuint framebuffer = 0; GLuint framebuffer = 0;
gl.glGenFramebuffers(1, &framebuffer); gl.glGenFramebuffers(1, &framebuffer);
if (framebuffer == 0) return false; if (framebuffer == 0) return false;
gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); if (isArray) {
gl.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, level, 0);
} else {
gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level);
}
Bool read = false; Bool read = false;
if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
gl.glReadBuffer(GL_COLOR_ATTACHMENT0); gl.glReadBuffer(GL_COLOR_ATTACHMENT0);
@@ -2029,18 +2044,18 @@ namespace MobileGL::MG_Util::SelfTest {
return read; return read;
} }
// Copies a kPacked16DstSize-square region out of layer 0 of the array's `sourceLevel` // Copies a kPacked16DstSize-square region out of (source, sourceTarget, sourceLevel)
// onto a freshly filled 2D destination and hands back the destination's texel (0, 0). // layer 0 onto a freshly 0xFFFF-filled 2D destination and hands back the destination's
// False when the copy raised an error or the readback could not run. // 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, Bool Packed16CopyLandsTexel(const GLESFunctionsTable& gl, GLuint source, GLenum sourceTarget,
GLubyte out[4]) { GLint sourceLevel, GLubyte out[4]) {
const GLuint destination = MakePacked16DstTexture(gl); const GLuint destination = MakePacked16FlatTexture(gl, Uint16{0xFFFF});
if (destination == 0) return false; if (destination == 0) return false;
Drain(gl); Drain(gl);
gl.glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, sourceLevel, 0, 0, 0, destination, gl.glCopyImageSubData(source, sourceTarget, sourceLevel, 0, 0, 0, destination,
GL_TEXTURE_2D, 0, 0, 0, 0, kPacked16DstSize, kPacked16DstSize, 1); GL_TEXTURE_2D, 0, 0, 0, 0, kPacked16DstSize, kPacked16DstSize, 1);
const Bool copied = gl.glGetError() == GL_NO_ERROR; const Bool copied = gl.glGetError() == GL_NO_ERROR;
const Bool read = copied && ReadPacked16DstTexel(gl, destination, out); const Bool read = copied && ReadPacked16Texel(gl, destination, false, 0, out);
gl.glDeleteTextures(1, &destination); gl.glDeleteTextures(1, &destination);
Drain(gl); Drain(gl);
return read; return read;
@@ -2058,9 +2073,9 @@ namespace MobileGL::MG_Util::SelfTest {
Bool ProbeCopyImageMirrorsPacked16FieldOrder(const GLESFunctionsTable& gl) { Bool ProbeCopyImageMirrorsPacked16FieldOrder(const GLESFunctionsTable& gl) {
if (!gl.glGenTextures || !gl.glBindTexture || !gl.glTexParameteri || !gl.glTexImage2D || if (!gl.glGenTextures || !gl.glBindTexture || !gl.glTexParameteri || !gl.glTexImage2D ||
!gl.glTexImage3D || !gl.glDeleteTextures || !gl.glCopyImageSubData || !gl.glGenFramebuffers || !gl.glTexImage3D || !gl.glDeleteTextures || !gl.glCopyImageSubData || !gl.glGenFramebuffers ||
!gl.glBindFramebuffer || !gl.glFramebufferTexture2D || !gl.glCheckFramebufferStatus || !gl.glBindFramebuffer || !gl.glFramebufferTexture2D || !gl.glFramebufferTextureLayer ||
!gl.glDeleteFramebuffers || !gl.glReadBuffer || !gl.glReadPixels || !gl.glPixelStorei || !gl.glCheckFramebufferStatus || !gl.glDeleteFramebuffers || !gl.glReadBuffer ||
!gl.glGetError) { !gl.glReadPixels || !gl.glPixelStorei || !gl.glGetError) {
return false; return false;
} }
@@ -2087,42 +2102,74 @@ namespace MobileGL::MG_Util::SelfTest {
Bool detected = false; Bool detected = false;
const GLuint array = MakePacked16ArrayTexture(gl); const GLuint array = MakePacked16ArrayTexture(gl);
GLubyte control[4] = {0, 0, 0, 0}; const GLuint flatSource = MakePacked16FlatTexture(gl, kPacked16Word);
GLubyte subject[4] = {0, 0, 0, 0}; GLubyte machinery[4] = {0, 0, 0, 0};
// THE CONTROL: the identical copy out of the array's LEVEL 0, which is clean on the GLubyte direct[4] = {0, 0, 0, 0};
// affected driver too. It proves glCopyImageSubData works between a 5551 array and a GLubyte level0[4] = {0, 0, 0, 0};
// 5551 2D image at all, that the upload and the FBO readback round-trip the word, and GLubyte level1[4] = {0, 0, 0, 0};
// that only the mip level moves the answer - so a driver with no copy_image, or none // CONTROL ONE, the machinery: the same copy between two 2D images of the same
// for these formats, reaches no verdict instead of being reported as this. // three-level shape. Two identical allocations share the driver's layout whatever it
if (array == 0 || !Packed16CopyLandsTexel(gl, array, 0, control)) { // is, so this must deliver the word on ANY driver that can run copy_image on these
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-0 control copy could not run)", // formats at all - a driver that cannot reaches no verdict instead of being reported
// as this.
if (array == 0 || flatSource == 0 ||
!Packed16CopyLandsTexel(gl, flatSource, GL_TEXTURE_2D, 0, machinery)) {
MGLOG_I("[driver-bug] %s probe reached no verdict (the 2D-to-2D machinery control "
"could not run)",
kPacked16CopyProbeName); kPacked16CopyProbeName);
} else if (!Packed16TexelNear(control, kPacked16Expected)) { } else if (!Packed16TexelNear(machinery, kPacked16Expected)) {
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-0 control read back " MGLOG_I("[driver-bug] %s probe reached no verdict (the 2D-to-2D machinery control "
"(%d, %d, %d, %d) instead of the uploaded word's (%d, %d, %d, %d))", "read back (%d, %d, %d, %d) instead of the word's (%d, %d, %d, %d))",
kPacked16CopyProbeName, control[0], control[1], control[2], control[3], kPacked16CopyProbeName, machinery[0], machinery[1], machinery[2], machinery[3],
kPacked16Expected[0], kPacked16Expected[1], kPacked16Expected[2], kPacked16Expected[3]); kPacked16Expected[0], kPacked16Expected[1], kPacked16Expected[2], kPacked16Expected[3]);
} else if (!Packed16CopyLandsTexel(gl, array, 1, subject)) { } else if (!ReadPacked16Texel(gl, array, true, 1, direct) ||
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-1 subject copy could not run)", !Packed16TexelNear(direct, kPacked16Expected)) {
// CONTROL TWO, the array's own round trip: reading the level DIRECTLY decodes the
// driver's own storage and must deliver the word whatever layout it picked. A wrong
// answer here means the UPLOAD is what corrupts - a different defect, and one the
// widening's raw-copy reasoning says nothing about.
MGLOG_I("[driver-bug] %s probe reached no verdict (the array's own level-1 readback "
"answered (%d, %d, %d, %d) instead of the word - the upload, not the copy, "
"is what diverges here)",
kPacked16CopyProbeName, direct[0], direct[1], direct[2], direct[3]);
} else if (!Packed16CopyLandsTexel(gl, array, GL_TEXTURE_2D_ARRAY, 0, level0) ||
!Packed16CopyLandsTexel(gl, array, GL_TEXTURE_2D_ARRAY, 1, level1)) {
MGLOG_I("[driver-bug] %s probe reached no verdict (an array-source subject copy "
"could not run)",
kPacked16CopyProbeName); 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]);
} else { } else {
// The clean verdict is logged too: on a device run the FIRST question is whether // THE SUBJECTS: the same copy out of the array's levels 0 and 1. The mirror is an
// this probe executed at all, and a silent clean path is indistinguishable from a // allocation property, not a level property - the affected device delivers it from
// probe that never ran. // BOTH levels of this array in MobileGL's context - so a mirror from EITHER level
MGLOG_I("[driver-bug] %s probe: a copy out of the array's level 1 delivered the word " // is the finding, and each must match the mirror PREDICTION, not merely differ
"intact - the field order is consistent", // from the word: anything else is a different defect and reaches no verdict.
kPacked16CopyProbeName); const Bool level0Mirrored = Packed16TexelNear(level0, kPacked16Mirrored);
const Bool level1Mirrored = Packed16TexelNear(level1, kPacked16Mirrored);
const Bool level0Clean = Packed16TexelNear(level0, kPacked16Expected);
const Bool level1Clean = Packed16TexelNear(level1, kPacked16Expected);
if (level0Mirrored || level1Mirrored) {
detected = true;
MGLOG_I("[driver-bug] %s probe: copies out of the array delivered level 0 "
"(%d, %d, %d, %d) and level 1 (%d, %d, %d, %d) - the 1_5_5_5_REV "
"re-encoding of the word - THE ALLOCATION'S FIELD ORDER IS MIRRORED",
kPacked16CopyProbeName, level0[0], level0[1], level0[2], level0[3],
level1[0], level1[1], level1[2], level1[3]);
} else if (!level0Clean || !level1Clean) {
MGLOG_I("[driver-bug] %s probe reached no verdict (array copies read back level 0 "
"(%d, %d, %d, %d) and level 1 (%d, %d, %d, %d), neither the word nor its "
"mirror)",
kPacked16CopyProbeName, level0[0], level0[1], level0[2], level0[3],
level1[0], level1[1], level1[2], level1[3]);
} else {
// The clean verdict is logged too: on a device run the FIRST question is
// whether this probe executed at all, and a silent clean path is
// indistinguishable from a probe that never ran.
MGLOG_I("[driver-bug] %s probe: copies out of both array levels delivered the "
"word intact - the field order is consistent",
kPacked16CopyProbeName);
}
} }
if (flatSource != 0) gl.glDeleteTextures(1, &flatSource);
if (array != 0) gl.glDeleteTextures(1, &array); if (array != 0) gl.glDeleteTextures(1, &array);
Restore(gl, saved); Restore(gl, saved);
return detected; return detected;
@@ -2314,13 +2361,15 @@ namespace MobileGL::MG_Util::SelfTest {
const Bool widened = MG_Config::Features.EsprytWidenPacked16Storage != const Bool widened = MG_Config::Features.EsprytWidenPacked16Storage !=
MG_Config::QuirkOverride::ForceOff; MG_Config::QuirkOverride::ForceOff;
String detail = String detail =
"the driver's physical field order for a 16-bit packed texel (RGB565 / RGB5_A1 / " "the driver stores SOME 16-bit packed images (RGB565 / RGB5_A1 / RGBA4) with the "
"RGBA4) at a non-zero mip level of a GL_TEXTURE_2D_ARRAY is the *_REV mirror of " "R/G/B/A fields packed from the other end of the word - which allocations get the "
"the order every other image uses, so a glCopyImageSubData - a raw texel-block " "*_REV layout depends on shape and context history (measured here on a 30x30x12 "
"move - between such a level and any other image lands the R/G/B/A fields " "three-level 2D array, every level of it) - so a glCopyImageSubData, a raw "
"texel-block move, between a mirrored allocation and a plain one lands the fields "
"reversed (a 5551 word 0x0047 arrives as 0x8C20). Uploads and readbacks of the " "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 " "same image 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. "; "this probe's second control - so only the raw-copy path ever crosses the two "
"layouts. ";
if (widened) { if (widened) {
detail += detail +=
"MobileGL stores these three formats as 8-bit-per-channel ES storage on this " "MobileGL stores these three formats as 8-bit-per-channel ES storage on this "
@@ -2330,13 +2379,13 @@ namespace MobileGL::MG_Util::SelfTest {
"memory for images of those formats; override with " "memory for images of those formats; override with "
"MOBILEGL_WIDEN_PACKED16_STORAGE"; "MOBILEGL_WIDEN_PACKED16_STORAGE";
return DriverBugFinding{ return DriverBugFinding{
"glCopyImageSubData mirrors 16-bit packed texels at a non-zero array mip level", "glCopyImageSubData mirrors 16-bit packed texels between differently-laid-out images",
DriverBugVerdict::Fixed, detail}; DriverBugVerdict::Fixed, detail};
} }
detail += "MOBILEGL_WIDEN_PACKED16_STORAGE=0 keeps the native narrow storage, so such " detail += "MOBILEGL_WIDEN_PACKED16_STORAGE=0 keeps the native narrow storage, so such "
"copies are left exactly as the driver delivers them, mirrored words included"; "copies are left exactly as the driver delivers them, mirrored words included";
return DriverBugFinding{ return DriverBugFinding{
"glCopyImageSubData mirrors 16-bit packed texels at a non-zero array mip level", "glCopyImageSubData mirrors 16-bit packed texels between differently-laid-out images",
DriverBugVerdict::Unfixable, detail}; DriverBugVerdict::Unfixable, detail};
} }
+26 -23
View File
@@ -299,31 +299,34 @@ namespace MobileGL::MG_Util::SelfTest {
const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual( const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual(
const MG_External::GLESFunctionsTable& gl); 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 // Copies one known GL_UNSIGNED_SHORT_5_5_5_1 word out of BOTH mip levels of a GL_RGB5_A1
// level 1 into a plain 2D image with glCopyImageSubData and reads the landed texel back. // 2D array into plain 2D images with glCopyImageSubData and reads the landed texels back.
// Returns true only when the level-1 copy delivers the word's 5_5_5_1 <-> 1_5_5_5_REV // Returns true only when a copy from EITHER level delivers the word's 5_5_5_1 <->
// field-order mirror while the identical level-0 copy delivers the word itself. // 1_5_5_5_REV field-order mirror while both controls below hold.
// //
// The affected Mali stores 16-bit packed texels (RGB565 / RGB5_A1 / RGBA4) at a non-zero // The affected Mali stores SOME 16-bit packed allocations (RGB565 / RGB5_A1 / RGBA4) with
// mip level of a 2D array in the *_REV field order every other image does NOT use. // their fields packed from the other end of the word, and the mirrored layout is an
// Uploads and readbacks decode that layout consistently, so nothing but a raw texel-block // ALLOCATION property, not a mip-level one: on the failing device this probe's 30x30x12
// move can see it - which is exactly what glCopyImageSubData is defined to be, and why // three-level array delivers the mirror from level 0 and level 1 alike, which is what
// the whole KHR-GL4x.copy_image rgb5/rgb5_a1/rgba4 x *2d_array* matrix fails there while // vetoed the first deployment's "level 0 is the clean control" design. Which allocations
// every other suite touching these formats passes. The textures reproduce the failing // mirror depends on shape AND context history (a raw standalone context mirrors one- and
// shape verbatim - THREE-level chains on both endpoints (30/15/7 x12 for the array, // two-level 30x30x12 arrays but not a fresh three-level one; MobileGL's live context
// 7/3/1 for the plain 2D image), exactly the CTS's FUNCTIONAL_TEST_N_LEVELS = 3 // mirrors the three-level one too), so the probe measures IN SITU - this context is the
// allocation - because a 14x14 base's level 1 measured clean on the same driver, so // one the application's copies run in - on the CTS's own failing shape (three-level
// every deviation from the measured shape risks landing on the clean side of whatever // chains both endpoints: 30/15/7 x12 array, 7/3/1 plain, FUNCTIONAL_TEST_N_LEVELS = 3).
// allocation threshold picks the driver's layout, and a probe miss here is not a red // Uploads and readbacks decode each image's own layout consistently, so nothing but a raw
// test: it is the widening silently staying inert with all 18 bodies still failing. // texel-block move can see the divergence - 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 CONTROL is the identical copy out of mip level 0, which is clean on the affected // TWO CONTROLS. The machinery: an identical copy between two SAME-shape plain-2D images,
// driver too: it proves copy_image works between these images at all and that the // which share a layout whatever it is, so it must deliver the word on any driver that can
// upload/readback round trip is exact, so a driver that cannot host the shape reaches no // run copy_image on these formats - a driver that cannot reaches no verdict instead of
// verdict instead of being reported as this. The subject must also match the mirror // being reported as this. And the array's own round trip: a direct FBO readback of its
// PREDICTION, not merely differ from the expectation - a copy that delivered anything // level 1 must answer the word, or the UPLOAD is what corrupts - a different defect. The
// else is a different defect and reaches no verdict either. Restores every piece of GL // subjects must also match the mirror PREDICTION, not merely differ from the word - a
// state it touches. // 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); Bool ProbeCopyImageMirrorsPacked16FieldOrder(const MG_External::GLESFunctionsTable& gl);
// ProbeCopyImageMirrorsPacked16FieldOrder(), evaluated at most once per process. The // ProbeCopyImageMirrorsPacked16FieldOrder(), evaluated at most once per process. The
@@ -344,11 +344,12 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
// GL_RGB565 are ES formats an application can legitimately ask for - the same // GL_RGB565 are ES formats an application can legitimately ask for - the same
// normalization picks the storage for glRenderbufferStorage - so widening them // normalization picks the storage for glRenderbufferStorage - so widening them
// used to be declined as "a memory decision, not a correctness one". The 18 // 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 // KHR-GL4x.copy_image.functional bodies on Mali falsified that: the driver
// 16-bit packed storage keeps a MIRRORED field order at a non-zero mip level of a // stores SOME packed16 allocations with a MIRRORED field order (allocation-scoped,
// 2D array, so a raw glCopyImageSubData between such a level and any other image // shape- and context-dependent; the failing 30x30x12 arrays are mirrored at every
// delivers the channels reversed (0x0007 -> 0x3800 for a 5551 word: the 1_5_5_5_REV // level), so a raw glCopyImageSubData between a mirrored allocation and a plain
// re-encoding of the same fields). Where that is measured - // one 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 // 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 // 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: // memory argument stands. Nothing about the REPORTED precision moves either way:
@@ -47,9 +47,11 @@ namespace MobileGL {
// Store the three 16-bit packed normalized formats (GL_RGB565, GL_RGB5_A1, GL_RGBA4) // 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 // 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 // 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 // storage cannot be trusted as a raw-copy endpoint: some Mali drivers store SOME
// MIRRORED field order for these texels at a non-zero mip level of a 2D array, so // packed16 allocations with a MIRRORED field order (which ones depends on shape and
// glCopyImageSubData (a raw texel-block move) delivers the channels reversed. The // context history - measured on a three-level 30x30x12 2D array, every level of it),
// so glCopyImageSubData (a raw texel-block move) between a mirrored allocation and a
// plain one delivers the channels reversed. The
// (format, type) transfer pair does not move with the bit - it is already 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. // UNorm8 component layout the canonical shadow holds for all three formats.
// Reported precision does not move either: GL_TEXTURE_*_SIZE and // Reported precision does not move either: GL_TEXTURE_*_SIZE and