[Fix] (Espryt): allocate the packed16 probe's textures uploads-first, the order a minted backend texture performs and the one the Mali layout choice keys on

This commit is contained in:
2026-08-28 00:22:15 -04:00
parent 5d140a41ce
commit 5dbbbbd7eb
+35 -22
View File
@@ -1934,17 +1934,20 @@ namespace MobileGL::MG_Util::SelfTest {
// 30/15/7 x12; the plain endpoints are 7/3/1), against plain-2D endpoints.
//
// WHAT THE DEVICE MEASUREMENTS ACTUALLY SHOWED (round 2): the mirrored field order is a
// property of the WHOLE ALLOCATION, not of a mip level. In MobileGL's live context this
// array shape lands in the *_REV layout at EVERY level - the first deployment's control
// copy out of LEVEL 0 delivered the mirror too, which is what vetoed its own verdict -
// while the small arrays the CTS's passing iterations used (7- and 15-texel bases; its
// src/dst dim loop is {7, 15}, so a base-30 array only ever appears at level 1) land in
// 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.
// property of the WHOLE ALLOCATION, not of a mip level, and the driver picks it from
// the texture state IN FORCE WHILE THE LEVELS ARE UPLOADED. Measured raw on the
// affected Mali (same shape, same data, same context): three uploads with NEAREST and
// MAX_LEVEL=2 already set land in the plain layout, while the same three uploads on a
// fresh texture still at the driver defaults - parameters set only afterwards, which
// is exactly the order a freshly minted MobileGL backend texture performs - land in
// the *_REV layout at EVERY level; one- and two-level allocations and params-first
// uploads under a mipmapped MIN_FILTER mirror too. The small arrays the CTS's passing
// iterations used (7- and 15-texel bases; its src/dst dim loop is {7, 15}, so a
// base-30 array only ever appears at level 1) land plain, which is why the failures
// looked per-mip-level from the QPA alone. The probe therefore allocates exactly the
// way MobileGL does (uploads first), measures IN SITU, and treats a mirror delivered
// from ANY level as the finding, with machinery controls below instead of a per-level
// "clean" assumption that does not exist.
constexpr GLsizei kPacked16BaseSize = 30;
constexpr GLsizei kPacked16Layers = 12;
constexpr GLsizei kPacked16DstSize = 7;
@@ -1969,18 +1972,22 @@ namespace MobileGL::MG_Util::SelfTest {
constexpr Int kPacked16Tolerance = 4;
// A three-level GL_RGB5_A1 2D array (30/15/7, twelve layers each) allocated the way
// MobileGL's own mutable-texture path allocates one (glTexImage3D per level), with
// every texel of every level holding kPacked16Word. MAX_LEVEL is clamped to the
// CTS's makeTextureComplete(0, 2) shape, which also keeps the chain complete - some
// drivers refuse glCopyImageSubData on an incomplete texture.
// MobileGL's own mutable-texture path allocates one, ORDER INCLUDED: glTexImage3D per
// level FIRST, on a fresh texture still at the driver defaults, and the parameters
// only afterwards. The order is load-bearing on the affected Mali: uploads-then-params
// (what a freshly minted backend texture gets - the storage sync runs before the
// parameter re-push, see SyncTextureObjectToBackend) lands this allocation in the
// mirrored layout at every level, while the same calls with NEAREST and MAX_LEVEL set
// BEFORE the uploads land it in the plain one - a params-first probe measured "clean"
// in the very context whose params-after textures were mirroring, which is exactly the
// miss round two exists to fix. MAX_LEVEL still ends at the CTS's
// makeTextureComplete(0, 2) shape, which keeps the chain 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, kPacked16Levels - 1);
for (GLint level = 0; level < kPacked16Levels; ++level) {
const GLsizei size = kPacked16BaseSize >> level;
const Vector<Uint16> words(
@@ -1988,6 +1995,9 @@ namespace MobileGL::MG_Util::SelfTest {
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.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, kPacked16Levels - 1);
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
return texture;
}
@@ -1998,21 +2008,24 @@ namespace MobileGL::MG_Util::SelfTest {
// 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.
// or kPacked16Word for the machinery control's source. Uploads first, parameters
// after, for the same in-situ fidelity as the array above - this is the allocation
// discipline every MobileGL-minted texture gets, and the shape the failing bodies'
// clean plain endpoints had.
GLuint MakePacked16FlatTexture(const GLESFunctionsTable& gl, Uint16 fill) {
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, kPacked16Levels - 1);
for (GLint level = 0; level < kPacked16Levels; ++level) {
const GLsizei size = std::max<GLsizei>(kPacked16DstSize >> level, 1);
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_UNSIGNED_SHORT_5_5_5_1, texels.data());
}
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, kPacked16Levels - 1);
gl.glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}