mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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.
This commit is contained in:
@@ -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 `Resolve<Family>SubsystemArm()` 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
|
||||
// `Resolve<Family>SubsystemArm()` 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
|
||||
|
||||
@@ -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,
|
||||
|
||||
+79
-13
@@ -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 Resolve<Family>SubsystemArm 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 Resolve<Family>SubsystemArm 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"
|
||||
|
||||
@@ -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
|
||||
// Resolve<Family>SubsystemArm() 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
|
||||
@@ -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<Uint8>(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. |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
@@ -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<StateObject*, Entry>` (`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.
|
||||
|
||||
Reference in New Issue
Block a user