Files
MobileGL/docs/Disaggregated/notes/p5e/BRIEF-P5E.md
T
swung0x48 aba329cc53 [Docs] (Disaggregated): P6's plan and contract draft; the working notes move from a local directory into the repository
Two things land together: the plan and contract draft P6 needs before it starts, and the
working notes that until now existed on exactly one machine.

1. P6's plan and contract draft (neither started; not one line of code)

     docs/Disaggregated/P6-SPAWN-PLAN.md      the package plan
     docs/Disaggregated/P6-CONTRACT-DRAFT.md  the contract draft (moves to
                                              MG_Remote/CONTRACT-P6.md when c6 lands)

   The three findings that mattered, all written into the documents:

   a. P6 is SMALLER than its roadmap cell suggests. The transport primitives are already in
      the tree and already tested: SocketDoorbell including hangup detection and the two
      lost-wakeup fences (Doorbell.cpp:211-360, four cases in FdPassingTest), SCM_RIGHTS fd
      passing, ShmSegment::Adopt, and the SurfaceOp / WindowKind schema already defined in
      protocol.fbs. inproc uses a real second shm mapping rather than an alias, and
      SessionRings.h:22-28 says that was done so today is not the first time it runs. P6 is
      assembly plus a control plane plus a process, not "write a socket transport".

   b. The evidence for "P6 is only a transport swap" has EXPIRED. It was bought by P5c, whose
      audit found 59 direct memory accesses in a tree that already looked clean. P5e changed
      the tree again. So the first package, a6, is a read-only audit that writes no code, and
      every contract row that depends on it is marked PENDING a6 rather than decided in
      advance.

   c. P6 does not get a game running on the phone. ANativeWindow* is meaningless in the child,
      and protocol.fbs:186-196's own comment says Android transfers the window out of band -
      that band is P12. P6 lands pbuffer / surfaceless / offscreen, which is what HeadlessGL,
      the split-equivalent lane and trace replay need, and which is exactly what P6's own exit
      gate measures. A real window reaching the server is a named refusal. This gets its own
      section because "P6 is done" would otherwise read as "the game runs now".

   The contract adds rule G: a role may not name a process-local handle. It extends P5c's
   rule E (a role may not name the other role's memory) from memory to identity - EGLDisplay /
   EGLSurface / EGLContext / ANativeWindow* / fd number / pid / mapped address either cross as
   a token this contract defines or they do not cross, and a descriptor crosses only through
   SCM_RIGHTS, where the kernel renumbers it.

   What P5e left for P6 gets its own section. The latch semantics are already designed
   (ARCHITECTURE.md:504) and are copied unchanged. What is new is that after run-ahead the
   client no longer waits while the peer process can DIE: the client holds records that were
   published and will never be applied, and Android's freezer suspends the child without
   closing anything. So slow and dead must be distinguishable WITHOUT a timeout threshold.
   That is P6's only new design; everything else is carriage. It shares a predicate with E1's
   outstanding re-specification (ID-122), and the two should be designed together.

   Two gates carried over from P5e's lessons: G2 requires integration-spawn's test-name set to
   match integration-split's name for name, because a differing set means the lane is quietly
   running less; and the arm-proof gate requires every entry to record the child pid and
   transport=spawn in its OWN private log - ID-124 established that this kind of false green
   really happens, and a spawn lane that actually ran monolith would pass every other gate.
   S2's shape must be settled before it is written, and the document says why: E1 is the
   cautionary case.

2. The working notes move into the repository (docs/Disaggregated/notes/, 415 .md files)

   ROADMAP / CURRENT_STAGE_PROGRESS / MEASUREMENTS cite ruling numbers, audit line numbers and
   the provenance of gate figures from ~/w7/notes/, a directory that exists on one machine and
   is under no version control. A record cited hundreds of times that lives only locally is not
   a record, so it moves in whole, with its directory structure intact.

   Only .md moves. Scripts, run logs, census and gate result JSON, patches, APKs and nm dumps
   stay where they are: they are evidence DATA rather than documents - large, regenerated per
   run, and mostly carrying absolute paths and device serials that mean something only on the
   machine that produced them. The mapping and this split are written in
   docs/Disaggregated/notes/README.md.

   Two exceptions are recorded there too: P6's BRIEF has been promoted to a first-class
   document and keeps no copy under notes/, so the two cannot drift apart; and recovered/ holds
   material salvaged from failed workflow runs, which may duplicate the like-named phase
   directories and is kept because a few of those were the only complete version at the time.

   CI's citation lint takes docs/Disaggregated/*.md only (non-recursive), and notes/ is
   deliberately outside it: those notes' file:line citations were written against their own
   code heads, and resolving them against today's HEAD would fail wholesale - which is not an
   error, it is their correct state as a historical record. To check one, use the revision it
   declares (--rev).

3. Inconsistencies fixed in passing

   - README.md's status line still said P5e was in progress while the stage table already said
     closed.
   - CURRENT_STAGE_PROGRESS's next-step item 1 still listed the exit gate as pending, though
     (a) and (b) were run in the previous commit; rewritten to closed plus the one item that
     actually remains (E1).
   - ROADMAP's P6 row still had the pre-phase plan in three of its cells; it now carries the
     package order, the scope boundary and the two new gates.

Checked with check_doc_citations.py: 125 citations across docs/Disaggregated/*.md, 0 problems.
2026-09-18 23:31:39 -04:00

323 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BRIEF-P5E — the package plan for retiring the lockstep on the Espryt draw path
Contract: `CONTRACT-P5E-draft.md` (same directory; becomes `MG_Remote/CONTRACT-P5E.md` at c0e).
Base `feat/disaggregated @ 2fde7034`. Paths under `MobileGL/`. Eight packages; seven of them can be
implemented in parallel worktrees once c0e and id have landed. Every line-range below is at the
base head and names the ONLY region the package may edit in a shared file; a package that needs a
line outside its range asks the integrator, who either widens the range or lands the edit in c0e.
## 0 The shape of the landing, in one paragraph
Nothing on Minecraft's steady draw path can go fire-and-forget until EVERY per-draw family reads
records instead of the frontend: `PrepareForDraw` touches the VAO (vi), the program (pg), the unit
textures and samplers (tx2), the bound FBO and its attachment lists (fb) and the UBO points (sb) on
every draw, hit or miss. So the smallest first landing that lets the client stop waiting at draws
is c0e + id + {vi, sb, pg, tx2, fb} + ra, with the caps bit flipped in ONE integration commit after
all of them. What makes it small is what is left barriered and trailing (§5): XFB, client vertex
arrays, CopyTex / GetTexImage / readbacks, `set_storage_block_binding`, the texture half of
`resource_subdata`, `set_texture_params`' reply — none of which is on the steady path. The
contract's barriered predicate (§2.1) is what lets those keep P5C's semantics while draws run
ahead, and the unconditional detector (§3.3) is what makes a missed site a named abort instead of
a wrong picture.
## 1 Dependency order
```
c0e (the contract, the wire rows, the knobs; inert) -- lands FIRST, no behaviour change
└─ id (registry rekey, by-handle resolvers, the guard) -- lands SECOND, lockstep unchanged
├─ vi (VAO / VBO / IBO) ┐
├─ sb (set_shader_buffers, binding points) │ parallel worktrees, any order,
├─ pg (program twin, archive, bindings) │ each rebased on id
├─ tx2 (textures, samplers, unit windows) │
└─ fb (FBO, attachments, images, blit) ┘
└─ ra (the wait rule, credit, gPipeInputs, flow control) -- parallel from day one on c0e;
its LAST commit is the integration
commit: kMGPipeP5eRunAheadReady=true
```
Cross-package couplings that must be known before branching: pg's `BlockBindings` tail is what sb's
UBO loop indexes with (`DirectGLES.cpp:5463` vs `:5464`); tx2's `SyncMipmapsToBackendByHandle` /
`SyncTextureToBackendByHandle` are what fb's attachment sync and image sync call; fb's reverse
index is what tx2's `ScopedDetachedTextureFramebufferAttachments` needs; sb's deletion of the
backend GPU-write marks and fb's deletion of `MarkWritableImageBufferTexturesGpuWritten` are one
edit split across two files. Each such seam is a declared signature in c0e (a header declaration
with a body that asserts "not landed") so both sides compile from day one.
## 2 Packages
### c0e — the contract and the wire (integrator; lands first)
**Scope.** `MG_Remote/CONTRACT-P5E.md`; `MG_Pipe/PipeCalls.def` (the `WaitClass` column on every row,
`SetProgramBindings` opcode 80 appended); `scripts/gen_pipe_wire.py` (or whichever generator emits
`PipeWire.inc`) + regenerated `MG_Pipe/generated/*.inc` (`MGPipeWaitClassFor`, the new op's thunk /
table slot / codec layout, route row null); `MG_Pipe/MGPipeTypes.h` (`kCapRunAheadApply`,
`MGPProgramBindings` + tail PODs, `MGPProgramDesc::LinkStatus`, `kDrawClientArrays`,
`kMGPipeMaxBufferBindingPoints`); `MG_Pipe/MGPipeValueTypes.h` (`MGPipeImageAccess` enum, encode/
decode); `MG_Pipe/MGPipe.h` (bit 13, default mask `0x3fff`); `MG_Impl/Pipe/SetHashSuppressor.h`
(four slots); `MG_Impl/Pipe/Tracker.h:97-99, 204-209` (bits 15-17 → subsystem 13); `Config.h`
`IpcTable::RunAhead/PresentCredit`; `ConfigLoader.cpp:356-403`; `MG_Pipe/PipeApply.h` declarations
of every cross-package seam (`MGPipeApplierCurrentRecordIsBarriered()`, `MGPipeBarriered(...)`, the
sb/pg applier fields, the by-handle sync signatures) with asserting bodies; `MG_Pipe/FieldOwnership.def`
re-annotations; `MG_Pipe/Coverage.def` rows; `MG_Test/Pipe/PipeCatalogueTest.cpp` (sizes, opcode
80, the null route pins); `MG_Test/Pipe/TrackerTest.cpp:385-387` (bit 13); `docs/Disaggregated/
ROADMAP.md` row; `MG_Backend/Init.cpp` (`kMGPipeP5eRunAheadReady = false`, the DirectGLES arm that
will publish bit 10). **Red-once.** The ABI fingerprint (`SessionRings.h:498-524`) changes with the
new opcode and `PipeCatalogueTest` pins the new sizes; `MagmaPipeIdentityTest` pins no bit 10 on
DirectVulkan. **Quick gate.** Unit green; `integration-split` green (nothing behaves differently);
G1 0/0/0/0.
### id — identity (S7's spine; lands second)
**Scope.** `MG_Backend/DirectGLES/SlotTables.h` (composite band `m_band`; `ForEachLive` re-typed as
`fn(MGPipeHandle, const BackendPtr&)`; the frontend-keyed members kept as monolith glue, each
asserting `!MGPipeApplierIsUnbarrieredApply()` in split builds); `Managers.h:309-640` (registry
forwarding, `ResolveVaoTwin(handle)` / `ResolveProgramTwin(handle)` / `ResolveTextureTwin(handle)`
declarations in the `ResolveSamplerCsoTwin` shape, `AdoptTwinByHandle` as the body);
`Managers.cpp:188-372` (teardown, `OnFrontendStateObjectDestroyed` — delete the NoSession hop
`:224-246` and the scope at `:256`; `ReleaseTwinsForWireObjectDeath`); `MG_Impl/Pipe/SlotAllocator.{h,cpp}`
(the guard: exemption iff scope AND barriered record; `MGPipeFrontendKeyedRegistryScope` kept as a
class until the family packages delete their sites; `MGPipeReverseAnnouncementScope` →
`MagmaP7AllocatorDebtScope`, backend-kind-keyed); `MG_Backend/DirectVulkan/VulkanRenderer.cpp:4554,
4634, 8986` + `MG_Impl/Pipe/ResourceTracker.h:723` (the rename only); `MG_Remote/Server/PipeApplier.cpp`
`ApplyOne` sets `CurrentRecordBarriered` from `MGPipeBarriered` (the one line ra also touches —
id owns it, ra rebases); `MG_Test/SanityTest.cpp:3499-3960, 4061, 4241, 4378, 4898` (cases moved to
the legacy arm or rewritten by handle; new `ACompositeHandleDoesNotGrowTheOrdinaryTable`);
`MG_Test/Wire/RemoteClientTest.cpp:1866-1913` (`...IsAllowed` deleted; new
`...FromTheApplyThreadIsFatalEvenAtTheOldDebtSites` posting `HandleOf` through `RunOnApplyThread`
with the barriered flag forced false by a test hook). **Red-once.** With the hook forcing
"unbarriered", `HandleOf` from the apply thread aborts `Fatal{RoleViolation, "MGPipeSlots"}`; put
the exemption back → the case is green → red by expectation. **Quick gate.** Unit + `integration-split`
green (lockstep behaviour unchanged: every record is barriered until ra). **New names (G2/G14):**
`ResolveVaoTwin/ResolveProgramTwin/ResolveTextureTwin(MGPipeHandle)`, `MagmaP7AllocatorDebtScope`,
`MGPipeApplierCurrentRecordIsBarriered` — all `MOBILEGL_BUILD_DISAGGREGATED`.
### vi — VAO / vertex input / index (S1)
**Scope.** `DirectGLES.cpp:699-925` (`SyncVaoAttributeBuffersByHandle`, the IBO arm), `:1602-1719`
(`ResolveVaoTwin` handle arm, `SyncCurrentVAO`, `SyncCurrentVertexAttributeValues`), `:4770-4776`
(the VAO lines of `PrepareForDraw` ONLY), `:6447-6480` (restart helpers), `:6718-6782`
(`DrawArrays`/`MultiDrawArrays` arms: monolith-only); `Managers.h:1204-1300` (`ResolvedDrawBuffers`:
`frontend` leaves the split arm, `iboSerial` added), `:1286-1290`; `Managers.cpp:4881-4892`, `:5160-5350`
(`SyncToBackendFromApplier` — signature only), `:5364-5468` (`SyncClientSideAttributesForDrawArrays`:
monolith-only); `MG_Remote/Client/EmitTables.cpp:484-525, 640-690` (`kDrawClientArrays` + the
run-ahead refusal beside `CLIENT_INDICES`); `MG_Test/Pipe/VertexInputEmitTest.cpp` + a
`SanityTest.cpp` sibling beside `:4083`. Trailing inside vi: `ResolvedDrawBuffers` deletion after a
measurement. **Red-once.** (1) Revert `ResolveVaoTwin` to `Find(vao.get())` → `Fatal{RoleViolation,
"MGPipeSlots"}` on the first `integration-split` draw once ra flips the bit; before that, the id
test hook. (2) Publish `set_vertex_buffers` for A,B, repoint the frontend VAO at C without
re-emitting, sync → A,B ensured; the revert ensures C. (3) Same on `set_index_buffer`. (4)
`glDrawArrays` with a client array under run-ahead → the named refusal; revert → red under
`integration-split-strict` (`DoublePrecisionScenario` if it uses client arrays, else a new scenario).
**Quick gate.** Unit + `integration-split` green; `VertexAttribBindingScenario`,
`VertexArrayEnableDisableScenario`, `PrimitiveRestartScenario`, `ResidentIndexScenario`,
`MultiDrawScenario`, `DrawParametersScenario`, `HandleRecycleScenario`, `ObjectSubsystemControlScenario`
(the `0x0ff`/`0x17f` A/B keeps its verdicts). **New names:** `ResolveVaoTwinByHandle` (or the id
resolver), `SyncVaoAttributeBuffersByHandle`'s new signature.
### sb — buffer binding points (S5)
**Scope.** `MG_State/GLState/BufferState/BufferState.h:69-75` (bind-point generations, push-only);
`MG_Impl/GLImpl/Buffer/GL_Buffer.cpp:1501-1520, 1624-1650` + `SetNamedTransformFeedbackBinding` (the
bumps); `MG_Impl/Pipe/Tracker.h:520-560, 630-640` (bits 15/16/17 shutters); new
`MG_Impl/Pipe/ShaderBufferEmit.h`; `MG_Impl/Pipe/PipeFill.cpp` (`SubsystemForEmitter` `:1989-2028`
row + pairing asserts `:2104-2126`, the validate order `:3225-3233`, the reset list `:3129-3143`,
the `kMGPipeMaxBufferBindingPoints`/84 pin); `MG_Pipe/PipeApply.{h,cpp}` (`MGPipeApplySetShaderBuffers`,
the applier fields declared by c0e); `MG_Pipe/PipeRoute.{h,cpp}` + `MG_Remote/Client/WireTables.cpp`
(route rows for op 38); `MG_Remote/Server/PipeApplier.cpp` (the one sink method `OnSetShaderBuffers`);
`DirectGLES.cpp:477-673` (`SyncBufferBindingPointsByRecord`, `MarkShaderStorageBuffersGpuWritten`
deleted under transport, `SyncAtomicCounterBuffers`), `:979-990` (call lines), `:5464-5497` (the
point half of the UBO loop; `:5463` is pg's); `scripts/gen_pipe_dirty_surface.py:87-90` + `DirtySurface.def`
rows; `MG_Test/Pipe/TrackerTest.cpp`, new `ShaderBufferEmitTest.cpp`, `PipeCatalogueTest.cpp:170-171,
242-243` (invert); `MG_IntegrationTest` A/B with bit 13 cleared. **Red-once.** (a) Revert the handle
walk under a live wire → `Fatal{UnmigratedPipeInput, "GetBufferBindingPoint@DrawVbo"}` on
`SsboArrayDynamicIndexScenario` / `StorageBufferRegrowScenario` / `AtomicCounterScenario` under
strict. (b) `TrackerTest`: `glBindBufferBase(UNIFORM,1,A); Update(); glBindBufferBase(UNIFORM,1,B);
Update()` sets bit 15 on the second update — red against `Tracker.h:636` today. (c)
`GetBufferBindingPointCount`'s `rsp` non-zero before, 0 after, on `AtomicCounterScenario`. (d)
Delete the backend marks and `ProducerMarkCount(ShaderStorageBinding)` stays non-zero while
`SsboArrayLengthScenario`'s readback sees the writes. **Quick gate.** Unit + `integration-split` +
strict on the four SSBO/atomic scenarios and the three XFB ones; the bit-13-cleared A/B reproduces
today's picture. **New names:** `MGPipeApplySetShaderBuffers`, `MGPipeShaderBufferEmitterInstance`,
`SyncBufferBindingPointsByRecord` (file-local), `BufferState::NoteBindPointChanged`.
### pg — programs (S4)
**Scope.** `MG_Impl/Pipe/ProgramEmit.h` (archive encode per link via `ProgramArtifactsCodec` into
SEG_STAGE; `EmitProgramBindings`; `LinkStatus`; the failed-relink rule); `MG_State/GLState/ProgramState/
ProgramArtifactsCodec.{h,cpp}` (completeness: name `:242`'s skipped member); `MG_Pipe/PipeApply.{h,cpp}`
(`MGPipeApplyCreateShaderState` adopts the blobs into record-owned artefacts — the companion
pointers go under a transport; `MGPipeApplySetProgramBindings`); `MG_Pipe/PipeRoute.*` +
`WireTables.cpp` (op 80 route); `PipeApplier.cpp` (`OnSetProgramBindings`; trailing:
`set_storage_block_binding` by `ShaderCso`); `DirectGLES.cpp:4097-4110` (the stash), `:4230-4318`
(`ResolveGlobalConstantsRecord(handle)`, `MapUBO` refusal), `:4320-4510` (`SyncCurrentProgram(handle)`
incl. the broadcast decline arm `:4383-4404` → Fatal under transport), `:4781` (`PrepareForDraw`'s
program line), `:5317-5560` except `:5464-5497` (sb) and `:5561-5632` (tx2), `:5670-5740`
(`GetCurrentBackendProgram`), `:6120-6130` (`PrepareForCompute`'s program line), `:10018-10042`
(`ShaderStorageBlockBinding`: monolith body kept; trailing handle arm); `Managers.h:2338-2541`;
`Managers.cpp:10757-13080` (`SyncToBackend(h, record)`, `CacheResourceLocations`,
`ReseedShaderStorageBlockBindings`, the signature); `MG_Test/Pipe/ProgramEmitTest.cpp` (beside `:251`).
**Red-once.** (1) Empty the applier's block-binding tail → `UniformInitializerScenario` /
`Glsl420DeclarationScenario` wrong pixels + `RefusedObjectCalls`. (2) Leave the blob refs at `Size 0`
with the companions removed → `Fatal{ProtocolCorruption}` (`PipeApply.cpp:2858-2871`). (3) Delete
the five scopes without re-keying → `Fatal{RoleViolation, "MGPipeSlots"}` (after ra). (4) Strict lane
names `GetProgramForDraw@draw_vbo` with the row still pulled; green after. (5) Force bit 12 off
under run-ahead → `Fatal{UnmigratedVerb, "set_global_constants"}`. **Quick gate.** Unit +
`integration-split`; `RelinkStageSetScenario`, `PostLinkAttachScenario`, `ProgramPipelineScenario`,
`AsyncCompileScenario`, `CsoContentAddressingScenario`, `HandleRecycleScenario`; the archive cost
measured (links/frame, bytes/link, ms on the GL thread) on MC + a shader pack and reported before
the package is declared done. **New names:** `MGPipeApplySetProgramBindings`, `EmitProgramBindings`,
`SyncCurrentProgramByHandle`.
### tx2 — textures and samplers (S2)
**Scope.** `DirectGLES.cpp:1723-1806` (`SyncTextureToBackendByHandle`), `:1867-2098` (unit
epoch/capture: monolith-only; decline → refusal), `:2127-2140` (`UnitTextureSyncEntry` re-shape,
push-only under `#if`), `:2321-2391` (the unit half), `:4834-4948` (`ResolveAndBindUnitTextures`),
`:4985-5026`, `:5051-5170` (`BindCurrentUnitSamplers`, decline → refusal), `:5245-5305`
(`BindCurrentTextures` with ruling 6's key), `:5561-5632` (the sampler pass; the mint deleted),
`:8725-8760` + `:9560-9600` (`GenerateMipmap` by `VerbMipRes`), `:9161, 9216` (mip shape `HandleOf`
→ handle), trailing `:9714-9735` (CopyTex endpoint) and `:11884` (`GetTexImage`); `Managers.cpp:5816-5975`
(`RequireImageBindableStorage` entry refusal), `:6902-6985`, `:7049-7400` (`SyncMipmapsToBackend(h,
rec)`), `:8472-8560`, `:8712-8900`, `:13184-13290` (sampler identity family deleted under
transport), `:13457-13486`; `Managers.h:1626-1660` (`IsDrawSyncCleanByRecord`), `:1785-1812`,
`:2678-2700`; `MG_Backend/DirectGLES/MipmapStorage.cpp:39-51` (guard list widened);
`PipeApplier.cpp` (the window checks at draw/dispatch dispatch — ONE function `CheckUnitWindows(st)`
called from `OnDrawVbo`/`OnLaunchGrid`); `MG_Test/Wire/RemoteClientTest.cpp:1938-1978`;
`MG_Test/SanityTest.cpp:3075-3144` (+ the ABA sibling); `SampledSetStalenessScenario`,
`TextureParamsWithoutASamplerViewScenario` kept. **Red-once.** (1) Revert any handle arm → the
widened guard aborts by accessor name. (2) Revert the window arm of `ResolveAndBindUnitTextures` →
strict lane `Fatal{UnmigratedPipeInput, "GetTextureUnitObject@DrawVbo"}`. (3) ABA: recycle a slot to
`{s, g+1}` while the old entry is in the work list → the twin is re-minted; skip the Gen reset → red.
(4) Narrow `SamplerEmit.h:735-737`'s count by one → `Fatal{ProtocolCorruption, "SetSamplerViews.Count"}`.
(5) `GenerateMipmap` with the row `kWaitNone` and the unit walk restored → `Fatal{UnmigratedPipeInput,
"GetActiveTextureUnit@...}` (`F1WireScenario.GenerateMipmap*Pixels`). **Quick gate.** Unit +
`integration-split`; `PipeSlotPeek` asserts zero registry-scope entries per frame for this family.
**New names:** `SyncTextureToBackendByHandle`, `SyncMipmapsToBackendByHandle`,
`ResolvePushedTextureParams/BuiltinSampler(MGPipeHandle)`, `IsDrawSyncCleanByRecord`.
### fb — framebuffers, attachments, images, blit (S3)
**Scope.** `DirectGLES.cpp:179-241` (the doors), `:2140-2180` (list keys), `:2227-2331`
(`SyncReadFramebufferTextureAttachments`), `:2405-2470` (the draw-FBO list), `:2519-2914` (images:
`SyncImageTextureBinding(const MGPImageView&)`, `ResolveShaderImageRecord` deleted,
`MarkWritableImageBufferTexturesGpuWritten` deleted under transport, the sweep gate), `:3017-3377`
(`SyncCurrentFBO*`), `:4514-4754` (`BindCurrentFBO`, `SyncAndBind*`, `ForceBindCurrentFBO`),
`:6180-6192` (`Clear`), `:8182-8420` (both blit arms), `:8837-8935` (the detach walk + reverse index),
`:9688-9740` (`SyncRenderbufferObjectToBackend` by handle); `Managers.cpp:9395-9405, 9585-9742,
9834-9953, 10044-10429, 10847-10850, 13530-13584`; `Managers.h:1857, 1943-1970, 2819-2830`
(reverse index texture → FBO slots); `MG_Test/Pipe/FramebufferEmitTest.cpp` (server twin of `:558`),
`MG_Test/Pipe/ImageEmitTest.cpp` (beside `:452`: the three constants on both sides), a new
default-framebuffer-resize re-emit case. **Red-once.** (1) `SyncAttachmentSurface` takes the
frontend attachment again → strict `Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot"}` on
`SnormAttachmentScenario`. (2) The draw-FBO list key back to the pointer compare → `Fatal{RoleViolation,
"MGPipeSlots"}` (after ra). (3) `MGPipeDecodeImageAccess` mapped 1↔2 → `FormatlessImageBakeScenario` /
`ImageLoadStoreSsoScenario` red + the unit case. (4) The named blit back on the frontend objects →
`CopyImageLayeredScenario`, `LayeredAttachmentShapeScenario`. (5) The per-point version memo kept as
a second gate → `CrossFrameBufferScenario` / `P4aSeamAuditScenario` F-3 must stay green WITHOUT it.
**Quick gate.** Unit + `integration-split` with the framebuffer/sampler bits set; the seam greps
(`DirectGLES.cpp:3086-3106, 2607-2610, 2636-2638`) silent; the object A/B re-baselined for
`SyncReadFramebufferTextureAttachments`. **New names:** `SyncToBackendByHandle` (RBO/FBO overloads),
`MarkBufferGpuWrittenByHandle` (declared; body deleted under transport with sb), the reverse index.
### ra — the transport mechanics (S6; parallel from day one; lands last)
**Scope.** `MG_Remote/Client/ClientSession.{h,cpp}` (`RunAheadArmed`, `EmitAndWaitTails` §2.3,
`ReleaseFillPins`, `DrainEventRing` at every park exit, `Finish`, `RefusePipeInputsTouch...` §3.5,
the stats line `credit-waits`); `MG_Remote/Client/EmitTables.cpp:977-1053` (`EmitPresent` credit +
`FrameSerial`) and the `Finish` slot; `MG_Remote/Client/BackendObject_Remote.cpp` (wait before every
`Server*` EGL forwarder); `MG_Remote/Client/WireTables.cpp:332-352` + `MG_Pipe/PipeRoute.h:270-276`
(`wantReply` for the buffer half); `MG_Remote/Client/CapsMirror.*` (latch); `MG_Impl/Pipe/PipeFill.cpp:84-86,
546-556, 1955+, 2962-3306` (§3.1, §3.4); `MG_Backend/MGPipe/PipeInputs.{h,cpp}` (server-private
stamp, §3.2-3.3); `MG_Remote/Server/PipeApplier.cpp` (`OnPresent` credit return, `StampVerbBoundary`,
`MGPipeBarriered` body; `ApplyOne`'s flag line is id's); `MG_Remote/Server/ServerSession.{h,cpp}`
(present credit, event producer flow control); `MG_Remote/Server/ServerLoop.cpp:375-392` (park
predicate); `MG_Remote/Transport/EventRing.h`, `SessionRings.h`; `MG_Backend/Init.cpp` (flip
`kMGPipeP5eRunAheadReady` — the integration commit); `MG_Impl/Pipe/PipeMutation.h:216-236` (the
unbarriered-enqueue finding); `.github/workflows/test.yml:1140-1206` (the strict lane's allowlist
step); `docs/Disaggregated/ARCHITECTURE.md` §11, §14, §17; `MG_Test/Wire/{RemoteClientTest,
SessionTest, ServerLoopTest}.cpp`. **Red-once.** (1) Encode present without `WaitForPresentAck` →
the `SessionTest.cpp:522-549` twin in `RemoteClientTest` parks a credit-1 client behind a stalled
apply thread; without the wait the third present publishes and `Waits()` is unchanged → red by
count. (2) Leave one `CopyField` in an unbarriered fill → `Fatal{RoleViolation, "gPipeInputs"}` by
name (`RemoteGuards...FillWhileTheApplierOwnsIt`'s shape with `RunAhead=1`). (3) Count instead of
Fatal under an unbarriered record → the strict lane's rewritten step turns from green to red on the
first pulled row. (4) Keep `Fatal{EventRingOverflow}` → a unit case publishing 300 KiB of
`kEventGpuWritten` behind an unwaited sequence aborts; with flow control it completes. (5) Publish
bit 10 for Magma → `MagmaPipeIdentityTest` red. (6) `MOBILEGL_IPC_RUN_AHEAD=0` renders identically
(SSIM); `MOBILEGL_IPC_VERB_BARRIER=0` under run-ahead is the documented red. **Quick gate (before the
flip).** Unit + `integration-split` green with the bit unpublished (every branch inert);
`RemoteGuards` strict arms green. **New names:** `MGPipeBarriered`, `MGPipeWaitClassFor` (c0e),
`ClientSession::RunAheadArmed/ReleaseFillPins`, `MGPipeServerStampVerbBoundary`'s new signature,
`IpcTable::RunAhead/PresentCredit`.
## 3 The integration gate (the commit that flips `kMGPipeP5eRunAheadReady`)
1. Unit green under both `MOBILEGL_IPC_STRICT_ERRORS` arms.
2. `integration-split` green (111+ entries plus the new ones), broad inproc census with zero
regressions vs `2fde7034`, the 79 traces with no new first blocker.
3. `integration-split-strict` GREEN as a hard lane: the only admitted `[BARRIER-PULLED, ...]` markers
are the §7 allowlist (`<field>@<verb>` for readbacks, XFB, CopyTex, `set_storage_block_binding`);
any other marker fails the lane. Every scenario summary shows `rsp` = 0 on unbarriered records.
4. The three negative controls behave as documented: `RUN_AHEAD=0` identical SSIM; `VERB_BARRIER=0`
red at a barriered row; `RUN_AHEAD=1` on Magma logs once and runs lockstep.
5. Every package's red-once re-run on the integrated tree (a revert of any one handle arm goes red
in the strict lane by field and verb).
6. G1 0/0/0/0, G2, G14, G5; the push build under `Transport=monolith` renders the four A/B traces
with unchanged SSIM.
7. `MOBILEGL_IPC_AUDIT=1` clean (no `0xDD` read) with the client a present ahead — the pmap push
without a reply is the new consumer of retired staging.
## 4 The device exit (Redmi, MC 26.3 VD12, 30 s in-world window)
Arms: monolith; inproc lockstep (`MOBILEGL_IPC_RUN_AHEAD=0`); run-ahead credit 1; run-ahead credit 2.
Per arm: fps; per-thread CPU ms/frame (client GL thread, `mgl-srv-apply`); `cliwait/clipark`,
`credit-waits`, `srv/srvpark` from the wire ledger and the stats line (`ClientSession.cpp:1161-1189`,
`ServerLoop.cpp:410-414`); `simpleperf` on both threads — the exit condition is that
`WaitForApplied` self on the client falls from 27.9% to the present-credit wait alone and
`DrainRing`'s real work is unchanged; the per-op wait tally must show NO `kWaitApplied` row and no
reply row firing per draw on the steady path (what remains per frame — the texture subdata for the
lightmap, `set_texture_params` replies, fences — is listed by op in the report as the trailing
inventory); `rsp` = 0 on unbarriered records; SSIM of the four A/B traces vs monolith unchanged.
The P5D report's method section (`P5D-INPROC-PERFORMANCE.md:58-71`) is the template; the bench
scripts the brief named are not on this machine and are re-created beside it. Recorded, and
GATED on: fps ≥ lockstep's, client-thread ms/frame < lockstep's, SSIM unchanged.
## 5 What trails (after the integration commit, each its own small package)
| item | owner | why it can trail |
|---|---|---|
| `GenerateMipmap` `kWaitApplied` → `kWaitNone` (if tx2 lands the `VerbMipRes` arm late) | tx2 | not on the steady path |
| `set_storage_block_binding` by `ShaderCso` | pg | after-link only |
| CopyTex endpoint / `GetTexImage` / readbacks by handle (the barriered scope sites `6166, 8837, 9161, 9216, 10029, 11884`) | tx2 / fb | barriered rows keep P5C's semantics (P8/P9 phases) |
| `resource_subdata` texture half without a reply (D-D5 accept-by-construction) | ra + tx2 | one wait per frame for the lightmap today |
| `set_texture_params` without a reply (the default-texture self-heal moved client-side) | tx2 | creation-time traffic |
| client vertex arrays staged (`{BindingIndex, MGHostSpan}` tail) | P8 | refused under run-ahead |
| XFB migration (`set_stream_output_targets`, the program row, the targets) | P3b/P4b/P9 | lockstep by escalation (i) |
| `ResolvedDrawBuffers` deletion | vi | measurement first |
| composite pipelines' uniform mirror vs `set_program_bindings` fire rate | pg | unmeasured; pipeline workloads only |
| program-derived `set_shader_buffers` window | sb | narrowing, not correctness |
| `MOBILEGL_IPC_EVENT_KB` (ring size under SSBO-heavy `kEventGpuWritten`) | ra | Iris-class workloads |
| `Flush` as the doorbell point (ARCHITECTURE §11.6); credit-2 latency study | ra | measurement |
| thread placement | — | out of scope by instruction; the per-thread numbers survive it |
## 6 The rulings the integrator makes (conflicts between scout designs, with the resolution)
| # | conflict | resolution (contract §9) |
|---|---|---|
| 1 | S7: handle arms run under `Transport=monolith` too (family bit selects); S1–S5: `Transport != Monolith` keeps the frontend arms under monolith | S1–S5's shape. The push-monolith build keeps its bytes and behaviour, the verify comparator keeps its frontend arm, and `RUN_AHEAD=0` becomes a pure wait-rule A/B on identical server code. |
| 2 | Brief/S7 assumed client vertex arrays are refused under split; S1 read the code: they are not (`EmitTables.cpp:651-685`) | Verified S1 is right. Refuse under `RunAheadArmed` with `kDrawClientArrays`; lockstep unchanged; staging is P8's. |
| 3 | S5: XFB/atomic waits decided from client state at emit; S6: a static per-op wait table | The wire-derived predicate `MGPipeBarriered(op, payload, st)` — static column + XFB-active from `MGPContextValues` + the draw flag — computed identically on both sides. |
| 4 | S6 §4(6): a WAITED op may no longer read the frontend; S1–S5 keep readback/CopyTex/GetTexImage sites barriered | S6's argument is wrong for a single-context client parked in its own wait; barriered records keep P5C's semantics (rule F is scoped to unbarriered records). This is what keeps the first landing small. |
| 5 | S4 §4.6: `ShaderStorageBlockBinding` is a GL-thread entry; S6: `set_storage_block_binding` is a `kCtxVerb` applied on the apply thread through `GetProgramObject(GlName)` | Verified S6 (`PipeCalls.def:243`, `PipeApplier.cpp:805-808`). `kWaitApplied` now; pg's trailing item resolves by `ShaderCso`. |
| 6 | S2: `BindCurrentTextures` memo on `SamplerViewsSerial` alone; S4: on the program handle + serials | Both (a relink with unchanged resolution still needs the twin's new unit assignments). |
| 7 | S3 R1: the image sweep gate needs S2's serial design | `(ShaderImagesSerial, TextureShutterSerial, ContextSerial, g_backendContextGeneration)`; keep the union walk. |
| 8 | S4 R1: encode the archive per link vs pin a `SharedPtr` | Encode (rule B); measure; lazy encode at first bind as the fallback. |
| 9 | S4 R2: a failed relink of a bound program | Re-issue with `LinkStatus = 0` iff the frontend reports it unlinked; pg verifies the frontend's actual behaviour; never `object_death`. |
| 10 | S5 R2: high-water vs program-derived window | High-water (`TouchedBufferBindingPointCount`, already carried); 84 on the wire, backend clamps. |
| 11 | S5 R1: one suppressor slot for three classes | Three slots. |
| 12 | S7 R1: Magma's four apply-thread allocator sites vs an unconditional guard | One renamed scope, backend-kind-keyed; Magma is lockstep and every record is barriered there. |
| 13 | S6 §3(9)/S7: the deferred-destroy queue "loses its producer" | It does not (barriered fills pin objects); the queue stays live; an unbarriered enqueue is the finding. |
| 14 | S6 §3(3)/S2: `GenerateMipmap` waits until the unit read is gone | Verified (`ClientSession.cpp:900-914`; `VerbMipRes` already exists at `DirectGLES.cpp:8736`). tx2 flips the row. |
| 15 | S3 R5: does an EGL resize re-emit the default-framebuffer record | Verified yes (`ClientSession.cpp:210-221` → `TextureObject.cpp:84-102` → `Tracker.h:594`); fb pins it. |
| 16 | S3 vs the brief: `Access` "has no encoding" | Verified S3: `MGPipeEncodeImageAccess` exists (`ImageEmit.h:163-176`); the server comment is stale; the decode moves to `MGPipeValueTypes.h`. |
| 17 | S6 §7: `resource_subdata`'s texture half | Buffer half `kWaitNone` in ra; texture half keeps its reply; trailing. |
| 18 | present credit default | 1. |
| 19 | S1 R4 / S2 R1 (A8): the var-tail windows are not pinned | The window rule in §5.3 with the sink check at every draw/dispatch; `set_vertex_buffers`' window is pinned by a test in vi (same class, same validate step). |
Open items that need a ruling the scouts could not settle and this brief does not: (a) whether
`DoublePrecisionScenario` uses client arrays under split (decides whether vi's refusal needs a new
scenario as its red-once); (b) the archive cost number (ruling 8's fallback trigger); (c) the NoSession
server-only fixtures that lose death delivery when id deletes the hop (`grep RunOnApplyThread
MG_Test/Wire`) — id enumerates them in its first commit and the integrator rules keep/rewrite.