From 76bfe8802a5cadfc7c7cdc6fea435ef4c3925c7f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 23 Sep 2026 10:56:56 -0400 Subject: [PATCH] [MG_IntegrationTest] (P7): ObjectSubsystemControl reads the D3 refusal sentence and the four F1Wire decline cases are Magma-only --- MobileGL/MG_IntegrationTest/CMakeLists.txt | 12 ++- .../Scenarios/F1WireScenario.cpp | 36 +++++++ .../ObjectSubsystemControlScenario.cpp | 94 ++++++++++++------- 3 files changed, 103 insertions(+), 39 deletions(-) diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 152b9fc1..445a9421 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -3935,9 +3935,15 @@ if (MOBILEGL_BUILD_DISAGGREGATED AND MOBILEGL_PIPE_VERIFY) # this build with MOBILEGL_PIPE_VERIFY=0 in the same environment, the same four assertions # (GL error 0x0502 where the decline promises none, or none where it promises one; the # overlap copy writes the level the decline leaves alone) - because Espryt does not decline - # those shapes at all. No DirectGLES split lane had ever run them; that is a finding about the - # DirectGLES whole-binary set on split, not about verify, and it is recorded in - # notes/p7/verify-split.md rather than hidden in a green. The DirectVulkan half keeps them. + # those shapes at all. That is a finding about the DirectGLES whole-binary set on split, not + # about verify, and it is recorded in notes/p7/verify-split.md rather than hidden in a green. + # This lane was NOT the only DirectGLES split lane to reach them: the primary integration job + # runs every integration-gpu entry under MOBILEGL_TRANSPORT=inproc, and there the same four + # were red in CI (the depth-stencil and overlap pair from run 35781886930, the two blits from + # 35793903365). Each case now skips by backend name at its top (F1WireScenario.cpp, + # "Magma wire-arm decline"), in every DirectGLES lane; this subtraction is therefore + # redundant, and kept so the verify-split registration and its counts do not move. The + # DirectVulkan half keeps them. set(MGL_ITEST_VERIFY_SPLIT_GLES_MAGMA_DECLINE_FILTER "F1WireScenario.GenerateMipmapDepthStencilDeclinesAndKeepsTheSession:F1WireScenario.MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession:F1WireScenario.MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession:F1WireScenario.CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone") diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index e668ec30..5c9a6c3b 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -633,8 +633,20 @@ TEST_F(F1WireScenario, GenerateMipmapDepthWithoutNativeBlitPixels) { // so it fires on every driver. // Red once (executed, reverted): restore the MagmaWireFatal and this case dies with // Fatal{UnmigratedVerb, "Magma:depth-stencil-mipmap@P7"}. +// +// MAGMA ONLY, like the three other `*Declines*` cases below. Rule I and §3.2 are MG_Backend/ +// DirectVulkan's wire-arm contract, but the DirectGLES whole-binary entry discovers this case +// too and reaches its body whenever the process resolves a split transport - the primary +// integration lane runs every integration-gpu entry under MOBILEGL_TRANSPORT=inproc. Espryt +// answers this shape with GL_INVALID_OPERATION and leaves level zero and the session intact +// (measured, DirectGLES x inproc on lavapipe): a different answer, not a broken decline. TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) { if (!Ready()) return; + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, depth-stencil-mipmap): Espryt (" + << Gl().BackendName() + << ") answers a depth/stencil glGenerateMipmap with GL_INVALID_OPERATION and " + "leaves level 0 intact, which is not the no-error decline this case pins"; GLuint depthStencil = 0; glGenTextures(1, &depthStencil); glBindTexture(GL_TEXTURE_2D, depthStencil); @@ -1767,6 +1779,15 @@ TEST_F(F1WireScenario, MultisampleColorBlitScalesAndFlipsThroughTheResolveScratc // case dies as Fatal{UnmigratedVerb, "Magma:multisample-blit-shape@P7"}. TEST_F(F1WireScenario, MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession) { if (!Ready()) return; + // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). Espryt on the split + // arm leaves the destination untouched but no GL_INVALID_OPERATION reaches the client, which + // GL 4.6 core 18.3.1 asks for here - an Espryt debt of the same class as + // DepthStencilReadbackMatrixScenario's scaled-resolve gate, not this decline's to pin. + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, multisample-blit-shape): Espryt (" + << Gl().BackendName() + << ") writes nothing for a scaled blit onto a multisample destination but " + "records no GL_INVALID_OPERATION - a P3b/P4b debt, not this decline"; GLuint msFbo = 0, msRenderbuffer = 0; glGenFramebuffers(1, &msFbo); glBindFramebuffer(GL_FRAMEBUFFER, msFbo); @@ -1854,6 +1875,15 @@ TEST_F(F1WireScenario, MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeep // case dies as Fatal{UnmigratedVerb, "Magma:multisample-blit-aspect@P7"}. TEST_F(F1WireScenario, MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession) { if (!Ready()) return; + // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). The decline itself + // is a recorded Magma debt (CONTRACT-P7 §12: a same-rectangle 1 -> N blit is legal sample + // replication in GL 4.6 core 18.3.1); Espryt on the split arm neither replicates nor + // records an error, which is neither answer this case can pin. + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, multisample-blit-aspect): Espryt (" + << Gl().BackendName() + << ") writes nothing for a single-sample to multisample blit and records no " + "GL error - a P3b/P4b debt, not this decline"; GLuint msFbo = 0, msRenderbuffer = 0; glGenFramebuffers(1, &msFbo); glBindFramebuffer(GL_FRAMEBUFFER, msFbo); @@ -2047,6 +2077,12 @@ TEST_F(F1WireScenario, CopyImageInPlaceAcrossLevelsAndLayers) { // no GL error, and the level still holds exactly what it held. TEST_F(F1WireScenario, CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone) { if (!Ready()) return; + // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). GL leaves this copy + // undefined and Espryt performs it; only the Magma wire arm promises to record nothing. + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, copy-image-in-place): GL 4.6 core " + "18.3.2 leaves an overlapping same-subresource copy undefined and Espryt (" + << Gl().BackendName() << ") performs it"; const GLuint array = MakeInPlaceCopyTexture(); ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.setup"; // (0,0)-(4,4) onto (2,2): four of the sixteen texels are in both rectangles. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp index 4e03fc5e..cce3474a 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp @@ -188,11 +188,21 @@ void main() { oColor = texture(uTex, vUv); } // ORDER - see the direction check below. // // Espryt's text is one MGLOG_E from the helper the three dependent families share - // (Managers.cpp, PipeSubsystemDependencyMissing): "MGPipe: (bit N) is set but - // (bit M) is clear; - REFUSING the dependent bit and running the legacy arm. Set - // both bits, or clear both". Three spellings are accepted per bit - the constant's name, - // "(bit N)", and the hexadecimal mask - so the assertion pins the DECISION and the - // DIRECTION, and not the family-specific prose in . + // (Managers.cpp, PipeSubsystemDependencyMissing). Since P3b/P4b wave 2-D package D3 + // (1c36987e, ID-P7-28) that helper reads the family's row from MG_Pipe/SubsystemDeps.def + // instead of taking one hand-written dependency bit, and it computes the missing bits + // rather than naming them: "MGPipe: (bit N) is set but MOBILEGL_PIPE_PUSH=0x + // does not carry every bit MG_Pipe/SubsystemDeps.def says it requires (requires 0x, + // missing 0x): - REFUSING the dependent bit and running the legacy arm. Set every + // bit of the row, or clear the family's own". The SET bit is accepted in three spellings - + // the constant's name, "(bit N)", and the hexadecimal mask - and the NEEDED bit is read + // from the one place the helper names it, the "missing 0x)" field, so the assertion + // pins the DECISION and the DIRECTION, and not the family-specific prose in . + // + // THE OLD SENTENCE (" (bit N) is set but (bit M) is clear; ...") IS GONE FROM THE + // TREE, and this matcher used to require its " is clear": after D3 both refusal lanes went + // red with the refusal sitting on the ERROR line the lane was reading (CI run + // 35792628470 onward). Nothing about the decision changed - only the wording did. // // THE DIRECTION IS THE HALF THIS FILE USED TO BE MISSING (review F-v2-m1). The first form // of the matcher asked "does the line name bit A?" AND "does the line name bit B?", which @@ -207,12 +217,18 @@ void main() { oColor = texture(uTex, vUv); } // away at all times. // // What makes the direction readable is the sentence's own shape: the SET bit is named - // before " is set but " and the NEEDED bit between that and " is clear". So the check is - // four offsets in strictly increasing order, and it is the sentence Espryt emits rather - // than a re-statement of it. + // before " is set but " and the NEEDED bit in the "missing 0x)" field after it. So the + // check is three offsets in strictly increasing order, and it is the sentence Espryt + // emits rather than a re-statement of it. The field is matched WITH its closing + // parenthesis, so "missing 0x400)" cannot be satisfied by "missing 0x4000)" (bit 14), and + // the mirror sentence cannot satisfy it either: there the SET-bit spellings first occur + // inside that field, after " is set but ". constexpr const char* kSaysItRefused = "REFUSING the dependent bit and running the legacy arm"; constexpr const char* kSaysWhichIsSet = " is set but "; - constexpr const char* kSaysWhichIsClear = " is clear"; + + std::string SaysWhichIsMissing(const std::string& neededMaskHex) { + return "missing " + neededMaskHex + ")"; + } // The earliest offset at which any accepted spelling of one bit appears, or npos. The // EARLIEST rather than any: a spelling that also occurs later in (Espryt's @@ -234,7 +250,8 @@ void main() { oColor = texture(uTex, vUv); } // what it found: a reader of a green refusal lane must be able to see the sentence. std::string FindTheRefusalLine(const std::string& log, const std::vector& bitThatWasSet, - const std::vector& bitThatWasNeeded) { + const std::string& neededMaskHex) { + const std::string missingField = SaysWhichIsMissing(neededMaskHex); std::size_t pos = 0; while (pos <= log.size()) { const std::size_t newline = log.find('\n', pos); @@ -244,18 +261,15 @@ void main() { oColor = texture(uTex, vUv); } const std::size_t refusedAt = line.find(kSaysItRefused); const std::size_t setAt = EarliestSpellingOffset(line, bitThatWasSet); const std::size_t setClauseAt = line.find(kSaysWhichIsSet); - const std::size_t neededAt = EarliestSpellingOffset(line, bitThatWasNeeded); - const std::size_t clearClauseAt = line.find(kSaysWhichIsClear); + const std::size_t missingAt = line.find(missingField); const bool everyPartIsThere = refusedAt != std::string::npos && setAt != std::string::npos && - setClauseAt != std::string::npos && neededAt != std::string::npos && - clearClauseAt != std::string::npos; - // " ... is set but ... ... is clear", strictly in that - // order. Swapping the caller's two arguments breaks the chain, which is the - // whole of F-v2-m1. + setClauseAt != std::string::npos && missingAt != std::string::npos; + // " ... is set but ... missing )", strictly in that order. + // Swapping the caller's two arguments breaks the chain, which is the whole of + // F-v2-m1. const bool inTheRightDirection = - everyPartIsThere && setAt < setClauseAt && setClauseAt < neededAt && - neededAt < clearClauseAt; + everyPartIsThere && setAt < setClauseAt && setClauseAt < missingAt; if (atErrorSeverity && inTheRightDirection) { return line; } @@ -266,14 +280,18 @@ void main() { oColor = texture(uTex, vUv); } } // The three accepted spellings of each of the two P4a bits this file's two refusal lanes - // are about. MGPipe.h: bit 10 = kMGPipeSubsystemTextureResources = 0x400, - // bit 11 = kMGPipeSubsystemSamplers = 0x800. + // are about, when the bit is the one that was SET; the mask alone when it is the one that + // was NEEDED (the helper prints `required & ~mask` with %llx). MGPipe.h: bit 10 = + // kMGPipeSubsystemTextureResources = 0x400, bit 11 = kMGPipeSubsystemSamplers = 0x800. + constexpr const char* kSamplerBitMask = "0x800"; + constexpr const char* kTextureResourceBitMask = "0x400"; + std::vector SamplerBitSpellings() { - return {"kMGPipeSubsystemSamplers", "(bit 11)", "0x800"}; + return {"kMGPipeSubsystemSamplers", "(bit 11)", kSamplerBitMask}; } std::vector TextureResourceBitSpellings() { - return {"kMGPipeSubsystemTextureResources", "(bit 10)", "0x400"}; + return {"kMGPipeSubsystemTextureResources", "(bit 10)", kTextureResourceBitMask}; } class ObjectSubsystemControlScenario : public ScenarioTest { @@ -626,15 +644,16 @@ void main() { oColor = texture(uTex, vUv); } // FindTheRefusalLine: a substring search over the whole file cannot go red for the // reason this case claims (F-M6). const std::string refusal = - FindTheRefusalLine(log, SamplerBitSpellings(), TextureResourceBitSpellings()); + FindTheRefusalLine(log, SamplerBitSpellings(), kTextureResourceBitMask); EXPECT_FALSE(refusal.empty()) << "MOBILEGL_PIPE_PUSH=0x9ff sets the sampler subsystem (bit 11) without the texture " "resource subsystem (bit 10) it depends on, and no single ERROR line of the " "library's log both says it REFUSED and names the two bits. D-K2 requires ONE " "MGLOG_E naming both and a fall back to the legacy sampler arm; a mask that is " "silently half-honoured is the failure this case exists to catch, and it is " - "invisible in the pixels by construction. Accepted spellings per bit are the " - "constant's name, '(bit 11)' / '(bit 10)', and '0x800' / '0x400'. The log was " + "invisible in the pixels by construction. The set bit may be spelled by the " + "constant's name, '(bit 11)' or '0x800', before ' is set but '; the needed bit " + "must be the helper's 'missing 0x400)' field after it. The log was " << log.size() << " bytes and is at " << PipeStatsWindow::LibraryLogPath() << "."; if (!refusal.empty()) { // Printed on the pass as well: a reader of a green refusal lane must be able to @@ -742,15 +761,17 @@ void main() { oColor = texture(uTex, vUv); } // The same line shape as the 0x9ff arm, with the two bits' roles swapped: the bit that // was SET is the texture-resource one and the bit it NEEDED is the sampler one. The // swap is now a REAL difference between the two cases: FindTheRefusalLine requires the - // set bit to be named before " is set but " and the needed bit after it (F-v2-m1), so - // this call and the 0x9ff one above accept disjoint sentences. Espryt's is - // "kMGPipeSubsystemTextureResources (bit 10) is set but kMGPipeSubsystemSamplers - // (bit 11) is clear; MGPTextureParams::BuiltinSampler is a SamplerCso handle, only - // bit 11 mints sampler CSOs, and the applier's verdict for a null one is - // Fatal{ProtocolCorruption} - REFUSING the dependent bit and running the legacy arm. - // Set both bits, or clear both" (Managers.cpp, ResolveTextureResourceSubsystemArm). + // set bit to be named before " is set but " and the needed bit in the "missing 0x)" + // field after it (F-v2-m1), so this call and the 0x9ff one above accept disjoint + // sentences. Espryt's is "kMGPipeSubsystemTextureResources (bit 10) is set but + // MOBILEGL_PIPE_PUSH=0x5ff does not carry every bit MG_Pipe/SubsystemDeps.def says it + // requires (requires 0x880, missing 0x800): - REFUSING the dependent + // bit and running the legacy arm. Set every bit of the row, or clear the family's own" + // (Managers.cpp, PipeSubsystemDependencyMissing via + // ResolveTextureResourceSubsystemArm). The row requires bit 7 as well; 0x5ff carries + // it, so the missing field is bit 11 alone. const std::string refusal = - FindTheRefusalLine(log, TextureResourceBitSpellings(), SamplerBitSpellings()); + FindTheRefusalLine(log, TextureResourceBitSpellings(), kSamplerBitMask); EXPECT_FALSE(refusal.empty()) << "MOBILEGL_PIPE_PUSH=0x5ff sets the texture resource subsystem (bit 10) without " "the sampler subsystem (bit 11) that MGPTextureParams::BuiltinSampler depends on, " @@ -758,8 +779,9 @@ void main() { oColor = texture(uTex, vUv); } "two bits. Only bit 11 mints sampler CSOs, so every set_texture_params emitted " "under this mask would carry a null BuiltinSampler and the applier's Fatal is the " "next thing that happens - which is why this pair is a refusal at bring-up and " - "not the harmless mirror of the 0x9ff one. Accepted spellings per bit are the " - "constant's name, '(bit 10)' / '(bit 11)', and '0x400' / '0x800'. The log was " + "not the harmless mirror of the 0x9ff one. The set bit may be spelled by the " + "constant's name, '(bit 10)' or '0x400', before ' is set but '; the needed bit " + "must be the helper's 'missing 0x800)' field after it. The log was " << log.size() << " bytes and is at " << PipeStatsWindow::LibraryLogPath() << "."; if (!refusal.empty()) { std::cout << "[ ObjectSubsystemControl ] refusal line: " << refusal << std::endl;