From 519445f94658fe4a046b887dd26f77a2505465c5 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 22 Sep 2026 17:21:49 -0400 Subject: [PATCH] [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. --- MobileGL/MG_Impl/Pipe/PipeFill.h | 12 +- MobileGL/MG_Impl/Pipe/TextureEmit.h | 7 +- MobileGL/MG_Pipe/MGPipe.h | 92 ++++++- MobileGL/MG_Pipe/SubsystemDeps.def | 73 ++++++ MobileGL/MG_Remote/CONTRACT-P5.md | 6 +- .../MG_Test/Backend/DirectGLES/CMakeLists.txt | 8 + .../Backend/DirectGLES/SubsystemDepsTest.cpp | 247 ++++++++++++++++++ docs/Disaggregated/notes/p34b/espryt-d2.md | 145 ++++++++++ 8 files changed, 568 insertions(+), 22 deletions(-) create mode 100644 MobileGL/MG_Pipe/SubsystemDeps.def create mode 100644 MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.h b/MobileGL/MG_Impl/Pipe/PipeFill.h index 311e3c48..e4bf6bc1 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.h +++ b/MobileGL/MG_Impl/Pipe/PipeFill.h @@ -61,9 +61,15 @@ namespace MobileGL::MG_Pipe { // 47 DirectGLES cases at ID-41). With them the four families emit NOTHING in that state and // the legacy pull path runs exactly as it does on a pull build. // - // D-K2's TABLE IS IN PipeFill.cpp, ONCE: bit 9 requires bit 10, bit 10 requires bits 7 and - // 11, bit 11 requires bit 10, bit 12 depends on nothing - the client mirror, bit for bit, of - // the four `ResolveSubsystemArm()` refusals in DirectGLES/Managers.cpp. + // D-K2's ROWS ARE IN MG_Pipe/SubsystemDeps.def, ONCE (P3b/P4b R-5), and this comment no + // longer restates them - restating them here is half of how the rule ended up with six + // statements, two of which were wrong. PipeFill.cpp's kMGPipeP4aFamilyDependencies is still + // the client's own copy and is still the bit-for-bit mirror of the four + // `ResolveSubsystemArm()` refusals in DirectGLES/Managers.cpp; switching it to read + // the .def is the integrator's one-line change (PipeFill.cpp is the contract package's file + // for the whole phase, so the emitter packages do not edit it). + // MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp drives BOTH readers at every interesting + // mask and compares them against the .def, so the two cannot drift in the meantime. // // It is exported for the unit gate and for no other caller: the gate itself is // FamilyIsLive() inside PipeFill.cpp, every birth hook and every `wants()` row resolves diff --git a/MobileGL/MG_Impl/Pipe/TextureEmit.h b/MobileGL/MG_Impl/Pipe/TextureEmit.h index cbac2736..3110692b 100644 --- a/MobileGL/MG_Impl/Pipe/TextureEmit.h +++ b/MobileGL/MG_Impl/Pipe/TextureEmit.h @@ -100,9 +100,10 @@ namespace MobileGL::MG_Pipe { // THE FLIP IS THE WHOLE SWITCH AND NOTHING ELSE MOVES: PipeFill.cpp ORs this constant into // kMGPipeWiredSubsystems, static_asserts it is 0-or-its-own-bit, gates every birth hook on // FamilyIsLive(kMGPipeSubsystemTextureResources, this), and gates DrainTextureSubData on - // the same OR. Bit 9 (framebuffer) REQUIRES bit 10 (D-K2), because every MGPSurface::Res - // names a texture or renderbuffer handle the applier must hold a record for - so this - // package may never be integrated with only one of the two constants set. + // the same OR. D-K2's rows - including bit 9's dependency on this family - are in + // MG_Pipe/SubsystemDeps.def, once (P3b/P4b R-5), and are not restated here; what this + // comment still has to say is the consequence for INTEGRATION ORDER, which no table can + // carry: this package may never be integrated with only one of the two constants set. inline constexpr Uint64 kMGPipeWiredTextureSubsystem = kMGPipeSubsystemTextureResources; static_assert(kMGPipeWiredTextureSubsystem == 0 || kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources, diff --git a/MobileGL/MG_Pipe/MGPipe.h b/MobileGL/MG_Pipe/MGPipe.h index e30d0a13..c7e6fa9b 100644 --- a/MobileGL/MG_Pipe/MGPipe.h +++ b/MobileGL/MG_Pipe/MGPipe.h @@ -112,17 +112,30 @@ namespace MobileGL::MG_Pipe { // path that regressed are four different findings, and clearing one must not disarm the // other three. // - // THREE OF THEM HAVE A DEPENDENCY and it is diagnosed at the first use, never half-run - - // one ResolveSubsystemArm per family beside the backend's existing - // ResolveResourceSubsystemArm, modelled on the bit-8-requires-bit-7 refusal it already - // ships, and lazy rather than at bring-up because a pre-flight child dying on a signal - // makes a whole lane SKIP green: bit 11 requires bit 10 because - // every MGPBoundView::Texture and MGPImageView::Res names a Texture handle and only bit 10 - // puts one in the slot table; bit 9 requires bit 10 because MGPSurface::Res does; and bit - // 10 requires bit 7 because a buffer texture's BufferForTexBuffer names a Buffer handle. - // The mirror pairs (10 without 11, 10 without 9, 7 without 10) are all fine, and are - // stated as such because an unreachable branch that says something different is how the - // reachable one drifts. Bit 12 depends on nothing. + // FOUR OF THE SIX FAMILIES CARRY A DEPENDENCY and it is diagnosed at the first use, never + // half-run - one ResolveSubsystemArm per family beside the backend's existing + // ResolveResourceSubsystemArm, and lazy rather than at bring-up because a pre-flight child + // dying on a signal makes a whole lane SKIP green. + // + // THE ROWS ARE IN SubsystemDeps.def, ONCE (P3b/P4b R-5), and this comment no longer + // restates them. It used to, and it was WRONG in two ways that nothing could fail: it said + // "THREE OF THEM" when there are six rows, and it said "the mirror pairs (10 without 11, + // 10 without 9, 7 without 10) 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." The audit found the same pair of defects repeated in + // MG_Backend/DirectGLES/Managers.h's header, i.e. in the header of the file that + // implements the refusal. + // + // THE MIRROR PAIRS THAT REALLY ARE FINE, said out loud rather than left as an absence, + // because a table is only trustworthy if what it does NOT contain was decided: + // - bit 10 set, bit 9 clear: FINE. The legacy FBO sync reaches the texture twin through + // SyncTextureObjectToBackend, which dispatches to the handle arm by itself. + // - bit 11 set, bit 9 clear: FINE, for the same reason - a sampler view names a texture, + // never a framebuffer. + // - bit 7 set, bit 10 clear: FINE, and it is P3a's shipped configuration. + // - bit 12 set with any or none of 9/10/11: FINE, per its own row. + // - bit 10 set, bit 11 clear and its mirror are NOT fine; that is the fourth row. inline constexpr Uint64 kMGPipeSubsystemFramebuffer = 1ull << 9; // set_framebuffer_state inline constexpr Uint64 kMGPipeSubsystemTextureResources = 1ull << 10; // texture + renderbuffer // resource_*, set_texture_params @@ -137,8 +150,9 @@ namespace MobileGL::MG_Pipe { // path that regressed and a buffer path that regressed are different findings, and // clearing one must not disarm the other. // - // BIT 13 REQUIRES BIT 7, and for bit 11's reason: every MGPBufferRange::Res names a Buffer - // handle, and only bit 7 puts one in the slot table. + // Its dependency row is in SubsystemDeps.def with the other five (P3b/P4b R-5). It used to + // be stated here instead, fifteen lines away from the block that stated the other three - + // which is how "THREE OF THEM" above stayed wrong through two phases. inline constexpr Uint64 kMGPipeSubsystemBufferBindings = 1ull << 13; // bits 14..62 reserved for the later phases, allocated in ROADMAP order. // NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of CSOs, so @@ -166,6 +180,58 @@ namespace MobileGL::MG_Pipe { (kMGPipeSubsystemsMigratedAtP4a | kMGPipeSubsystemBufferBindings), "the P5e phase constant and P5e's subsystem bit have drifted"); + // ---- D-K2's dependency rule, in ONE place (P3b/P4b R-5) -------------------------------- + // + // The rows, their reasons and the argument for a table rather than six comments are in + // SubsystemDeps.def. This is the expansion both readers are meant to end up on; the + // anti-drift property is already live through + // MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp, which drives the client check and the + // server gate at every interesting mask and compares both against these rows. +#include "SubsystemDeps.def" + + struct MGPipeSubsystemDependencyRow { + Uint64 Family; // exactly one kMGPipeSubsystem* bit + Uint64 Requires; // the bits MOBILEGL_PIPE_PUSH must ALSO carry; 0 is a row, not a gap + const char* Why; + }; + +#define MGL_SUBSYSTEM_DEPENDENCY_ROW(family, requires_, why) {family, requires_, why}, + inline constexpr MGPipeSubsystemDependencyRow kMGPipeSubsystemDependencies[] = { + MGL_SUBSYSTEM_DEPENDENCY_LIST(MGL_SUBSYSTEM_DEPENDENCY_ROW)}; +#undef MGL_SUBSYSTEM_DEPENDENCY_ROW + + // The bits `subsystem` additionally needs, or 0 for a family with no row. A family with no + // row and a family whose row says 0 are the same answer ON PURPOSE: both mean "nothing else + // is required", and the table covers its families exhaustively so the two cannot be + // confused for "I forgot to look". + inline constexpr Uint64 MGPipeSubsystemRequires(Uint64 subsystem) { + Uint64 required = 0; + for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) { + if ((subsystem & row.Family) != 0) required |= row.Requires; + } + return required; + } + + // NOT TRANSITIVE, and that is load-bearing: each reader tests the required BITS against + // MOBILEGL_PIPE_PUSH alone and never the other family's liveness, because Espryt's + // resolvers classify their arms from that mask alone (PipeFill.cpp's own argument; + // TextureEmitTest.cpp pins it). A transitive closure here would make the two sides disagree + // at some mask - the failure this table exists to prevent. + inline constexpr Bool MGPipeSubsystemDependenciesAreSet(Uint64 subsystem, Uint64 pushMask) { + const Uint64 required = MGPipeSubsystemRequires(subsystem); + return (pushMask & required) == required; + } + + static_assert(MGPipeSubsystemRequires(kMGPipeSubsystemTextureResources) == + (kMGPipeSubsystemResources | kMGPipeSubsystemSamplers), + "D-K2's fourth row (bit 10 requires bit 11) has gone missing again"); + static_assert(MGPipeSubsystemRequires(kMGPipeSubsystemPrograms) == 0, + "bit 12 depends on nothing, and that is a row rather than an absence"); + static_assert(!MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, 0x7ffull), + "0x7ff is the mask whose texture family Espryt refuses; the table must agree"); + static_assert(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, 0x1fffull), + "0x1fff is P4a's own arm and every row of it must be satisfied"); + // The catalogue itself. Only macros, so it is safe to expand inside the namespace, and // consumers (the unit test, later the transport) get MGP_CALL_LIST from this header. #include "PipeCalls.def" diff --git a/MobileGL/MG_Pipe/SubsystemDeps.def b/MobileGL/MG_Pipe/SubsystemDeps.def new file mode 100644 index 00000000..24966946 --- /dev/null +++ b/MobileGL/MG_Pipe/SubsystemDeps.def @@ -0,0 +1,73 @@ +// MobileGL - MobileGL/MG_Pipe/SubsystemDeps.def +// 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, IN ONE PLACE (P3b/P4b R-5). +// +// WHAT WAS WRONG. The rule "bit X requires bit Y" was stated in SIX places, and the audit found +// two of them WRONG in the same direction: +// +// * MG_Pipe/MGPipe.h's comment said "THREE OF THEM HAVE A DEPENDENCY" (there are six rows) and +// "The mirror pairs (10 without 11, 10 without 9, 7 without 10) are all fine" - but 10 +// without 11 is exactly D-K2's FOURTH row, refused by the server at +// Managers.cpp ResolveTextureResourceSubsystemArm and withheld by the client at +// PipeFill.cpp's table. PipeFill.cpp says so out loud: "The brief's original +// 'bit 10 without 11 is fine' is WITHDRAWN for P4a as built."; +// * MG_Backend/DirectGLES/Managers.h's header comment repeats both defects, in the header of +// the file that IMPLEMENTS the refusal. +// +// Neither wrong comment could fail anything, which is the whole argument for a table: the two +// EXECUTABLE statements of the rule are in different link units with no shared header, and the +// four PROSE statements drifted because nothing compared them to anything. +// +// WHAT THIS FILE IS. The rule, once, as rows. It is a preprocessor-only table like +// MG_Remote/FatalFamilies.def - no generator reads it - and it carries pure bit constants, so +// including it from MGPipe.h keeps check_include_closure.py's value-header rule satisfied. +// +// WHO READS IT. MGPipe.h turns it into kMGPipeSubsystemDependencies[] and +// MGPipeSubsystemRequires(). The two executable statements of the rule are: +// +// CLIENT dependency check MG_Impl/Pipe/PipeFill.cpp's kMGPipeP4aFamilyDependencies / +// P4aFamilyDependenciesAreSet (bits 9-12), plus P5eFamilyIsLive's +// hand-coded bit-13 row. +// SERVER consumer gate MG_Backend/DirectGLES/Managers.cpp's four +// ResolveSubsystemArm() over PipeSubsystemDependencyMissing +// (bits 9-12), plus ResolveVertexInputSubsystemArm (bit 8) and +// DirectGLES.cpp's ResolveBufferBindingSubsystemArm (bit 13). +// +// THEY ARE NOT SWITCHED OVER IN THIS COMMIT, deliberately: PipeFill.cpp is the contract +// package's file for the whole phase (TextureEmit.h states that rule) and Managers.cpp belongs +// to wave 2-D package D1 while it is in flight. The anti-drift property does not wait for that, +// because MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp drives BOTH readers at every +// interesting mask and compares them against these rows - a change to either side alone reds. +// Switching the two readers to read this table is two one-line changes for the integrator. +// +// THE ROWS ARE NOT TRANSITIVE, and that is load-bearing rather than an oversight: each reader +// tests the required BITS against MOBILEGL_PIPE_PUSH alone, never the other family's liveness, +// because Espryt's resolvers classify their arms from that mask alone. Making the rows +// transitive here would make the two sides disagree at some mask, which is the failure the +// table exists to prevent. PipeFill.cpp:1576-1581 argues this at length and +// TextureEmitTest.cpp:1278-1283 pins it. +// +// THE MIRROR PAIRS THAT STAY FINE are stated in MGPipe.h beside the table rather than here, +// because an absence is not a row. + +// clang-format off + +// X(Family, Requires, Why) +// Family - exactly one kMGPipeSubsystem* bit +// Requires - the bits MOBILEGL_PIPE_PUSH must ALSO carry for that family to be live; 0 is a +// ROW and not an absence, so the table covers its families exhaustively +#define MGL_SUBSYSTEM_DEPENDENCY_LIST(X) \ + X(kMGPipeSubsystemVertexInput, kMGPipeSubsystemResources, "a vertex buffer binding names a Buffer handle and only bit 7 puts one in the slot table (P3a's shipped shape; ResolveVertexInputSubsystemArm)") \ + X(kMGPipeSubsystemFramebuffer, kMGPipeSubsystemTextureResources, "every MGPSurface::Res names a Texture or Renderbuffer handle and only bit 10 populates those two slot tables (ResolveFramebufferSubsystemArm)") \ + X(kMGPipeSubsystemTextureResources, kMGPipeSubsystemResources | kMGPipeSubsystemSamplers, "a buffer texture's BufferForTexBuffer names a Buffer handle (bit 7); and MGPTextureParams::BuiltinSampler is a SamplerCso HANDLE only bit 11 mints, whose null the applier treats as Fatal{ProtocolCorruption} rather than a decline - D-K2's fourth row, ID-14/ID-15, which WITHDRAWS the brief's 'bit 10 without 11 is fine'") \ + X(kMGPipeSubsystemSamplers, kMGPipeSubsystemTextureResources, "every MGPBoundView::Texture and MGPImageView::Res names a Texture handle and only bit 10 populates that slot table; with the row above this is SYMMETRIC - bits 10 and 11 are one arm with two switches (ResolveSamplerSubsystemArm)") \ + X(kMGPipeSubsystemPrograms, 0, "a ShaderCso handle names no texture and no buffer, the archive rides beside the record as a companion pointer, and the server's extra specialisation inputs come from state the backend already holds (ResolveProgramSubsystemArm)") \ + X(kMGPipeSubsystemBufferBindings, kMGPipeSubsystemResources, "every MGPBufferRange::Res names a Buffer handle and only bit 7 puts one in the slot table, so without it every server-side EnsureBufferResourceForHandle would mint a twin with no record behind it (P5e sb; ResolveBufferBindingSubsystemArm)") + +// clang-format on diff --git a/MobileGL/MG_Remote/CONTRACT-P5.md b/MobileGL/MG_Remote/CONTRACT-P5.md index 54635c20..ed23ac26 100644 --- a/MobileGL/MG_Remote/CONTRACT-P5.md +++ b/MobileGL/MG_Remote/CONTRACT-P5.md @@ -67,9 +67,9 @@ P2/P3a; everything else that is not a plain scalar is here. | **`MGPCaps::CallMask` layout** | bits **0..8** = `MGPCapBit`, unchanged. bits **9..31** reserved. bits **32..47** = the **consumer mask**: bit `32+n` means "the server has a consumer for MGPipe subsystem bit `n`". bits **48..63** reserved. | a clear consumer bit = "this server does not consume that family; emit nothing for it". | `CapsMirror::ServerConsumes` — the **only** legal client-side source | c0's ruling, and the thing that makes R-8 implementable at all: R-8 says the client's liveness gates must read the `CallMask` mirror, but `CallMask` as declared has only nine feature bits and no per-family bit. Constants and the two fold/test helpers are in `MG_Remote/CapsCodec.h`; `CapsCodec.cpp` asserts the block does not collide with `MGPCapBit` and that P4a's `0x1fff` fits sixteen bits. | | **ABI agreement** | `Hello`/`Welcome` assert both peers agree on `sizeof(DynamicBackendParameters)`, `sizeof(MGPCaps)`, `sizeof(GLFunctionsTable)` and `buildFingerprint`. A mismatch is `Fatal{AbiMismatch}` and **never** a downgrade. | — | s1 | The compositional assertion above means the caps block's literal size is ABI-dependent. P6's spawn is same-machine, same-binary and inherits this unchanged. A fixed-width rewrite of `DynamicBackendParameters` is **P7's** account, not P5's. | | **`MGPSubData::Target`** | Packed: **low byte = `MGPipeResourceTarget`, high byte = the cube-face upload target**. Read only through `MGPipeSubDataResourceTargetOf` / `MGPipeSubDataUploadTargetOf`. Whole field `== 0` is the **buffer** half; a low byte naming `Buffer`, `Renderbuffer` or `>= MGPipeResourceTarget::Count` is `Fatal{ProtocolCorruption}`. | whole field 0 = buffer upload | applier | P4a ID-12. Already settled; copied here because a decoder that open-codes it is the class-1 defect. | -| **`MGPImageView::Access`** | The three-value encoding at `ImageEmit.h:146-159`, verbatim. | — | decoder, applier | **This encoding has only ever lived in a package header** (P4a R-3). This table is its first wire reader, so it is now contract. | -| **`MGPSamplerView::Target`** | The encoding at `SamplerEmit.h:900`, verbatim. | — | decoder, applier | Same status as the row above. | -| **`MGPFramebufferState::DrawBuffers[8]`** | The `-1` / default-token narrowing at `FramebufferEmit.h:146-163`, verbatim. A draw-buffer entry naming a colour attachment outside the record's own array is `Fatal{ProtocolCorruption}` (the applier already says so). | `-1` = "no attachment" | decoder, applier | Same status. | +| **`MGPImageView::Access`** | `0 = GL_READ_ONLY`, `1 = GL_WRITE_ONLY`, `2 = GL_READ_WRITE`. Named by `kMGPipeImageAccess{ReadOnly,WriteOnly,ReadWrite}` and validated by `MGPipeImageAccessIsValid` / `MGPipeDecodeImageAccess`, all in `MG_Pipe/MGPipeValueTypes.h`. Any other value is `Fatal{ProtocolCorruption, "ImageView.Access"}` at the READER. **Not** `MGPImageBind::Access`, which carries the raw GL token (`0x88B8`/`0x88B9`/`0x88BA`) and is a different field. | — | decoder, applier | **This encoding only ever lived in a package header** (P4a R-3); P5e ruling 16 moved it to `MGPipeValueTypes.h`, which is where both roles now read it. P3b/P4b R-3 replaced this row's line-range citation with the ENUMERATOR NAMES: the old pointer (`ImageEmit.h:146-159`) had drifted off the encoding entirely, which is the failure mode this table's own `MGPSubData::Target` row warns about. | +| **`MGPSamplerView::Target`** | `MobileGL::TextureTarget` cast to `Uint8` (`SamplerEmit.h`'s `m_lastView.Target = static_cast(texture.GetTarget())`), 11 values. **Not** `SamplerEmit.h`'s `SamplerTarget[]`, which is a DIFFERENT, biased encoding of the same enum (`TextureTarget + 1`, `0` = "this program samples nothing here") living 200 lines away and client-internal. | — | decoder, applier | P3b/P4b R-3: the old line citation had drifted by six lines. **Known gap, stated rather than closed here**: unlike `MGPipeResourceTarget` this encoding has no `Count` bound, no `…IsValid` predicate and no `Fatal{ProtocolCorruption}` for an out-of-range value — the decoders at `DirectGLES.cpp` cast straight back. Giving it the `MGPipeImageAccess` treatment in `MGPipeValueTypes.h` is the remaining half of R-3 and is not this package's file. | +| **`MGPFramebufferState::DrawBuffers[8]`** | An index INTO THIS RECORD'S OWN `Color[]` array; `-1` = NONE. The four default-framebuffer tokens map to `0`. Narrowed by `MGPipeDrawBufferIndex` and bounded by `MGPipeDrawBufferIsInsideTheWireWidth` (`MG_Impl/Pipe/FramebufferEmit.h`); the applier re-checks `< -1 \|\| >= kMGPipeMaxColorAttachments` and raises `Fatal{ProtocolCorruption}` (`MG_Pipe/PipeApply.cpp`). `kMGPipeMaxColorAttachments == MobileGL::kMGMaxDrawBuffers` is `static_assert`ed. | `-1` = "no attachment" | decoder, applier | P3b/P4b R-3: the old line citation (`FramebufferEmit.h:146-163`) was off by one and is replaced by the FUNCTION NAMES, which move with the code. | | **`MGPReplySlot::Id`** | **= the record's sequence number** (R-3). No new id space, no allocator. The server writes the answer into `SEG_REPLY[seq % slots]` and **stamps `seq` back into the slot header** so a wrong-slot read is detectable rather than plausible. | seq is 1-based; `0` = "no record / not encoded" | client barrier wait | `ARCHITECTURE.md:124`: the wire carries no per-record seq field, so seq *is* the ordinal. `MGPReplySlot` exists (`MGPipeTypes.h:78-81`) and **no payload of the ten `kReplySlot` calls contains one** — which is exactly why the id must be derived rather than carried. P9 generalises this to "seq is the id's initial value", which extends the rule rather than overturning it. | | **reply slot header** | `{Uint64 Seq; Int32 Status; Uint32 Size;}` — 16 bytes, then the payload. `Status`: **0 = OK, 1 = DECLINED, 2 = ERROR**. | — | client | **`DECLINED` is a real answer, not a failure.** It is how `MapPersistent` says `nullptr` (R-6) and how the four `Bool` acceptance entry points say `false` (R-5). A client that treats DECLINED as an error re-creates ID-39's 66 lost uploads from the other side. | | **which rows own a reply slot** | **14, and `kMGPipeCallFlags` is the single source of truth** (R-16). The ten always-declared answers plus the **four acceptance rows** — `ResourceCreate`, `ResourceRespecify`, `ResourceSubData`, `SetTextureParams`. | a row without `kReplySlot` has no slot and must never be posted to | s1 sizes the pool from it; w1 posts against it | The four are exactly the `MGPipeApply*` entry points returning `Bool` (`PipeApply.h:820`, `:868`, `:897`, `:1023`); `MapPersistent`'s `void*` is the fifth answer and was already declared. They carried no flag because in monolith the answer is a direct call's return value and there was nothing to declare — but this table already said DECLINED is how they answer, so the catalogue and the contract could not both stand. Pinned by `PipeCatalogueTest`: 14 by count **and** the four by name, so a row cannot lose the flag while another gains one. | diff --git a/MobileGL/MG_Test/Backend/DirectGLES/CMakeLists.txt b/MobileGL/MG_Test/Backend/DirectGLES/CMakeLists.txt index d8dedeaf..1ce38d55 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/CMakeLists.txt +++ b/MobileGL/MG_Test/Backend/DirectGLES/CMakeLists.txt @@ -53,6 +53,14 @@ add_executable(ContextEpochTest ContextEpochTest.cpp) target_include_directories(ContextEpochTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL) target_link_libraries(ContextEpochTest PRIVATE GTest::gtest_main ${LINK_LIBRARIES}) gtest_discover_tests(ContextEpochTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) + +# P3b/P4b R-5: D-K2's dependency rule had six statements and nothing compared them. This binary +# is where the comparison can live, because it is the one that links BOTH the client check +# (MG_Impl/Pipe, through MG_Pipe) and the server gate (MG_Backend/DirectGLES's resolvers). +add_executable(SubsystemDepsTest SubsystemDepsTest.cpp) +target_include_directories(SubsystemDepsTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL) +target_link_libraries(SubsystemDepsTest PRIVATE GTest::gtest_main ${LINK_LIBRARIES}) +gtest_discover_tests(SubsystemDepsTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(EsslShaderPassTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(BaseInstanceInjectionTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(ViewportIndexRoutingTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) diff --git a/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp new file mode 100644 index 00000000..923f7919 --- /dev/null +++ b/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp @@ -0,0 +1,247 @@ +// 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 +// ResolveSubsystemArm(), 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 + +#include +#include +#include + +#if MOBILEGL_PIPE_PUSH +#include +#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 InterestingMasks() { + Vector 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(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"; + } +} diff --git a/docs/Disaggregated/notes/p34b/espryt-d2.md b/docs/Disaggregated/notes/p34b/espryt-d2.md index 45040f2d..d6a3d4c4 100644 --- a/docs/Disaggregated/notes/p34b/espryt-d2.md +++ b/docs/Disaggregated/notes/p34b/espryt-d2.md @@ -195,3 +195,148 @@ probe hits (`MGITEST_PIPE_CLIENT_TEXTURE_UPLOAD_EMITTER_PRESENT=1` is in every l lands"). The `else()` arm is KEPT — it is what makes the arming decision falsifiable in both directions — but it is unreachable on this tree and now says so. * `notes/p4a/p4a-results/gates-v1.md`'s re-run table + +--- + +## Slice 3 — the memo purity gate + +`scripts/ci/espryt_memo_purity.py`, a sibling of `link_seam_purity.py`, wired into `test.yml` +beside it. Scoped to the BRACES of the families the plan names (`ResolvedTextureBindingMemo`, +`SamplerPassMemo`, `UnitSamplerLookupMemo`, `TwinLookupMemo`, `StateBackendObjectRegistry`) plus +the image sweep's seven file statics, because these two files pass frontend pointers around +legitimately in hundreds of places and a file-wide grep would be a list of exceptions. + +Guard-awareness is a small conditional walker, not a preprocessor: it recognises +`#if MOBILEGL_PIPE_LEGACY_MEMOS`, `#if !MOBILEGL_BUILD_DISAGGREGATED` and the `#else` of +`#if MOBILEGL_BUILD_DISAGGREGATED`, which is every spelling these files use. + +**Six allow-list entries, each with its reason.** The two that matter: +`StateBackendObjectRegistry`'s `UnorderedMap` (`Managers.h:330`, and the +`m_entries` member behind it) and `ResolvedTextureBindingMemo`'s type-erased `const void* program` +are the PULL build's only arms — deleting them moves pull `.text` and fails G1, so they retire +with the pull path (P13). The other four are memo VALUES rather than keys. + +**Red-once (R-16)**: 7 negative controls in `--self-test`, 4 positive controls (a +`LEGACY_MEMOS` arm, the `#else` of a `DISAGGREGATED` arm, the allow-listed type-erased key, and a +frontend pointer outside every memo family) — plus one taken against the real tree: injecting +`MG_State::GLState::ProgramObject* regressionKey` into the actual `ResolvedTextureBindingMemo` +reddens the gate and names `DirectGLES.cpp:7266`. + +**What the sweep found that is NOT in scope**: seven unguarded frontend-pointer-keyed containers +under `MG_Backend/DirectVulkan` (`VkTextureManager`'s five `TextureIdentity`-keyed containers, +`VkClearManager`'s two, `VkRenderPassManager`'s two raw `RenderbufferObject*` maps, and +`VulkanRenderer::VaoDrawMemo`'s `vaoKey`+`vaoLifetimeId` pair sitting beside the correctly fenced +handle key). Magma's key inventory is P7 wave 2's, so the gate does not look there; recorded here +so the next pass meets a known list. + +--- + +## Slice 4 — R-5 (D-K2's dependency rule in one place) and R-3 + +### R-5 + +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:115-125` and `MG_Backend/DirectGLES/Managers.h:690-695` +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 is 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_assert`s over it: + +| family | requires | +|---|---| +| bit 8 VertexInput | bit 7 | +| bit 9 Framebuffer | bit 10 | +| bit 10 TextureResources | bit 7 **and bit 11** | +| bit 11 Samplers | bit 10 | +| bit 12 Programs | — (a ROW, not an absence) | +| bit 13 BufferBindings | bit 7 | + +Bits 8 and 13 were the two the old comment did not know about; bit 13's row lived fifteen lines +further down in `MGPipe.h`, 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 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**: +`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 is in that binary because that binary is +the one that links both sides. + +Both comparisons are **one-directional**, and each for a measured reason: + +* the server resolvers also fold in the family's own bit and (for the framebuffer one) D-C3's + separate wire-caps refusal, so a resolver may say NO for a reason of its own but may never say + YES at a mask the table refuses; +* `MGPipeP4aFamilyEmits` is a QUADRUPLE — the operator's bit, the wired constant, a registered + consumer, and the dependency rule — 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. That was the assertion being wrong, not the product; it is recorded because the + temptation to assert equality here is strong and wrong. + +**Red-once (R-16), twice:** + +1. drop D-K2's fourth row from the `.def` → **compile-time** failure, `MGPipe.h:225` and `:230` + ("D-K2's fourth row (bit 10 requires bit 11) has gone missing again"). +2. make the SERVER's `ResolveSamplerSubsystemArm` stop checking bit 10 (throwaway patch to + `Managers.cpp`, reverted) → `TheServerConsumerGateNeverArmsAFamilyTheTableRefuses` reds and + names the mask: "at MOBILEGL_PIPE_PUSH=0x9ff the SERVER's ResolveSamplerSubsystemArm ARMED the + handle arm for family 0x800 while MG_Pipe/SubsystemDeps.def says its dependency bits are not + all set." `0x9ff` is exactly the lane `CMakeLists.txt` already pins for G12. + +Stale prose corrected in `MGPipe.h` (both blocks), `MG_Impl/Pipe/PipeFill.h` and +`MG_Impl/Pipe/TextureEmit.h`. **`MG_Backend/DirectGLES/Managers.h:690-695` is NOT corrected here** +— it is package D1's file. Its two defects are named above so the integrator can take them in one +line. + +### R-3 (the cheap half) + +`MG_Remote/CONTRACT-P5.md` already claimed the image-access, sampler-view-target and draw-buffer +encodings as contract, but did so **by pointing at package-header line ranges, and all three had +drifted** — `ImageEmit.h:146-159` no longer holds the encoding at all, `SamplerEmit.h:900` is off +by six lines, `FramebufferEmit.h:146-163` by one. That is the exact failure the table's own +`MGPSubData::Target` row warns about. + +All three rows now state the encoding **by enumerator and function name** rather than by line, and +the two confusable siblings are named out loud: `MGPImageBind::Access` carries the raw GL token +(not this encoding), and `SamplerEmit.h`'s `SamplerTarget[]` is a different, biased encoding of the +same enum living 200 lines from the wire one. + +**The remaining half of R-3 is stated, not closed**: `MGPSamplerView::Target` has no `Count` +bound, no `...IsValid` predicate and no `Fatal{ProtocolCorruption}` for an out-of-range value, +unlike `MGPipeResourceTarget`. Giving it the `MGPipeImageAccess` treatment in +`MGPipeValueTypes.h` is a `MG_Pipe` change outside this package's declared footprint. + +--- + +## Slice 5 — the raw-depth-fetch sampler, step 1 only + +Step 1 done: `ContextEpochTest` gains a case that drives `GetRawDepthFetchSampler()` on BOTH arms +in one process and asserts they are two different `BackendSamplerObject`s — the observable form of +`DirectGLES.cpp:334-336`'s prose claim that the legacy pair "is not constructed or consulted by +the server". + +**Step 2 NOT done, and the package's own condition is why.** Every deletion candidate — the two +file-static `SharedPtr`s (`DirectGLES.cpp:82-83`), the monolith arm (`:361-371`), +`NeedsRawDepthFetchSampler` (`:393-399`) and the pre-handle sampler-pass branch (`:8015-8024`) — +is UNCONDITIONAL code. None sits behind `#if !MOBILEGL_BUILD_DISAGGREGATED`; the pull build +(`DISAGGREGATED=OFF`, `PIPE_PUSH=OFF`) compiles and links all of it, and the monolith arm is the +ENTIRE body `GetRawDepthFetchSampler` has there. Deleting any of it moves pull `.text` and fails +G1's 0/0/0/0 — `ARCHITECTURE.md:340` states that rule for exactly this class of +retired-but-compiled code. Filed as **P13**, beside the existing S7 entry in +`notes/p5f/f0-statics.md`. + +--- + +## Slice 6 — the census + +`notes/p34b/split-scenario-census.md` carries the table. Twelve scenarios newly gating in the +three GLES split arms; `ProgramPipelineScenario`'s nine non-storage-block cases deliberately not +registered (ScenarioFixture's armed-lane rule); DirectVulkan arms deliberately none, with the +tier-2 informational replay named as the reason it is not a coverage hole.