[MG_IntegrationTest] (P7): ObjectSubsystemControl reads the D3 refusal sentence and the four F1Wire decline cases are Magma-only

This commit is contained in:
2026-09-23 13:23:09 -04:00
parent f1ce655b42
commit 76bfe8802a
3 changed files with 103 additions and 39 deletions
+9 -3
View File
@@ -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")
@@ -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.
@@ -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: <A> (bit N) is set but <B>
// (bit M) is clear; <why> - 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 <why>.
// (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: <A> (bit N) is set but MOBILEGL_PIPE_PUSH=0x<mask>
// does not carry every bit MG_Pipe/SubsystemDeps.def says it requires (requires 0x<R>,
// missing 0x<M>): <why> - 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<M>)" field, so the assertion
// pins the DECISION and the DIRECTION, and not the family-specific prose in <why>.
//
// THE OLD SENTENCE ("<A> (bit N) is set but <B> (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<M>)" 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 <why> (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<std::string>& bitThatWasSet,
const std::vector<std::string>& 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;
// "<set bit> ... is set but ... <needed bit> ... 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;
// "<set bit> ... is set but ... missing <needed bit>)", 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<std::string> SamplerBitSpellings() {
return {"kMGPipeSubsystemSamplers", "(bit 11)", "0x800"};
return {"kMGPipeSubsystemSamplers", "(bit 11)", kSamplerBitMask};
}
std::vector<std::string> 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<M>)"
// 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): <the row's why> - 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;