From 7fd45509684a89d453ac028dfe302b5dc325fc2f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 27 Aug 2026 23:20:00 -0400 Subject: [PATCH] [Fix] (Espryt): copy before any FBO attach in the packed16 probe - attaching the array relayouts it to the plain order and was neutralizing the subject --- MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp | 62 ++++++++++--------- MobileGL/MG_Util/SelfTest/DriverBugProbes.h | 39 ++++++------ 2 files changed, 53 insertions(+), 48 deletions(-) diff --git a/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp b/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp index c6f4dbd4..edfd65f8 100644 --- a/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp @@ -1934,23 +1934,23 @@ 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, and WHICH allocations get - // it is a driver heuristic that keys on texture state during the uploads AND on - // context history - and the two interact. Measured on the affected Mali, same shape, - // same data: in a raw standalone context, uploads-on-a-default-state texture mirror - // while params-first-NEAREST allocations stay plain; in MobileGL's live context the - // first deployment's params-first array MIRRORED while a later uploads-first one came - // out PLAIN - the raw ordering rule inverted. One- and two-level allocations and - // params-first uploads under a mipmapped MIN_FILTER also mirrored raw. 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. No single allocation - // recipe is therefore entitled to speak for "the" layout: the probe allocates the - // SAME client-visible texture several ways - the minted-backend order (uploads - // first), the params-first order, and the CTS copy-test shape (MAX_LEVEL bounded, - // MIN_FILTER left at its mipmapped default; copy tests never set filters) - measures - // IN SITU, and a mirror delivered from ANY level of ANY variant is the finding, each - // variant guarded by its own upload round-trip control. + // a property of the WHOLE ALLOCATION, not of a mip level - a 30x30x12 packed16 array + // is born in the mirrored layout at every level, 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) are born plain, which is why the + // failures looked per-mip-level from the QPA alone. AND the layout is not fixed for + // the allocation's lifetime: FBO-ATTACHING the array transitions it to the plain + // (renderable) layout, content preserved. That transition is what produced every + // seemingly contradictory measurement of this campaign - a probe that direct-read its + // array before copying relayouted its own subject and reported the device clean in + // the very process whose CTS copies kept mirroring, and the raw matrix's one + // "clean" 30x30x12 array was exactly the one that had been direct-read first. It is + // also why the CTS's own "source image was not modified" checks always passed: they + // read through an FBO attach, after the copy already went wrong. So: subject copies + // FIRST, every control that attaches the array AFTER, and because the driver's + // allocation heuristic beyond the size threshold is not fully mapped, the probe tries + // several allocation recipes of the same client-visible texture and a mirror from ANY + // level of ANY recipe is the finding. constexpr GLsizei kPacked16BaseSize = 30; constexpr GLsizei kPacked16Layers = 12; constexpr GLsizei kPacked16DstSize = 7; @@ -2114,21 +2114,29 @@ namespace MobileGL::MG_Util::SelfTest { return true; } - // One recipe's whole measurement: allocate, round-trip control, both subject copies. - // Only a mirror that matches the PREDICTION while the variant's own round trip is - // clean counts; everything else is that variant's no-verdict (logged as such). + // One recipe's whole measurement: allocate, both subject copies, THEN the round-trip + // control. The order is load-bearing: FBO-ATTACHING THE ARRAY TRANSITIONS IT to the + // plain (renderable) layout on the affected driver, so a round-trip read taken before + // the copies RELAYOUTS the subject and measures a texture the application's copy + // never sees - round two's first deployment did exactly that and reported the device + // clean while the CTS bodies kept failing in the same process. Copies first, the + // control after: the attach-driven transition preserves content, so the read still + // answers "the upload was intact" without disturbing what the copies measured. Only a + // mirror that matches the PREDICTION while that control holds counts; everything else + // is that recipe's no-verdict (logged as such). Bool RunPacked16Recipe(const GLESFunctionsTable& gl, Packed16Recipe recipe) { Bool mirrored = false; const GLuint array = MakePacked16ArrayTexture(gl, recipe); GLubyte direct[4] = {0, 0, 0, 0}; GLubyte level0[4] = {0, 0, 0, 0}; GLubyte level1[4] = {0, 0, 0, 0}; - if (array == 0 || !ReadPacked16Texel(gl, array, true, 1, direct)) { - MGLOG_I("[driver-bug] %s probe [%s]: no verdict (the array could not be built or " - "read back)", + if (array == 0 || !Packed16CopyLandsTexel(gl, array, GL_TEXTURE_2D_ARRAY, 0, level0) || + !Packed16CopyLandsTexel(gl, array, GL_TEXTURE_2D_ARRAY, 1, level1)) { + MGLOG_I("[driver-bug] %s probe [%s]: no verdict (a subject copy could not run)", kPacked16CopyProbeName, Packed16RecipeName(recipe)); - } else if (!Packed16TexelNear(direct, kPacked16Expected)) { - // The variant's own round trip: reading the level DIRECTLY decodes the driver's + } else if (!ReadPacked16Texel(gl, array, true, 1, direct) || + !Packed16TexelNear(direct, kPacked16Expected)) { + // The recipe'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 means the UPLOAD is what corrupts - a different defect, and one the // widening's raw-copy reasoning says nothing about. @@ -2137,10 +2145,6 @@ namespace MobileGL::MG_Util::SelfTest { "copy, is what diverges)", kPacked16CopyProbeName, Packed16RecipeName(recipe), 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 [%s]: no verdict (a subject copy could not run)", - kPacked16CopyProbeName, Packed16RecipeName(recipe)); } else if (Packed16TexelNear(level0, kPacked16Mirrored) || Packed16TexelNear(level1, kPacked16Mirrored)) { mirrored = true; diff --git a/MobileGL/MG_Util/SelfTest/DriverBugProbes.h b/MobileGL/MG_Util/SelfTest/DriverBugProbes.h index cce35844..1ad151a4 100644 --- a/MobileGL/MG_Util/SelfTest/DriverBugProbes.h +++ b/MobileGL/MG_Util/SelfTest/DriverBugProbes.h @@ -307,29 +307,30 @@ namespace MobileGL::MG_Util::SelfTest { // // The affected Mali stores SOME 16-bit packed allocations (RGB565 / RGB5_A1 / RGBA4) with // their fields packed from the other end of the word. The mirrored layout is an - // ALLOCATION property, not a mip-level one - the failing device delivers the mirror from - // level 0 and level 1 alike, which is what vetoed the first deployment's "level 0 is the - // clean control" design - and WHICH allocations get it is a heuristic keying on texture - // state during the uploads and on context history, measured to INVERT between a raw - // standalone context and MobileGL's live one. No single allocation recipe is therefore - // entitled to speak for the layout: the probe builds the CTS's failing shape (three-level - // chains both endpoints: 30/15/7 x12 array, 7/3/1 plain, FUNCTIONAL_TEST_N_LEVELS = 3) - // with three recipes - uploads-first (the minted-backend-texture order), params-first, - // and the CTS copy-test shape (MAX_LEVEL bounded, MIN_FILTER left mipmapped-default) - - // in situ, in the very context the application's copies run in. Uploads and readbacks - // decode each image's own layout consistently, so nothing but a raw 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. + // ALLOCATION property, not a mip-level one - the failing device's 30x30x12 array is born + // mirrored at level 0 and level 1 alike, which is what vetoed the first deployment's + // "level 0 is the clean control" design - and it is not fixed for the allocation's + // lifetime either: FBO-ATTACHING the array transitions it to the plain layout, content + // preserved, which is why a probe that direct-reads its array before copying relayouts + // its own subject and measures a texture the application's copies never see (the second + // deployment's miss), and why the CTS's "source not modified" checks always passed. The + // probe builds the CTS's failing shape (three-level chains both endpoints: 30/15/7 x12 + // array, 7/3/1 plain, FUNCTIONAL_TEST_N_LEVELS = 3) with several allocation recipes, + // copies FIRST, in situ, and a mirror delivered from any level of any recipe is the + // finding. Uploads and readbacks decode each image's layout of the moment consistently, + // so nothing but a raw 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. // // CONTROLS. The machinery: an identical copy between two SAME-shape plain-2D images, // which share a layout whatever it is, so it must deliver the word on any driver that can // run copy_image on these formats - a driver that cannot reaches no verdict instead of - // being reported as this. And per recipe, the array's own round trip: a direct FBO - // readback of its level 1 must answer the word, or the UPLOAD is what corrupts - a - // different defect. The subjects must also match the mirror PREDICTION, not merely differ - // from the word - a copy that delivered anything else is a different defect and reaches - // no verdict either. Restores every piece of GL state it touches. + // being reported as this. And per recipe, AFTER its subject copies, the array's own round + // trip: a direct FBO readback of its level 1 must answer the word, or the UPLOAD is what + // corrupts - a different defect. The subjects must also match the mirror PREDICTION, not + // merely differ from the word - 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