mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-15 14:03:23 +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.
251 lines
20 KiB
Markdown
251 lines
20 KiB
Markdown
# S5 — buffer binding points and the remaining rows (P5e scouting report)
|
||
|
||
Head `2fde7034`. Paths under `MobileGL/` unless they start with `docs/`. `DirectGLES.cpp` =
|
||
`MG_Backend/DirectGLES/DirectGLES.cpp`, `Managers.cpp` = `MG_Backend/DirectGLES/Managers.cpp`.
|
||
Kinds: **live** = frontend deref; **id** = lifetime id + client allocator probe; **rec** = record.
|
||
|
||
## 1. The family's live reads at this head
|
||
|
||
`GetBufferBindingPoint` is one row (`FieldOwnership.def:80-81`, BARRIER_PULLED, "P3b/P4b, P7")
|
||
whose storage is four raw bases into `Array<Array<BindingSlotRange1D,84>,4>`
|
||
(`PipeInputs.h:809`, filled `PipeFill.cpp:126-131`, `BufferState.h:29`, `:84-86`).
|
||
|
||
| site | reads | kind | when |
|
||
|---|---|---|---|
|
||
| `SyncBufferBindingPoints` `DirectGLES.cpp:491-531`, called `:979` (draw) / `:988-989` (compute) | count `GetTouchedBufferBindingPointCount` `:493`; `GetBufferBindingPoint(target,i)` `:510`; `point.GetBoundObject()` `:511`; `EnsureBufferResource(obj)` `:517`; `point.GetRange()` `:523` → `obj->GetSize()` (`Types.h:220-229`); `obj->GetSize()` `:524,:527-528` | rec (count, P5c rv); live + id (body) | count on EVERY draw; body 0 iterations on Minecraft (touched SSBO count 0) |
|
||
| `MarkShaderStorageBuffersGpuWritten` `:600-615`, called `:980`, `:990` | count `:601`; point + `GetBoundObject()` `:604-605`; `MarkBufferGpuWritten(obj)` `:606` → `HandleOfBuffer` (`Managers.cpp:2922-2941`, scope `:2933`) | rec; live + id | every draw AND every dispatch |
|
||
| UBO loop in `BindCurrentProgramWithResources` `:5447-5497` | `currentProgram->GetUniformBlockBinding(i)` `:5463`; `GetBufferBindingPoint(Uniform,binding)` `:5464`; `GetBoundObject()` `:5465`; `GetRange()` `:5466`; `GetBufferResource(bufferObj.get())` `:5472`; `IsBufferDrawClean(bufferObj.get(),…)` `:5475` → `HandleOfBuffer` (`Managers.cpp:3155-3159`); `EnsureBufferResource` `:5478` | live + id | **1–3 per draw on Minecraft, memo hit or miss** — `drawCleanEpoch` (`:5457,:5473-5479`) short-circuits the STORAGE work only, never the point read or the bind |
|
||
| `SyncAtomicCounterBuffers` `:617-673`, called `:5504-5506` | `GetBufferBindingPointCount(AtomicCounter)` `:621` (**sticky forward**, `PipeFill.cpp:1870-1874`); point `:628`; `GetBoundObject()` `:630`; `EnsureBufferResource` `:636`; `GetRange`/`GetSize` `:642-650`; `MarkBufferGpuWritten` `:658` | live + id | only for programs with counter blocks |
|
||
| `SyncTransformFeedbackBindingPoints` `:562-592`, called `:1420` | point `:569`; `GetBoundObject()` `:570`; `EnsureBufferResource` `:574`; `GetRange`/`GetSize` `:583-589` | live + id | only on a pending span |
|
||
| `StartPendingTransformFeedback` `:1381-1510`, called `:4829` | returns at `:1386` with no pending span; else `GetTransformFeedbackProgram()` `:1387` (`FieldOwnership.def:106`), `program->GetTransformFeedbackBufferCount()` `:1406`, point walk `:1407-1417`, and `xfb.targets.push_back({bufferObject,…})` `:1417` **holds the frontend `SharedPtr` past the verb** | live | tail of EVERY draw (one bool test in steady state) |
|
||
| `ReadbackCapturedRanges` `:1118-1160` (from `EndTransformFeedback` `:1527`) | under a transport still `HandleOfBuffer(target.buffer.get())` `:1138` | live + id | per span close |
|
||
| `GetBufferBindingSlot` `PipeInputs.h:808`, filled `PipeFill.cpp:116-124`, in kDraw's mask (`FillPoints.def:162`) | 15 pointers copied at every kDraw fill; **no inproc draw-path reader** — `:948-957` is the record arm (`MGPipeApplier().VerbIndirectBuffer`), `:961-966` the monolith `else` | cost only | every draw |
|
||
| `RecordError` | already a `kEventGlError` post under a transport (`PipeFill.cpp:1915-1931`); no frontend write from the apply thread | rec | — |
|
||
| `HasOpenTransformFeedbackSpan`, `GetProgramObject`, `ValidateProgramName`, `GetTextureObject` | sticky forwards, off the steady draw path (`FieldOwnership.def:147-165`) | — | — |
|
||
|
||
Every `id` above runs inside `MGPipeReverseAnnouncementScope` — CONTRACT-P5C §3.1's **first
|
||
named exemption**, granted verbatim because "`set_shader_buffers` and
|
||
`set_stream_output_targets` sit in the catalogue but are P4b's to emit"
|
||
(`CONTRACT-P5C.md:157-176`, guard `MG_Impl/Pipe/SlotAllocator.cpp:22-38`). This family IS that
|
||
exemption; retiring it retires the scope.
|
||
|
||
## 2. What the records and the applier already carry
|
||
|
||
- **The counts, and only the counts.** `MGPContextValues::TouchedBufferBindingPointCount[15]`
|
||
(`MGPipeTypes.h:1692-1705`), emitted by `EmitContextValues` (`PipeFill.cpp:2679-2704`),
|
||
RECORD_SUPPLIED since P5c rv. `:493`, `:601` and the client's own sweep
|
||
(`GpuWritePending.cpp:61`, `:77`) all read it.
|
||
- **`set_shader_buffers` is catalogued and dead.** `PipeCalls.def:174`, `MGPShaderBuffers`
|
||
(`MGPipeTypes.h:910-919`), `MGPBufferRange` (`:901-906`), wire op 38
|
||
(`generated/PipeWire.inc:110,:211,:310,:698-704`), thunk (`PipeThunks.inc:168`), table slot
|
||
(`PipeTables.inc:62`), codec layout + bounds + host-span honesty pass
|
||
(`PipeWireCodec.cpp:204`, `:648-661`, `:1778-1801`) — and then `return false`: **no route row
|
||
(`PipeRoute.h`/`.cpp` have none), no `Mono_`/`Wire_` binding, no
|
||
`MGPipeApplySetShaderBuffers`, no applier state, no emitter.** `PipeCatalogueTest.cpp:170-171`
|
||
and `:242-243` pin both slots null. `set_stream_output_targets` is identical
|
||
(`PipeCalls.def:175`, `MGPipeTypes.h:922-928`, `PipeWireCodec.cpp:1805`).
|
||
- **The dirty bits exist and own nothing.** `MGPipeDirty::NewConstBuffers/NewShaderBuffers/
|
||
NewSoTargets` (`Tracker.h:97-99`), `MGPipeSubsystemForDirty` answers 0 for all three
|
||
(`Tracker.h:204-209`), pinned by `TrackerTest.cpp:385-387`. `SetHashSuppressor.h:65-66` marks
|
||
both slots "P4b".
|
||
- **The buffer twin is already handle-capable.** `IsBufferDrawCleanByHandle`
|
||
(`Managers.cpp:3109-3153`) reads the frontend only under monolith (`:3143-3145`);
|
||
`EnsureBufferResourceForHandle(nullptr, h)` (`Managers.cpp:3193-3222`) needs no object and is
|
||
already called that way at `DirectGLES.cpp:953`.
|
||
- **The GPU-write set already moved sides.** `MarkShaderStorageBindings`
|
||
(`GpuWritePending.cpp:55-66`), `MarkAtomicCounterBindings` (`:68-81`),
|
||
`MarkTransformFeedbackTargets` (`:37-53`), driven from `MarkGpuWritesForDraw` (`:145-151`) in
|
||
`BeforeDrawVerb`. `GpuWritePending.h:9-55` is the ruling. The backend's `:606`, `:658`,
|
||
`:2775` are a **duplicate** under a transport.
|
||
|
||
## 3. The gap
|
||
|
||
**3.1 The shutter is not merely unowned, it is wrong.** Bits 15/16/17 read
|
||
`ctx.GetAnyBufferChangeGeneration()` (`Tracker.h:525`, `:636-639`) — the buffer CONTENT
|
||
aggregate (`BufferState.h:69-72`). `glBindBufferBase/Range` mutate the point through a returned
|
||
reference (`GL_Buffer.cpp:1501-1520`, `:1624-1650`: `point.Bind()`, `point.SetRange()`), which
|
||
moves `BindingSlot::m_version` (`Types.h:189-194`) and nothing the tracker reads. So
|
||
`glBindBufferBase(UNIFORM,1,A); draw; glBindBufferBase(UNIFORM,1,B); draw` fires no bit — the
|
||
same defect class as P4a's `glBindSampler` hole (`Tracker.h:612-621`).
|
||
**And the mutation is invisible to the dirty-surface gate**: `MUTATOR_PREFIXES`
|
||
(`scripts/gen_pipe_dirty_surface.py:87-90`) has no `Touch`, and the point write is not a
|
||
`pGLContext->` call at all, so `BindBufferBase_State`/`BindBufferRange_State` have no row in
|
||
`DirtySurface.def` (83 rows; the nearest is `SetNamedTransformFeedbackBinding` →
|
||
`kPulledEveryVerb`, `:295`).
|
||
|
||
**3.2 New frontend state (push-build only).** `BufferState::NoteBindPointChanged(target)` beside
|
||
`NoteBufferChanged` (`BufferState.h:69-75`), one `Uint64` per `BufferBindPointTargets` entry,
|
||
bumped by the two entry points above and by `SetNamedTransformFeedbackBinding`. `#if
|
||
MOBILEGL_PIPE_PUSH` so the pull build's object does not resize (G1) — the licence
|
||
`TextureState::NoteImageUnitTouched` took at P5d r3 (`GpuWritePending.cpp:96-108`).
|
||
|
||
**3.3 Shutters.** bit 15 `Mix(bindPointGen[Uniform], programIdentity)` (the emitted window is
|
||
resolved FOR THE PROGRAM IN USE, 3.4/7.2 — `UseProgram`'s row `DirtySurface.def:311` already
|
||
carries this pattern); bit 16 `Mix(bindPointGen[ShaderStorage], bindPointGen[AtomicCounter])`;
|
||
bit 17 `Mix(bindPointGen[TransformFeedback], ctx.GetTransformFeedbackGeneration())`. The content
|
||
aggregate LEAVES all three — it is the resource family's business, and mixing it in fires on any
|
||
`glBufferSubData` anywhere. That is exactly the narrowing bit 10 already took
|
||
(`Tracker.h:545-553`).
|
||
|
||
**3.4 The record.** One `MGPShaderBuffers` + `MGPBufferRange[Count]` per **Class**
|
||
(`Uniform=0 | ShaderStorage=1 | AtomicCounter=2`, `MGPipeTypes.h:911`). `Start = 0`;
|
||
`Count` = the class's window (7.2) clamped to a new `kMGPipeMaxBufferBindingPoints`, pinned
|
||
against `BufferState.h:29`'s 84 in `PipeFill.cpp` where both are visible — the shape
|
||
`kMGPipeMaxVertexAttribs` is pinned with at `PipeFill.cpp:2093-2095`.
|
||
Entry: `Res = MGPipeSlots().Acquire(MGPipeKind::Buffer, obj->GetLifetimeId())` or null;
|
||
`Offset/Size` from `point.GetRange()` resolved CLIENT-side, with `Size = kMGPipeWholeBuffer`
|
||
(`MGPipeTypes.h:406`) for a base binding so the SERVER re-resolves against its own descriptor
|
||
(7.3). `WritableMask` (`MGPipeTypes.h:914`) set per entry for ShaderStorage/AtomicCounter — it
|
||
is what replaces the backend mark's membership question.
|
||
`HostSpanCount = 0` **always** for Espryt: `kCapNeedsHostUboBytes` is 0 for the whole of P5 by
|
||
ruling (`CONTRACT-P5.md:64`, `MG_Backend/Init.cpp:150`, `PipeWireCodec.cpp:1786`) and the bit is
|
||
Magma's named-UBO ring (`docs/Disaggregated/ARCHITECTURE.md:113`, `:121`, `:140`). The codec's
|
||
honesty pass (`PipeWireCodec.cpp:1783-1799`) stays the guard that says so if it ever is not.
|
||
**XFB is NOT this record**: `set_stream_output_targets` carries a `Generation`
|
||
(`MGPipeTypes.h:922-928`) this payload has no field for, and capture points are span-scoped
|
||
state latched at Begin. Keep the catalogue's split.
|
||
|
||
**3.5 Hash + suppressor.** `XXH64` over the tail with `Class/Start/Count` mixed —
|
||
`MGPipeShaderImageSetContentHash`'s shape (`ImageEmit.h:53-58`). `SetHashSuppressor.h:65` has
|
||
**one** slot for the call while the record is per class; three classes sharing it would make
|
||
each emission cancel the others'. Split into three slots (see 7.1).
|
||
|
||
**3.6 Subsystem bit.** Bit 13 is free (`MGPipe.h:105-109`: "bits 13..62 reserved"). Add
|
||
`kMGPipeSubsystemBufferBindings = 1ull << 13`; the three dirty bits leave
|
||
`MGPipeSubsystemForDirty`'s `default: return 0` arm (`Tracker.h:204-209`);
|
||
`SubsystemForEmitter` (`PipeFill.cpp:1989-2028`) gains a `SetShaderBuffers` row with the pairing
|
||
`static_assert`s beside P4a's (`PipeFill.cpp:2104-2126`). `TrackerTest.cpp:385-387` is rewritten,
|
||
not deleted.
|
||
|
||
**3.7 Coverage / ownership.** `Coverage.def`'s EMITTED list gains
|
||
`GetBufferBindingPoint → SetShaderBuffers`; SHAPE-ONLY like P4a's six (`Coverage.def:199-213`),
|
||
landing in `EmittedCallSuppliesTheWholeField`'s FALSE arm — the field is a pointer no applier can
|
||
produce. `FieldOwnership.def:80-81` stays BARRIER_PULLED until the four Espryt consumers are
|
||
gone, then becomes RECORD_SUPPLIED. `GetBufferBindingPointCount` (`:147-148`, `:179-180`) is
|
||
answered from the applier's `Count[Class]` and its forward becomes FATAL under split — the
|
||
`InvalidateCompileEnv` shape (`FieldOwnership.def:163-165`).
|
||
|
||
## 4. The retirement design (server shape)
|
||
|
||
**4.1 Applier state**, beside the three unit sets (`PipeApply.h:673-686`):
|
||
`Array<MGPBufferRange, kMGPipeMaxBufferBindingPoints> BoundShaderBuffers[3]`, `Uint32 Start[3]`,
|
||
`Count[3]`, `WritableMask[3]`, and ONE `Uint64 ShaderBuffersSerial` advanced on every applied
|
||
record and **never zeroed by `MGPipeApplierReset`** — `VertexBuffersSerial`'s rule, stated at
|
||
`PipeApply.h:609-616`. Entry point `MGPipeApplySetShaderBuffers(hdr, ranges)` validating
|
||
`Class < 3` and `Start + Count <= capacity`; the codec's bounds gate already runs
|
||
(`PipeWireCodec.cpp:648-661`).
|
||
|
||
**4.2 `SyncBufferBindingPoints` → `SyncBufferBindingPointsByRecord(Class, glTarget)`.** Count
|
||
from `st.Count[Class]`; per entry `EnsureBufferResourceForHandle(nullptr, entry.Res)`; the
|
||
whole-vs-range test becomes `entry.Offset == 0 && entry.Size == kMGPipeWholeBuffer`, so
|
||
`obj->GetSize()` disappears from the file. The clean probe is
|
||
`IsBufferDrawCleanByHandle(entry.Res, resource, nullptr)`, which
|
||
`Managers.cpp:3109-3153` already accepts with a null frontend. **No twin key is introduced**: a
|
||
binding point is not an object, it names one by handle. "Is it clean" is answered from
|
||
`resource->syncedChangeSerial` vs `ResourceRecordOf(res)->Serial` (`Managers.cpp:3151`) — both
|
||
server-owned — plus `ShaderBuffersSerial` for the SET.
|
||
|
||
**4.3 The GPU-write marks are deleted under a live wire.** `:606`, `:658` and `:2775`
|
||
(`MarkWritableImageBufferTexturesGpuWritten` `:2763-2781`). The client already owns the set
|
||
(2., `GpuWritePending.h:9-55`), and under run-ahead the server's walk is over client memory, so
|
||
keeping it is the one thing that cannot work. `WritableMask` survives as the server's record of
|
||
which points P9's narrowing channel will name.
|
||
|
||
**4.4 The UBO loop** `:5463-5480`: `GetUniformBlockBinding(i)` is **S4's** (the reflection
|
||
archive). Given the binding index, `point` becomes `st.BoundShaderBuffers[Uniform][binding]` in
|
||
the SAME index space — `Start` is 0 and a binding at or above `Count` means "nothing bound",
|
||
which is what the frontend array's default says too. `GetBufferResource(bufferObj.get())` `:5472`
|
||
becomes `FindBufferResourceForHandle(entry.Res)`.
|
||
|
||
**4.5 `SyncAtomicCounterBuffers`**: `pointCount` from `st.Count[AtomicCounter]`; the `glBindings`
|
||
list stays the backend program's (server-owned since link).
|
||
|
||
**4.6 XFB stays LOCKSTEP.** `StartPendingTransformFeedback` `:1381`,
|
||
`SyncTransformFeedbackBindingPoints` `:562`, the `targets` vector `:1417` and
|
||
`ReadbackCapturedRanges`' `HandleOfBuffer` `:1138` are not migrated by this package, because
|
||
(i) `GetTransformFeedbackProgram` is an object row no scout migrates, (ii) `xfb.targets` HOLDS
|
||
frontend `SharedPtr`s across two verbs, which rule C forbids an applier entry point outright,
|
||
and (iii) `ROADMAP.md:32` puts "XFB scatter 搬到 client" in P3b/P4b. Cost on the Espryt steady
|
||
path is zero: `:1386` returns on every draw. **The wait rule S6 needs from me:** a draw waits
|
||
`appliedSeq == seq` iff `ctx.IsTransformFeedbackActive()` — the flag already crosses in
|
||
`MGPContextValues` (`MGPipeTypes.h:1698`) and is client state, so the client can decide without
|
||
asking. Same for a dispatch whose program declares atomic counters, until 4.5 lands.
|
||
|
||
**4.7 `GetBufferBindingSlot`**: drop the 15-pointer copy from the kDraw residual fill under a
|
||
live wire (`PipeFill.cpp:116-124`, a class-and-transport test in the walk at `:3252-3269`). No
|
||
inproc draw-path reader exists; kReadback/kBlitOrCopy/kTextureOp keep it. **Not** a
|
||
`Coverage.def` row — that would claim the whole 15-target field is supplied, the half-truth
|
||
`Coverage.def:62-70` already refuses.
|
||
|
||
## 5. Monolith / G1
|
||
|
||
- Everything new is `#if MOBILEGL_PIPE_PUSH` and gated on
|
||
`(PipePush & kMGPipeSubsystemBufferBindings)`, so the **pull build is byte-identical** and the
|
||
new `BufferState` counter does not resize the pull build's object.
|
||
- Under `Transport=monolith` the record still crosses and the applier still stores it; the
|
||
BACKEND keeps the frontend walk behind `MG_Config::Transport != Monolith`, the exact
|
||
`if (…) else` shape `DirectGLES.cpp:948-966` already uses for the indirect buffer. The push
|
||
build under monolith therefore renders through the same code as today.
|
||
- The residual-fill skip for `GetBufferBindingPoint` is gated on a live wire, so under monolith
|
||
the pointer rows are still copied and `MOBILEGL_PIPE_VERIFY`'s comparator
|
||
(`PipeFill.cpp:470-510`) still compares them.
|
||
- G2/G14: no new backend entry point and no new exported symbol —
|
||
`SyncBufferBindingPointsByRecord` is a file-local function inside `BufferImpl`.
|
||
|
||
## 6. Red-once
|
||
|
||
- **R-S5-a (record).** Revert the handle arm to the frontend walk under a live wire:
|
||
`SsboArrayDynamicIndexScenario` / `StorageBufferRegrowScenario` / `AtomicCounterScenario` under
|
||
`integration-split-strict` must abort with
|
||
`Fatal{UnmigratedPipeInput, "GetBufferBindingPoint@DrawVbo"}` — the server stamp withdraws the
|
||
row (`MG_Backend/MGPipe/PipeInputs.cpp:155-175`), message shape
|
||
`PoisonOmissionScenario.cpp:85-88`.
|
||
- **R-S5-b (shutter).** `TrackerTest`: `glBindBufferBase(UNIFORM,1,A); Update();
|
||
glBindBufferBase(UNIFORM,1,B); Update();` must set bit 15 on the second `Update`. Against
|
||
`Tracker.h:636` today it does not — that is the red.
|
||
- **R-S5-c (the sticky forward).** `rsp` for `GetBufferBindingPointCount` must be non-zero on
|
||
`AtomicCounterScenario` before and **0** after, with the forward aborting.
|
||
- **R-S5-d (no double marking).** Delete the backend marks (4.3) and
|
||
`ProducerMarkCount(GpuWriteProducer::ShaderStorageBinding)` (`GpuWritePending.h`) must stay
|
||
non-zero while `SsboArrayLengthScenario`'s readback still sees the shader's writes.
|
||
- **R-S5-e (catalogue).** `PipeCatalogueTest.cpp:170-171`, `:242-243` invert; the inversion is
|
||
itself the proof the route landed.
|
||
- Quick gate: unit + `integration-split` + `integration-split-strict` on the four SSBO/atomic
|
||
scenarios and the three XFB ones; an A/B with bit 13 cleared must reproduce today's picture.
|
||
|
||
## 7. Risks and what I could not settle
|
||
|
||
1. **One suppressor slot, three classes** (`SetHashSuppressor.h:65`). Needs a ruling: three
|
||
slots (my recommendation — the per-family A/B and fire tallies stay meaningful) or a
|
||
`(slot, Class)` key.
|
||
2. **The emitted window.** Minecraft reaches UBOs through the PROGRAM's block bindings `:5463`,
|
||
not through the touched high-water mark. A high-water window makes `Count` 84 for a program
|
||
that binds block 0 at GL point 83 — 2 KB per emission (suppressed while nothing moves, but
|
||
paid on the first emission after any bind). A program-derived sticky window
|
||
(`ImageEmit.h:88-97`'s shape) is cheaper and is why bit 15 mixes program identity in.
|
||
**Unsettled:** whether a narrow window covers `SyncComputeBuffers`' frontend-indexed Uniform
|
||
pass (`:988`), which exists precisely because compute does NOT go through the per-program
|
||
remap (`:967-978`).
|
||
3. **`GetRange()` resolution moves to the client** (`Types.h:220-229`). A base binding resolved
|
||
at emit freezes the size; GL resolves at use. Hence `Size = kMGPipeWholeBuffer` and a
|
||
server-side re-resolve. That token needs a table-0 row in CONTRACT-P5E; without it a
|
||
`glBufferData` between emission and apply silently binds the old extent under run-ahead.
|
||
4. **The dirty-surface gate will go red** the moment `Touch` joins `MUTATOR_PREFIXES`
|
||
(`gen_pipe_dirty_surface.py:87-90`): every `Touch*` mutator in `MG_Impl/GLImpl` and
|
||
`MG_State/GLState` then needs a row. I did not enumerate them.
|
||
5. **Reset discipline.** `MGPipeApplierReset` clears the three windows but ADVANCES
|
||
`ShaderBuffersSerial`; the emitter's latch must reset with it, in `PipeFill.cpp:3129-3143`'s
|
||
list, or the first emission after a make-current is suppressed as unchanged.
|
||
6. **Capacity on the wire.** 84 (the frontend's, `BufferState.h:29`) or the ES-clamped
|
||
`MaxUniformBufferBindings` (`DirectGLES.cpp:500-504` clamps at the backend)? Keeping 84 on the
|
||
wire and clamping at the backend preserves today's behaviour exactly; a client-side clamp
|
||
would read a device capability from the wrong side.
|
||
7. **`MarkWritableImageBufferTexturesGpuWritten`** `:2763-2781` straddles S3 and S5: its unit
|
||
walk is an image row, its `textureBuffer->GetBufferBindingSlot().GetBoundObject()` `:2775` is
|
||
a buffer row. The client twin already covers it (`GpuWritePending.cpp:83-115`), so the server
|
||
site is simply deleted under a live wire — but that deletion must be ONE edit with S3's.
|
||
8. **Conflict to flag for S8:** S6 will want "everything but reply rows is fire-and-forget".
|
||
4.6 says XFB spans and (until 4.5 lands) counter-carrying dispatches are exceptions, decided
|
||
from `MGPContextValues`' `IsTransformFeedbackActive` on the CLIENT. If S6's design instead
|
||
asks the caps snapshot or the server, the two disagree.
|