mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-14 21:43:24 +09:00
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.
214 lines
15 KiB
Markdown
214 lines
15 KiB
Markdown
# 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|TextureParamsWithoutASamplerView|F1WireScenario|UnboundImageDescriptorScenario|HandleRecycleScenario` |
|
||
| 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 `Uint16`s 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.
|