mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-16 06:23: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.
934 lines
69 KiB
Markdown
934 lines
69 KiB
Markdown
# Scout — the frontend (MG_State/GLState + MG_Impl/GLImpl) as P4a's client emission must read it
|
||
|
||
Tree: `C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg` @ `37da3c3a`
|
||
(feat/disaggregated, P3a closed). Every path below is relative to `MobileGL/` unless it starts
|
||
with `docs/`. Every line number was opened in this tree; where a file is large I name the exact
|
||
range I read.
|
||
|
||
Read alongside: `~/w7/notes/p3a/INTEGRATOR-DECISIONS.md` (ID-1..24). The rulings that bind P4a
|
||
directly are ID-11/ID-13 (G5 byte-identical functions — none of them are in P4a's files),
|
||
ID-12/ID-8b (**the applier keeps object records across make-current; only
|
||
`MGPipeApplierReleaseObjectRecords` drops them; the client therefore needs no re-publication on
|
||
`FreshlyPrimed`, but must say so as a rule**), ID-18/ID-21 (**leak-at-exit storage: every MGPipe
|
||
process singleton is heap-constructed and never destroyed; a new P4a singleton MUST follow**),
|
||
and ID-19 (P3a already costs +699/+422 ns/draw; P4a's per-verb emission budget is tight).
|
||
|
||
---
|
||
|
||
## 0. The one-paragraph shape
|
||
|
||
P4a's kinds are **Framebuffer, Texture, Renderbuffer, SamplerCso/SamplerViewCso, ShaderCso**
|
||
(`MG_Pipe/MGPipeHandles.h:24-40`). Unlike P3a's buffers, **five of the six already raise a death
|
||
notice** and **all six already have a lifetime id**; what none of them has is a client-minted
|
||
handle, a client emitter, or an applier record. The frontend versions the emitters need already
|
||
exist and are already read by the tracker's shutters (`MG_Impl/Pipe/Tracker.h:303-312`) — bits
|
||
11..14 (`NewFramebuffer`, `NewSamplerViews`, `NewSamplers`, `NewShaderImages`) plus bits 6..8
|
||
(`NewShader`, `NewShaderBindings`, `NewGlobalConstants`) are **computed, latched and counted
|
||
today and emitted by nobody**. P4a is therefore mostly "wire the emitters onto shutters that
|
||
already fire", plus four genuinely new pieces of frontend work: the resolved `ReadSurface`, the
|
||
framebuffer `Complete` bit, the texture region list on the wire, and the program archive blob.
|
||
|
||
---
|
||
|
||
## 1. The object classes: ids, versions, constructors, destructors
|
||
|
||
### 1.1 Summary table (the hook points)
|
||
|
||
| kind | class + header | lifetime id | ctor | dtor / death | already raises |
|
||
|---|---|---|---|---|---|
|
||
| Framebuffer | `FramebufferObject`, `MG_State/GLState/FramebufferState/FramebufferObject.h:105-191` | `m_lifetimeId` `:176`, `GetLifetimeId()` `:165`, allocator `FramebufferObject.cpp:20-24` | `FramebufferObject.cpp:126-135` | `FramebufferObject.cpp:27-37` (`#if MOBILEGL_PIPE_PUSH` only) | `NotifyStateObjectDestroyed(MGPipeKind::Framebuffer, …)` `:36` |
|
||
| Texture | `TextureObjectBase : ITextureObject`, `TextureState/TextureObject.h:117-234` | `m_lifetimeId` `:205`, `GetLifetimeId()` decl `:84` / def `TextureObject.cpp:334-336`, allocator `TextureObject.cpp:19,26-28` | `TextureObject.cpp:56-...` | `TextureObject.cpp:31-41` — **virtual, declared on the BASE** so 2D/3D/cube/buffer/view all announce once (`TextureObject.h:120-128`) | `MGPipeKind::Texture` `TextureObject.cpp:40` |
|
||
| Renderbuffer | `RenderbufferObject`, `RenderbufferState/RenderbufferObject.h:23-71` | `m_lifetimeId` `:64`, `GetLifetimeId()` `:58`, allocator `RenderbufferObject.cpp:21,24-26` | `RenderbufferObject.cpp:28` | `RenderbufferObject.cpp:31-41` | `MGPipeKind::Renderbuffer` `:40` |
|
||
| Sampler | `SamplerObject`, `SamplerState/SamplerObject.h:16-79` | `m_lifetimeId` `:76`, `GetLifetimeId()` `:63`, allocator `SamplerObject.cpp:20-24` | `SamplerObject.cpp:26-27` | `SamplerObject.cpp:30-40` | `MGPipeKind::SamplerCso` `:39` |
|
||
| Program | `ProgramObject`, `ProgramState/ProgramObject.h:25-...` | `m_lifetimeId` `:1353`, `GetLifetimeId()` `:1262`, allocator `ProgramObject.cpp:26-30` | inline `ProgramObject.h:55` | `ProgramObject.cpp:32-45` (`CancelLink()` then the notice) | `MGPipeKind::ShaderCso` `ProgramObject.cpp:43` |
|
||
| Shader | `ShaderObject`, `ProgramState/ShaderObject.h:24`, dtor `:52` | **NONE** | — | no notice | — |
|
||
| ProgramPipeline | `ProgramPipelineObject`, `ProgramState/ProgramPipelineObject.h:19-170` | **NONE** | — | no notice | — (composites are ordinary `ProgramObject`s, §9.4) |
|
||
| Buffer (P3a, the model) | `BufferState/BufferObject.cpp` | `g_nextBufferLifetimeId` `:20`, `AllocateLifetimeId` `:23-25` | `:35-47` — **mints the handle unconditionally, emits create only if the subsystem is on** | `:49-65` — `MGPipeEmitResourceDestroyAndFree` returns the latch, and the legacy `OnDestroy` runs only when it says false | emits `resource_destroy` itself (no notice) |
|
||
| VAO (P3a, the CSO model) | `VertexArrayState/VertexArrayObject.cpp:41-62` | | | `MGPipeEmitVertexElementsDestroyAndFree(m_lifetimeId)` — **backend-neutral, client-side**, emit → notice → free slot, in that fixed order | |
|
||
|
||
### 1.2 The two death shapes, and which one each P4a kind takes
|
||
|
||
`MG_State/GLState/StateObjectDeathNotice.h` (whole file, 62 lines) is the P2 mechanism: one
|
||
`StateObjectDeathOps{ void(*OnDestroyed)(MGPipeKind, Uint64 lifetimeId) }` (`:39-43`), a plain
|
||
non-atomic global `g_stateObjectDeathOps` (`:45`, written at backend bring-up, read from
|
||
destructors — both on the context thread), `SetStateObjectDeathOps` `:47`, and
|
||
`NotifyStateObjectDestroyed` `:55-59`. Declares nothing at all in a pull build (`:12`, `:62`).
|
||
|
||
**The P3a ruling P4a inherits (ID-15 C-1, `MG_Pipe/PipeMutation.h:127-141`):** a death path that
|
||
lives only in a backend's death-ops table **is no path at all under a backend that installs
|
||
none** — Magma installs none — so every VAO leaked its slot and its ~1.3 KB applier record and
|
||
Fatal'd past 65536 slots. The fix was `MGPipeEmitVertexElementsDestroyAndFree`, a **client-side**
|
||
helper taking the lifetime id (the object is already gone) that emits `delete_*`, raises the
|
||
notice, and frees the slot in that fixed order (`PipeMutation.h:136-141`,
|
||
`MG_Impl/Pipe/PipeFill.cpp:801-...`).
|
||
|
||
**Therefore: for all five P4a kinds the notice at the destructor is NOT sufficient.** Each
|
||
destructor must call a client helper of the same shape. The five call sites are the five `#if
|
||
MOBILEGL_PIPE_PUSH` destructor bodies listed in 1.1 — they already exist, so P4a edits five
|
||
existing lines rather than adding five destructors (which would resize the pull build's symbol
|
||
set and break G1).
|
||
|
||
### 1.3 The per-object version counters P4a's shutters and records need
|
||
|
||
**Framebuffer** (`FramebufferObject.h`):
|
||
- `m_attachmentVersions` `:178`, one `Uint16` per attachment point (`FramebufferAttachmentTypeCount`
|
||
= 41 entries, `:24-71`), read whole via `GetAllFramebufferAttachmentVersions()` `:152-154`.
|
||
- `m_objectVersion` `:190`, `GetObjectVersion()` `:156` — bumped by `BumpAttachmentVersion`
|
||
(`FramebufferObject.cpp:231-235`), by `SetReadBuffer` (`:205-210`) and by the five
|
||
`Set Default*` setters (macro `:216-229`). **`SetDrawBuffer` bumps only through
|
||
`BumpAttachmentVersion(buffer)` (`:195-199`) — i.e. it versions the *new* attachment slot, not
|
||
the draw-buffer array**, which is a real quirk: `glDrawBuffers` writing `NONE` over `COLOR0`
|
||
bumps `m_attachmentVersions[None]`, not `[Color0]`.
|
||
- `m_drawBuffers` `:180` (`Array<FramebufferAttachmentType, MAX_DRAW_BUFFERS=kMGMaxDrawBuffers>`,
|
||
`:107,113`), `m_readBuffer` `:181`, defaults seeded in the ctor `:126-135`
|
||
(`BackLeft` for name 0, `Color0` otherwise).
|
||
- `FramebufferAttachmentObject` `:74-103` is the attachment struct: `SharedPtr<ITextureObject>`
|
||
**or** `SharedPtr<RenderbufferObject>` (`:96-97`), `m_textureUploadTarget` `:98`, `m_textureLevel`
|
||
`:99`, `m_textureLayer` `:100`, `m_layered` `:101`, `m_isValid` `:102`. `IsComplete()`
|
||
`FramebufferObject.cpp:89-99`, `GetSize()` `:101-119` (resolves `Unknown` upload target to
|
||
`GetUploadTargets()[0]`).
|
||
- **`FramebufferState`** `FramebufferState/FramebufferState.h:15-45`: `m_framebufferObjects`
|
||
(`UnorderedMap<Uint, SharedPtr<…>>`) `:41`, `m_bindingSlots[Draw|Read]` `:43-44`,
|
||
`NoteAttachmentChanged`/`GetAnyAttachmentGeneration` `:33-34` under `#if MOBILEGL_PIPE_PUSH`
|
||
`:28-40`. `MarkFramebufferObjectForDeletion` `FramebufferState.cpp:59-72` rebinds any slot
|
||
holding the victim to FBO 0 and erases the map entry.
|
||
|
||
**Texture** (`TextureState/TextureObject.h`): `ITextureObject` `:23-115` is the interface (39
|
||
pure virtuals); `TextureObjectBase` `:117-234` the storage-free base; `TextureObjectMipmap`
|
||
`:236-303` adds the level API; `TextureObjectWithOneMipmap` `:344-378` holds
|
||
`MipmapUploadTargetArray<1> m_textureStorage` `:377`. Concrete leaves: `TextureObject1D/2D/3D.h`,
|
||
`TextureObject2DCube.h`, `TextureObjectBuffer.h`, `TextureObjectView.h`.
|
||
Three independent counters, all on the base:
|
||
- `m_textureParamsVersion` (Uint16) `:213`, `GetTextureParamsVersion()` `:158` — the
|
||
glTexParameter family.
|
||
- `m_shapeVersion` (Uint64, **starts at 1**) `:217`, `GetShapeVersion()` `:160`, bumped **only**
|
||
by `BumpShapeVersion()` (`:202`, def `TextureObject.cpp:44-54`), which also calls
|
||
`pGLContext->BumpSamplingResolutionGeneration()` `:53`. Invalidates the completeness memo
|
||
`m_completeMemoShapeVersion[2]` / `m_completeMemoValue[2]` `:220-221`, read by
|
||
`IsMipmapCompleteForFilterCached` `TextureObject.cpp:298-307`.
|
||
- `m_contentVersion` (Uint64, **starts at 1**) `:224`, `GetContentVersion()` `:159`, bumped by
|
||
`MarkStorageDirty(dirty=true)` (`TextureObject.cpp:379-386`), `MarkStorageDirtyRegion`
|
||
(`:392-398`) and `BumpContentVersion()` (`:309-312`).
|
||
- Other state the descriptor needs: `m_internalFormat` `:207`, `m_sampler` `:208` (**every texture
|
||
owns a `SamplerObject(0)`**, `TextureObject.cpp:58`), `m_swizzleParams` `:209`, `m_levelRange`
|
||
`:211` (`{0,1000}`), `m_immutableLevels` `:212`, `m_depthStencilTextureMode` `:225`,
|
||
`m_viewMinLevel/NumLevels/MinLayer/NumLayers` `:228-231`, `m_samples` `:232`,
|
||
`m_fixedSampleLocations` `:233`.
|
||
- Views: `TextureObjectView.h:44-135`, `m_storageOwner` `:134`, `GetViewStorageOwner()` override
|
||
`:49`; the base returns a static null `TextureObject.cpp:338-343`. `IsTextureView()`
|
||
`TextureObject.h:102`. A view is composed onto the ROOT at creation, so one hop always reaches
|
||
storage (`:90-101`) — that is exactly `MGPResourceDesc::ViewOf`.
|
||
- Texture buffers: `TextureObjectBuffer.h:16-48` — `m_bufferBindingSlot` `:46`,
|
||
`m_bufferRangeOffset` `:47`, `m_bufferRangeSize` `:48` with `kWholeBuffer = ~0` `:28`,
|
||
`GetBufferRangeSizeInBytes()` `:35-41` **resolves live against the buffer's current size** —
|
||
which is exactly the `kMGPipeWholeBuffer` / "resolved live" contract of
|
||
`MGPipeTypes.h:173-177`.
|
||
- `TextureState` `TextureState/TextureState.h:39-173`: `MAX_TEXTURE_IMAGE_UNITS = 192` `:42`,
|
||
`MAX_PER_STAGE_TEXTURE_IMAGE_UNITS = 32` `:47`; `m_textureUnits` `:166`,
|
||
`m_imageTextureBindings` `:167`, `m_textureObjects` `:169`, `m_defaultTextureObjects` `:172`
|
||
(name 0, one per target, immortal). `ImageTextureBinding` `:18-37` = `{Texture, Level, Layered,
|
||
Layer, Access, Format, Version}` with `Bind()` `:27-36` bumping `Version`.
|
||
- `TextureUnit` `TextureState/TextureUnit.h:15-26`: `m_slots[TextureTargetCount]` `:24` +
|
||
`m_sampler` `:25`; `SetSamplerObject` `TextureUnit.cpp:28-40` bumps
|
||
`pGLContext->BumpTextureBindGeneration()` `:39`.
|
||
|
||
**Renderbuffer** — **the outlier: no version at all.** `RenderbufferObject.h:63-70` has
|
||
`m_externalIndex`, `m_lifetimeId`, `m_internalFormat`, `m_width`, `m_height`, `m_samples`,
|
||
`m_allocated`, `m_componentSizes` and nothing else. `SetInternalFormat`
|
||
(`RenderbufferObject.cpp:96-99`), `AllocateStorage` (`:101-105`) and `SetSamples` (`:107-109`)
|
||
write the members and **bump nothing and notify nothing**. See §5.2 — this is a real P4a hole.
|
||
|
||
**Sampler** — `m_version` (Uint16) `SamplerObject.h:77`, `GetVersion()` `:58`;
|
||
`BumpVersion()` `:73` (def `SamplerObject.cpp:43-54`) is the **only** writer, called by all 14
|
||
setters (`:56-...`), each of which early-outs on an unchanged value. It does three things:
|
||
`++m_version`, `pGLContext->BumpSamplingResolutionGeneration()` (`:50`), and
|
||
`MGP_NOTE_AGGREGATE(TextureParams)` (`:53`). `m_samplerParameters` `:78` is the whole
|
||
`SamplerParameters` POD that `MGPSamplerDesc::Parameters` ships byte-for-byte
|
||
(`MGPipeTypes.h:283-287`; `docs/Disaggregated/ARCHITECTURE.md:4.1` insists `borderColorForm`
|
||
travels with it). `SamplerState` `SamplerState.h:17-31` is just a map + `IndexGenerator`.
|
||
|
||
**Program** — five counters, all read by the tracker (`Tracker.h:251-260`):
|
||
- `m_linkVersion` (mutable Uint32) `ProgramObject.h:1418`, `GetLinkVersion()` `:793` — bumped
|
||
**only** by `BumpLinkObservableVersions()` (decl `:1340`), called at Link()'s prologue and at
|
||
both publish sites (`ProgramObject.cpp:72`, `:119`).
|
||
- `m_backendStateVersion` (mutable) `:1399`, `GetBackendStateVersion()` `:790`.
|
||
- `m_blockBindingVersion` `:1401`, `GetBlockBindingVersion()` `:1013`, bumped by
|
||
`SetUniformBlockBinding` `:1020-1027` (and the storage-block setter).
|
||
- `m_imageUnitVersion` `:1394`, `GetImageUnitVersion()` `:855`, bumped inside
|
||
`SetUniformSamplerOrImageUnitIndex` **only for image uniforms** `:847-849`.
|
||
- `m_uniformWriteSetVersion` `:1393`, `GetUniformWriteSetVersion()` `:601`.
|
||
- `m_uboContentVersion` (mutable) `:1417`, `GetUBOContentVersion()` `:740`,
|
||
`MarkUBOContentDirty()` `:741-743` — **`~0u` is reserved as the backends' "never uploaded"
|
||
sentinel and the wrap skips it**.
|
||
- `m_artifacts` `:1426` and `m_spirv` `:1429` are private and reachable only through
|
||
`Artifacts()` / `Spirv()`, which join `m_pendingLink` `:1434` / `m_pendingSpirv` `:1438`
|
||
(invariant I5, `:1264-1275`).
|
||
|
||
---
|
||
|
||
## 2. The mutation entry points, per family
|
||
|
||
Frontend GL entry points live in `MG_Impl/GLImpl/<Family>/GL_<Family>.cpp` in a **two-layer
|
||
shape**: a `Foo_State(...)` function that mutates `MG_State` and validates, and a public `Foo(...)`
|
||
that calls it then dispatches through `MG_Backend::gBackendFunctionsTable.GL.Foo` behind an
|
||
`MGP_FILL(Verb)`. P4a's emission sites are the `_State` bodies (mutation) and, for the verbs,
|
||
the `MGP_FILL` point.
|
||
|
||
### 2.1 Framebuffer — `MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp` (182 KB), decls
|
||
`GL_Framebuffer.h:16-96`
|
||
|
||
- Attach: `FramebufferTexture{,1D,2D,3D,Layer}` `GL_Framebuffer.h:44-49`,
|
||
`NamedFramebufferTexture{,1D,2D,3D,Layer}` `:50-55`, `FramebufferRenderbuffer` `:34`,
|
||
`NamedFramebufferRenderbuffer` `:35-36`. All land in `FramebufferObject::AttachTexture`
|
||
(`FramebufferObject.cpp:137-142`) / `AttachRenderbuffer` (`:144-148`) / `Detach` (`:150-153`),
|
||
each ending in `BumpAttachmentVersion` (`:231-235`) → `++m_attachmentVersions[type]`,
|
||
`++m_objectVersion`, `MGP_NOTE_AGGREGATE(FramebufferAttachment)`.
|
||
- Draw/read buffers: `DrawBuffer` / `DrawBuffers` / `ReadBuffer` `:78-80` and their `Named*`
|
||
forms `:56-58`. Implementations: `DrawBuffersForFramebuffer_State`
|
||
`GL_Framebuffer.cpp:1719-1830`, `DrawBuffers_State` `:1831-1836`, `DrawBuffer_State`
|
||
`:1838-1848`, `ReadBufferForFramebuffer_State` `:1850-1899` (ends at `fbo->SetReadBuffer(attType)`
|
||
`:1898`), `ReadBuffer_State` `:1901-1906`, `NamedFramebufferDrawBuffers_State` `:1908-1916`.
|
||
- Default geometry (`ARB_framebuffer_no_attachments`): `FramebufferParameteri` `:71`,
|
||
`NamedFramebufferParameteri` `:73` → the five setters `FramebufferObject.h:146-150`, defined by
|
||
the macro at `FramebufferObject.cpp:216-229`.
|
||
- Renderbuffer storage: `RenderbufferStorage` / `RenderbufferStorageMultisample` `:23-25`,
|
||
`NamedRenderbufferStorage{,Multisample}` `:30-32`; bodies
|
||
`RenderbufferStorageMultisample_State` `GL_Framebuffer.cpp:817-842`,
|
||
`AllocateRenderbufferStorage_State` `:844-858`, `RenderbufferStorage_State` `:860-866`.
|
||
**None of them notify anything** (§5.2).
|
||
- `BindFramebuffer` `:85`, `BindRenderbuffer` `:38`, `DeleteFramebuffers` `:81`,
|
||
`DeleteRenderbuffers` `:37`, `Blit{,Named}Framebuffer` `:75-77,83-84`, `Invalidate*` `:61-66`,
|
||
`CheckFramebufferStatus` `:82` / `CheckNamedFramebufferStatus` `:69`, `ReadPixels`/`ReadnPixels`
|
||
`:16-18`, `ClearBuffer*` `:19-22` / `ClearNamedFramebuffer*` `:59-60,67-68`.
|
||
- `DefaultFramebufferInfo` `GL_Framebuffer.h:87-96` holds the default FBO plus its three synthetic
|
||
attachments; `pDefaultFramebufferInfo->defaultFBO` is the identity every "is this the default"
|
||
test compares against (`GL_Framebuffer.cpp:1834`, `:1844`, `:1904`). This is exactly the four
|
||
comparisons `MGPipeHandles.h:67-73` retires into `kMGPipeDefaultFramebuffer{0,1}`.
|
||
|
||
### 2.2 Texture — `MG_Impl/GLImpl/Texture/GL_Texture.cpp` (426 KB), decls `GL_Texture.h`
|
||
|
||
Storage / respecify (each → `MGPResourceDesc` create-or-respecify):
|
||
- `TexImage3D_State` `:2352`, `TexImage2D_State` `:2524`, `TexImage1D_State` `:2678` — each
|
||
`AllocateStorage(...)` (`:2473`, `:2621`, `:2734`), optional `UpdateMipmapSubData` (`:2515`,
|
||
`:2668`, `:2754`), then `MarkStorageDirty(..., true)` (`:2518`, `:2674`, `:2758`).
|
||
- `TexImage2DMultisample_State` `:2308`, `TexImage3DMultisample_State` `:2264`.
|
||
- `CompressedTexImage{1,2,3}D_State` `:4984`, `:4904`, `:4830` (`AllocateStorage` `:4895`/`:4975`,
|
||
`MarkStorageDirty` `:4901`/`:4981`).
|
||
- `TexStorage{1,2,3}D` `:6002`, `:6027`, `:6052` — allocate every level with
|
||
`MarkStorageDirty(..., false)` (`:5554`, `:5628`, `:5698`) then `TruncateMipmapLevels`
|
||
(`:5564`, `:5644`, `:5714`). `TexStorage{2,3}DMultisample` `:6110`, `:6121`, shared validator
|
||
`TexStorageMultisample_State` `:6098`.
|
||
- DSA `TextureStorage*` / `TextureSubImage*` / `CompressedTextureSubImage*` — `GL_Texture.h:41-60`.
|
||
- `TextureView` `:5838` (decl `GL_Texture.h:83`) → `TextureState::CreateTextureViewObject`
|
||
(`TextureState.h:55-58`).
|
||
- `TextureBuffer` / `TextureBufferRange` `GL_Texture.h:73-74`.
|
||
- The generated-mip storage grow: `GL_Texture.cpp:528-539` (`AllocateStorage`,
|
||
`MarkStorageDirty(false)`, `TruncateMipmapLevels`, then **`BumpContentVersion()` `:539`** —
|
||
the level SET grew without any CPU byte being dirty).
|
||
|
||
Sub-image (each → `MGPSubData` + regions):
|
||
- `TexSubImage3D_State` `:1725` → `MarkStorageDirtyRegion(target, level, {x,y,z}, {w,h,d})`
|
||
`:1820-1821`.
|
||
- `TexSubImage2D_State` `:1825` → `MarkStorageDirtyRegion(..., {x,y,0}, {w,h,1})` `:1940`.
|
||
- `TexSubImage1D_State` `:1945` → `:2007`.
|
||
- `CompressedTexSubImage{1,2,3}D_State` `:4818`, `:4658`, `:4489`.
|
||
- `ClearTexImage` `:1102` / `ClearTexSubImage` `:1118` → `MarkStorageDirty(..., true)` `:1006`,
|
||
`:1097`.
|
||
- Copy family: `CopyTexImage2D_State` `:4389` / `_Backend` `:4467`, `CopyTexSubImage2D_Backend`
|
||
`:4028`, `CopyTexSubImage3D_State` `:3986`, `CopyTexSubImage1D_State` `:4372`,
|
||
`CopyTexImage1D_State` `:4474`, DSA `CopyTextureSubImage{1,2,3}D` `GL_Texture.h:133-136`.
|
||
- `CopyImageSubData` `GL_Texture.h:143`, validator `ValidateCopyImageSubData_State`
|
||
`GL_Texture.cpp:4263`, backend hop `CopyImageSubData_Backend` `:4034` (verb
|
||
`CopyImageSubData`, class `kBlitOrCopy`, `MG_Pipe/FillPoints.def:113`) — the payload is
|
||
`MGPCopyRegion` (`MGPipeTypes.h:708-716`).
|
||
|
||
Parameters (each → `MGPTextureParams`, or the sampler-owned half):
|
||
- `TexParameter{f,i,fv,iv,Iiv,Iuiv}_State` `GL_Texture.cpp:2012`, `:2110`, `:2126`, `:2163`,
|
||
`:2194`, `:2224`; DSA `TextureParameter*` `GL_Texture.h:62-67`.
|
||
- They land on `TextureObjectBase::Set*`, each of which bumps `m_textureParamsVersion` and fires
|
||
`MGP_NOTE_AGGREGATE(TextureParams)`: `SetInternalFormat` `TextureObject.cpp:98-119` (also
|
||
`BumpShapeVersion` `:116` and a `BumpTextureBindGeneration` for the default-texture
|
||
Unknown↔defined transition `:108-113`), `SetBorderColor` `:136-145`, `SetBorderColorI`
|
||
`:151-160`, `SetBorderColorUI` `:166-175`, `SetSwizzleParam` `:202-225`, `SetSwizzleParamRGBA`
|
||
`:227-233`, `SetBaseLevel` `:239-252` (+`BumpShapeVersion` `:251`), `SetMaxLevel` `:254-264`
|
||
(+`:263`), `SetImmutableLevels` `:274-284`, `SetSamples` `:318-322`, `SetFixedSampleLocations`
|
||
`:328-332`, `SetDepthStencilTextureMode` **inline in the header** `TextureObject.h:188-193`.
|
||
- **TEXTURE_BORDER_COLOR / MIN_FILTER / WRAP_* are NOT texture-object state**: they live on the
|
||
texture's own `SamplerObject` (`TextureObject.cpp:125-179`), so a `glTexParameteri(GL_TEXTURE_
|
||
MIN_FILTER)` runs `SamplerObject::BumpVersion` and fires `MGP_NOTE_AGGREGATE(TextureParams)`
|
||
through *that* path.
|
||
|
||
Mipmap generation: `GenerateMipmap` `GL_Texture.cpp:6690-6709` (`EnsureGeneratedMipmapStorage
|
||
Allocated` `:6707` then `GenerateMipmap_Backend` `:1623`), `GenerateTextureMipmap` `:6711-6720`
|
||
(binds temporarily, `WithTemporarilyBoundNamedTexture`), guard `ValidateGenerateMipmapTexture`
|
||
`:6675-6688`, auto-gen hook `MaybeAutoGenerateMipmap` `:1628`. Verb `GenerateMipmap`, class
|
||
`kTextureOp` (`FillPoints.def:114`), payload `MGPMipPlan` (`MGPipeTypes.h:741-745`).
|
||
|
||
Binds: `BindTexture_State` `:4995`, `BindTextureUnit` `:6296`, `BindTextures` `:6395`,
|
||
`BindImageTexture` `:6612-6670`, `BindImageTextures` `:6418`. **`BindImageTexture` is the one
|
||
that is already a verb** (`FillPoints.def:122`, class `kTextureOp`): it writes
|
||
`GetImageTextureBinding(unit).Bind(...)` `:6665-6666`, calls `NoteTextureUnitTouched(unit)`
|
||
`:6667`, then `MGP_FILL(BindImageTexture)` `:6668` and dispatches `:6669`.
|
||
`glBindTexture`/`glBindSampler` are **not** verbs — they route through
|
||
`TextureState::NoteUnitTouched` (`TextureState.h:78-100`) only.
|
||
|
||
Readback: `GetTexImage_State` `:5251` / `_Backend` `:5079`, `GetCompressedTexImage_State` `:3801`,
|
||
`GetTextureSubImage` `GL_Texture.h:75-76`, `CopyTextureImageToClientOrPBO_State`
|
||
`GL_Texture.cpp:5375`.
|
||
|
||
### 2.3 Sampler — `MG_Impl/GLImpl/Sampler/GL_Sampler.cpp` (27 KB), decls `GL_Sampler.h:14-29`
|
||
`SamplerParameter{f,i,fv,iv,Iiv,Iuiv}` `:15-20`, `BindSampler` `:29`, `BindSamplers` `:28`,
|
||
`GenSamplers` `:25`, `CreateSamplers` `:27`, `DeleteSamplers` `:26`, the six `Get*` queries.
|
||
All setters funnel to `SamplerObject::Set*` → `BumpVersion()` (§1.3). `glDeleteSamplers` goes
|
||
through `GLContext::MarkSamplerObjectForDeletion` (`Core.cpp:1228-1240`), which **unbinds the
|
||
sampler from all 192 units first** (`:1232-1237`) so `TextureUnit::SetSamplerObject`
|
||
(`TextureUnit.cpp:28-40`) bumps the bind generation.
|
||
|
||
### 2.4 Program / pipeline — `MG_Impl/GLImpl/Program/GL_Program.cpp` (188 KB) +
|
||
`GL_ProgramPipeline.cpp` (12.7 KB) + `ProgramInterface.cpp` (49 KB)
|
||
- `UseProgram_State` `GL_Program.cpp:1451-1481` (public `UseProgram` `:2530`) → refuses while XFB
|
||
is active-and-unpaused `:1457-1464`, refuses an unlinked program `:1473-1479`, then
|
||
`pGLContext->UseProgram(program)` `:1480` → `ProgramState::UseProgram` (`Core.cpp:393-395`).
|
||
- `LinkProgram_State` `:1395` (public `:2522`), `DeleteProgram_State` `:606` (public `:2434`).
|
||
- **The one uniform funnel: `Uniform_State<ItemCount,T>` `GL_Program.cpp:1483-1561`.** Every
|
||
`glUniform*` and `glProgramUniform*` reaches it once per LOCATION (`:1495-1496`). Non-opaque
|
||
arm: `MarkUniformWrittenAtLocation` `:1499` → `BufferUniformWrite` if SPIR-V is pending
|
||
`:1516-1519` → `MapUBO()`/`GetUBOOffset` `:1520-1532` → **bytes-equal dedupe `:1542`** →
|
||
`Memcpy` `:1543` → `MarkUBOContentDirty()` `:1544`. Opaque arm `:1545-1560`:
|
||
`SetUniformSamplerOrImageUnitIndex(location, unit)` `:1559`, which bumps
|
||
`m_imageUnitVersion` only for image uniforms (`ProgramObject.h:847-849`).
|
||
Wrappers: `Uniformv_State` `:1563-1587` (reads `GetProgramForUniform()` `:1567`),
|
||
`ProgramUniformv_State` `:1590`. Public entry points from `:2534` (`Uniform1f`), `:2553`
|
||
(`Uniform1i`), `:2999` (`ProgramUniform1i`), etc.
|
||
- `glUniformBlockBinding` → `ProgramObject::SetUniformBlockBinding` `ProgramObject.h:1020-1027`
|
||
(bumps `m_backendStateVersion` **and** `m_blockBindingVersion`).
|
||
- `glShaderStorageBlockBinding` is a **verb** (`FillPoints.def:124`, class `kProgramOp` — the
|
||
class exists solely because it syncs Espryt's render state and textures, `:150-151`, `:323-334`).
|
||
- Pipelines: `GL_ProgramPipeline.cpp`; frontend state `Core.h:200-215`
|
||
(`GenProgramPipelineNames` `:204`, `CreateProgramPipelineObject` `:205`,
|
||
`BindProgramPipelineObject` `:208` → `Core.cpp:1391`, `MaterializeProgramPipelineObject` `:211`,
|
||
`MarkProgramPipelineForDeletion` `:212`, `GetBoundProgramPipeline` `:215` → `Core.cpp:1429`,
|
||
`m_boundProgramPipeline` `Core.h:660`).
|
||
|
||
### 2.5 Pixel store — `glPixelStore{i,f}`
|
||
`GLContext::SetPixelStoreParam` `Core.cpp:1134-1136` → `RenderState::SetPixelStoreParam`
|
||
`RenderState/RenderState.cpp:828-856` (a 16-arm macro switch, Pack ×8 + Unpack ×8).
|
||
`GetPixelStoreParameters(Bool isUnpack)` `RenderState.cpp:885-887`. Storage:
|
||
`m_pixelStorePackParameters` / `m_pixelStoreUnpackParameters` `RenderState.h:180-181` — **deliberately
|
||
outside `RenderStateParameters m_parameters` `:177`, so they are NOT part of the CSO byte range,
|
||
and the setter bumps NO version** (`SetPixelStoreParam` never calls `BumpVersions()` `:159-162`).
|
||
|
||
---
|
||
|
||
## 3. Which mutation sites already carry a `MGP_NOTE_MUTATION` / `MGP_NOTE_AGGREGATE`
|
||
|
||
Macros: `MG_Pipe/PipeMutation.h:155-158` (push) / `:160-161` (pull no-ops).
|
||
`MGPipeAggregate` enum `:56-74`. Dispatch: `MG_Impl/Pipe/PipeFill.cpp:529-552`
|
||
(`MGPipeNoteAggregate`) and `:509` (`MGPipeNoteFrontendMutation`).
|
||
|
||
**Complete census of the P4a-relevant sites** (grep over `MG_State/` + `MG_Impl/`):
|
||
|
||
`MGP_NOTE_AGGREGATE(FramebufferAttachment)` — 3 sites, all in
|
||
`FramebufferState/FramebufferObject.cpp`: `:209` (`SetReadBuffer`), `:221` (the five default-geometry
|
||
setters via macro), `:234` (`BumpAttachmentVersion`, i.e. every attach/detach/draw-buffer write).
|
||
|
||
`MGP_NOTE_AGGREGATE(TextureParams)` — 13 sites: `TextureObject.cpp:118, 144, 159, 174, 224, 232,
|
||
250, 262, 283, 321, 331`; `TextureObject.h:192` (`SetDepthStencilTextureMode`);
|
||
`SamplerState/SamplerObject.cpp:53` (`BumpVersion`, the choke point for **all** sampler params).
|
||
|
||
`MGP_NOTE_AGGREGATE(TextureContent)` — 5 sites: `TextureObject.cpp:311` (`BumpContentVersion`),
|
||
`:383` (`MarkStorageDirty`), `:395` (`MarkStorageDirtyRegion`);
|
||
`TextureState/TextureObject2DCube.cpp:53, 65` (the cube overrides).
|
||
|
||
`MGP_NOTE_MUTATION(...)` — **only four sites in the whole tree, all in
|
||
`TextureState/TextureState.h`**: `:85` `GetMaxTouchedTextureUnit`, `:98` and `:111`
|
||
`GetTextureBindGeneration`, `:133` `GetSamplingResolutionGeneration`. (P3a widened
|
||
`gen_pipe_dirty_surface.py`'s scan root to `MG_State/GLState` precisely to bring these inside the
|
||
gate — `MG_Pipe/DirtySurface.def:150-153`.)
|
||
|
||
**Nothing at all fires for**: any `RenderbufferObject` mutation, any `ProgramObject` version bump,
|
||
`glUseProgram`, `glBindProgramPipeline`, `glBindImageTexture`'s `ImageTextureBinding::Bind`
|
||
(only `NoteUnitTouched` runs), and `SetPixelStoreParam`.
|
||
|
||
---
|
||
|
||
## 4. The dirty bits P4a inherits — already computed, emitted by nobody
|
||
|
||
`MG_Impl/Pipe/Tracker.h`. Enum `MGPipeDirty` `:57-84`; names `:106-125`;
|
||
`MGPipeSubsystemForDirty` `:130-154` (**returns 0 for every bit past 10** `:149-153`);
|
||
`MGPipeMixShutter` `:170-173`; `MGPipeWidenedCounter` `:186-204`.
|
||
|
||
Shutters computed in `MGPipeTracker::Update` `:215-361`:
|
||
|
||
| bit | name | shutter, verbatim | line |
|
||
|---|---|---|---|
|
||
| 6 | `NewShader` | `Mix(program->GetLifetimeId(), program->GetLinkVersion())` | `:252`, stored `:261` |
|
||
| 7 | `NewShaderBindings` | `Mix(Mix(Mix(ImageUnitVersion, BackendStateVersion), BlockBindingVersion), UniformWriteSetVersion)` | `:253-257`, `:262` |
|
||
| 8 | `NewGlobalConstants` | `Mix(LifetimeId, UBOContentVersion)` | `:258`, `:263` |
|
||
| 11 | `NewFramebuffer` | `Mix(ctx.GetAnyFramebufferAttachmentGeneration(), m_framebufferBind.Observe(ctx.GetFramebufferBindingSlot(Draw).GetVersion()))` | `:303-306` |
|
||
| 12 | `NewSamplerViews` | `Mix(textureContent, ctx.GetTextureBindGeneration())` | `:307-308` |
|
||
| 13 | `NewSamplers` | `Mix(textureParams, ctx.GetSamplingResolutionGeneration())` | `:309-310` |
|
||
| 14 | `NewShaderImages` | `Mix(Mix(textureContent, textureParams), program->GetImageUnitVersion())` | `:311-312` |
|
||
|
||
`textureContent` / `textureParams` are read at `:266-267`. The program is read from
|
||
`ctx.GetCurrentProgram()` `:246` **and deliberately not `GetProgramForDraw()`** — "the tracker must
|
||
not force a compile just to answer did the shader move" `:242-245`. **P4a must respect that**: the
|
||
emitter, which needs the artifacts, is the thing that joins.
|
||
|
||
Also: `m_primed` / `FreshlyPrimed()` `:349-350`, `:417-420`; `Reset()` `:378-392` (called from
|
||
`Update` whenever `m_context` moves `:219-222`); the fire tallies `:353-359`.
|
||
`kMGPipeDirtyEmittedAtP2` `:96-99` and `kMGPipeDirtyEmittedAtP3a` `:102-104` — **P4a adds a
|
||
`kMGPipeDirtyEmittedAtP4a` constant rather than editing either** (the rule is stated at `:93-95`).
|
||
|
||
Subsystem bits: `MG_Pipe/MGPipe.h:72-95`; `kMGPipeSubsystemResources = 1<<7` `:84`,
|
||
`kMGPipeSubsystemVertexInput = 1<<8` `:85`, `kMGPipeSubsystemsMigratedAtP3a = 0x1ff` `:95`.
|
||
Bit 63 is the CSO content-addressing negative control `:90`. **Bits 9+ are free for P4a.**
|
||
|
||
---
|
||
|
||
## 5. The dirty-surface rows P4a's kinds already carry, and the two holes
|
||
|
||
`MG_Pipe/DirtySurface.def`. Legend `:101-143` (`kImmediate`, `kReverseChannel`, `kNoBackendRead`,
|
||
`kExplicitDestroy`, `kUnpublishedDestroy`, `kPulledEveryVerb`, `kPulledPartialShutter|BIT`).
|
||
Gate: `scripts/gen_pipe_dirty_surface.py --check` / `--self-test`, scan root now **both**
|
||
`MG_Impl/GLImpl` and `MG_State/GLState`, and a `MGP_NOTE_MUTATION` site counts as a publish
|
||
mechanism whose "mutator" is the enclosing function (`:16-28`, `:145-155`).
|
||
|
||
Rows P4a touches:
|
||
- `X(BumpTextureBindGeneration, NEW_SAMPLER_VIEWS)` `:248`
|
||
- `X(BumpSamplingResolutionGeneration, NEW_SAMPLERS)` `:256` (added at P3a by the widened root)
|
||
- `X(NoteUnitTouched, kPulledPartialShutter|NEW_SAMPLER_VIEWS)` `:271` — the bind generation moves
|
||
only on the `bindingChanged` arm; `GetMaxTouchedTextureUnit` has **no shutter at all** `:265-266`
|
||
- `X(SetPixelStoreParam, kPulledPartialShutter|NEW_PIXEL_PACK)` `:237` with the reasoning at
|
||
`:224-236`: the setter writes BOTH halves, only the pack half has a bit, so
|
||
`glPixelStorei(GL_UNPACK_ALIGNMENT, 8)` **moves nothing at all**
|
||
- `X(SetActiveTextureUnit, kImmediate)` `:164`
|
||
- Deaths `:299-307`: `MarkFramebufferObjectForDeletion`, `MarkRenderbufferObjectForDeletion`,
|
||
`MarkSamplerObjectForDeletion`, `MarkTextureObjectForDeletion` are all `kExplicitDestroy`;
|
||
**`MarkProgramForDeletion`, `MarkProgramPipelineForDeletion`, `MarkShaderForDeletion` are
|
||
`kUnpublishedDestroy` `:301-302, :305`** — a recorded hole ("programs, program pipelines and
|
||
shaders have no per-object handle on the wire at all in P2", `:113-124`). **P4a closes the
|
||
program row and must decide explicitly what to do with pipeline and shader** (a pipeline has no
|
||
wire object; a composite does — see §9.4).
|
||
- `MGP_DIRTY_SURFACE_UNDECIDED_LIST` `:332` is **empty**; every bit answer above is derived and
|
||
checked in both directions.
|
||
|
||
### 5.1 Hole A — `glUseProgram` has no row and no publisher
|
||
`GLContext::UseProgram` (`Core.cpp:393-395`) writes `ProgramState::m_currentProgram`
|
||
(`ProgramState.h:100`). The scanner sees `pGLContext->UseProgram(...)` from
|
||
`GL_Program.cpp:1467`/`:1480`, so a row is owed. Bit 6's shutter mixes lifetimeId ⊕ linkVersion
|
||
(`Tracker.h:252`), so a switch between two programs DOES move it — the row's honest answer is
|
||
`NEW_SHADER`, but **it is not there today**, meaning the `--check` gate will fail the moment the
|
||
scan is re-run against a `UseProgram` call site it now recognises. Verify before writing the brief.
|
||
|
||
### 5.2 Hole B — a renderbuffer respecify publishes nothing
|
||
`RenderbufferObject::{SetInternalFormat, AllocateStorage, SetSamples}`
|
||
(`RenderbufferObject.cpp:96-109`) bump no version and raise no notice, and
|
||
`AllocateRenderbufferStorage_State` (`GL_Framebuffer.cpp:844-858`) adds nothing. Bit 11's shutter
|
||
(`Tracker.h:303-306`) is `anyAttachmentGeneration ⊕ drawFboBindVersion` — neither moves when an
|
||
**already-attached** renderbuffer is re-storaged. So
|
||
`glBindRenderbuffer; glRenderbufferStorage(newsize)` on a renderbuffer that is already an FBO
|
||
attachment is invisible to the framebuffer bit. P4a needs either (a) a version + aggregate on
|
||
`RenderbufferObject` (matching `TextureObjectBase::BumpShapeVersion`, which does exactly this and
|
||
routes through `BumpSamplingResolutionGeneration`), or (b) `resource_respecify` emitted straight
|
||
from the storage entry points the way `BufferObject::NotifyRespecify` does
|
||
(`BufferObject.cpp:67-79`) — option (b) matches ARCHITECTURE's rule that renderbuffers share
|
||
`MGPResourceDesc` (`docs/Disaggregated/ARCHITECTURE.md` §4.1 table).
|
||
|
||
---
|
||
|
||
## 6. The pixel-store shadows — two of them, on opposite sides
|
||
|
||
### 6.1 Frontend: unpack is fully resolved client-side, pack is not
|
||
Every texture upload calls
|
||
`MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(pixels,
|
||
pGLContext->GetPixelStoreParameters(true), …)` before touching the level shadow — call sites
|
||
`GL_Texture.cpp:905, 1780-1782, 1902, 1991-1992, 2502-2503, 2655-2656, 2749-2750, 6195-6196`
|
||
(decl `MG_Util/Texture/PixelStoreProcessor.h:16`, def `PixelStoreProcessor.cpp:858`). The result
|
||
is a **tightly packed** buffer copied row by row into the level shadow
|
||
(`GL_Texture.cpp:1803-1817`). This is why `docs/Disaggregated/ARCHITECTURE.md:` §3.2's "explicitly
|
||
not migrated" list contains `set_pixel_unpack_state` ("前端已在 `glTexImage` 时解析压缩格式、强制默
|
||
认 unpack") and why `MGPPixelPackState` is PACK-only (`MGPipeTypes.h:531-542`).
|
||
**Consequence for P4a: `MGPSubData`'s bytes are already tight; `MGPSubRegion::SrcRowStride /
|
||
SrcSliceStride` (`MGPipeTypes.h:611-612`) describe the LEVEL SHADOW's pitch, not the
|
||
application's.**
|
||
Pack, by contrast, is read live at readback: `GL_Texture.cpp:5356`, `:5411`,
|
||
`DirectGLES.cpp:6593`, `:8043`, `:9530`, `:9909`.
|
||
|
||
### 6.2 Espryt: two process-wide driver shadows, asymmetrically invalidated — the pollution risk
|
||
- **UNPACK**: `ScopedDefaultUnpackState`, `MG_Backend/DirectGLES/Managers.cpp:4851-4918`. Shadow
|
||
statics `s_synced, s_alignment, s_rowLength, s_skipRows, s_skipPixels, s_imageHeight,
|
||
s_skipImages` at `:4911-4917` — **`static inline` class members, i.e. one set per PROCESS, not
|
||
per context**. `EnsureShadowSynced()` `:4874-4891` pins the driver to GL defaults exactly once
|
||
and sets `s_synced = true` `:4878`. **`s_synced` is written at `:4878` and read at `:4875` and
|
||
nowhere else in the file — there is no invalidation path at all.** The class comment `:4842-4850`
|
||
states the invariant it depends on: "The backend unpack state is set ONLY by MobileGL's own
|
||
save/restore helpers … so the shadow stays accurate". Any glPixelStorei on an `UNPACK_*` from
|
||
outside those helpers, or a driver context whose unpack state is not the pinned default,
|
||
desynchronises it permanently. The known outside writer is the ring path's
|
||
`glPixelStorei(GL_UNPACK_ROW_LENGTH, texelSize.x())` at `Managers.cpp:6400` (guarded by
|
||
`!ringStaged`, comment `:6297`).
|
||
- **PACK**: `PixelStoreImpl`, declared `MG_Backend/DirectGLES/Managers.h:1783-1806`
|
||
(`struct PackState{Alignment,RowLength,SkipRows,SkipPixels}` `:1793-1802`), defined
|
||
`Managers.cpp:8004-8054` — file-scope `g_packState` / `g_packStateKnown` `:8006-8007`,
|
||
`PinPackState` `:8009-8016`, `ApplyPackState` `:8019-8040`, `CurrentPackState` `:8042-8049`,
|
||
`InvalidatePackStateCache` `:8051-8053`. **This one IS invalidated**, at MakeCurrent
|
||
(`DirectGLES.cpp:10578`) and at context destroy (`:11112`). `ScopedPackState` RAII
|
||
`DirectGLES.cpp:6577-6588`, `PackStateFromContext` `:6592-6593`; users at `:7092, 8477, 8588,
|
||
8683, 9103, 9432`.
|
||
- Header note `Managers.h:1789-1791`: `PACK_IMAGE_HEIGHT / SKIP_IMAGES / SWAP_BYTES / LSB_FIRST`
|
||
have **no ES equivalent** and are honoured on the CPU from frontend context state — i.e. the
|
||
boundary must keep carrying the full `PixelStoreParameters` (28 B, `MGPipeTypes.h:538-542`),
|
||
not the four ES-expressible fields.
|
||
|
||
**P4a's stake**: once texture upload is emitted by handle, Espryt's upload path stops being the
|
||
thing that reads the frontend's unpack state (ARCHITECTURE.md:252 says it "改为从描述符取步长,
|
||
`UNPACK_ROW_LENGTH` 从 `SrcRowStride/bpp` 设"), so the P4a emitter has to carry the strides that
|
||
`ScopedDefaultUnpackState`'s pin currently makes unnecessary — and the asymmetric invalidation
|
||
above becomes a correctness dependency rather than a performance one.
|
||
|
||
---
|
||
|
||
## 7. `ReadSurface` resolution and framebuffer completeness
|
||
|
||
`MGPFramebufferState` (`MG_Pipe/MGPipeTypes.h:355-372`, 304 B) demands three things the client
|
||
must resolve **before** the record goes out:
|
||
- `MGPSurface ReadSurface` `:361` — "The RESOLVED read surface, not an index. This is what
|
||
structurally closes the read-buffer-shared-FBO defect class."
|
||
- `Uint8 Complete` `:364`.
|
||
- `Uint64 ContentHash` `:370` — both the server's render-pass memo key and the client's emission
|
||
suppressor.
|
||
`MGPSurface` itself `:343-353` (24 B): `{Res, InternalFormat, Kind(Texture|Renderbuffer|None),
|
||
Layered, Level, Layer, UploadTarget}` — **`InternalFormat` is inline so four cross-object masks
|
||
fall out with zero lookups** (`:341-342`).
|
||
|
||
**Where the client gets each piece:**
|
||
- Bound FBO: `ctx.GetFramebufferBindingSlot(FramebufferTarget::Draw|Read)`
|
||
(`Core.h:547`, `FramebufferState.cpp:49-57`); default-FBO identity is
|
||
`FramebufferImpl::pDefaultFramebufferInfo->defaultFBO` (`GL_Framebuffer.h:89, 95`).
|
||
- Attachments: `fbo->GetAllAttachmentObjects()` (`FramebufferObject.h:131`,
|
||
`FramebufferObject.cpp:159-161`) or per point `GetAttachment(type)` `:155-157`;
|
||
then `IsTexture()`/`IsRenderbuffer()`/`GetTexture()`/`GetRenderbuffer()`/`GetTextureLevel()`/
|
||
`GetTextureLayer()`/`IsLayered()`/`GetTextureUploadTarget()`
|
||
(`FramebufferObject.cpp:53-87`), format from `ITextureObject::GetFormat()` or
|
||
`RenderbufferObject::GetInternalFormat()`.
|
||
- **The read surface**: `fbo->GetReadBuffer()` (`FramebufferObject.h:137`) then
|
||
`fbo->GetAttachment(readBuffer)` — this is the exact two-step every consumer already does:
|
||
frontend `GL_Framebuffer.cpp:2373, 2921, 2992`, `Framebuffer/Validators.cpp:185`,
|
||
`Getter/GL_Getter.cpp:343, 2114`, `Texture/GL_Texture.cpp:4445`; Espryt
|
||
`Managers.cpp:7286, 7391, 7398, 7415`; Magma `VkRenderPassManager.cpp:618`,
|
||
`VulkanRenderer.cpp:1806, 1823, 8755, 9243, 10261-10264`.
|
||
**The defect class it closes** is visible in Espryt's
|
||
`BackendFramebufferObject::SyncReadBufferToBackend` (`Managers.cpp:7410-7430`), whose comment at
|
||
`:7424-7426` says it must `Bind(FramebufferTarget::Read)` first "When this is reached from
|
||
`SyncCurrentFBO`'s 'same FBO as draw' skip path" — the shared-FBO case where the read-buffer
|
||
sync was skipped. With the surface pre-resolved on the client, that whole hazard is
|
||
unrepresentable.
|
||
- **`Complete`**: `FramebufferObject::CheckCompleteness()` `FramebufferObject.cpp:163-193` — walks
|
||
the 41 attachment slots, skips `!IsValid()`, requires equal `GetSize()` and per-attachment
|
||
`IsComplete()`, and requires at least one valid attachment. The *GL-visible* status is more
|
||
than that: `CheckFramebufferStatus_State` `GL_Framebuffer.cpp:2265-2296` adds
|
||
`HasDefinedAttachment` (to pick `INCOMPLETE_ATTACHMENT` vs `MISSING_ATTACHMENT` `:2284-2286`),
|
||
`HasNonRenderableColorAttachment` → `UNSUPPORTED` `:2288-2290`, and
|
||
`ActiveBackendRejectsDistinctDepthStencil() && HasUnsupportedDistinctDepthStencilAttachments`
|
||
`:2291-2294` (**this one reads the backend**, so it cannot be part of a purely-client answer —
|
||
see the risk note in §14). `CheckNamedFramebufferStatus_State` `:2298-...` is the same body.
|
||
The TODO block at `:2279-2282` records that DRAW_BUFFER / READ_BUFFER / MULTISAMPLE /
|
||
LAYER_TARGETS incompleteness is not implemented.
|
||
- `Width/Height/Layers/Samples/FixedSampleLocations` for a no-attachment FBO come from
|
||
`GetDefaultWidth/Height/Layers/Samples/FixedSampleLocations` (`FramebufferObject.h:141-145`).
|
||
- `DrawBuffers[8]` from `GetDrawBuffers()` `:135` (an array of `FramebufferAttachmentType`, so the
|
||
client converts to the `Int8` attachment index / `-1` the payload wants, `MGPipeTypes.h:362`).
|
||
|
||
---
|
||
|
||
## 8. Texture sub-data accumulation — the union box and the region list already exist
|
||
|
||
`MG_State/GLState/TextureState/MipmapStorage.h` is where a P4a `MGPSubData` + `MGPSubRegion[]`
|
||
is built from; the frontend structure and the payload were designed against each other.
|
||
|
||
- `struct MipmapDirtyRegion` `:26-39` — `{IntVec3 lo, hi}` half-open, `Empty()` `:29`,
|
||
`CoversWholeLevel(levelSize)` `:30-33`, `TexelCount()` `:34-38`. Cleared (all zero) while clean
|
||
`:20-25`.
|
||
- Union box per level: `m_dirtyRegions` `:119`; `MarkDirtyRegion(level, offset, size)` `:58`
|
||
unions and sets the flag; `GetDirtyRegion(level)` `:60` reads it, meaningful only while
|
||
`IsDirty(level)` `:54`.
|
||
- **Behind it, the disjoint rect list**: `kMaxDirtyRects = 96` `:75`, `m_dirtyRects` `:127`
|
||
(per-level `Vector<MipmapDirtyRegion>`), `GetDirtyRects(level, out, maxRects)` `:82`,
|
||
`InsertDirtyRect` `:114`. The design notes `:62-81` are load-bearing for the wire format:
|
||
the union box "stays the source of truth (every write funnels through
|
||
`MarkDirty`/`MarkDirtyRegion` into BOTH representations), backends OPT IN to the list purely as
|
||
an upload-size refinement"; **`GetDirtyRects` returning 0 means "upload the union box instead"
|
||
and covers every reason at once** — tracking unavailable, a single rect, more rects than
|
||
`maxRects`, or a summed area close enough to the box's that one big upload wins `:76-81`.
|
||
96 slots because a 16-slot list forced far-apart merges to >90% of the box's area on
|
||
Minecraft's ~100 sprites/frame `:68-74`. An EMPTY list is the resting state and always means
|
||
"the union box is the whole story" `:120-126`.
|
||
- Other level state the descriptor and the blob need: `m_texelSizes` `:116`, `m_data` `:117`
|
||
(the level shadow), `m_isDirty` `:118`, `m_compressedData`/`m_compressedFormats` `:128-129`
|
||
(kept **beside**, not instead of, the uncompressed shadow — rationale `:84-92`),
|
||
`m_requestedCompressedFormats` `:130` (`:99-109`).
|
||
- The `ITextureObject` face of all of this: `MarkStorageDirty` `TextureObject.h:252`,
|
||
`MarkStorageDirtyRegion` `:257-262` (**base fallback degrades to whole-level**),
|
||
`GetStorageDirtyRegion` `:264-267` (**base fallback returns the whole level**),
|
||
`GetStorageDirtyRects` `:274-281` (**base fallback returns 0**), `IsStorageDirty` `:253`,
|
||
`MapMipmapData` `:251`, `GetMipmapTexelSize` `:243`, `GetMipmapByteSize` `:244`,
|
||
`GetMipmapLevelCount` `:242`. Concrete impls
|
||
`TextureObject.cpp:379-410` (`TextureObjectWithOneMipmap`), cube overrides
|
||
`TextureObject2DCube.cpp:53, 65`.
|
||
- The payload side: `MGPSubRegion` `MGPipeTypes.h:607-614` (`X,Y,Z,W,H,D,SrcOffset,SrcRowStride,
|
||
SrcSliceStride`; **strides CARRIED, not inferred from a pointer comparison** `:603-606`),
|
||
`MGPSubData` `:636-648` (`Res, Target, Level, SourceIsVerbatimLevelShadow, UnionBox,
|
||
RegionCount, Blob`). "Carries the union box AND the region list so the SERVER picks the upload
|
||
shape — the decision belongs on the side that pays the GPU cost. Mali prices texture upload by
|
||
JOB COUNT: ~100 sprite rects against one union box measured +6 ms/frame" `:616-618`.
|
||
The blob rule `:624-635`: a non-zero `Blob.Size` that is not exactly the record's own byte size
|
||
is `Fatal{ProtocolCorruption}`; **zero means "not declared"**, which is what a monolith emission
|
||
is. `MGPSubDataComplete` `:686-691` is the forward terminator for a server-initiated pull.
|
||
- Espryt's existing consumer, whose shape `MGPSubRegion` copies: `UnpackStagingBlock`
|
||
`Managers.cpp:4934-4942` (`src, rowBytes, rows, slices, srcRowStride, srcSliceStride, offset`),
|
||
with the ring-staging contract at `:4920-4933` and `:6297`.
|
||
|
||
---
|
||
|
||
## 9. Program artifacts, reflection, per-stage SPIR-V, the link version
|
||
|
||
`MG_State/GLState/ProgramState/ProgramArtifacts.h` (35 KB, 576 lines) is the P0.5 extraction that
|
||
makes `MGPProgramDesc`'s reflection blob possible — it includes only `<Includes.h>` (`:10`), never
|
||
`ShaderObject.h` or `SpvcSession.h`.
|
||
|
||
- `kInvalidUniformOffset = ~0u` `:26`.
|
||
- `struct TypeFacts` `:31-59` — 20 flattened predicates (13 `Bool` + 7 4-byte scalars),
|
||
**POD of exactly 44 bytes**, asserted `:551-552`.
|
||
- `struct ResourceReflection` `:63-86` — one flattened `glslang::TObjectReflection`
|
||
(`name, glDefineType, offset, size, index, counterIndex, arrayStride, topLevelArraySize,
|
||
topLevelArrayStride, binding, location, stages, arraySize, TypeFacts type`); aliased as
|
||
`UniformReflection`/`BlockReflection`/`PipeInputReflection`/`PipeOutputReflection` `:88-91`.
|
||
- `struct XfbVarying` `:95-120` — `{name, type, size, bufferIndex, offsetBytes, byteSize,
|
||
packedOffsetBytes, blockInstanceName, blockName, blockMemberIndex, blockMemberElement}`.
|
||
- `struct LinkArtifacts` `:160-343` — the whole link output in one movable block; `program`
|
||
(`SharedPtr<glslang::TProgram>`) is **live only between LinkProgram() and the end of
|
||
DoReflection** and is null for an L1-memo-served link `:161-165`; then the four owned
|
||
reflection vectors `:168-171`, `lastStageIsFragment` `:177`, `computeLocalSize` `:178`, …,
|
||
ending at `xfbVaryingNameMaxLength` / `xfbNeedsScatteredCapture` / `xfbPackedStride` `:340-342`.
|
||
Indexed by **TProgram index**, deliberately `:155-158`.
|
||
- `struct SpirvArtifacts` `:359-399` — `generatedSpirv` (`Vector<Vector<unsigned>>`, **one module
|
||
per entry of the linked-shader snapshot, in that order**) `:360`, `enableSpirvValidation` `:361`,
|
||
`uniformOffsets` `:364`, `globalUboScratch` `:365`, `reservedNumSamplesOffset` `:370`,
|
||
`spirvStatus` `:377`, `nativeFloat64` `:386`, `pointSizeDemoted` `:398`.
|
||
- **The archive**: `VisitFields(Self&, Visitor)` free templates `:401-547`, one per type, the same
|
||
table serving serializer (const) and deserializer (non-const) `:403-406`. This is the
|
||
ready-made writer for `MGPProgramDesc::Reflection`.
|
||
- **Trip wires** `:549-575`: `sizeof(TypeFacts)==44`; per-STL pins under `__GLIBCXX__`
|
||
`MGL_RESOURCEREFLECTION_SIZE 128`, `MGL_XFBVARYING_SIZE 128`, `MGL_LINKARTIFACTS_SIZE 1056`,
|
||
`MGL_SPIRVARTIFACTS_SIZE 88` `:558-562`; the libc++ (NDK) branch is **inert until the integrator
|
||
pins it** `:563-565` — that pin is a P4a chore if the archive crosses on device.
|
||
|
||
`MGPProgramDesc` (`MG_Pipe/MGPipeTypes.h:323-335`, 192 B): `{Cso, StageMask (== the linked stage
|
||
set), GlobalUboSize, ReservedNumSamplesOffset, SpirvStatus, NativeFloat64, PointSizeDemoted,
|
||
EnableSpirvValidation, MGPBlobRef Spirv[6], MGPBlobRef Reflection}`.
|
||
|
||
### 9.1 The "as last linked" rule the emitter must obey
|
||
`ProgramObject::GetLinkedShaderSnapshot()` `ProgramObject.h:106` returns
|
||
`Vector<LinkedShaderRef>` (`:99-103`, `{shader, source, node}`), rebuilt in Link()'s prologue.
|
||
`HasLinkedShaderStage(stage)` `:115-120`. **The header states it twice** (`:92-98`, `:121-133`):
|
||
`glAttachShader`/`glCompileShader` take effect only at the next link and neither moves
|
||
`m_linkVersion`, so **anything keyed on the link generation must consume this snapshot, never the
|
||
live attach list** (`GetAttachedShaders()` `:89-90`), and the SPIR-V module list is sized and
|
||
indexed by the snapshot, not by the attach list. `StageMask` therefore comes from the snapshot.
|
||
|
||
### 9.2 The join gate
|
||
`Artifacts()`/`Spirv()` join through `EnsureLinkJoined()` (inline `ProgramObject.h:1275`, blocking
|
||
half `ProgramObject.cpp:50-83`) and `EnsureSpirvJoined()` (`ProgramObject.cpp:89-122`).
|
||
`JoinPendingLink` moves the node out FIRST to avoid re-entry `:55-60`, moves the artifacts
|
||
`:66`, and calls `BumpLinkObservableVersions()` `:72` (the *second* bump; the first was at
|
||
enqueue `:67-71`). `GLContext::GetProgramForDraw` `Core.cpp:609-628` is join site J1 and calls
|
||
`currentProgram->JoinLinkAndSpirv()` `:627` — **both phases, and the comment `:622-626` explains
|
||
why phase A alone reopens the hazard**. `GetProgramForDispatch` `Core.cpp:763-...`,
|
||
`GetProgramForUniform` `:786-...`.
|
||
|
||
**P4a's emitter must be the joiner.** ARCHITECTURE.md:198 says `create_shader_state` is emitted
|
||
"从编译池的终止 continuation" — i.e. from the compile pool's terminal continuation, not from the
|
||
draw — which is the monolith-unreachable async win. That is a design decision for the brief, not
|
||
a fact about this tree: today nothing emits it at all.
|
||
|
||
### 9.3 What the server still specialises (do NOT try to push it)
|
||
`docs/Disaggregated/ARCHITECTURE.md:263` lists the eight extra inputs a backend program depends on
|
||
(draw-FBO snorm/unorm clamp mask, fragColor broadcast count, storage-block binding signature,
|
||
atomic-counter set, live image formats, patch params; Magma adds default-FB height for FragCoord
|
||
Y-flip and the XFB layout): `create_shader_state` publishes the **artifacts**, the server
|
||
specialises at verb time.
|
||
|
||
### 9.4 Program pipelines and the composite band
|
||
`GLContext::GetProgramForDraw` `Core.cpp:630-...`: when no program is current it takes the bound
|
||
pipeline `:631`, **joins every graphics stage program** `:642-645`, computes
|
||
`pipeline->ComputeDrawProgramSignature()` `:647` (`ProgramPipelineObject.h:94-98`, an
|
||
`Array<Uint64, kGraphicsStageCount*2>` of per-stage lifetimeId+linkVersion), checks
|
||
`GetCachedDrawProgram(signature)` `:648-651` (`ProgramPipelineObject.h:149`) and otherwise
|
||
**flattens the pipeline into a fresh `MakeShared<ProgramObject>(0u)`** `:661` — deliberately
|
||
unnamed (`:658-660`), graphics stages only (`:663-668`: a composite carrying a compute module
|
||
SIGSEGVs inside `vkCreateGraphicsPipelines` on Adreno 830).
|
||
`RefreshCompositeUniforms` `:649` / `SetMirroredUniformVersions` `:606`
|
||
(`ProgramPipelineObject.h:123-148`).
|
||
**A composite is an ordinary `ProgramObject` with its own lifetime id**, so it fits the ShaderCso
|
||
slot space — and `MGPipeHandles.h:83-97` already reserves the top 1/16 of that space for exactly
|
||
these (`kMGPipeShaderCsoSlotLimit = 1<<20` `:88`, `kMGPipeShaderCsoCompositeSlotBase` `:89-90`,
|
||
`MGPipeIsCompositeShaderSlot` `:92-94`), and `SlotAllocator.cpp:19-23` refuses the band from the
|
||
ordinary allocator. `ProgramPipelineObject` itself has **no lifetime id and no wire object**
|
||
(`:19-170`; the only identity is `m_everBound` `:170` / `GetEverBound` `:51`).
|
||
|
||
`ShaderObject` (`ProgramState/ShaderObject.h:24`, dtor `:52`) has **no lifetime id** — consistent
|
||
with `kUnpublishedDestroy` and with the design (shaders never cross).
|
||
|
||
---
|
||
|
||
## 10. `PipeInputs` — what already exists for these kinds, and what fills it
|
||
|
||
`MG_Backend/MGPipe/PipeInputs.h` (736 lines). Storage list
|
||
`MGP_INPUT_STORAGE_LIST` `:73-129`; the block `struct PipeInputs` `:158-687`; three storage
|
||
classes V/O/F documented `:144-153`; `sizeof(PipeInputs) < 20 KB` asserted `:714`; the
|
||
field-count identity `:709-712`.
|
||
|
||
**P4a-relevant accessors and their storage:**
|
||
|
||
| accessor | decl | storage member | class |
|
||
|---|---|---|---|
|
||
| `GetFramebufferBindingSlot(FramebufferTarget)` | `:508-517` | `m_framebufferBindingSlot[kFramebufferTargetCount]` `:681` | O (raw ptr into the live context) |
|
||
| `GetImageTextureBinding(Int unit)` ×2 | `:518-533` | `m_imageTextureBindingBase` `:682` | O |
|
||
| `GetTextureUnitObject(Int unit)` | `:549-...` | `m_textureUnitBase` `:686` | O |
|
||
| `GetProgramForDraw()` | `:539-543` | `m_programForDraw` `:684` | O (`SharedPtr`) |
|
||
| `GetProgramForDispatch()` | `:534-538` | `m_programForDispatch` `:683` | O |
|
||
| `GetTransformFeedbackProgram()` | `:544-548` | `m_transformFeedbackProgram` `:685` | O |
|
||
| `GetActiveTextureUnit()` | `:199-203` | `m_activeTextureUnit` `:629` | V |
|
||
| `GetMaxTouchedTextureUnit()` | `:313-317` | `m_maxTouchedTextureUnit` `:647` | V |
|
||
| `GetTextureBindGeneration()` | `:398-401` | `m_textureBindGeneration` `:662` | V |
|
||
| `GetSamplingResolutionGeneration()` | — | `m_samplingResolutionGeneration` `:661` | V |
|
||
| `GetTextureContextId()` | `:403-...` | `m_textureContextId` `:663` | V |
|
||
| `GetPixelStoreParameters` | — | `m_pixelStore` (`[0]`=pack, `[1]`=unpack) `:103` | V |
|
||
| `GetClampReadColor()` | `:244-248` | `m_clampReadColor` `:635` | V |
|
||
| **forwarded (F, sticky)** | `:558-577` | none | `GetProgramObject(Uint)` `:570`, `GetTextureObject(Uint)` `:571`, `ValidateProgramName` `:574`, `GetBufferBindingPointCount` `:569`, `HasOpenTransformFeedbackSpan` `:572`, `InvalidateCompileEnv` `:573`, `RecordError` `:577` |
|
||
|
||
`kMGPipeForwardedFieldCount = 7` `:135`, asserted equal to the sticky count `:136-137`. The F
|
||
accessors carry **no** `MGP_INPUT_CHECK` / `MGP_INPUT_VERIFY_READ` and the reason is stated
|
||
`:563-568`.
|
||
The two friend doors: `MGPipeFillAccess` `:611` (client filler) and `MGPipeApplyAccess` `:618`
|
||
(the applier scattering render-state chunks straight into `m_renderState`).
|
||
`gPipeInputs` `:706` — **`inline PipeInputs& gPipeInputs = *new PipeInputs();`**, leak-at-exit,
|
||
with the full ID-18 rationale at `:692-705` (its O-class members are `SharedPtr`s to FRONTEND
|
||
objects; destroying the block at exit runs `~VertexArrayObject` → `~BufferObject` →
|
||
`MGPipeApplyResourceDestroy` → the backend's twin tables). **Any P4a `SharedPtr<ProgramObject>` /
|
||
`SharedPtr<ITextureObject>` added here inherits that rule verbatim.**
|
||
|
||
### 10.1 `FillPoints.def` rows that fill them
|
||
`MG_Pipe/FillPoints.def`. Verb list `:79-148` (69 verbs, **must be exactly the member set of
|
||
`MG_Backend::GLFunctionsTable` in declaration order** `:17-22`, enforced by `gen_pipe.py`);
|
||
class list `:152-153` (nine classes); the may-read table `MGP_FILL_FIELD_LIST` `:158-338`.
|
||
|
||
P4a's fields, by class:
|
||
- `GetFramebufferBindingSlot`: `kDraw` `:200`, `kDispatch` `:219`, `kClear` `:232`,
|
||
`kBlitOrCopy` `:247`, `kTextureOp` `:291`, `kReadback` `:296`, `kProgramOp` `:328`.
|
||
- `GetTextureUnitObject`: `kDraw` `:165`, `kDispatch` `:213`, `kClear` `:240`, `kBlitOrCopy` `:256`,
|
||
`kTextureOp` `:278`, `kReadback` `:298`, `kProgramOp` `:329`.
|
||
- `GetImageTextureBinding`: `:170, 218, 245, 261, 279, 308, 334`.
|
||
- `GetTextureContextId` / `GetTextureBindGeneration` / `GetSamplingResolutionGeneration` /
|
||
`GetMaxTouchedTextureUnit`: the same seven classes (`:166-169, 214-217, 241-244, 257-260,
|
||
280-283, 304-307, 330-333`).
|
||
- `GetActiveTextureUnit`: `kBlitOrCopy` `:255`, `kTextureOp` `:277`, `kReadback` `:297`.
|
||
- `GetProgramForDraw`: `kDraw` `:161`. `GetProgramForDispatch`: `kDispatch` `:209`.
|
||
- `GetPixelStoreParameters`: `kReadback` ONLY `:294`.
|
||
- `GetClampReadColor`: `kReadback` `:299`.
|
||
- The eight statically over-approximated rows are **all KEPT** with reasons `:30-70`; three of
|
||
them are P4a-adjacent (`kTextureOp/kDispatch + IsCapabilityEnabled` because Magma's
|
||
`GenerateMipmap` and `PrepareStorageImageTextures` materialise a queued clear that reads
|
||
`GL_FRAMEBUFFER_SRGB` `:43-49`; `kTextureOp + GetBufferBindingPoint` / `GetFramebufferBindingSlot`
|
||
for the depth-mipmap shader path `:284-292`). **`P3a STATUS` `:65-70` says the omission sweep
|
||
rides the full gl44to46 caselist run and every row stays until it lands** — so P4a must not
|
||
retire one on desktop evidence either.
|
||
|
||
### 10.2 `Coverage.def` rows P4a's calls will claim
|
||
`MG_Pipe/Coverage.def`. Accessor→call map `:29-132`; sticky list `:144-151`; delta list `:156-158`;
|
||
**emitted list `:198-232`** (what a call now SUPPLIES, so the residual fill loop may skip it).
|
||
Existing accessor rows for P4a's kinds: `GetFramebufferBindingSlot → SetFramebufferState` `:84`,
|
||
`GetImageTextureBinding → SetShaderImages` `:85`, `GetActiveTextureUnit → SetSamplerViews` `:30`,
|
||
`GetMaxTouchedTextureUnit → SetSamplerViews` `:88`, `GetSamplingResolutionGeneration →
|
||
SetSamplerViews` `:109`, `GetTextureBindGeneration → SetSamplerViews` `:112`,
|
||
`GetTextureContextId → SetSamplerViews` `:113`, `GetTextureObject → SetSamplerViews` `:114`,
|
||
`GetTextureUnitObject → SetSamplerViews` `:115`, `GetProgramForDraw → SetDrawProgram` `:100`,
|
||
`GetProgramForDispatch → SetDispatchProgram` `:99`, `GetProgramObject → CreateShaderState` `:101`,
|
||
`GetPixelStoreParameters → SetPixelPackState` `:94`.
|
||
**None of them is in the EMITTED list yet.** The two standing warnings for P4a:
|
||
- `GetPixelStoreParameters` is **deliberately absent from EMITTED** `:173-181` — the field is
|
||
`m_pixelStore[2]` and `set_pixel_pack_state` carries the pack half only, so a row there would be
|
||
a half-truth that neither the poison nor the verify comparator could see. **P4a must not add it.**
|
||
- P3a's own row (`GetBoundVertexArray → BindVertexElements` `:202`) is "shape-only" and the note
|
||
`:191-197` says the decision of whether an emitted call supplies the WHOLE field lives in
|
||
`PipeFill.cpp`'s `EmittedCallSuppliesTheWholeField` (`:1116`), **deliberately rather than
|
||
silently by the row's presence**. Same discipline applies to every P4a row.
|
||
|
||
---
|
||
|
||
## 11. The payloads P4a fills (all already declared, `MG_Pipe/MGPipeTypes.h`)
|
||
|
||
`MGPResourceDesc` `:150-176` (88 B) — one create/respecify shape for buffers, **every texture
|
||
target** and renderbuffers: `Target` `:152`, `StorageKind` (== `TextureStorageType`) `:153`,
|
||
`BindMask` `:157` (the `ELEMENT_ARRAY` bit is the index-mirror switch `:154-156`),
|
||
`InternalFormat` "already resolved to an uncompressed fallback by the client" `:158`,
|
||
`Width/Height/Depth` `:159`, `ArrayLayers/Levels/Samples` `:160`,
|
||
`FixedSampleLocations/Immutable` `:161`, `Usage`/`StorageFlags` `:162-163`,
|
||
`HasDefinedContent` `:164`, `ImageBindableHint` `:165`, `GlNameForDiag` (**diagnostics only** —
|
||
a GL name is never an identity `:167-170`), `ViewOf` `:172`, `BufferForTexBuffer` `:173`,
|
||
`BufOffset/BufSize` with `kMGPipeWholeBuffer = ~0` `:174, 177`.
|
||
`MGPipeResourceRespecifyNeedsAck(desc)` `:680-682` — `desc.Immutable != 0` (rationale `:668-679`).
|
||
|
||
Other P4a payloads: `MGPSamplerDesc` `:283-287` (32 B); `MGPSamplerView` `:293-304` (36 B, view
|
||
restrictions only — rationale `:289-292`: an attachment-only / image-only / CopyImageSubData-only
|
||
texture has no sampler view to hang params on); `MGPTextureParams` `:307-318` (32 B, per texture
|
||
OBJECT, with `ForceResync` `:314` mirroring Espryt's `m_forceTextureParamsResync`);
|
||
`MGPSurface` `:343-353`; `MGPFramebufferState` `:355-372`; `MGPSamplerViews` `:440-444`,
|
||
`MGPSamplerStates` `:447-451`, `MGPImageView` `:453-462`, `MGPShaderImages` `:464-468` (all
|
||
`{Start, Count, ContentHash}` var-tail headers); `MGPProgramDesc` `:323-335`;
|
||
`MGPGlobalConstants` `:507-513`; `MGPSubData`/`MGPSubRegion` `:607-648`; `MGPCopyRegion`
|
||
`:708-716`; `MGPMipPlan` `:741-745`; `MGPReadbackInfo` `:748-756`; `MGPClear` `:729-739`;
|
||
`MGPBlit` `:718-725`.
|
||
|
||
Catalogue rows (`MG_Pipe/PipeCalls.def`, `MGP_CALL_LIST_DOCUMENTED_COUNT 71` `:75`):
|
||
`CreateSamplerState/DeleteSamplerState` `:104-105`, `CreateSamplerView/DeleteSamplerView`
|
||
`:106-107`, `CreateShaderState/BindShaderState/DeleteShaderState` `:108-110`,
|
||
`SetFramebufferState` `:113`, `SetSamplerViews` `:117`, `BindSamplerStates` `:118`,
|
||
`SetShaderImages` `:119`, `SetGlobalConstants` `:122`, `SetDrawProgram`/`SetDispatchProgram`
|
||
`:126-127`, `SetTextureParams` `:133`, `ResourceSubData` `:134`, `ResourceSubDataComplete` `:136`,
|
||
`ResourceCopyRegion` `:139`, `GenerateMipmap` `:140`, `GetTextureImage` `:141`, `Blit` `:143`,
|
||
`Clear` `:144`, `ReadPixels` `:145`. **Record numbering never churns — the wire opcode is the
|
||
1-based position, so a new call is APPENDED to its group** `:26-29`.
|
||
|
||
---
|
||
|
||
## 12. The client emission machinery P4a mirrors
|
||
|
||
- **Slot allocator** `MG_Impl/Pipe/SlotAllocator.{h,cpp}` — `Allocate` `cpp:38-81`,
|
||
`AllocateFor` `:83-95`, `FindByLifetimeId` `:97-105`, `Acquire` `:107-111`, `Free` `:113-130`
|
||
(**stale-gen free is a no-op** `:117-119`), `IsLive` `:132-137`, `HighWater` `:151-153`,
|
||
`Reset` `:161-168`. **Gen moves only on reuse** `cpp:66-75`, `h:24-28`. `MGPipeSlots()`
|
||
`cpp:170-181` is **never destroyed** with the ID-18 rationale spelled out at `:171-179`.
|
||
- **CSO cache** `MG_Impl/Pipe/CsoCache.h` — the exact template for a P4a sampler-CSO or
|
||
program-CSO cache: version-first skip `:14-25`, hash + **memcmp confirm** `:84-98`
|
||
(a bare 64-bit equality would alias two states — `:20-24`), `Mint` `:138-172`, LRU `Evict`
|
||
`:174-183` (emits `delete_*` and frees the slot), capacity 64 `:53`, counters `:57-69`,
|
||
`s_hashForTest` seam `:121-128`, `MGPipeCsoCacheInstance()` never destroyed `:197-203`.
|
||
- **Set-hash suppressor** `MG_Impl/Pipe/SetHashSuppressor.h` — `MGPipeSuppressorSlot` `:44-53`
|
||
already reserves `SetSamplerViews` (P3b), `BindSamplerStates` (P3b), `SetShaderImages` (P4b),
|
||
`SetShaderBuffers` (P4b), `SetStreamOutputTargets` (P4b). **There is no
|
||
`SetFramebufferState` slot** — P4a adds one (or latches `MGPFramebufferState::ContentHash`
|
||
itself). `ShouldEmit` `:61-67` (**hash 0 is reserved for "never emitted"; a computed 0 remaps to
|
||
1** `:31-33`), `Invalidate`/`InvalidateAll` `:71-75`. The requirement a wired slot puts on its
|
||
hash is stated `:23-29`: it must cover **every** input the record carries, not just the set.
|
||
- **Resource tracker** `MG_Impl/Pipe/ResourceTracker.h` — `MGPipeBindMaskForBufferTarget`
|
||
`:96-134` + the exhaustiveness `static_assert` `:136-148`, `MGPipeBuildResourceDesc` `:171-...`,
|
||
`MGPipeBuildSubDataRecord` `:225-...`, **`MGPipeForEachSubDataRecordRange` `:257-...` (the
|
||
splitter for ranges past a single record's bound)**, `class MGPipeResourceTracker` `:282-...`
|
||
(`Acquire` `:292`, `Find` `:303`, `Resolve` `:313`, `Retire` `:325`, `NotePublished` `:342`,
|
||
`BindMask` `:355`, `NoteBoundAs` `:369`, `RefreshBindMask` `:407`, counters `:448-463`,
|
||
`ResetForTest` `:493`), `MGPipeResourceTrackerInstance()` never destroyed `:554-559`, and the
|
||
reverse-channel callbacks `MGPipeClientOnBufferWriteback` `:570-...`,
|
||
`MGPipeClientOnGpuWritten` `:596-...`, `MGPipeInstallClientResourceCallbacks` `:623-...`.
|
||
- **Vertex-input emitter** `MG_Impl/Pipe/VertexInputEmit.h` — the CSO-with-blob template:
|
||
wire builders `:78-104`, `:106-121`, content hash `:123-129`,
|
||
`class MGPipeVertexInputEmitter` `:136-...` (`EmitVertexElements` `:157`, `EmitVertexBuffers`
|
||
`:200`, `EmitIndexBuffer` `:256`, `RecordIsPublished` `:307`, `NoteRecordDestroyed` `:319`,
|
||
`Reset` `:340`, `EmitCreate` `:373`, the blob staging array `:413`), instance never destroyed
|
||
`:433-441`.
|
||
- **The validate point** `MG_Impl/Pipe/PipeFill.cpp:1504-1620+`: poison serial `:1518-1524`,
|
||
verb + identity `:1525-1527`, the no-context early exit that still clears the pending base
|
||
instance `:1528-1536`, the dirty walk `:1544-1545`, the `wants()` lambda that goes through
|
||
**`MGPipeSubsystemForDirty` and never names a subsystem constant** `:1548-1557`, the
|
||
`FreshlyPrimed()` block `:1570-1585` (CSO cache reset, `MGPipeApplierReset`, suppressor
|
||
invalidate-all, vertex-input emitter reset — **and the note at `:1574-1582` that the resource
|
||
tracker is deliberately NOT reset**), then the per-subsystem emissions `:1587-1615`.
|
||
P4a's emitters go in this ladder.
|
||
Also here: `SubsystemForEmitter` `:957-...` + its pairing `static_assert`s `:984-1032`,
|
||
`kMGPipeWiredSubsystems` `:1071`, `EmittedCallSuppliesTheWholeField` `:1116`,
|
||
`MGPipeNoteAggregate` `:529-552`, and the seven F-class forwarder bodies `:875-906`.
|
||
- **Applier** `MG_Pipe/PipeApply.h` — `MGPipeResourceOps` `:74-86` (+`Set/Get` `:90-91`),
|
||
slot bounds `kMGPipeMaxResourceSlots = 1<<20` / `kMGPipeMaxVertexElementsSlots = 1<<16`
|
||
`:112-113` with the "a slot at or above these is `Fatal{ProtocolCorruption}`, never a resize"
|
||
rule `:97-111`, `MGPipeResourceRecord` `:117-132`, `MGPipeVertexElementsRecord` `:141-152`,
|
||
`MGPipeApplierState` `:154-...` — **and the explicit split at `:194-218`: object records
|
||
(`Resources` `:217`, `VertexElementsCsos` `:218`) survive a make-current and are cleared only by
|
||
the object's own death signal or `MGPipeApplierReleaseObjectRecords` `:300`; working state
|
||
(`BoundVertexElements` `:236`, `VertexBuffers` `:240`, …) is cleared by `MGPipeApplierReset`
|
||
`:291`**. Refusal counters `:228-229` exist because "a no-op nobody can see is a dropped call
|
||
nobody can see" `:220-227`.
|
||
|
||
---
|
||
|
||
## 13. Unit tests under `MG_Test` that cover these frontend objects
|
||
|
||
All registered with `LABELS unit` via `gtest_discover_tests` (e.g.
|
||
`MG_Test/Framebuffer/CMakeLists.txt:20`, `MG_Test/Texture/CMakeLists.txt:20, 38`).
|
||
|
||
| file | size | suites | what it pins for P4a |
|
||
|---|---|---|---|
|
||
| `MG_Test/Framebuffer/FramebufferTest.cpp` | 1669 L, **61 `TEST`** | `FramebufferTest`, `PackedReadbackEncodeTest` | attach/detach, draw/read buffer validation, completeness |
|
||
| `MG_Test/Texture/TextureTest.cpp` | 6714 L, **192 `TEST`** | `TextureTest`, `SharedExponentRGB9E5Test` | the whole texture surface; **ID-8 records that P3a's wave1/wave2 both appended at the same point here and collided — resolve by union, keep every name** |
|
||
| `MG_Test/Texture/TextureViewTest.cpp` | 551 L | `TextureViewTest` (`:147, 159, 169, 178, 198, 224, 236, 259`, …) | view state, `TEXTURE_VIEW_*`, composition onto the root, "a framebuffer attach is bounded by the view's own level count" `:198` — directly `MGPResourceDesc::ViewOf` / `MGPSamplerView` |
|
||
| `MG_Test/Texture/VkClearManagerTest.cpp` | | | the queued-clear materialisation `FillPoints.def:284-289` names |
|
||
| `MG_Test/State/ObjectLifetimeIdTest.cpp` | 158 L, 6 `TEST(ObjectLifetimeIdTest,…)` | | **the id invariant P4a's handles rest on**: constructs/destroys on the heap 64× (`:57-58`), publishes through a volatile sink so the new/delete pair cannot be elided (`:42-46`), asserts id ≠ 0 (`:72-73`) and strictly increasing (`:74-76`), and **skips rather than passes quietly if the allocator never repeats an address** (`:21-24`). Covers `BufferObject`, `RenderbufferObject`, `VertexArrayObject` (`:34-36`) — **not** Texture/Framebuffer/Sampler/Program, all of which now have ids. Cheapest P4a test win. |
|
||
| `MG_Test/State/RenderStateTest.cpp` | | | render state incl. the pixel-store setters |
|
||
| `MG_Test/Program/ProgramTest.cpp` | 4990 L | | link, reflection, uniforms |
|
||
| `MG_Test/Program/ProgramArtifactsTest.cpp` | 133 L, 5 `TEST(ProgramArtifacts,…)` | `AliasesAreTheSameTypes` `:28`, `TypeFactsIsPodOf44Bytes` `:44`, `VisitFieldsCoversEveryMember` `:80`, `VisitFieldsPassesConstnessThrough` `:90`, `SizesArePinnedOnThisToolchain` `:115` | **the archive contract `MGPProgramDesc::Reflection` serialises through**; `:115` is where the libc++/NDK numbers get read from (`ProgramArtifacts.h:553-557`) |
|
||
| `MG_Test/Program/ProgramPipelineCompositeTest.cpp` | 724 L | `ProgramPipelineCompositeTest` | the composite path of §9.4 |
|
||
| `MG_Test/Program/{AsyncCompile,AsyncLink,AsyncSpirvPhase,AsyncTeardown,OptimisticStatus,ParallelShaderCompile}Test.cpp` | | | the join gates a P4a emitter will trip |
|
||
| `MG_Test/Program/ProgramInterfaceTest.cpp`, `XfbBlockVaryingTest.cpp`, `TessellationLinkTest.cpp` | | | reflection surface |
|
||
| `MG_Test/Pipe/*` | | `PipeCatalogueTest` (585 L — payload sizes and the 71-record arithmetic), `PipeInputsTest` (644 L), `TrackerTest` (917 L), `CsoCacheTest` (200 L), `SlotAllocatorTest` (292 L), `ResourceEmitTest` (1874 L), `VertexInputEmitTest` (635 L), `RenderStateSpansTest` (1368 L), `MagmaPipeIdentityTest` (217 L) | **`ResourceEmitTest` and `VertexInputEmitTest` are the two files a P4a `*EmitTest` is modelled on**; `PipeCatalogueTest` is where every new payload size assertion lands |
|
||
| `MG_Test/Util/PipeStatsTest.cpp` | | `CounterNamesAreStable` (`:260`), `SummaryLineCarriesEveryClassAndGate` (`:137`) | any new `PipeStats::CallClass` must be pinned in both |
|
||
| `MG_Test/ScopedPipeVerb.h` | | | the fixture helper for driving a verb in a unit test |
|
||
|
||
Fixture trap to carry forward — **ID-14**: `MG_Test/Buffer/BufferTest.cpp`'s `ScopedBackendOps`
|
||
scoped only `BufferBackendOps`, so under a push build 26 of 86 dispatch cases routed into the
|
||
pipe; `83302ca2` made it save/null/restore the pipe table too, the way `ResourceEmitTest`'s
|
||
`ApplierGuard` scopes the applier. **Any P4a test that installs a backend ops table needs the same
|
||
two-scope shape**, and the follow-up (a pipe-shaped mock so the same assertions run over the
|
||
handle path) is still open.
|
||
|
||
---
|
||
|
||
## 14. Named risks and open questions for the brief
|
||
|
||
1. **`Complete` cannot be a purely client-side answer today.**
|
||
`CheckFramebufferStatus_State` (`GL_Framebuffer.cpp:2291-2294`) consults
|
||
`ActiveBackendRejectsDistinctDepthStencil()`, and `HasNonRenderableColorAttachment` `:2288`
|
||
reads the backend's probed format-capability cache (the comment at `:101` says so).
|
||
`FramebufferObject::CheckCompleteness()` (`FramebufferObject.cpp:163-193`) is backend-free but
|
||
is a **weaker** answer. Decide explicitly which one `MGPFramebufferState::Complete` carries and
|
||
say so in the payload comment; a client that emits the GL-visible status is reading the backend
|
||
from the client side, which P4a is supposed to be removing.
|
||
2. **Renderbuffer respecify publishes nothing** (§5.2) — a real correctness hole, not a
|
||
narrowing item.
|
||
3. **`glUseProgram` has no `DirtySurface.def` row** (§5.1); the gate is likely to go red the
|
||
moment P4a touches that path.
|
||
4. **Espryt's unpack shadow is never invalidated** (§6.2, `Managers.cpp:4911` vs
|
||
`DirectGLES.cpp:10578`/`:11112`) — once P4a's descriptor carries the strides, the shadow stops
|
||
being a benign optimisation.
|
||
5. **`FramebufferObject::SetDrawBuffer` versions the wrong attachment slot**
|
||
(`FramebufferObject.cpp:195-199`): it bumps `m_attachmentVersions[buffer]`, i.e. the value being
|
||
written, not the index being written to. `m_objectVersion` and the aggregate still move, so bit
|
||
11 is safe; a *narrower* P4a shutter built on `m_attachmentVersions` would not be.
|
||
6. **The composite band's bookkeeping is unwritten.** `MGPipeHandles.h:83-94` and
|
||
`SlotAllocator.cpp:19-23` reserve and defend the band, but nothing mints from it yet, and a
|
||
composite is created on a cache miss inside `GetProgramForDraw` (`Core.cpp:661`) — a
|
||
`MakeShared` on the draw path, whose death is an ordinary `~ProgramObject`.
|
||
7. **`ProgramArtifacts.h`'s libc++ size pins are inert** (`:563-565`). If the archive is to cross
|
||
on device in P4a, the integrator owes those four numbers from an NDK build.
|
||
8. **`MGPProgramDesc` is 192 B with six SPIR-V blobs; the ring caps one record at half its
|
||
capacity** (`ARCHITECTURE.md` §4: `RingProducer::MaxRecordBytes()`), so `create_shader_state` is
|
||
named there as one of the two emissions that must be split by the emitter. `MGPipeForEach
|
||
SubDataRecordRange` (`ResourceTracker.h:257`) is the existing splitter shape.
|
||
9. **ID-19's cost budget.** P3a already added +699 (Espryt) / +422 (Magma) ns/draw and rd12 moved
|
||
+30%/+27% on device. Every P4a per-verb emission needs a suppressor slot or a version-first
|
||
skip *before* it hashes anything — the `MGPipeCsoCache::Acquire` shape (`CsoCache.h:14-25`),
|
||
not a hash-every-verb shape.
|
||
10. **`kUnpublishedDestroy` for pipelines and shaders** (`DirtySurface.def:301-302, 305`) should be
|
||
resolved deliberately: a program pipeline has no wire object and a shader never crosses, so the
|
||
honest P4a outcome may be "the program row becomes `kExplicitDestroy`, the other two stay
|
||
`kUnpublishedDestroy` with an updated reason", not "all three close".
|