Files
MobileGL/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp
T
swung0x48 519445f946 [MG_Pipe, MG_Impl, MG_Test, docs] (P7): D-K2's dependency rule in one table, with the case that fails when the two readers disagree
P3b/P4b R-5 (and R-3's cheap half), landing under the P7 program (PLAN-PH-P34B-P7.md
1.2, wave 2-D package D2).

The rule had SIX statements and nothing compared them, and two of the four prose ones
were wrong in the same way: MG_Pipe/MGPipe.h and MG_Backend/DirectGLES/Managers.h both
said "THREE OF THEM HAVE A DEPENDENCY" (there are six rows) and both said "the mirror
pairs (10 without 11, ...) are all fine" - when 10-without-11 is precisely D-K2's FOURTH
row (ID-14/ID-15), refused by the server and withheld by the client, with PipeFill.cpp
saying so out loud: "The brief's original 'bit 10 without 11 is fine' is WITHDRAWN for
P4a as built." Neither wrong comment could fail anything; Managers.h's copy sits in the
header of the file that implements the refusal.

The rows, once, in MG_Pipe/SubsystemDeps.def (X-macro, FatalFamilies.def's convention),
expanded by MGPipe.h into kMGPipeSubsystemDependencies[] / MGPipeSubsystemRequires() /
MGPipeSubsystemDependenciesAreSet() with four static_asserts. Six rows: 8->7, 9->10,
10->{7,11}, 11->10, 12->none (a ROW, not an absence), 13->7. Bits 8 and 13 are the two
the old comment did not know about; bit 13's row lived fifteen lines further down in the
same header, which is how "THREE OF THEM" survived two phases.

THE TWO READERS DO NOT YET READ IT, deliberately: MG_Impl/Pipe/PipeFill.cpp is the
contract package's file for the whole phase (TextureEmit.h states that rule) and
MG_Backend/DirectGLES/Managers.cpp belongs to wave 2-D package D1 while it is in flight.
Switching them is two one-line changes for the integrator. The anti-drift property does
not wait: MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp is the first thing in the tree
that calls BOTH readers at the same mask - sweeping the phase constants, the two
documented refusal lanes and one mask per row - and compares each against the .def. It
lives in that binary because that binary links both sides.

Both comparisons are one-directional, each for a measured reason. The server resolvers
also fold in the family's own bit and D-C3's wire-caps refusal, so a resolver may say NO
for its own reasons but never YES where the table refuses. MGPipeP4aFamilyEmits is a
QUADRUPLE and the first version of this case asserted equality and reddened at 0x1fff on
the sampler and program families because a bare unit binary registers no consumer - the
assertion being wrong, not the product, and recorded in the note because the temptation
to assert equality here is strong.

Red-once (R-16), twice: dropping the fourth row from the .def fails at COMPILE time
(MGPipe.h's static_asserts); making the server's ResolveSamplerSubsystemArm stop checking
bit 10 (throwaway, reverted) reds the runtime case and names the mask - 0x9ff, which is
exactly the lane CMakeLists.txt already pins for G12.

R-3's cheap half: CONTRACT-P5.md claimed the image-access, sampler-view-target and
draw-buffer encodings as contract by pointing at package-header LINE RANGES, and all
three had drifted (ImageEmit.h:146-159 no longer holds the encoding at all). All three
rows now state the encoding by enumerator and function name, and the two confusable
siblings are named. The remaining half - MGPSamplerView::Target still has no Count bound,
no ...IsValid and no Fatal for an out-of-range value - is stated in the row rather than
closed, being an MG_Pipe change outside this package's footprint.

Managers.h:690-695's copy of the wrong prose is NOT corrected here: it is package D1's
file, and its two defects are named in notes/p34b/espryt-d2.md so the integrator can take
them in one line.

G1 after this change: pull .text 10822147 = 0xa52203, nm --defined-only identical to
~/w7/p7-before/pull-syms.txt (30570 symbols) - the new inline constexpr table does not
reach the pull build.
2026-09-22 17:31:51 -04:00

248 lines
14 KiB
C++

// MobileGL - MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp
// Copyright (c) 2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
//
// D-K2's SUBSYSTEM DEPENDENCY RULE: THE CASE THAT FAILS WHEN THE TWO SIDES DISAGREE (P3b/P4b
// R-5).
//
// THE RULE HAD SIX STATEMENTS AND NOTHING COMPARED THEM. Two of the four prose ones were
// WRONG - MG_Pipe/MGPipe.h and MG_Backend/DirectGLES/Managers.h both said "THREE OF THEM" when
// there are six rows, and both said "the mirror pairs (10 without 11, ...) are all fine" when
// 10-without-11 is D-K2's FOURTH row. Neither wrong comment could fail anything. The two
// EXECUTABLE statements are in different link units with no shared header:
//
// CLIENT dependency check MG_Impl/Pipe/PipeFill.cpp's kMGPipeP4aFamilyDependencies, reached
// through MGPipeP4aFamilyEmits().
// SERVER consumer gate MG_Backend/DirectGLES/Managers.cpp's four
// Resolve<Family>SubsystemArm(), over PipeSubsystemDependencyMissing.
//
// This file is the first thing in the tree that calls BOTH at the same mask and compares them,
// and it compares each against MG_Pipe/SubsystemDeps.def - the one place the rule is now
// written. A change to either reader alone reds here; a change to the .def alone reds here
// twice.
//
// WHY THE TWO READERS DO NOT YET READ THE .def THEMSELVES. PipeFill.cpp is the contract
// package's file for the whole phase (MG_Impl/Pipe/TextureEmit.h states that rule in full) and
// Managers.cpp belongs to wave 2-D package D1 while it is in flight. Switching them is two
// one-line changes for the integrator; the anti-drift property does not wait for it, because
// this file supplies it from the outside.
//
// THE SERVER RESOLVERS ARE CALLED DIRECTLY, NOT THROUGH THEIR LATCHED ACCESSORS. Managers.h
// wraps each one in a `static const Bool enabled = Resolve...()` that runs once per process;
// calling the resolver itself is what lets this case sweep masks. That is also why the case
// restores MG_Config::Features.PipePush before it returns: a latched accessor evaluated later
// in the same binary would otherwise read a mask this test left behind.
#include <gtest/gtest.h>
#include <Config.h>
#include <MG_Backend/DirectGLES/Managers.h>
#include <MG_Pipe/MGPipe.h>
#if MOBILEGL_PIPE_PUSH
#include <MG_Impl/Pipe/PipeFill.h>
#endif
using namespace MobileGL;
using namespace MobileGL::MG_Pipe;
namespace {
struct PushMaskScope {
Uint64 Saved = MG_Config::Features.PipePush;
explicit PushMaskScope(Uint64 mask) { MG_Config::Features.PipePush = mask; }
~PushMaskScope() { MG_Config::Features.PipePush = Saved; }
};
// Every mask the rule can be interestingly asked about: the phase constants, the two
// documented refusal lanes (0x7ff withholds bit 11, 0x5ff withholds bit 9), and one mask per
// row with exactly that row's required bits knocked out.
Vector<Uint64> InterestingMasks() {
Vector<Uint64> masks{0ull,
kMGPipeSubsystemsMigratedAtP2,
kMGPipeSubsystemsMigratedAtP3a,
kMGPipeSubsystemsMigratedAtP4a,
kMGPipeSubsystemsMigratedAtP5e,
0x7ffull,
0x5ffull,
0x9ffull};
for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) {
masks.push_back(kMGPipeSubsystemsMigratedAtP5e & ~row.Requires);
masks.push_back(kMGPipeSubsystemsMigratedAtP5e & ~row.Family);
}
return masks;
}
String Describe(Uint64 mask) {
char buffer[32];
std::snprintf(buffer, sizeof(buffer), "0x%llx", static_cast<unsigned long long>(mask));
return String(buffer);
}
} // namespace
// ---------------------------------------------------------------------------------------------
// 1. The table itself is exhaustive and well-formed.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, EveryRowNamesExactlyOneFamilyAndNoRowRequiresItself) {
Uint64 seen = 0;
for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) {
EXPECT_NE(row.Family, 0ull) << "a row with no family is not a row";
EXPECT_EQ(row.Family & (row.Family - 1), 0ull)
<< "a row must name EXACTLY ONE bit; " << Describe(row.Family) << " names several, and "
<< "MGPipeSubsystemRequires ORs every matching row - a multi-bit family would make a "
"query for one of its bits answer for all of them";
EXPECT_EQ(row.Family & row.Requires, 0ull)
<< Describe(row.Family) << " requires itself, which no reader can ever satisfy";
EXPECT_EQ(seen & row.Family, 0ull)
<< "two rows name " << Describe(row.Family) << "; MGPipeSubsystemRequires would OR "
<< "them and the duplicate would be invisible";
seen |= row.Family;
EXPECT_NE(row.Why, nullptr);
EXPECT_NE(row.Why[0], '\0') << "a row with no reason is how the rule drifted in the first "
"place";
}
// The families the rule is about. Bit 8 and bit 13 are in the table too, which is the half
// MGPipe.h's old "THREE OF THEM" comment was missing.
EXPECT_EQ(seen, kMGPipeSubsystemVertexInput | kMGPipeSubsystemFramebuffer |
kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers |
kMGPipeSubsystemPrograms | kMGPipeSubsystemBufferBindings);
}
TEST(SubsystemDepsTest, TheFourthRowIsPresentAndTheWithdrawnMirrorPairIsNotFine) {
// ID-14/ID-15. This is the row two comments in the tree denied existed, and the reason this
// case is written as an assertion rather than a paragraph.
EXPECT_NE(MGPipeSubsystemRequires(kMGPipeSubsystemTextureResources) & kMGPipeSubsystemSamplers,
0ull)
<< "bit 10 no longer requires bit 11. If that is intended, the withdrawal recorded in "
"PipeFill.cpp and in BRIEF-P4A's amendment has been reversed and Espryt's "
"ResolveTextureResourceSubsystemArm must stop refusing 0x7ff in the same commit";
EXPECT_FALSE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, 0x7ffull));
EXPECT_FALSE(MGPipeSubsystemDependenciesAreSet(
kMGPipeSubsystemSamplers,
kMGPipeSubsystemsMigratedAtP5e & ~kMGPipeSubsystemTextureResources))
<< "bit 11 without bit 10 must not be live - the symmetric half of the fourth row";
// ...and the mirrors that really are fine.
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemPrograms, 0ull))
<< "bit 12 depends on nothing";
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(
kMGPipeSubsystemTextureResources,
kMGPipeSubsystemResources | kMGPipeSubsystemSamplers | kMGPipeSubsystemTextureResources))
<< "bit 10 with 7 and 11 and WITHOUT bit 9 is fine and is not a row";
}
// ---------------------------------------------------------------------------------------------
// 2. THE CLIENT DEPENDENCY CHECK never emits a family the table refuses.
//
// ONE-DIRECTIONAL, AND FOR A MEASURED REASON. MGPipeP4aFamilyEmits is a QUADRUPLE: the
// operator's bit, the family's wired constant, a backend having registered the consumer, and
// the dependency rule (PipeFill.cpp's `wants()`). Only the fourth conjunct is this file's
// subject, and the other three are false in a bare unit binary with no backend registered - the
// first version of this case asserted equality and reddened at 0x1fff on the sampler and program
// families for exactly that reason, which is the assertion being wrong rather than the product.
//
// So the direction that IS this rule's: the client may withhold a family for reasons of its own,
// but it may never EMIT one whose dependency bits are unset. That is the direction in which a
// disagreement means records crossing to a server whose matching handle arm did not come up.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheClientNeverEmitsAFamilyTheTableRefuses) {
#if MOBILEGL_PIPE_PUSH
const Uint64 families[] = {kMGPipeSubsystemFramebuffer, kMGPipeSubsystemTextureResources,
kMGPipeSubsystemSamplers, kMGPipeSubsystemPrograms};
int emitted = 0;
for (const Uint64 mask : InterestingMasks()) {
for (const Uint64 family : families) {
if (!MGPipeP4aFamilyEmits(family, mask)) continue;
++emitted;
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(family, mask))
<< "at MOBILEGL_PIPE_PUSH=" << Describe(mask) << " the CLIENT emits family "
<< Describe(family)
<< " while MG_Pipe/SubsystemDeps.def says its dependency bits are not all set. "
"Those records reach a server whose matching handle arm refused to come up - "
"the half-run subsystem D-K2 exists to make impossible. The two statements of "
"the rule have drifted; whichever moved, the other has to move with it.";
EXPECT_NE(mask & family, 0ull)
<< "the client emitted a family whose own bit is clear at " << Describe(mask);
}
}
// NOT VACUOUS-PROOF, but a loud marker: a unit binary registers no backend consumer, so
// MGPipeP4aFamilyEmits may legitimately be false everywhere here. Recorded rather than
// asserted, because asserting it would make this case depend on whether the binary happens
// to have pulled in a consumer registration.
RecordProperty("client_emitting_combinations", emitted);
#else
GTEST_SKIP() << "the client dependency check is compiled only under MOBILEGL_PIPE_PUSH";
#endif
}
// ---------------------------------------------------------------------------------------------
// 3. THE SERVER CONSUMER GATE agrees with the table at every interesting mask.
//
// The resolvers answer "did this family's handle arm come up", which is the dependency rule AND
// the family's own bit AND, for the framebuffer one, D-C3's separate wire-caps refusal. So the
// comparison is one-directional where it has to be: a resolver may say NO for a reason of its
// own, but it may never say YES at a mask the table refuses - that is the direction in which a
// disagreement is a half-run subsystem rather than an extra refusal.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheServerConsumerGateNeverArmsAFamilyTheTableRefuses) {
using namespace MobileGL::MG_Backend::DirectGLES;
struct Resolver {
const char* Name;
Uint64 Family;
Bool (*Resolve)();
};
const Resolver resolvers[] = {
{"ResolveFramebufferSubsystemArm", kMGPipeSubsystemFramebuffer,
&ResolveFramebufferSubsystemArm},
{"ResolveTextureResourceSubsystemArm", kMGPipeSubsystemTextureResources,
&ResolveTextureResourceSubsystemArm},
{"ResolveSamplerSubsystemArm", kMGPipeSubsystemSamplers, &ResolveSamplerSubsystemArm},
{"ResolveProgramSubsystemArm", kMGPipeSubsystemPrograms, &ResolveProgramSubsystemArm},
};
for (const Uint64 mask : InterestingMasks()) {
PushMaskScope scope(mask);
for (const Resolver& resolver : resolvers) {
const Bool armed = resolver.Resolve();
if (!armed) continue;
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(resolver.Family, mask))
<< "at MOBILEGL_PIPE_PUSH=" << Describe(mask) << " the SERVER's " << resolver.Name
<< " ARMED the handle arm for family " << Describe(resolver.Family)
<< " while MG_Pipe/SubsystemDeps.def says its dependency bits are not all set. "
"The client withholds the family at this mask, so the server is running a "
"handle arm nobody is feeding - which is the half-run subsystem D-K2 exists "
"to make impossible.";
EXPECT_NE(mask & resolver.Family, 0ull)
<< resolver.Name << " armed with its own bit clear at " << Describe(mask);
}
}
}
// ---------------------------------------------------------------------------------------------
// 4. ...and the refusal really happens at the two documented lanes, so that test 3 is not
// vacuously green because every resolver said NO for an unrelated reason.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheTwoDocumentedRefusalLanesStillRefuseAndTheFullMaskStillArms) {
using namespace MobileGL::MG_Backend::DirectGLES;
{
PushMaskScope scope(kMGPipeSubsystemsMigratedAtP5e);
EXPECT_TRUE(ResolveTextureResourceSubsystemArm())
<< "the texture family's handle arm does not come up at its own phase mask, so every "
"refusal assertion in this file is vacuous";
EXPECT_TRUE(ResolveSamplerSubsystemArm());
EXPECT_TRUE(ResolveProgramSubsystemArm());
}
{
// 0x7ff: bits 0..10, i.e. the texture family WITHOUT the sampler family - D-K2's fourth
// row, and the mask CMakeLists.txt's refusal lane pins.
PushMaskScope scope(0x7ffull);
EXPECT_FALSE(ResolveTextureResourceSubsystemArm())
<< "0x7ff is the mask whose texture family Espryt refuses by name; if it now arms, "
"D-K2's fourth row has been dropped on the server side while the client and the "
".def still carry it";
}
}