mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 00:28:31 +09:00
[Docs] (MG_Remote): CONTRACT-P5B.md - the wire records, sinks, refusals and rulings for the 25 measured class-C slots
This commit is contained in:
@@ -0,0 +1,438 @@
|
|||||||
|
# CONTRACT-P5B — the wire contract for the 25 measured class-C slots
|
||||||
|
|
||||||
|
Authority: this file, beside `CONTRACT-P5.md`, which stays authoritative for everything it
|
||||||
|
covers (table 0's encodings, the byte carriers, field ownership, role/thread ownership, the
|
||||||
|
knobs, R-1…R-17). This file ADDS rows to those tables for the verb-migration phase P5b
|
||||||
|
(`~/w7/notes/p5/INTEGRATOR-DECISIONS.md` ID-66, ID-68; the census
|
||||||
|
`~/w7/notes/p6/census-classC.md`) and makes the rulings the census left open. Where it
|
||||||
|
disagrees with CONTRACT-P5.md, this file is newer and wins, and §6 lists every such place.
|
||||||
|
|
||||||
|
**How to change it.** Package c0b's file, edited by the integrator first. A migration package
|
||||||
|
that needs a row changed goes through the integrator; the packages compile against the rows
|
||||||
|
below from day one, which is the whole reason the file exists.
|
||||||
|
|
||||||
|
Base: `feat/disaggregated @ c9878d69` (P5's c1g landed). Every `file:line` below was read at
|
||||||
|
that commit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §0 What P5b is, and the four rules above every row
|
||||||
|
|
||||||
|
P5 landed the first IPC frame on the reduced path: six verbs cross the wire to a real apply
|
||||||
|
thread under `MOBILEGL_TRANSPORT=inproc`, and 64 of the 71 client slots are
|
||||||
|
`Fatal{UnmigratedVerb, "<slot>"}`. The census measured which of the 64 real workloads hit
|
||||||
|
first: **every Minecraft trace first-stops at `DrawElements`**
|
||||||
|
(`improved-transparency-minecraft-26.3` at `DrawElementsInstancedBaseVertex`), and the
|
||||||
|
inproc integration lane's 505 verb aborts are led by `BindImageTexture` 138,
|
||||||
|
`BeginTransformFeedback` 95, `DrawElements` 56, `DispatchCompute` 49, `PatchParameteri` 43,
|
||||||
|
`CopyImageSubData` 30, `MemoryBarrier` 18, `MultiDrawElementsBaseVertex` 18, `ClearBufferiv`
|
||||||
|
12, `ClearBufferuiv` 8 and a measured tail of 15. **P5b migrates those 25, in four packages,
|
||||||
|
under inproc, with the lane's abort count and the traces' first-blocker list as the only
|
||||||
|
gates.**
|
||||||
|
|
||||||
|
The four rules every row below is held to. A, B and C are CONTRACT-P5 §0's, restated only
|
||||||
|
where P5b sharpens them; D is new.
|
||||||
|
|
||||||
|
**Rule A — a content record declares its bytes.** Unchanged. P5b adds ONE content-carrying
|
||||||
|
row (`set_storage_block_binding`'s block name) and it is `kHasBlob`, staged whole in
|
||||||
|
`SEG_STAGE`, `Size = strlen + 1`, never 0.
|
||||||
|
|
||||||
|
**Rule B — no host pointer crosses.** Unchanged, and P5b is the phase that ARMS the one
|
||||||
|
conditional `MGHostSpan` on the hot path: `draw_vbo`'s user-index span
|
||||||
|
(CONTRACT-P5 table 1 row 16). d1 fills it with `Ptr = nullptr, Seg = SEG_STAGE`, the encoder
|
||||||
|
and decoder run all four honesty arms on it, and the sink resolves it through
|
||||||
|
`MG_Pipe::MGPipeHostBytes`. `kCapNeedsHostIndexBytes` and `kCapNeedsHostUboBytes` STAY 0 in
|
||||||
|
P5b — they are about the SERVER needing the bytes of VBO-backed index buffers (P8's index host
|
||||||
|
mirror, ARCHITECTURE §10.3), not about a client array the client alone holds.
|
||||||
|
|
||||||
|
**Rule C — an applier entry point may not hold a pointer past its return.** Unchanged, and
|
||||||
|
it now binds the sinks: every `WireVerbSink::On*` argument that points into a record or a
|
||||||
|
staged run (`ranges`, `userIndices`, `indirect`, the block `name`) is valid for the call only.
|
||||||
|
|
||||||
|
**Rule D — a P5b verb crosses AS THE CALL, not as a reading of it.** A record for one of the
|
||||||
|
25 carries the GL arguments the frontend handed the backend slot - the GL enums verbatim,
|
||||||
|
the GL names the backend keys on, the offsets as the app spelled them - plus the handle the
|
||||||
|
P7/P8 form will dispatch on instead. The server's `ServerVerbSink` reproduces the backend
|
||||||
|
call the monolith makes, and the backend keeps reading the frontend state it reads today
|
||||||
|
through the BARRIER-PULLED fields of its verb class (CONTRACT-P5 table 2, `rsp`). That is
|
||||||
|
what makes migrating a verb a two-file change per slot rather than a backend rewrite, it is
|
||||||
|
why the monolith adapter is byte-identical (nothing on the monolith path changes at all),
|
||||||
|
and it is also the honest statement of the debt: P5b makes the verbs CROSS; retiring the
|
||||||
|
pulls they read is P3b/P4b/P7/P8's, exactly as CONTRACT-P5 table 2 already says, one row per
|
||||||
|
field. Under `MOBILEGL_IPC_STRICT_ERRORS=1` every such pull is still `Fatal`, so the debt
|
||||||
|
stays measurable.
|
||||||
|
|
||||||
|
**The census's correction, made structural.** None of the five P5 class-B verbs has an
|
||||||
|
`MGPipeApply*` entry point; they reach `WireVerbSink`. None of the 25 P5b slots has one
|
||||||
|
either and NONE GAINS ONE (§6.1). The seam a migration package writes is one
|
||||||
|
`ServerVerbSink::On<Row>` body plus one emit-table row on the client - and both already exist
|
||||||
|
as named stubs on this head.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 Table 0 additions — encodings the P5b rows introduce
|
||||||
|
|
||||||
|
| field | the ruling | zero means | who reads it |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **`MGPClear::Kind` / `::ValueClass`** | `kMGPipeClearKind{Whole=0, Color=1, Depth=2, Stencil=3, DepthStencil=4}`, `kMGPipeClearValueClass{Float=0, Int=1, Uint=2}`, in `MGPipeTypes.h` beside the struct. **One spelling.** Through P5 the numbers lived in `Client/EmitTables.h` and `Server/PipeApplier.h` as two hand-minted copies, each flagged for the integrator; both now alias the contract's. The GL-entry → Kind map is written beside the constants. | `Whole` with `BufferMask = 0`: a clear of nothing, legal | f1's emitters, `ServerVerbSink::OnClear` (live for all five kinds since v1) |
|
||||||
|
| **`MGPDrawFlagBit::kDrawIsIndirect` (1<<5)** | The draw's ranges come from a `GL_DRAW_INDIRECT_BUFFER`; `draw_vbo`'s SECOND TAIL is one `MGPDrawIndirect` instead of the user-index span. **Exclusive with `kDrawHasUserIndices`** and **`NumDraws` must be 0** - both `Fatal{ProtocolCorruption}` at the layout, on both sides. | clear = the range tail describes the draw | d1's emitters, the layout, `OnDrawVbo` |
|
||||||
|
| **`draw_vbo`'s second tail** | `WireRecordLayout::SecondTailIsHostSpans` says what it is: spans (`kDrawHasUserIndices`, and `set_shader_buffers` always) or the indirect block (`kDrawIsIndirect`). The encoder's host-span honesty pass reads the flag, not "a second tail exists" - a 40-byte indirect block read as spans is one span and a quarter of garbage. | — | codec |
|
||||||
|
| **GL enums on the wire** | `MGPMipPlan::Target`, `MGPCopyRegion::SrcTarget/DstTarget`, `MGPCopyFromFramebuffer::Target/InternalFormat`, `MGPImageBind::Access/Format`, `MGPPatchParameter::Pname`, `MGPMemoryBarrier::Bits`: **the GL token verbatim**, in a field wide enough for it (every GL texture target and access token fits a `Uint16`; formats and bitfields take a `Uint32`). Rule D: the sink passes the token to the backend slot that takes it. NOT `MGPipeResourceTarget`: the tree has no resource-target → GL-enum inverse to spend on a field the backend only ever forwards. | 0 = no target / no bits, refused where the backend would refuse it | sinks |
|
||||||
|
| **`MGPImageBind::Access`** | the GL access token (`GL_READ_ONLY` 0x88B8 …), NOT `MGPImageView::Access`'s three-value encoding (CONTRACT-P5 table 0 row `MGPImageView::Access`). Two different records, two different jobs: the set record describes a resolved unit for the draw prep, this record reproduces a call. | — | `OnBindShaderImage` |
|
||||||
|
| **GL names on the wire (`GlName` fields)** | `MGPCopyRegion::SrcGlName/DstGlName`, `MGPImageBind::GlName`, `MGPStreamOutputBind::GlName`, `MGPStorageBlockBinding::GlName`. **A GL name is never an identity** (ARCHITECTURE 4.2.1); it is either the key the backend has ALWAYS used (`XfbImpl::g_xfbObjects[name]`, `DirectGLES.cpp:1401`) or the key of a BARRIER-PULLED sticky forward the P5b sink resolves through (`MGB_CTX->GetTextureObject(name)`, `GetProgramObject(name)` - CONTRACT-P5 table 2's forward rows, retired by P7/P9). Every such field sits beside the handle that replaces it. | 0 = GL's name 0 (the default object / no texture) | sinks, through the forwards |
|
||||||
|
| **the block name blob** | `MGPStorageBlockBinding::Name`: `SEG_STAGE`, `Size = strlen + 1`, the NUL travels. Decoder bound: 4096 bytes; a run whose last byte is not NUL is `Fatal{ProtocolCorruption, "SetStorageBlockBinding.Name"}`. | `Size == 0` is Fatal (rule A) | decoder |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 Table 1 additions — the 25 slots, by package
|
||||||
|
|
||||||
|
Columns per slot: **wire record** (row it rides, every new field and its carrier) · **class**
|
||||||
|
(all move C → B when the package flips them) · **sink** (`WireVerbSink` method; `ServerVerbSink`
|
||||||
|
override, a named-Fatal stub on this head) · **reply slot** · **thread rule** · **refusals** ·
|
||||||
|
**monolith**.
|
||||||
|
|
||||||
|
Every row: **no reply slot** (nothing the client acts on comes back - ID-31/R-5 apply to
|
||||||
|
the four acceptance rows only), **thread rule = CONTRACT-P5 table 3 unchanged** (the verb
|
||||||
|
barrier holds; the client's `EmitAndWait` blocks until `appliedSeq >= seq`; the server stamps
|
||||||
|
the verb boundary named in `FieldOwnership.def`'s `MGP_VERB_OP_LIST` before the sink runs;
|
||||||
|
`gPipeInputs` has one writer at a time), **monolith = untouched** (the client emit table
|
||||||
|
exists only under split; the backend slot is called directly as today; G2/G14 name-for-name).
|
||||||
|
Those three columns are therefore stated once here and the rows carry only what differs.
|
||||||
|
|
||||||
|
Refusal vocabulary, shared by all four packages: a record the codec cannot prove is
|
||||||
|
`Fatal{ProtocolCorruption, "<row>.<field>"}`; a shape the sink does not implement yet is
|
||||||
|
`Fatal{UnmigratedVerb, "<GL slot>[+<QUALIFIER>]"}` - the SAME family the client's class-C
|
||||||
|
table raises and the census greps, with the qualifier naming the arm
|
||||||
|
(`DrawElements+CLIENT_INDICES`, `CopyImageSubData+RENDERBUFFER`,
|
||||||
|
`ClearNamedFramebufferfv+UNBOUND`); a backend that leaves the slot null DECLINES
|
||||||
|
(`return false`), which is the monolith's null-slot answer in the same words.
|
||||||
|
|
||||||
|
### d1 — indexed / instanced / multi-draw / indirect (10 measured + 9 companions)
|
||||||
|
|
||||||
|
**One row: `draw_vbo` (59), `MGPDrawInfo` + `MGPDrawRange[NumDraws]` + a conditional second
|
||||||
|
tail.** ROADMAP P8's "`draw_vbo` 收编 multi-draw 族" is exactly this: the twenty GL draw entry
|
||||||
|
points collapse onto one record, and the `MGPDrawRange` array already has the shape the
|
||||||
|
`glMultiDraw*` family has. The P5 sink (`Server/PipeApplier.cpp` `OnDrawVbo`) implements
|
||||||
|
`NumDraws == 1`, non-instanced, VBO-backed; d1 fills in the cross product.
|
||||||
|
|
||||||
|
| slot (census entries) | record fields | sink dispatch (`GLFunctionsTable` slot the sink calls) |
|
||||||
|
|---|---|---|
|
||||||
|
| `DrawElements` (56 + every Minecraft trace) | `IndexSize` = 1/2/4 from `type`; `IndexResource` = the VAO's bound element buffer handle (the same handle `set_index_buffer` (32) carried at validate); one range `{Start = indices / IndexSize, Count = count, IndexBias = 0}`; `Flags = 0` | `DrawElementsBaseVertex(mode, count, type, (void*)(Start*IndexSize), 0)` - the P5 arm, unchanged |
|
||||||
|
| `DrawElementsBaseVertex` (4) | as above, `IndexBias = basevertex` | `DrawElementsBaseVertex(…, basevertex)` |
|
||||||
|
| `DrawElementsInstancedBaseVertex` (trace-only, `improved-transparency-minecraft-26.3`) | `InstanceCount = instancecount`, `IndexBias = basevertex` | `DrawElementsInstancedBaseVertex(mode, count, type, offset, instancecount, basevertex)` |
|
||||||
|
| `DrawArraysInstanced` (2) | `IndexSize = 0`, range `{first, count, 0}`, `InstanceCount` | `DrawArraysInstanced` |
|
||||||
|
| `DrawArraysInstancedBaseInstance` (4) | + `StartInstance = baseinstance`. The vertex-FETCH base instance already crossed in `set_vertex_buffers::BaseInstance` at validate (D-H1; `MGP_SET_BASE_INSTANCE` runs before `MGP_FILL`, `GL_Drawing.cpp:663-688`); `StartInstance` is `gl_BaseInstance`'s value and feeds the GL call | `DrawArraysInstancedBaseInstance` |
|
||||||
|
| `MultiDrawArrays` (2) | `IndexSize = 0`, `NumDraws = drawcount`, range i = `{first[i], count[i], 0}` | `MultiDrawArrays(mode, first[], count[], drawcount)` - the sink rebuilds the two arrays from the ranges (bounded locals; rule C) |
|
||||||
|
| `MultiDrawElements` (2) | `NumDraws = drawcount`, range i = `{indices[i] / IndexSize, count[i], 0}` | `MultiDrawElementsBaseVertex(mode, count[], type, offsets[], drawcount, zeros[])` |
|
||||||
|
| `MultiDrawElementsBaseVertex` (18) | range i `IndexBias = basevertex[i]` | `MultiDrawElementsBaseVertex(…, basevertex[])` |
|
||||||
|
| `MultiDrawElementsIndirect` (2) | `Flags = kDrawIsIndirect`, `NumDraws = 0`, second tail `MGPDrawIndirect{Buffer = the bound GL_DRAW_INDIRECT_BUFFER handle, ParameterBuffer = null, Offset = (Uint64)indirect, ParameterOffset = 0, Stride = stride, DrawCount = drawcount}` | `MultiDrawElementsIndirect(mode, type, (void*)Offset, DrawCount, Stride)` |
|
||||||
|
| `MultiDrawArraysIndirectCount` (2) | as above with `IndexSize = 0`, `ParameterBuffer = the bound GL_PARAMETER_BUFFER handle`, `ParameterOffset = (Uint64)drawcount` (the GLintptr the call spells `drawcount`), `DrawCount = maxdrawcount` | `MultiDrawArraysIndirectCount(mode, (void*)Offset, (GLintptr)ParameterOffset, DrawCount, Stride)` - the backend reads the count where it does today (`DirectGLES.cpp:6510`) |
|
||||||
|
| companions, unmeasured, same row, d1 owns the slots: `DrawElementsInstanced`, `DrawElementsInstancedBaseInstance`, `DrawElementsInstancedBaseVertexBaseInstance`, `DrawRangeElements`, `DrawRangeElementsBaseVertex` (`Flags |= kDrawHasIndexRange`, `MinIndex/MaxIndex = start/end`), `MultiDrawArraysIndirect`, `MultiDrawElementsIndirectCount`, `DrawArraysIndirect`, `DrawElementsIndirect` (`kDrawIsIndirect`, `DrawCount = 1`) | | |
|
||||||
|
|
||||||
|
**Sink:** `WireVerbSink::OnDrawVbo(const MGPDrawInfo&, const MGPDrawRange* ranges, const
|
||||||
|
MGHostSpan* userIndices, const MGPDrawIndirect* indirect)` - the P5 signature plus the
|
||||||
|
indirect block. On this head the sink declines by name: `MultiDrawArrays` /
|
||||||
|
`MultiDrawElements` for `NumDraws != 1`, `DrawArraysInstanced` / `DrawElementsInstanced` for
|
||||||
|
the instanced arms, `DrawElements+CLIENT_INDICES` for a span,
|
||||||
|
`MultiDraw{Arrays,Elements}Indirect` for the block.
|
||||||
|
|
||||||
|
**Client-side index arrays (no element buffer bound) — the P8 resolve-on-client rule,
|
||||||
|
applied.** The bytes exist on the client only, so the client resolves them: d1's emitter
|
||||||
|
stages `count * IndexSize` bytes at `indices` into `SEG_STAGE` and names the run in the
|
||||||
|
existing conditional `MGHostSpan` tail (`Flags |= kDrawHasUserIndices`, `Ptr = nullptr, Seg =
|
||||||
|
SEG_STAGE`). The codec already runs all four honesty arms on it
|
||||||
|
(`PipeWireCodec.cpp` `CheckHostSpanIsHonest(span, segments)`); the sink resolves it with
|
||||||
|
`MGPipeHostBytes` and passes the pointer as `indices`. This is the shape CONTRACT-P5 table 1
|
||||||
|
row 16 froze and table 0 said "P5 must produce none of"; P5b produces it for exactly the
|
||||||
|
case where nothing else can. **`kCapNeedsHostIndexBytes` stays 0**: it asks for the bytes of
|
||||||
|
VBO-backed index buffers (restart rewriting, multi-draw flattening) and is answered by P8's
|
||||||
|
`Server/IndexHostMirror` (ARCHITECTURE §10.3), not by a per-draw span. **A multi-draw with
|
||||||
|
client-side indices** (`indices[i]` are `drawcount` separate client pointers, one span can
|
||||||
|
name one run) is refused by name, `Fatal{UnmigratedVerb, "MultiDrawElements+CLIENT_INDICES"}`,
|
||||||
|
until P8's `HostResolve.cpp` flattens it; no measured workload has one.
|
||||||
|
|
||||||
|
**Client-side VERTEX arrays** (attribute pointers into client memory, no VBO) are NOT d1's
|
||||||
|
and are not a wire question: Espryt's `SyncClientSideAttributesForDrawArrays`
|
||||||
|
(`DirectGLES.cpp:6284-6291`) and its `DrawElements` twin upload them on the SERVER by reading
|
||||||
|
the frontend VAO through the BARRIER-PULLED `GetBoundVertexArray` (CONTRACT-P5 table 2, "P8").
|
||||||
|
Under inproc that read dereferences the client's pointers in the same address space and is
|
||||||
|
correct by the barrier; under spawn (P6) it is exactly the pull P8's `HostResolve.cpp`
|
||||||
|
(client array ranges, max-index scan) retires. d1 changes nothing there.
|
||||||
|
|
||||||
|
**`MinIndex / MaxIndex`** stay `~0` (unknown) for every P5b draw except the two
|
||||||
|
`DrawRangeElements*` companions; **`XfbCpuCapturedVertices`** rides only when the client's
|
||||||
|
XFB accounting has a value (`kDrawHasXfbCount`), which t2's rows make possible;
|
||||||
|
**`RestartIndex` / `kDrawPrimitiveRestart`** carry the frontend's primitive-restart state
|
||||||
|
verbatim (the backend's `ScopedRestartIndexSubstitution` reads the index bytes it needs on
|
||||||
|
its side - from the bound buffer, or from the resolved span).
|
||||||
|
|
||||||
|
**Stamp:** `DrawVbo → DrawArrays` (all twenty share `kDraw`'s mask; the Fatal NAME a pull
|
||||||
|
prints is `DrawArrays` for every one of them - known, accepted, and the reason d1 reports
|
||||||
|
next blockers by the wire's `UnmigratedVerb` name, which is exact).
|
||||||
|
|
||||||
|
**d1's gate:** the lane's `DrawElements` / `MultiDrawElementsBaseVertex` / `DrawArrays*` /
|
||||||
|
`DrawElementsBaseVertex` / `MultiDraw*` first-blocker entries → 0, each affected scenario at
|
||||||
|
its NEXT first blocker by name; **and every Minecraft trace reaches its next first blocker
|
||||||
|
under inproc, reported by name** (the census could not see past `DrawElements`).
|
||||||
|
|
||||||
|
### i1 — image / compute / barrier / copy-image / storage block (5 measured + 2 companions)
|
||||||
|
|
||||||
|
| slot (entries) | row (opcode) | record | sink | on this head |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `BindImageTexture` (138) | **`bind_shader_image` (72), NEW**, `MGPImageBind` 40 B | `Res` = the texture's handle (null for 0), `Unit`, `GlName`, `Level`, `Layer`, `Layered`, `Access` = GL token, `Format` = GL enum. Emitted at the call (`GL_Texture.cpp:6736`), AFTER the frontend has written the unit's `ImageTextureBinding` and `MGP_FILL(BindImageTexture)` has run - the record's verb boundary is what makes the server's read of that binding legal. `set_shader_images` (37) still travels at the next validate, untouched: it is the draw-prep set, this is the call. | `OnBindShaderImage(const MGPImageBind&)` → `GL.BindImageTexture(Unit, GlName, Level, Layered, Layer, Access, Format)`. Espryt ignores everything but `Unit` and syncs the unit's binding from the barrier-pulled `GetImageTextureBinding` (`DirectGLES.cpp:9154`, `:2471`); Magma's slot is a no-op (`DirectVulkan.cpp:665`), so the same call is right for both. | `Fatal{UnmigratedVerb, "BindImageTexture"}` stub |
|
||||||
|
| `DispatchCompute` (49) | `launch_grid` (60), `MGPGridInfo` | `GridX/Y/Z` = the three counts; `Block* = 0` in P5b (the local size is a link artifact the backend reads from its own program; P7 Magma may fill it from the reflection archive); `IsIndirect = 0`. Companion `DispatchComputeIndirect`: `IsIndirect = 1`, `IndirectBuffer` = the bound `GL_DISPATCH_INDIRECT_BUFFER` handle, `IndirectOffset = indirect`. The emitter keeps b1's pre-verb hook ORDER the class-C stub already has: `PushPersistentMapsBeforeVerb(); MarkGpuWritesForDispatch();` then the record (`EmitTables.cpp` `DispatchCompute_Unmigrated`). | `OnLaunchGrid(const MGPGridInfo&)` → `GL.DispatchCompute(GridX, GridY, GridZ)` / `GL.DispatchComputeIndirect(IndirectOffset)` | stub, `"DispatchCompute"` / `"DispatchComputeIndirect"` by `IsIndirect` |
|
||||||
|
| `MemoryBarrier` (18) | `memory_barrier` (61), `MGPMemoryBarrier` | `Bits` = the GLbitfield verbatim (the frontend already validated it and already folds `glTextureBarrier` onto it, `GL_Drawing.cpp:920-937`); `ByRegion = 0`. Companion `MemoryBarrierByRegion`: `ByRegion = 1`. | `OnMemoryBarrier(const MGPMemoryBarrier&)` → `GL.MemoryBarrier(Bits)` / `GL.MemoryBarrierByRegion(Bits)`; Espryt's atomic-counter lowering stays inside the backend (`DirectGLES.cpp:8836`) | stub |
|
||||||
|
| `CopyImageSubData` (30) | `resource_copy_region` (53), `MGPCopyRegion` **64 → 72 B** | `Src/Dst` handles (kind Texture, or Renderbuffer when the target is `GL_RENDERBUFFER`), `SrcTarget/DstTarget` = the GL targets verbatim, `SrcBox = {srcX, srcY, srcZ, w, h, d}`, `DstX/Y/Z`, `SrcLevel/DstLevel`, **`SrcGlName/DstGlName`** (new). | `OnResourceCopyRegion(const MGPCopyRegion&)` → rebuilds two `CopyImageEndpoint`s (`BackendObject.h:33`) from the GL names through the barrier-pulled `MGB_CTX->GetTextureObject(name)` (`rsp`) and calls `GL.CopyImageSubData(src, SrcTarget, SrcLevel, SrcBox.X/Y/Z, dst, DstTarget, DstLevel, DstX/Y/Z, SrcBox.W/H/D)`. **A renderbuffer endpoint is refused by name** (`"CopyImageSubData+RENDERBUFFER"`): no sticky forward hands out a renderbuffer object, and P7 is where the backend takes handles. | stub |
|
||||||
|
| `ShaderStorageBlockBinding` (4) | **`set_storage_block_binding` (75), NEW**, `MGPStorageBlockBinding` 40 B, `kHasBlob` | `ShaderCso` = the program's CSO handle, `GlName` = the GL program name, `Binding`, `Name` = the block name staged in `SEG_STAGE`, `Size = strlen + 1`. Emitted at `GL_Program.cpp:3394` after the frontend recorded the binding on the program (`SetShaderStorageBlockBinding`, `:3392` - which is what reseeds a rebuilt backend program and what `GL_BUFFER_BINDING` reports; the record is the "push it onto the already-built driver program" optimisation Espryt performs, `DirectGLES.cpp:9182-9218`). | `OnSetStorageBlockBinding(const MGPStorageBlockBinding&, const char* name)` → `GL.ShaderStorageBlockBinding(GlName, name, Binding)`; both backends resolve the program through the barrier-pulled `GetProgramObject(GlName)` / `TryGetDirectVulkanProgram` (`rsp`, P9). | stub |
|
||||||
|
|
||||||
|
**The `copy-image-shadow-mirror` emulation** (`DirectGLES.cpp:8997`, `MGPipeUnmigratedEmulation`
|
||||||
|
→ `Fatal{UnmigratedEmulation}` under any non-monolith transport, `PipeApply.cpp:2947`).
|
||||||
|
Migrating `CopyImageSubData` moves the first blocker from the client's
|
||||||
|
`Fatal{UnmigratedVerb, "CopyImageSubData"}` to the server's
|
||||||
|
`Fatal{UnmigratedEmulation, "copy-image-shadow-mirror"}` on every Espryt copy between two
|
||||||
|
textures with CPU shadows. **Ruling (i1): under split the server SKIPS the mirror** - the site
|
||||||
|
becomes `if (monolith) mirror else skip`, behind `#if MOBILEGL_BUILD_DISAGGREGATED` so the
|
||||||
|
pull build's code does not move (G1) - and the client-side mirror ROADMAP P8 names ("CopyImage
|
||||||
|
镜像搬到 client") is P8's. What the skip loses is bounded by two Fatals: a later `glGetTexImage`
|
||||||
|
of the destination served from the client shadow (class C, wave 3, P9) and a texture re-mint
|
||||||
|
that re-uploads it (`texture-remint-pull`, also Fatal). i1 reports which scenarios reach either.
|
||||||
|
|
||||||
|
**Stamps:** `BindShaderImage → BindImageTexture` (kTextureOp), `LaunchGrid → DispatchCompute`
|
||||||
|
(kDispatch; `GetProgramForDispatch` is FATAL in CONTRACT-P5 table 2 because "there is no
|
||||||
|
compute on the reduced path" - **i1 moves it to BARRIER_PULLED, "P7 (Magma), P8 (Espryt)"**, the
|
||||||
|
same shape as `GetProgramForDraw`, in `FieldOwnership.def`; that is a one-row edit in a c0b file
|
||||||
|
and i1 may make it), `MemoryBarrier → MemoryBarrier`, `ResourceCopyRegion → CopyImageSubData`
|
||||||
|
(new row, §6.3), `SetStorageBlockBinding → ShaderStorageBlockBinding`.
|
||||||
|
|
||||||
|
**i1's gate:** the lane's five first-blocker counts → 0; each affected scenario at its next
|
||||||
|
first blocker by name (the census predicts `DispatchCompute` → `MemoryBarrier` → readback for
|
||||||
|
the image-store cases, and the emulation Fatal for some copies).
|
||||||
|
|
||||||
|
### t2 — transform-feedback spans, the XFB object bind, the patch parameter (3 measured + 4 companions)
|
||||||
|
|
||||||
|
| slot (entries) | row (opcode) | record | sink | on this head |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `BeginTransformFeedback` (95) | `begin_stream_output` (62), `MGPStreamOutputBegin` | `PrimitiveMode` verbatim. Emitted at `GL_Drawing.cpp:1274` after the frontend's `BeginTransformFeedback(primitiveMode, program)` recorded the span - the capture program and the capture-buffer bindings the backend reads at the next draw (`StartPendingTransformFeedback`, `DirectGLES.cpp:1224-1250`) are `kXfbSpan`/`kDraw` BARRIER-PULLED fields (`GetTransformFeedbackProgram`, `GetBufferBindingPoint`…). **`set_stream_output_targets` (39) is NOT required for t2**: it has no applier and no producer, and the pulls cover the bindings while the barrier holds. Producing it is P9's (XFB scatter) and would need the consumer nobody can test yet (CONTRACT-P5 §7 `WireVerbSink` header). | `OnBeginStreamOutput(const MGPStreamOutputBegin&)` → `GL.BeginTransformFeedback(PrimitiveMode)` | stub |
|
||||||
|
| `PatchParameteri` (43) | **`patch_parameter` (73), NEW**, `MGPPatchParameter` 8 B | `Pname = GL_PATCH_VERTICES`, `Value`. Espryt pushes `glPatchParameteri` at the call (`DirectGLES.cpp:8760`) and NOT from its draw sync, so `set_patch_state` (43) - which carries the same number into the applier's working block at validate - cannot stand in; both travel, as both pushes happen today. Emitted at `GL_Drawing.cpp:844`. Magma registers no slot: the sink's null-slot DECLINE is the monolith's null-slot skip in the same words. | `OnPatchParameter(const MGPPatchParameter&)` → `GL.PatchParameteri(Pname, Value)` | stub |
|
||||||
|
| `BindTransformFeedback` (2) | **`bind_stream_output` (74), NEW**, `MGPStreamOutputBind` 16 B | `GlName` (Espryt's own key, `XfbImpl::g_xfbObjects[name]`, `DirectGLES.cpp:1401`; 0 = the default object), `LifetimeId` (the D21 counter-slot rekey's key; the frontend's `GetBoundTransformFeedbackLifetimeId`). Emitted at `GL_Drawing.cpp:1673` after `BindTransformFeedbackObject(id)`. | `OnBindStreamOutput(const MGPStreamOutputBind&)` → `GL.BindTransformFeedback(GlName)` | stub |
|
||||||
|
| companions, same rows, t2 owns the slots: `EndTransformFeedback` → `end_stream_output` (63) `MGPXfbAccounting{CapturedVertices, PrimitivesWritten, PrimitiveMode}` = the frontend's own accounting at the end (`GL_Drawing.cpp:1371`; the client's fence wait there is dropped per ARCHITECTURE 8.5 / CONTRACT-P5 table 2's "two new producers" - the capture targets are marked GPU-written instead); `PauseTransformFeedback` / `ResumeTransformFeedback` → (64)/(65) `MGPStreamOutputControl{Reserved = 0}`; `DeleteTransformFeedback` has no row in P5b (unmeasured; a `bind_stream_output` of name 0 is what the backend does on delete of the bound object, `DirectGLES.cpp:1422`, and the driver object leak until P9 is recorded, not solved). | | `OnEndStreamOutput` / `OnPauseStreamOutput` / `OnResumeStreamOutput` → the three GL slots | stubs |
|
||||||
|
|
||||||
|
**`FixupGsStripCaptureOrder` (`GL_Drawing.cpp:1290`)** probes `EndTransformFeedback != nullptr`
|
||||||
|
to decide whether the backend owns the capture. `SlotCaps.h` deliberately did not convert this
|
||||||
|
probe ("absent would be wrong"); with t2's emitter installed the slot is non-null on the
|
||||||
|
client, which for a server running Espryt is the right answer and for a server running Magma
|
||||||
|
(slot null on the server, the sink DECLINES) is WRONG in the P5 direction: the client would
|
||||||
|
skip a reorder Magma needs. **Ruling:** t2 answers the probe from the caps mirror - the
|
||||||
|
existing `kCapCpuXfbPrimitiveAccounting`-style shape, a new cap bit
|
||||||
|
`kCapBackendOwnsXfbCapture` (1<<9) in `MGPCapBit`, set by the server from its own table's
|
||||||
|
`EndTransformFeedback != nullptr` - through `MGL_BACKEND_SLOT_CAP`. That is the one new cap
|
||||||
|
bit of P5b and it is c0b's file; t2 adds it (§6.5).
|
||||||
|
|
||||||
|
**Stamps:** `BeginStreamOutput → BeginTransformFeedback`, `EndStreamOutput →
|
||||||
|
EndTransformFeedback`, `Pause/Resume` likewise (all pre-existing rows), `PatchParameter →
|
||||||
|
PatchParameteri` (kQuery: reads nothing), `BindStreamOutput → BindTransformFeedback` (kXfbSpan).
|
||||||
|
|
||||||
|
**t2's gate:** the three first-blocker counts → 0; each scenario at its next blocker by name
|
||||||
|
(the census predicts `EndTransformFeedback` or an XFB query end - the query family is wave 3
|
||||||
|
and stays `Fatal{UnmigratedVerb}`).
|
||||||
|
|
||||||
|
### f1 — the clear family, the framebuffer-sourced copies, mips (7 measured + 4 companions)
|
||||||
|
|
||||||
|
| slot (entries) | row (opcode) | record | sink | on this head |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `ClearBufferiv` (12), `ClearBufferuiv` (8), `ClearBufferfv` (6), `ClearBufferfi` (1) | `clear` (57), `MGPClear` | `Fbo = kMGPipeNullHandle` (the bound draw framebuffer, as `EmitClear` does), `Kind` / `ValueClass` per the table in `MGPipeTypes.h` beside the constants, `DrawBufferIndex = drawbuffer`, `ColorValue[4]` = the four values' bits (`memcpy`; a float clear crosses as its bit pattern, exactly as MOBILEGL_PIPE_VERIFY compares floats), `DepthValue`, `StencilValue`. Emitted at `GL_Framebuffer.cpp:2829-2850` with `BeforeReadOnlyVerb()` first, as `EmitClear`. | `OnClear` - **already live for all five kinds** (`PipeApplier.cpp` `OnClear`): `GL.ClearBuffer{fv,iv,uiv,fi}` by `Kind`/`ValueClass`. The only work is the client. | live |
|
||||||
|
| `ClearNamedFramebufferfv` (2) + companions `fi/iv/uiv` | `clear` (57) | as above with **`Fbo` = the named framebuffer's handle**, preceded by its `MGPipeFramebufferTarget::Named` record (ID-19; the frontend already emits it: `PipePublishFramebufferByName`, `GL_Framebuffer.cpp:690`). | `OnClear`: `Fbo` null or equal to the applier's `BoundFramebuffer[Draw]` → the bound-form clear above. **Otherwise refused by name, `Fatal{UnmigratedVerb, "ClearNamedFramebufferfv+UNBOUND"}`**: the backend slot takes a frontend `SharedPtr<FramebufferObject>` (`DirectGLES.cpp:9308`), no forward hands one out, and the bound-form entries re-sync the CLIENT's binding through the barrier-pulled `GetFramebufferBindingSlot` (`SyncCurrentFBO`), so a server-side rebind-clear-restore through the applier would be undone by the pull. The handle-keyed `SyncAndBindFramebufferObject` sibling Espryt needs (`DirectGLES.cpp:4409`) is P7's / P3b-P4b's, or f1's stretch behind `#if MOBILEGL_BUILD_DISAGGREGATED` if the one measured scenario needs it. | live for the bound case; the unbound refusal is f1's to add |
|
||||||
|
| `CopyTexImage2D` (3) + companion `CopyTexSubImage2D` | **`copy_framebuffer_to_texture` (76), NEW**, `MGPCopyFromFramebuffer` 48 B | `Dst` = the bound texture's handle at the active unit (P8 form), `Target` = the GL target verbatim, `Level`, `InternalFormat` (image form; 0 for sub-image), `X, Y, Width, Height` = the read-framebuffer rectangle, `XOffset, YOffset` (sub-image form), `SubImage = 0/1`. The image form's level redefinition has already crossed as the level's `resource_respecify` from the frontend's own `TexImage2D_State` (`GL_Texture.cpp:4541`) before the backend slot is reached (`:4550`); the record carries only the copy. The read framebuffer is the bound one (`set_framebuffer_state`). | `OnCopyFramebufferToTexture(const MGPCopyFromFramebuffer&)` → `GL.CopyTexImage2D(Target, Level, InternalFormat, X, Y, Width, Height, 0)` or `GL.CopyTexSubImage2D(Target, Level, XOffset, YOffset, X, Y, Width, Height)`; the backend resolves the bound texture through the barrier-pulled unit state (`DirectGLES.cpp:8528-8531`, kBlitOrCopy's mask). | stub, `"CopyTexImage2D"` / `"CopyTexSubImage2D"` |
|
||||||
|
| `GenerateMipmap` (2) | `generate_mipmap` (54), `MGPMipPlan` | `Res` = the bound texture's handle, `Target` = the GL target verbatim, `BaseLevel/LevelCount` from the texture's base level and level count (informational in P5b). Emitted at `GL_Texture.cpp:1681`. | `OnGenerateMipmap(const MGPMipPlan&)` → `GL.GenerateMipmap(Target)`; the backend resolves the bound texture through the barrier-pulled unit state (`DirectGLES.cpp:8769-8772`). | stub |
|
||||||
|
|
||||||
|
**The two mipmap emulations** (`generate-mipmap-storage` `DirectGLES.cpp:8051`,
|
||||||
|
`generate-mipmap-cpu-fallback` `:8702`, both `Fatal{UnmigratedEmulation}` under split) are
|
||||||
|
reached only for `R11FG11FB10F`, depth-only, `RGB16F` and `RGB32F` textures
|
||||||
|
(`DirectGLES.cpp:8774-8778`). **Ruling (f1):** migrating `GenerateMipmap` moves those cases'
|
||||||
|
first blocker to the emulation Fatal, reported by name; every other format renders. The
|
||||||
|
storage grow reads the frontend level shadows to DECIDE the levels and could be re-derived from
|
||||||
|
the applier's descriptor (`Levels`) - f1 may do that behind `#if MOBILEGL_BUILD_DISAGGREGATED`;
|
||||||
|
the CPU three-channel fallback needs `OnTextureWriteback` (ROADMAP P8/P9) and stays Fatal.
|
||||||
|
|
||||||
|
**Stamps:** `Clear → Clear` (pre-existing), `CopyFramebufferToTexture → CopyTexImage2D` (the
|
||||||
|
sub-image companion shares the kBlitOrCopy mask), `GenerateMipmap → GenerateMipmap`.
|
||||||
|
|
||||||
|
**f1's gate:** the seven first-blocker counts → 0; each scenario at its next blocker by name.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3 Table 2 — field ownership under P5b
|
||||||
|
|
||||||
|
**P5b adds no PipeInputs field and retires none.** Every one of the 25 verbs reads, on the
|
||||||
|
server, exactly the fields its verb class's may-read mask names (`FillPoints.def`) and finds
|
||||||
|
them in the class CONTRACT-P5 table 2 already assigned: RECORD_SUPPLIED where a record
|
||||||
|
carries them, BARRIER_PULLED (counted in `rsp`) for the frontend-object and shutter fields,
|
||||||
|
FATAL for the three off-path ones. Two of the three FATAL rows are reached by P5b verbs and
|
||||||
|
move:
|
||||||
|
|
||||||
|
| field | CONTRACT-P5 class | P5b | why |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `GetProgramForDispatch` | FATAL ("no compute on the reduced path") | **BARRIER_PULLED, "P7 (Magma), P8 (Espryt)"** | i1 puts compute on the path; the field is `GetProgramForDraw`'s twin (`DirectGLES.cpp:5779`, `VulkanRenderer.cpp:7327`) and takes its class and its retiring phases. i1 edits the row. |
|
||||||
|
| `GetTransformFeedbackPausedPrimitiveCounter` | FATAL ("reachable only from kQuery") | unchanged | its one reader is Magma's XFB query end (`kQuery`), and the query family is wave 3. |
|
||||||
|
| `GetBoundTransformFeedbackName` | FATAL (dead) | unchanged | |
|
||||||
|
|
||||||
|
**`rsp` grows, and that is the phase's measurement.** Every P5b verb that draws, dispatches
|
||||||
|
or copies pulls the same 20 non-sticky rows a P5 draw pulled plus the sticky forwards its arm
|
||||||
|
uses (`GetTextureObject` for `CopyImageSubData`, `GetProgramObject` for
|
||||||
|
`ShaderStorageBlockBinding`). The end-of-P5b `rsp` per Minecraft frame is the number that
|
||||||
|
sizes P3b/P4b/P7/P8, and each package's report carries it for its scenarios.
|
||||||
|
|
||||||
|
**The server stamp:** every P5b row that is a verb boundary has a `MGP_VERB_OP_LIST` row in
|
||||||
|
`FieldOwnership.def` on this head (18 rows; `FieldOwnershipTest` pins them). A P5b record that
|
||||||
|
arrived with NO stamp row would apply under the previous verb's serial and read a stale field
|
||||||
|
as fresh - the one silent failure the table has - which is why the six rows landed in c0b
|
||||||
|
rather than being left to the packages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 Table 3 — role and thread ownership under P5b
|
||||||
|
|
||||||
|
Unchanged, and the one thing to restate: **the verb barrier holds for every P5b verb.**
|
||||||
|
`ClientSession::EmitAndWait` blocks the GL thread until `appliedSeq >= seq` for every one of
|
||||||
|
the 25 records as it does for the five P5 verbs; the server's `PipeApplier::ApplyOne` stamps
|
||||||
|
the verb boundary, runs the sink, clears the stamp, and only then does the session publish
|
||||||
|
`appliedSeq` (R-1, R-9). The pre-verb hooks keep their P5 order on the client:
|
||||||
|
`PushPersistentMapsBeforeVerb()` then `MarkGpuWritesFor{Draw,Dispatch}()` then the record
|
||||||
|
(`EmitTables.cpp` `BeforeDrawVerb` / `DispatchCompute_Unmigrated`; ID-18). A P5b emitter for a
|
||||||
|
verb that reads buffers but starts no shader (the clears, the copies, the mip generation, the
|
||||||
|
barrier, the XFB and patch controls) calls `BeforeReadOnlyVerb()`.
|
||||||
|
|
||||||
|
**What the barrier buys P5b and what it costs.** It is why Rule D works at all - the server's
|
||||||
|
backend reads the client's `gPipeInputs` fill of the moment - and it is why P5b's inproc is
|
||||||
|
lockstep and slow (P5 said so; "P5 预期变慢" is P5b's expectation too, recorded, not gated).
|
||||||
|
Retiring it per family is P9's, after the pulls it protects are gone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 Knobs
|
||||||
|
|
||||||
|
**No new knob.** `MOBILEGL_TRANSPORT=inproc` with the P5 defaults is P5b's whole
|
||||||
|
configuration. One cap bit is added (§6.5, `kCapBackendOwnsXfbCapture`), which is not a knob.
|
||||||
|
`MOBILEGL_IPC_STRICT_ERRORS=1` remains the way to see the pulls a P5b verb costs, and
|
||||||
|
`MOBILEGL_IPC_AUDIT=1` remains rule C's control - now with a second consumer worth auditing,
|
||||||
|
the user-index span.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6 Rulings this file makes, and what would overturn each
|
||||||
|
|
||||||
|
1. **No P5b slot gains an `MGPipeApply*` entry point; all 25 reach `WireVerbSink`.** The
|
||||||
|
applier's 37 entry points are the object and state families and write `gPipeInputs` /
|
||||||
|
the applier's records; a verb is a backend CALL, and the census's correction - the five P5
|
||||||
|
class-B verbs already go to the sink - is the pattern, not the exception. The generated
|
||||||
|
route tables (`PipeRoute.h`, R-17, 33 + 4 = 37) are therefore untouched and
|
||||||
|
`PipeCatalogueTest`'s partition still reads 33 installed + (76 − 33) null + 4 escapes.
|
||||||
|
*Overturned by:* a verb that must be replayable from the monolith side through a table
|
||||||
|
(the MGPipe recorder, ROADMAP P13) - at which point every sink method becomes a table row
|
||||||
|
and the monolith adapter is the backend call; none of P5b needs it.
|
||||||
|
|
||||||
|
2. **Five rows appended, none inserted, opcodes 72..76.** `bind_shader_image`,
|
||||||
|
`patch_parameter`, `bind_stream_output`, `set_storage_block_binding`,
|
||||||
|
`copy_framebuffer_to_texture`. Each exists because the backend does work AT THE CALL that
|
||||||
|
no validate-time set record reproduces (§0 Rule D; the evidence per row is in
|
||||||
|
`MGPipeTypes.h` beside the struct). *Overturned by:* the backend moving that work into its
|
||||||
|
draw prep (e.g. Espryt applying `glPatchParameteri` from `SyncRenderState`), which is a G5
|
||||||
|
byte-identical region and therefore not P5b's to touch.
|
||||||
|
|
||||||
|
3. **`resource_copy_region` is `glCopyImageSubData` and nothing else.** CONTRACT-P5 / the
|
||||||
|
`FieldOwnership.def` header left "which of three verbs" to the emitting phase; P5b gives
|
||||||
|
the two framebuffer-sourced copies their own row (76) because they have a different
|
||||||
|
source (the read framebuffer, no handle) and a different shape (a level redefinition on
|
||||||
|
the image form). *Overturned by:* nothing cheaper than a second stamp rule.
|
||||||
|
|
||||||
|
4. **`kCapNeedsHostIndexBytes` stays 0; the user-index span is armed anyway.** The cap is the
|
||||||
|
server's request for VBO-backed index bytes (P8's mirror); the span is the client's only
|
||||||
|
way to hand over bytes only it has. The two were conflated in CONTRACT-P5 table 0's cap-bit
|
||||||
|
row ("the two bits are the only things that ask for [a span]") because P5 produced neither.
|
||||||
|
*Overturned by:* a measured draw whose client-side index array exceeds what one staged
|
||||||
|
run can hold at the default `SEG_STAGE` (32 MiB; the R-10 `maxrec=` counter is what would
|
||||||
|
say so), at which point d1 chunks or refuses by size.
|
||||||
|
|
||||||
|
5. **One new cap bit, `kCapBackendOwnsXfbCapture` (1<<9).** The `EndTransformFeedback !=
|
||||||
|
nullptr` probe at `GL_Drawing.cpp:1290` decides whether the CLIENT reorders captured
|
||||||
|
records; under split the answer is the SERVER's table, so it is a cap, set by the server
|
||||||
|
from `GL.EndTransformFeedback != nullptr` and read through `MGL_BACKEND_SLOT_CAP`. It is the
|
||||||
|
first of the "XFB span family" cap bits `SlotCaps.h` said would come; t2 adds it to
|
||||||
|
`MGPCapBit` (c0b's file, granted for this bit) in the same commit that flips the probe.
|
||||||
|
*Overturned by:* Magma registering `EndTransformFeedback`, which removes the difference.
|
||||||
|
|
||||||
|
6. **`MGPCopyRegion` widens 64 → 72 for two GL names.** A record with no producer and no
|
||||||
|
consumer has no wire history to preserve; the names are the barrier-pulled lookup keys
|
||||||
|
the P5b sink needs and the handles beside them are the identities P7 will use.
|
||||||
|
*Overturned by:* P7 landing handle-keyed endpoints, when the two fields become padding
|
||||||
|
again (and stay in the layout - a shrink would move every field behind them).
|
||||||
|
|
||||||
|
7. **Renderbuffer endpoints, unbound DSA clears and multi-draws with client-side indices are
|
||||||
|
refused BY NAME, ID-57's shape.** Each is a form no barrier-pulled forward can serve, none
|
||||||
|
was measured, and a named Fatal at the client or sink is the outcome R-4 asks for; a
|
||||||
|
silently wrong picture is the one it forbids. *Overturned by:* a measured workload hitting
|
||||||
|
one, which then gets its P7/P8 mechanism moved forward.
|
||||||
|
|
||||||
|
8. **The `copy-image-shadow-mirror` site is skipped under split; the two mipmap emulation
|
||||||
|
sites stay Fatal.** The mirror's loss is bounded by two other Fatals (§2 i1); the mipmap
|
||||||
|
storage grow may be re-derived from the descriptor, the CPU fallback cannot be without
|
||||||
|
`OnTextureWriteback`. *Overturned by:* a measured `GetTexImage` of a copy destination
|
||||||
|
before P9's readback carrier lands - which would show as the wave-3 `GetTexImage` Fatal
|
||||||
|
anyway.
|
||||||
|
|
||||||
|
9. **`GetProgramForDispatch` moves FATAL → BARRIER_PULLED.** §3. *Overturned by:* nothing;
|
||||||
|
the FATAL class was a statement about the reduced path, and P5b widens the path.
|
||||||
|
|
||||||
|
10. **The emit table's class-C list is partitioned per package** (`EmitTables.cpp`:
|
||||||
|
`MGR_UNMIGRATED_{D1,I1,T2,F1,TAIL}_SLOTS`, `kEmittedSlots{D1,I1,T2,F1}`, five ownership
|
||||||
|
`static_assert`s: 19 / 7 / 7 / 11 / 20 = 64). Four packages editing one X-macro list and
|
||||||
|
one count would conflict on every landing; four lists and four counts do not, and a
|
||||||
|
package that touches another's list breaks the other's assertion. *Overturned by:*
|
||||||
|
nothing - it is arithmetic, and the totals `UnmigratedSlotCount() == 64` /
|
||||||
|
`ImplementedVerbCount() == 5` the P5 tests pin are unchanged on this head.
|
||||||
|
|
||||||
|
11. **The server stubs are `Fatal{UnmigratedVerb, "<GL slot>"}`, the same family and name
|
||||||
|
as the client's**, suffixed "(server sink)" in the message text only. A client flipped
|
||||||
|
ahead of its server half therefore aborts under the census's own grep with the right
|
||||||
|
slot name, and the lane's first-blocker table stays meaningful across a partial landing.
|
||||||
|
*Overturned by:* nothing.
|
||||||
|
|
||||||
|
12. **Client-side vertex arrays are not a P5b question.** They are served on the server
|
||||||
|
through the barrier-pulled `GetBoundVertexArray` (CONTRACT-P5 table 2, "P8"), correct
|
||||||
|
under inproc by the barrier and the shared address space, and retired by P8's
|
||||||
|
`HostResolve.cpp`. *Overturned by:* P6's spawn transport - which is after P5b by ID-68.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §7 The 71 slots after P5b's contract commit, and after the four packages
|
||||||
|
|
||||||
|
On this head the partition is CONTRACT-P5 §7's, unchanged: **A = 2, B = 5, C = 64**
|
||||||
|
(`RemoteClientTest` pins it). When the four packages land it is **A = 2, B = 5 + 25 (+ the
|
||||||
|
companions each package flips, up to 44), C = the wave-3 tail (20 at least)**, and the
|
||||||
|
`static_assert`s in `EmitTables.cpp` say which package moved which slot.
|
||||||
|
|
||||||
|
The wave-3 tail no P5b package owns, by name, so nobody discovers it by grep: `BlitNamedFramebuffer`,
|
||||||
|
`GetTexImage`, `GetTextureImage`, `WaitSync`, `DeleteSync`, `FenceSync`, `ClientWaitSync`,
|
||||||
|
`GetSyncStatus`, `BeginTimeElapsedQuery`, `EndTimeElapsedQuery`, `QueryCounterTimestamp`,
|
||||||
|
`IsQueryResultAvailable`, `GetQueryResult64`, `DeleteBackendQuery`, `BeginOcclusionQuery`,
|
||||||
|
`EndOcclusionQuery`, `BeginXfbPrimitivesQuery`, `EndXfbPrimitivesQuery`, `GetGpuTimestampNs`,
|
||||||
|
`SetSwapInterval` - P9 (readbacks), P10 (queries, syncs, swap interval).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §8 Ownership
|
||||||
|
|
||||||
|
- `MG_Pipe/MGPipeTypes.h`, the three `.def` files, `FieldOwnership.def`, `gen_pipe*.py`,
|
||||||
|
`CONTRACT-P5.md`, `CONTRACT-P5B.md`: c0b's (inherited from c0). A package that needs a payload
|
||||||
|
shape changed goes through the integrator. Two grants are pre-made in this file: t2 may add
|
||||||
|
`kCapBackendOwnsXfbCapture` (§6.5); i1 may edit `GetProgramForDispatch`'s row (§6.9).
|
||||||
|
- `MG_Remote/Wire/PipeWireCodec.{h,cpp}`: w1's; c0b landed the sink methods and the arms; a
|
||||||
|
package edits ONLY the layout arm of a row it owns, and only with the integrator.
|
||||||
|
- `MG_Remote/Server/PipeApplier.{h,cpp}`: v1's; each package replaces ITS stub bodies and
|
||||||
|
nothing else (the per-package blocks are marked).
|
||||||
|
- `MG_Remote/Client/EmitTables.{h,cpp}`: c1's; each package edits ITS `MGR_UNMIGRATED_*_SLOTS`
|
||||||
|
list, ITS `kEmittedSlots*`, and adds ITS emitters in the class-B block.
|
||||||
|
- `MG_Test/Wire/PipeWireCodecTest.cpp`: each package appends cases for its rows below c0b's.
|
||||||
|
- The backends (`MG_Backend/DirectGLES`, `DirectVulkan`): untouched by c0b; a package that
|
||||||
|
must touch one does so behind `#if MOBILEGL_BUILD_DISAGGREGATED` and names the region in
|
||||||
|
its report (G1 admits no pull-build symbol motion; G5's untouched regions are pinned).
|
||||||
Reference in New Issue
Block a user