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
tx2 — textures and samplers by handle and window (P5e wave 2)
Branch p5e/tx2, head 1c872710, base f6cfcbd3 (landed wave 1). Tree ~/w7/p5e-tx2.
Five commits: 72e79020 (the family by handle), 85959090 (two the lane found), a08b3379 (tests),
99211a48 (the ABA case re-aimed), 1c872710 (a comment fix).
1 What changed, per file
Managers.h — BackendTextureObject gains m_pushedSyncHandle (§4.1's m_handle) +
NotePushedSyncHandle/PushedSyncHandle (:1940-1960, push-only, so the pull layout does not
move); IsDrawSyncCleanByRecord (:1740-1775); RequireImageBindableStorageByHandle
(:1675-1695); SyncTextureViewToBackendByRecord (:1645-1665); ResolveOwnRecord +
ResolvePushedBuiltinSampler's record parameter (:1895-1910).
Managers.cpp — ResolveOwnRecord (:8752-8772) is the whole of "the handle first, the
client allocator only failing that", and the two prologues lose their HandleOf probes; the three
sync bodies tolerate a null frontend object on the handle arm and answer GetTarget from
Desc.Target, the ~20 GetExternalIndex log reads from Desc.GlNameForDiag, the storage-kind
backstop from Desc.StorageKind (three macros beside the P4a/P5c ones);
SyncMipmapsToBackend's record resolution is hoisted to the top so the VIEW test can read
Desc.ViewOf (:7285-7310); the buffer arm reads Desc.BufferForTexBuffer / BufOffset / BufSize
and passes nullptr to EnsureBufferResourceForHandle (:8415-8600);
RequireImageBindableStorageByHandle (:5879-5930); SyncTextureViewToBackendByRecord
(:7015-7065); the two c0e seams bodied (:14100-14200).
DirectGLES.cpp — UnitTexturesByHandle() (:2170-2185) is the one arm selector
(Transport != Monolith && SamplerSubsystemEnabled() && TextureResourceSubsystemEnabled());
UnitTextureSyncByHandleEntry + its list and keys (:2186-2215); the unit half of
SyncNeccessaryTextures (:2420-2485); CurrentUnitBindingsEpoch's decline → refusal
(:2092-2112); ResolveAndBindUnitTextures' window arm (:4990-5065);
ResolveUnitSamplerBackend → refusal at the entry (:5205-5230); BindCurrentUnitSamplers'
decline → refusal (:5330-5350); BindCurrentTextures' ruling-6 key, entry selection included
(:5460-5600); the sampler pass by record (:5880-5960); NeedsRawDepthFetchSampler's
values-taking overload (:286-310); GenerateMipmapByRecord (:10062-10112) and
GenerateMipmap's VerbMipRes arm (:10120-10145); the two mip-shape HandleOf sites deleted
in favour of Generate{Depth,Color}Texture2DMipmapByRecord (:9660-9700);
ScopedDetachedTextureFramebufferAttachments' by-handle constructor over the applier's
framebuffer records (:9215-9290); both CopyTex destinations synced by handle (:9780, :9900).
MG_State/GLState/TextureState/MipmapStorage.cpp — the guard list gains the seven read-only
shape accessors through RefuseLegacyTextureShapeReadFromApplyThread, which is keyed on the
server backend being DirectGLES (ruling 12's shape). The mutator rows stay unconditional.
MG_Remote/Server/PipeApplier.cpp — CheckUnitWindows(st, verb) (:437-495) called from
OnDrawVbo (:515) and OnLaunchGrid (:738).
Tests — RemoteClientTest.cpp seven new MGL_TEXTURE_GUARD_TEST rows; SanityTest.cpp
ARecycledTextureSlotReMintsTheTwinOnTheHandleArm + its pull-build skip twin.
2 The kimi audit's texture/sampler rows (35-67)
RETIRED under a transport: 35, 36, 37 (the snapshot walk is now unreachable: the decline that
led to it is a named refusal) · 38, 39, 40, 42 (the unit list is st.BoundSamplerViews[],
the gate is IsDrawSyncCleanByRecord) · 47, 48, 49 on the by-handle entry (the frontend entry
survives as monolith glue and is still reached from fb's three attachment lists) · 50, 51, 52,
53 (ResolveAndBindUnitTextures is one pass over the window; the target is the view CSO's; the
unbind walks the fixed TextureTarget enum against g_boundTexturesCache) · 54, 55, 56, 57,
58 (the identity sampler family) · 59, 60, 62 (VerbMipRes + the descriptor) · 63 (the
detach sweep by handle over the framebuffer records) · 66 (the four HandleOf probes; they
survive only as the no-handle-noted fallback).
LEFT BARRIERED / trailing: 61 GenerateThreeChannelFloatMipmapOnCpu — named
Fatal{UnmigratedEmulation, "generate-mipmap-cpu-filter"} at the entry of the arm that would use
it; the pull is P8/P9's · 64 UpdateTextureBindingAtTarget's GetTextureUnitObject (CopyTex
is kWaitApplied) · 65 already monolith-only at the base.
NOT MINE (fb's rows, untouched): 41, 43, 44, 45, 46, 67. NOT FOUND: none — every row of 35-67 exists at this head.
3 Named behaviour deltas (G1 §)
- P5e-5, the narrowing. The unit list syncs the PROGRAM-RESOLVED texture per unit; the
frontend walk syncs every slot of every touched unit. What stops being synced as a side effect is
a texture bound to a touched unit that the program does not sample. Coverage is unchanged because
the union covers it — views ∪ image units ∪ attachment lists ∪ the waited texture ops — and the
lane proved the last term is load-bearing rather than rhetorical:
F1WireScenario.CopyTexSubImage2DPixelswent black, because a copy DESTINATION is bound to the active unit and sampled by nobody. Both CopyTex endpoints now sync themselves by handle — the only case the 111-entry lane found, and the shape any future "bound but not sampled" consumer has to copy. - Ruling 1 / G1. Every handle arm is selected by
Transport != MonolithAND the two family bits; the push-monolith build keeps the frontend view test,RequireImageBindableStorage, the unit walk and theGenerateMipmapbody token for token. The pull build compiles none of the new members, soBackendTextureObject's pull layout does not move and the admitted resize set stays empty. UnitTextureSyncEntryis NOT re-shaped in place. A second push-only struct (UnitTextureSyncByHandleEntry) and a second list are added beside it, because the same struct is also the draw-FBO and read-FBO attachment lists' — those are fb's, and one name carrying two shapes across two worktrees is the merge trap the contract package exists to avoid. Deviation from the brief's "re-shape, push-only"; the observable effect is identical.- Scout R4, made explicit in code and here.
IsDrawSyncCleanByRecordreads BOTHParams.ForceResync/Params.SamplerResync(the client's bits, read and never written back) ANDm_forceTextureParamsResync/m_forceSamplerResync(the server's, set byRecreateBackendTexture/ the image-bindable transition and cleared only by the prologue that consumed them). Neither side clears the other's. A gate that read one side would skip the sync the other is asking for, and for the sampler half that is not mis-filtering but an INCOMPLETE texture sampling (0,0,0,1). - Scout R5, resolved. The buffer-texture arm passes
nullptrtoEnsureBufferResourceForHandle: that function's three frontend reads are all inside its monolith arm (kimi row 29), sonullptris the whole of the split answer and is what §4.2's Buffer row prescribes. Whole-vs-range stops asking the buffer for its size —kMGPipeWholeBufferIS the question.
4 Red-once evidence (executed, quoted, reverted)
- Revert the handle arm (
UnitTexturesByHandle() → falseandMGB_STAGED_TEXTURE_LIVE → false),ctest -R DirectGLES.Split.TriangleScenario→ 0/2 passed:MGPipe: Fatal{RoleViolation, "texture-legacy-arm"} - the apply thread called TextureObjectMipmap::GetMipmapLevelCount on a frontend object. With an active transport the server reads the staged-texture store and the resource descriptor (CONTRACT-P5C §2, rule E); a frontend texture's level storage is client memory and this arm is monolith-only - Revert the unit work list's handle arm, full strict lane →
MGPipe: Fatal{UnmigratedPipeInput, "GetTextureUnitObject@Clear"} [BARRIER-PULLED, MOBILEGL_IPC_STRICT_ERRORS=1, retires in P5e (Espryt unbarriered), P7 (Magma)]on 9 entries — byte for byte the BEFORE table below. RevertingResolveAndBindUnitTextures' window arm ALONE changes nothing in the lane (48/14/13/6/3/2, identical to the after table): with fb and vi unlanded, no entry survives to a draw's unit walk, soGetTextureUnitObject@DrawVbocannot be any entry's FIRST pulled row. The brief's expected marker is therefore unreachable on this tree and the unit-list form above is the one that names the field. - ABA: drop the forward-Gen reset in
BackendSlotTable::GetOrCreate(handle)→ARecycledTextureSlotReMintsTheTwinOnTheHandleArmfails:Expected equality of these values: *secondSlot / Which is: (ptr = 0x592a99c35310, ...) / nullptrandValue of: MG_Pipe::MGPipeHandleIsNull((*secondSlot)->PushedSyncHandle()) / Actual: false / Expected: true. - Narrow
SamplerEmit.h'sCountby one → isplit 80% (22 of 111 red):MGPipe: Fatal{ProtocolCorruption, "SetSamplerViews.Count"} - draw_vbo applies with units 0..0 touched, so set_sampler_views must carry Start=0 and Count >= 1 (CONTRACT-P5E.md §5.3); the applied window is Start=0 Count=0, which drops the sync of at least one texture this draw samples— and the dispatch twin, verbatim, withlaunch_gridin place ofdraw_vbo. - Revert
GenerateMipmap'sVerbMipResarm, strict on the three split F1 GenerateMipmap entries →MGPipe: Fatal{UnmigratedPipeInput, "GetTextureUnitObject@GenerateMipmap"} [BARRIER-PULLED, ...]inp5b-f1-GenerateMipmapDepth.logandp5b-f1-GenerateMipmapPackedFloat.log; with the arm present those two abort on fb'sGetFramebufferBindingSlot@ReadPixelsinstead. Plain isplit also goes 2/3 red with the arm reverted.
5 Gate
| step | result |
|---|---|
| build | rc=0 |
| unit | 2214/2214 (2206 at the base + 8 new: 7 guard rows + the ABA case) |
integration-split |
111/111 |
| gens | all 7 rc=0; the one doc-citation line (CONTRACT-P5.md:524 Core.cpp:39 ambiguous) is pre-existing and not mine |
one |
242/242 on `SampledSetStalenessScenario |
| strict | 3 passed / 108 failed, unchanged from the base (the lane is red by design until fb, vi, pg and sb land) |
Strict markers, before and after, measured on THIS tree's own base (f6cfcbd3), 88 private logs:
| marker | before | after |
|---|---|---|
GetFramebufferBindingSlot@Clear (fb) |
39 | 48 |
GetBoundVertexArray@DrawArrays (vi) |
14 | 14 |
GetImageTextureBinding@BindImageTexture (fb) |
13 | 13 |
GetTextureUnitObject@Clear (tx2) |
9 | 0 |
GetTextureObject@CopyImageSubData (P5b debt) |
6 | 6 |
GetProgramForDispatch@DispatchCompute (pg) |
3 | 3 |
ValidateProgramName@ShaderStorageBlockBinding (pg) |
2 | 2 |
The only field that disappeared is mine; the only field that grew is fb's, by exactly the nine
entries that used to abort at mine first. GetTextureUnitObject is gone from the lane entirely.
6 Seams assumed, and what I coded against
SyncMipmapsToBackendByHandle(handle)is bodied as c0e declared it (handle only): it resolves the record itself and stampsm_pushedSyncHandle, so fb's attachment sync and image sweep can call it with nothing but aMGPSurface::Res/MGPImageView::Res.SyncTextureToBackendByHandlereturnsSharedPtr<...>&; a decline is a reference to a NULL twin, so callers must test it.ResolveTextureTwin(id's) is used unchanged and NOT edited - it does not stamp the handle; the two by-handle SYNC entries do.- fb's reverse index was not available, so
ScopedDetachedTextureFramebufferAttachments' handle arm walksMGPipeApplierState::FramebufferRecordFor()directly; if fb lands an index, that constructor is one call site to re-point. - pg's half of ruling 6: keyed on
{DrawProgram.{Slot,Gen}, ShaderCso.Serial, BindingsSerial}throughPipeShaderCsoRecordForHandle(st.DrawProgram); if pg movesBindingsSerial's writer the key follows with no edit here.BindCurrentTexturesstill RECEIVES pg'scurrentProgramand no longer reads it on the handle arm, entry selection included.
7 Rulings I need
- Texture VIEWS have no wire carrier for the view window.
Desc.ViewOfnames the storage owner, butminLevel/numLevels/minLayer/numLayersare on no record. The STEADY question is answered server-side (sync the storage twin by handle, comparem_viewSourceBackendTextureId, stamp the serial); the CREATION of the view still needs the frontend object and takes it when the monolith-glue note reaches one, with a namedMGLOG_E_ONCEwhen it does not.TextureViewScenariois not inintegration-split, so nothing goes red today. Ruling wanted: fourUint16s onMGPResourceDesc(it has no spare pad, so this grows it) in a later package, or the view arm stays barriered and trailing. GenerateMipmap'sWaitClassis NOT flipped, per ruling 14's escape. The unit read is gone, but the RGB16F/RGB32F CPU filter still reaches the client's level shadows (kimi 61), so the arm is not complete. Trailing item for tx2 or P8.CheckUnitWindowsadmits one silence: both counts still 0 means the client's sampler subsystem bit is clear and the backend runs its frontend walk, so there is no window to check. Narrowing carries a non-zero-but-too-small count and is refused. Confirm this is the intended reading of ID-95, or the check has to be armed off the caps/subsystem mask instead.- The widened
MipmapStoragerows are backend-kind-keyed. The unconditional widening aborted Magma'sBlitNamedFramebuffer→ResolveColorBlitBinding→IsComplete()(two isplit entries). Ruling 12's shape applied: the new READ rows abort on DirectGLES and are silent on DirectVulkan; the MUTATOR rows stay unconditional. Confirm, or Magma's blit path opensMGPipeTextureLegacyArmScopeinstead (VulkanRenderer.cpp is not in my scope).
8 What I did not do
- The
PipeSlotPeekzero-scope-entries-per-frame assertion.PipeSlotPeekreads the CLIENT allocator's occupancy; there is no per-frame counter ofMGPipeFrontendKeyedRegistryScopeentries and adding one would be hot-path instrumentation the roadmap forbids. The stronger observable already exists and is unconditional: once ra lands, ANY frontend-keyed probe from an unbarriered apply isFatal{RoleViolation, "MGPipeSlots"}. The family's remaining scope sites (DirectGLES.cpp:1745, :5137, :5223,Managers.cpp:5996, :8757) are all monolith-glue halves the transport arms return in front of; the draw path reaches none of them. - The CopyTex ENDPOINT resolve and
GetTexImage— barriered and trailing, as the brief scopes them.set_texture_params' reply and theresource_subdatatexture half — likewise untouched. - No measurement of this family's apply-thread share (scout R6): it belongs with the device exit.