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.
15 KiB
P5e package fb — framebuffers, attachments, images, blit (report v1)
Branch p5e/fb, head ca1beb69, base f6cfcbd3. Tree ~/w7/p5e-fb. Six commits; the fourth
(f4675d1a) is a SCAFFOLD that the sixth (ca1beb69) reverts — see §5.
1 What changed, per file (ranges at the NEW head)
DirectGLES.cpp — :242-264 the doors (RefuseFramebufferBindingSlotRead,
FramebufferRecordArmIsMandatory = Transport != Monolith && FramebufferSubsystemEnabled(),
ruling 1 / §5.8, which selects every arm below). :2171-2272 the list keys: a new
FboAttachmentSyncEntry {Res, backend}, six statics, SyncRecordAttachmentTextures — I did NOT
touch UnitTextureSyncEntry; tx2 owns its re-shape, the attachment lists borrow nothing, and a
type of their own avoids the collision. :2344-2412 SyncFramebufferAttachmentTexturesByRecord
(the core), selected at :2573. :2726-2968 images: ResolveShaderImageRecord and
g_imageRecordSeamIsAuthoritative DELETED; IssueImageTextureBind, GLAccessForImageView,
IsWritableImageBufferView, SyncImageTextureBinding(const MGPImageView&),
NoteImageUnitBoundWithoutReadingTheFrontend. :2976 the GPU-write marks. :3055-3180 the sweep
walk and its gate. :3290 FramebufferRecordMatchesBinding monolith-only. :3428-3543
SyncCurrentFBOByRecord's loop; :3571 / :4781 the two declines; :4920
SyncAndBindFramebufferByHandle; :4956 ForceBindCurrentFBO; :6570 Clear's debug
diagnostic. :8609-8806 the record blit overload + PushedReadBufferPoint + BlitEndpointRecord,
:8850 named arm, :8955 bound arm. :9409-9484 the detach walk. :10118 the RBO refusal.
:10561 BindImageTexture.
Managers.cpp — :4374 PipeTextureTargetForHandle; :9662 PushedSurfaceForAttachment
exported; :9675-9820 SyncAttachmentSurface loses its attachment parameter and both
cross-checks; :9951-10035 ApplyReadBufferFromRecord + SyncReadBufferToBackendByHandle;
:10331 the record-side empty-point test; :10525-10760 the reverse index +
BackendFramebufferObject::SyncToBackendByHandle; :11228 BoundImageUnitFormat;
:14025-14105 BackendRenderbufferObject::SyncToBackendByHandle; the two fb stubs deleted.
Managers.h — six declarations (§6.4, §6.8). Tests — FramebufferEmitTest.cpp +2,
ImageEmitTest.cpp +1, all three in the X-macro skip lists (G2/G14).
2 Kimi-audit rows 68-90
RETIRED (no frontend read under a transport): 68 (monolith-only), 69, 70 (NoteStateForHandle
deleted), 71, 74, 76, 77, 78, 79, 80, 81, 82, 85, 86, 87, 88, 89, 90.
LEFT as monolith glue, unreachable under a transport because SyncCurrentFBO / BindCurrentFBO
abort before them: 72, 73, 75, 83.
NOT MINE: 84 (SyncCurrentProgram's fragColor-broadcast half). Its record arm exists and is
what a transport takes; its decline arm still reads the binding slot, and that decline is in pg's
line range. A framebuffer row inside a program function — named so the integrator can rule whether
pg refuses it or fb widens. Nothing in the split lane reaches it.
NOT FOUND: none. Every row 68-90 exists at this head.
3 The rulings I was asked to answer
- ID-94 / ruling 16 — done.
Accessdecodes throughMGPipeValueTypes.h; the stale "the encoding does not exist at the contract commit" comment is deleted and corrected in place. - ID-86 / ruling 7 — done:
(ShaderImagesSerial, TextureShutterSerial, ContextSerial, g_backendContextGeneration), in their own three fields beside the frontend pair. The old comment's argument is KEPT because it is what decides which values are admissible: an applier serial moves when the CLIENT said something, strictly before the sweep the saying provoked, so the "re-minted inside this sweep" property survives the re-key. Never a backend re-mint counter. The window/high-water union is kept and re-argued in place. SyncReadFramebufferTextureAttachmentsungated on the framebuffer bit — the delta is SMALLER than S3 feared. The function is still not gated on the bit; what selects the record arm is the TRANSPORT plus the bit. So the object A/B moves only in its SPLIT arm, and the push-monolith arm is token-for-token what it was. It still needs re-baselining for the split arm — integrator, with the phase's other re-baselines.- The renderbuffer cross-check (base
Managers.cpp:9711) — fixed by DELETION, not gating (§4.4's instruction). A latent RULE violation, not a latent wrong-picture bug: the probe ran on the apply thread under split while its texture sibling was transport-gated, but insideMGPipeFrontendKeyedRegistryScopewith the client parked, so its answer was correct. It becomes a correctness bug the instant the record is unbarriered — which is why it is deleted, not gated.
4 Red-once evidence (executed, quoted verbatim)
R1 — SyncAttachmentSurface takes the frontend attachment again (reverted
SyncCurrentFBOByRecord to SyncToBackend(currentFBO, target), the only way the attachment sync
can be handed one). Full strict lane marker tables: with the arm, no GetFramebufferBindingSlot
marker at all; with it reverted, 2 Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"}.
The private log's line:
MGPipe: Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"} [BARRIER-PULLED, MOBILEGL_IPC_STRICT_ERRORS=1, retires in P5e (Espryt unbarriered), P7 (Magma)]
R2 — the draw-FBO list key back to the pointer compare, id's hook armed
(kMGPipeP5eClientWaitRuleLanded = true), on
ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels:
MGPipe: Fatal{RoleViolation, "MGPipeSlots"} - the apply thread called MGPipeSlots().HandleOf. ...
A named exemption scope admits it only while the record being applied is BARRIERED (barriered=0) ...
#3 MGPipeRefuseAllocatorFromApplyThread #4 BackendSlotTable<ITextureObject>::Find
#5 TextureImpl::SyncTextureObjectToBackend #6 TextureImpl::SyncNeccessaryTextures
#7 DirectGLES::Clear #8 ServerVerbSink::OnClear
Control (key restored, hook still armed): the entry passes OnClear untouched and aborts later at
#5 VertexArrayImpl::ResolveVaoTwin #6 PrepareForDraw #7 DrawArrays #8 OnDrawVbo — vi's site.
The isolation is by FRAME, because with the hook armed every unlanded family aborts somewhere; it
becomes a plain before/after once vi and tx2 land.
R3 — the image Access mapped 1↔2. (a) the SHARED table (WriteOnly = 2, ReadWrite = 1):
../MobileGL/MG_Test/Pipe/ImageEmitTest.cpp:511: Failure
Expected equality of these values: Which is: '\x2' (2) Which is: 1
../MobileGL/MG_Test/Pipe/ImageEmitTest.cpp:512: Failure
Expected equality of these values: Which is: '\x1' (1) Which is: 2
(b) the SERVER decode alone (GLAccessForImageView): Split.UnboundImageDescriptorScenario 13/13
and the whole split lane 111/111 stay GREEN — lavapipe does not enforce glBindImageTexture's
access, so the unit case is the only witness that exists here, which is why the brief asked for
it. FormatlessImageBakeScenario / ImageLoadStoreSsoScenario are MONOLITH-lane entries with no
split twin: they run the frontend arm and cannot reach the decode at all.
R4 — the named blit back on the frontend objects: DID NOT GO RED, and I stopped after two
attempts rather than manufacture one. Split.NamedBlit.F1WireScenario 4/4 green with the revert.
Three measured reasons: both endpoints of those cases are BOUND (hence synced) before the blit, so
the twin is already configured and the state note is not load-bearing for them; the
layered-destination substitute is gated on MG_Util::SelfTest::BlitIgnoresDestinationArrayLayer,
which lavapipe does not trip, so the aspect plan's body never executes in this lane on EITHER arm;
and with id's hook armed the entry aborts earlier at tx2's unlanded unit walk
(#5 SyncTextureObjectToBackend #6 SyncNeccessaryTextures #7 Clear). What IS shown:
Split.NamedBlit.F1WireScenario (4) and Split.CopyImageLayeredScenario (6) are green with the
record-driven blit — the substitution is behaviour-preserving. Ruling wanted: whether a
device-lane witness is required before this arm is accepted.
R5 — the per-point version memo as a second gate: the proof is that it is ABSENT and the lane is
green. SyncToBackendByHandle has no m_syncedFrontendAttachmentVersions at all (the record
hash plus g_attachmentBackendIdGeneration are the only gates) and integration-split is 111/111,
including Split.F1.F1WireScenario (13) and Split.CopyImageLayeredScenario (6). The
re-storaged-attached-RBO shape S3 named for P4aSeamAuditScenario F-3 is covered by the existing
FramebufferEmit.ARestoragedAttachedRenderbufferPublishesItsNewExtent, still green.
5 Gate
build rc=0 · unit 2209/2209 (2206 before the three new cases) · isplit 111/111 ·
strict 3/111 · gens all seven rc=0 · one 77/77 on Split.{F1,NamedBlit,Ct},
Split.CopyImageLayeredScenario, Split.UnboundImageDescriptorScenario, FramebufferEmit.,
ImageEmit. · seam greps SILENT (the image seam log is deleted; no framebuffer "does not describe
the binding it names" line in any of the 88 private logs). The one gens doc-citation complaint
(CONTRACT-P5.md:524) is PRE-EXISTING — verified with --rev f6cfcbd3.
Strict markers, base (22 private logs) vs now (88; the slice is four times larger because fb's field no longer aborts first on those entries):
| marker | base | now | owner |
|---|---|---|---|
GetFramebufferBindingSlot@Clear |
6 | 0 | fb — retired |
GetImageTextureBinding@BindImageTexture |
0 visible | 0 | fb — surfaced at 13 mid-package, fixed (§6.8) |
GetTextureUnitObject@Clear |
13 | 18 | tx2 |
GetTextureUnitObject@GenerateMipmap |
2 | 0 | tx2 |
GetBoundVertexArray@DrawArrays |
— | 14 | vi |
GetProgramForDispatch@DispatchCompute |
— | 7 | pg |
ValidateProgramName@ShaderStorageBlockBinding |
— | 2 | pg (barriered, §7 allowlist) |
GetTextureObject@CopyImageSubData |
— | 6 | P7/P9 (barriered) |
GetTextureObject@Clear |
— | 39 | the scaffold's own cost, gone with it |
THE HEAD DOES NOT PASS isplit, BY CONSTRUCTION. fb calls tx2's
SyncTextureToBackendByHandle / SyncMipmapsToBackendByHandle from four places. With c0e's
aborting stubs back, every entry whose framebuffer has a texture attachment dies on
Fatal{UnmigratedVerb, "TextureImpl::SyncTextureToBackendByHandle"}. Every number above was taken
at fcd7c5c8, where f4675d1a bodied the two seams by resolving the frontend texture from the
record's own Desc.GlNameForDiag through the GetTextureObject sticky forward — correct under
lockstep, deliberately NOT tx2's design, reverted at ca1beb69. fb and tx2 land together, or fb
after tx2.
6 Seams assumed, and deltas to name
SyncTextureToBackendByHandle(h, imageBindable)is the by-handle twin ofSyncTextureObjectToBackend(obj, …)— it does (or memo-skips) params, builtin sampler and mipmaps, and returns the twin. The two attachment lists make ONE such call per entry instead of the pre-handle arm's three, because by-handle forms ofSyncTextureParamsToBackend/SyncBuiltinSamplerToBackend/IsDrawSyncCleanare tx2's new names and are not declared seams. If tx2'sIsDrawSyncCleanByRecordends up OUTSIDE that call, fb's lists need a re-look.SyncMipmapsToBackendByHandle(h)drives storage from the descriptor with no frontend object — that is what letSyncAttachmentSurfacedrop its parameter.MarkWritableImageBufferTexturesGpuWrittendeleted under a transport — paired with sb's deletion of the backend storage-block marks. I gated at the TOP of the function, not at the two call sites, so the unit bookkeeping is out of an apply thread's reach too; sb should say whether it did the same.- The reverse index is maintained at
SyncToBackendByHandle, not atset_framebuffer_stateapply as §5.4's wording suggests. Same set: a framebuffer whose record was never synced has no driver attachment to detach, and every path that can have one syncs first. It lives inFramebufferImpl(NoteFramebufferTextureAttachments/FramebuffersAttachingTexture) and tx2 calls the second one if it wants it. UnitTextureSyncEntryuntouched (see §1).- Shared-code delta (G1): the image bind's format rules are factored into one
staticIssueImageTextureBindthat the PULL build compiles too, because the alternative was the same hundred lines of widening/split reasoning twice and a divergence there is an out-of-bounds image read, not a wrong pixel. One caller in the pull build, internal linkage, no new symbol. Named here rather than left to a G1 diff. Everything else is underMOBILEGL_PIPE_PUSH/MOBILEGL_BUILD_DISAGGREGATED. - Push-monolith delta:
SyncAttachmentSurface's two loud cross-checks and the N-6 refusal go on that arm too (no second answer left to check against), andSyncImageTextureBinding(Uint)no longer takes its four field values off the record there. Both behaviour-identical by construction — the client copies the unit's own binding, P4a's own argument — but they ARE token moves in the push-monolith build. The pull build is unaffected. - Out of my stated range, taken deliberately:
DirectGLES.cpp'sBindImageTexture(thebind_shader_imagesink target) was readingGetImageTextureBindingon an UNBARRIERED row and strict named it on 13 entries; no other package's range names it. It now records the high-water mark and defers to the sweep (the sink bumps the texture shutter serial right after, so the sweep cannot be suppressed).Managers.h:717gainedPipeTextureTargetForHandlebesidePipeTextureRecordForHandle. Ruling wanted:MGPImageBind::Resstops at the sink, so that funnel cannot resolve a twin at all — should the backendBindImageTextureentry eventually take the handle, or is deferring the permanent answer?
7 Is a blit still barriered?
No, and it need not be. Blit is kWaitNone in §2.2's static column and fb removes the last
reason S3 could see to escalate: both arms resolve every endpoint, aspect and texture from records
and applier state, and the two ForceBindCurrentFBO calls that restore the bindings afterwards are
handle-only. The predicate decides and ra owns the flip — fb changed no wait class, and asks
for none.
8 What I did not do
No device measurement, no adb, no pull-build (G1) compile — the gate configures the split flavour
only. The object A/B is not re-baselined (§3). The resize case has no negative control beyond the
suite's own AnUnchangedBindingPairEmitsNothing, which is what stops it passing because the
emitter fires unconditionally. Row 84 left to pg (§2).