Files
MobileGL/docs/Disaggregated/notes/p5e/reports/tx2-v1.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

15 KiB
Raw Blame History

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.CopyTexSubImage2DPixels went 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 != Monolith AND the two family bits; the push-monolith build keeps the frontend view test, RequireImageBindableStorage, the unit walk and the GenerateMipmap body token for token. The pull build compiles none of the new members, so BackendTextureObject's pull layout does not move and the admitted resize set stays empty.
  • UnitTextureSyncEntry is 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. IsDrawSyncCleanByRecord reads BOTH Params.ForceResync / Params.SamplerResync (the client's bits, read and never written back) AND m_forceTextureParamsResync / m_forceSamplerResync (the server's, set by RecreateBackendTexture / 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 nullptr to EnsureBufferResourceForHandle: that function's three frontend reads are all inside its monolith arm (kimi row 29), so nullptr is 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 — kMGPipeWholeBuffer IS the question.

4 Red-once evidence (executed, quoted, reverted)

  1. Revert the handle arm (UnitTexturesByHandle() → false and MGB_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
  2. 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. Reverting ResolveAndBindUnitTextures' 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, so GetTextureUnitObject@DrawVbo cannot 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.
  3. ABA: drop the forward-Gen reset in BackendSlotTable::GetOrCreate(handle) → ARecycledTextureSlotReMintsTheTwinOnTheHandleArm fails: Expected equality of these values: *secondSlot / Which is: (ptr = 0x592a99c35310, ...) / nullptr and Value of: MG_Pipe::MGPipeHandleIsNull((*secondSlot)->PushedSyncHandle()) / Actual: false / Expected: true.
  4. Narrow SamplerEmit.h's Count by 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, with launch_grid in place of draw_vbo.
  5. Revert GenerateMipmap's VerbMipRes arm, strict on the three split F1 GenerateMipmap entries → MGPipe: Fatal{UnmigratedPipeInput, "GetTextureUnitObject@GenerateMipmap"} [BARRIER-PULLED, ...] in p5b-f1-GenerateMipmapDepth.log and p5b-f1-GenerateMipmapPackedFloat.log; with the arm present those two abort on fb's GetFramebufferBindingSlot@ReadPixels instead. 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 stamps m_pushedSyncHandle, so fb's attachment sync and image sweep can call it with nothing but a MGPSurface::Res / MGPImageView::Res. SyncTextureToBackendByHandle returns SharedPtr<...>&; 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 walks MGPipeApplierState::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} through PipeShaderCsoRecordForHandle(st.DrawProgram); if pg moves BindingsSerial's writer the key follows with no edit here. BindCurrentTextures still RECEIVES pg's currentProgram and no longer reads it on the handle arm, entry selection included.

7 Rulings I need

  1. Texture VIEWS have no wire carrier for the view window. Desc.ViewOf names the storage owner, but minLevel/numLevels/minLayer/numLayers are on no record. The STEADY question is answered server-side (sync the storage twin by handle, compare m_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 named MGLOG_E_ONCE when it does not. TextureViewScenario is not in integration-split, so nothing goes red today. Ruling wanted: four Uint16s on MGPResourceDesc (it has no spare pad, so this grows it) in a later package, or the view arm stays barriered and trailing.
  2. GenerateMipmap's WaitClass is 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.
  3. CheckUnitWindows admits 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.
  4. The widened MipmapStorage rows are backend-kind-keyed. The unconditional widening aborted Magma's BlitNamedFramebuffer → 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 opens MGPipeTextureLegacyArmScope instead (VulkanRenderer.cpp is not in my scope).

8 What I did not do

  • The PipeSlotPeek zero-scope-entries-per-frame assertion. PipeSlotPeek reads the CLIENT allocator's occupancy; there is no per-frame counter of MGPipeFrontendKeyedRegistryScope entries 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 is Fatal{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 the resource_subdata texture half — likewise untouched.
  • No measurement of this family's apply-thread share (scout R6): it belongs with the device exit.