mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13: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.
816 lines
59 KiB
Markdown
816 lines
59 KiB
Markdown
# Scout — Espryt's framebuffer / renderbuffer / texture surface that P4a converts
|
||
|
||
Tree: `C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg`, branch `feat/disaggregated`
|
||
@ `37da3c3a` (P3a docs commit). Every line number below was opened in this tree. Paths are repo-relative
|
||
from `MobileGL/` unless stated.
|
||
|
||
Files that carry essentially all of it:
|
||
|
||
| file | lines | what P4a touches |
|
||
|---|---|---|
|
||
| `MG_Backend/DirectGLES/DirectGLES.cpp` | 11142 | `SyncCurrentFBO`, `BindCurrentFBO`, `ForceBindCurrentFBO`, `SyncAndBindFramebufferObject`, the two texture sync lists + the unit-bindings epoch, image units, readbacks, blits, CopyImage, GenerateMipmap, the scoped state guards |
|
||
| `MG_Backend/DirectGLES/Managers.cpp` | 10698 | `BackendFramebufferObject`, `BackendTextureObject`, `BackendRenderbufferObject`, `ScratchFBOImpl`, `PixelStoreImpl`, the unpack ring staging, the death-notice consumer |
|
||
| `MG_Backend/DirectGLES/Managers.h` | 2260 | the twin classes' members and the `TwinRegistry` alias / `StateBackendObjectRegistry` two-arm template |
|
||
| `MG_Backend/DirectGLES/SlotTables.h` | 577 | `BackendSlotTable` — the `{slot,gen}` table, both `GetOrCreate` overloads, `ReleaseByHandle`, the holder list |
|
||
| `MG_Backend/DirectGLES/Utils.cpp/.h` | 2405 / 543 | format caveat/widening predicates, `ReadbackImpl` client-store walks |
|
||
| `MG_Pipe/MGPipeTypes.h`, `MG_Pipe/PipeCalls.def` | — | the payloads P4a must fill (§7) |
|
||
|
||
---
|
||
|
||
## 0. The contract P4a is filling in (already written, all stubs today)
|
||
|
||
`MG_Pipe/PipeCalls.def` rows that P4a owns (none are new; only their emitters/appliers are):
|
||
|
||
- `:113` `X(SetFramebufferState, MGPFramebufferState, kCtxState, kNone)`
|
||
- `:118` `X(SetSamplerViews, MGPSamplerViews, kCtxState, kVarTail)`
|
||
- `:119` `X(BindSamplerStates, MGPSamplerStates, kCtxState, kVarTail)`
|
||
- `:120` `X(SetShaderImages, MGPShaderImages, kCtxState, kVarTail)`
|
||
- `:133` `X(SetTextureParams, MGPTextureParams, kCtxObject, kNone)`
|
||
- `:140` `X(GenerateMipmap, MGPMipPlan, kCtxObject, kNone)`
|
||
- `:141` `X(GetTextureImage, MGPReadbackInfo, kCtxObject, kReplySlot)`
|
||
- `:139` `X(ResourceCopyRegion, MGPCopyRegion, kCtxObject, kNone)`
|
||
- `:142/:143` `X(Blit, MGPBlit, kCtxVerb, kNone)`, `X(Clear, MGPClear, kCtxVerb, kNone)`
|
||
- `:144` `X(ReadPixels, MGPReadbackInfo, kCtxVerb, kReplySlot)`
|
||
- CSO create/delete: `:105-108` `CreateSamplerState` / `DeleteSamplerState` / `CreateSamplerView` / `DeleteSamplerView`.
|
||
|
||
Payload shapes (`MG_Pipe/MGPipeTypes.h`), with the fields that decide P4a's work:
|
||
|
||
- `MGPResourceDesc` `:150-175`, 88 B. **One create/respecify shape for buffers, every texture target and
|
||
renderbuffers** — `Target`, `StorageKind` (== `TextureStorageType`), `BindMask`, `InternalFormat`,
|
||
`Width/Height/Depth`, `ArrayLayers/Levels/Samples`, `FixedSampleLocations`, `Immutable`,
|
||
`HasDefinedContent`, **`ImageBindableHint` (`:166`, "client-side everImageBound; pre-emptive
|
||
allocation")**, `ViewOf` (`:173`, texture-view storage owner), `BufferForTexBuffer` + `BufOffset/BufSize`
|
||
(`:174-175`) for a buffer texture. So texture creation reuses P3a's `resource_create/respecify`.
|
||
- `MGPSurface` `:342-352`, 24 B: `Res`, `InternalFormat` **inline** (so the cross-object masks fall out with
|
||
no lookup), `Kind` (Texture|Renderbuffer|None), `Layered`, `Level`, `Layer`, `UploadTarget`.
|
||
- `MGPFramebufferState` `:354-371`, 304 B: `Fbo` (`kMGPipeDefaultFramebuffer` for the default FBO),
|
||
`Color[8]`, `Depth`, `Stencil`, **`ReadSurface` — "the RESOLVED read surface, not an index. This is what
|
||
structurally closes the read-buffer-shared-FBO defect class" (`:359-361`)**, `DrawBuffers[8]` as
|
||
`Int8` attachment indices with **-1 = NONE**, `Width/Height/Layers/Samples`,
|
||
`FixedSampleLocations/IsDefault/Complete`, and `ContentHash` (`:369`) doing two jobs: the server's
|
||
render-pass memo key and the **client-side emission suppressor**.
|
||
*Note for the implementer:* `Color[8]` and `DrawBuffers[8]` are **8**, while the frontend today carries
|
||
`FramebufferObject::MAX_DRAW_BUFFERS` draw buffers and `Color0..Color31` attachment points
|
||
(`Managers.h:1579-1581` computes `MAX_COLOR_ATTACHMENT_SLOTS` as `Color31-Color0+1` = 32). The
|
||
attachment-relocation table (§1.4) is 32 wide. This is a real width mismatch P4a has to rule on.
|
||
- `MGPSamplerView` `:288-303`, 36 B: "ONLY the view restrictions. Everything a `glTexParameter` writes lives
|
||
on `set_texture_params` instead, **because a texture that is only an FBO attachment, only an image binding
|
||
or only a `glCopyImageSubData` endpoint has no sampler view to hang it on**" (`:288-291`). Carries
|
||
`InternalFormat` (aliasing format for `glTextureView`), `Target`, `MinLevel/NumLevels/MinLayer/NumLayers`,
|
||
`Samples`, `FixedSampleLocations`.
|
||
- `MGPTextureParams` `:306-318`, 32 B: `Res`, `BaseLevel/MaxLevel`, `Swizzle[4]`, `DepthStencilMode`,
|
||
**`ForceResync` — "Mirrors `m_forceTextureParamsResync`" (`:312-313`)**, `MinLod/MaxLod/LodBias`.
|
||
- `MGPBoundView` `:428-434` / `MGPSamplerViews` `:436-440`; `MGPImageView` `:443-452` /
|
||
`MGPShaderImages` `:454-458`; `MGPSamplerStates` `:442-446`.
|
||
- `MGPCopyRegion` `:708-716` (64 B), `MGPBlit` `:718-727` (56 B), `MGPClear` `:729-739` (48 B),
|
||
`MGPMipPlan` `:741-745` (16 B), `MGPReadbackInfo` `:748-756` (64 B, `Res` null for `read_pixels`:
|
||
"the bound read surface answers").
|
||
|
||
`MGPTextureParams` has **no border colour and no filter/wrap/compare/anisotropy**; those live in
|
||
`MGPSamplerDesc` (`:283-287`, a blob of `SamplerParameters`) — but Espryt today pushes filters/wrap/LOD/
|
||
anisotropy **onto the texture object** through `SyncBuiltinSamplerToBackend` (§2.4). That mapping
|
||
(built-in sampler → sampler CSO, or → an extra params field) is P4a's second contract ruling.
|
||
|
||
---
|
||
|
||
## 1. How Espryt resolves the current draw/read framebuffer today
|
||
|
||
### 1.1 The FBO twin and its table
|
||
|
||
`FramebufferImpl::BackendFramebufferObject` — `Managers.h:1537-1610`.
|
||
Members (all of them are the server-side state P4a must relocate):
|
||
|
||
| member | line | meaning |
|
||
|---|---|---|
|
||
| `m_backendFBOId` | `Managers.h:1561` | driver FBO name, `glGenFramebuffers` in the ctor (`Managers.cpp:7015`) |
|
||
| `m_contextGeneration` | `:1562` | ES context generation the id belongs to; dtor only deletes when it matches (`Managers.cpp:7035`) |
|
||
| `m_frontendDrawBuffers[MAX_DRAW_BUFFERS]` | `:1570-1571` | the app's draw-buffer array verbatim (holes / reversals allowed) |
|
||
| `m_backendDrawBuffers[MAX_DRAW_BUFFERS]` | `:1577` | the ES-legal compacted array actually handed to `glDrawBuffers` |
|
||
| `m_backendColorSlots[32]` | `:1593` | **the permutation**: where frontend `COLOR_ATTACHMENTn` physically lives as a backend `COLOR_ATTACHMENTm` |
|
||
| `m_frontendReadBuffer` / `m_backendReadBuffer` | `:1600-1601` | read-buffer memo |
|
||
| `m_syncedFrontendAttachmentVersions` | `:1604` | per-attachment-point memo, keyed on FRONTEND attachment versions |
|
||
| `m_syncedBackendIdGeneration` | `:1609` | `g_attachmentBackendIdGeneration` at the last attachment walk |
|
||
|
||
Table: `extern TwinRegistry<FramebufferObject, BackendFramebufferObject, MGPipeKind::Framebuffer>
|
||
g_backendFramebufferObjects;` — declared `Managers.h:1612-1613`, defined `Managers.cpp:7754-7755`.
|
||
`TwinRegistry` is the alias at `Managers.h:547-553` that swallows the kind in the pull build;
|
||
`StateBackendObjectRegistry` (`Managers.h:303-540`) dispatches to `m_slotTable`
|
||
(`BackendSlotTable`, `Managers.h:538`) when `EsprytSlotTablesEnabled()` (`SlotTables.h:132-135`),
|
||
otherwise to the legacy `UnorderedMap<StateObject*, Entry>` (`Managers.h:319`).
|
||
|
||
The other two tables of this family:
|
||
- `TextureImpl::g_backendTextureObjects` — `Managers.h:1521-1522`, defined `Managers.cpp:7001`.
|
||
- `RenderbufferImpl::g_backendRenderbufferObjects` — `Managers.h:2257-2258`, defined `Managers.cpp:10695-10696`.
|
||
- (`SamplerImpl::g_backendSamplerObjects` — `Managers.h:2230-2231`, defined `Managers.cpp:10583`.)
|
||
|
||
All four still **mint** their handle inside `MG_Backend` off a frontend `GetLifetimeId()`
|
||
(`SlotTables.h:231-232`) — that is the debt `SlotTables.h:70-77` records against itself and that
|
||
P4a discharges for these kinds, exactly as P3a's seventh table (`BufferImpl::BackendBufferResourceTable`,
|
||
`Managers.h:690-692`) already does with `GetOrCreate(MGPipeHandle)` (`SlotTables.h:278-326`) and
|
||
`ReleaseByHandle` (`SlotTables.h:350-361`).
|
||
|
||
### 1.2 The frontend binding slot read
|
||
|
||
`GetFramebufferBindingSlotChecked(FramebufferTarget)` — `DirectGLES.cpp:173-193`. On the handle arm it is a
|
||
plain `MGB_CTX->GetFramebufferBindingSlot(target)` (`:176`); on the legacy arm it goes through the raw
|
||
pointer cache `g_fbSlotCache` / `g_fbSlotCacheContext` (`:156-157`, `:179-188`), which the comment at
|
||
`:159-172` explains was a **poison/verify bypass** and is therefore kept only for the pull control.
|
||
Five call sites: `:1944`, `:2242`, `:3080`, `:3210`, `:3300`.
|
||
|
||
### 1.3 `SyncCurrentFBO` — `DirectGLES.cpp:2229-2315`
|
||
|
||
Sequence, verbatim:
|
||
|
||
1. `:2233-2235` — three `CollectGarbageIfNeeded()` ticks (framebuffer, texture, renderbuffer). These are
|
||
no-ops on the handle arm (`Managers.h:477-491`).
|
||
2. `:2237` — walks `{Draw, Read}` in that order; `lastUpdatedFBO` carries the draw object forward.
|
||
3. **The four-part memo**, `:2254-2266`:
|
||
`slotVersion` (`slot.GetVersion()`), `objectVersion` (`currentFBO->GetObjectVersion()`), the raw
|
||
`currentPtr` (identity only), and `g_attachmentBackendIdGeneration`. All four stamped together by
|
||
`StampSyncedFBO` (`:2221-2227`) into `g_fboSyncedSlotVersions` / `g_fboSyncedObjectVersions` /
|
||
`g_fboSyncedObjects` / `g_fboSyncedBackendIdGenerations` (declared `Managers.h:1690-1710`,
|
||
defined `Managers.cpp:7756-7762`). The comment at `:2245-2253` records the bug that fixed:
|
||
leaving the slot version to `ForceBindCurrentFBO` alone made the early-out never fire and a
|
||
Minecraft-shaped frame re-ran the whole attachment walk on all 5495 draws.
|
||
4. `:2268-2271` — no FBO bound at all → `MGLOG_E_ONCE`, continue.
|
||
5. **Default-framebuffer branch** `:2273-2284`: nothing to sync *except* resetting
|
||
`g_alphaWidenedDrawBufferMask` / `g_integerColorDrawBufferMask` to 0 for the DRAW target, then stamp.
|
||
6. **Same-FBO-as-draw skip** `:2286-2302`: when the READ target names the object already synced as DRAW,
|
||
the attachment/draw-buffer work is skipped but `SyncReadBufferToBackend` is still called
|
||
(`:2295-2298`), through a `Find` on the twin table. This is the `read-buffer shared FBO` fix
|
||
(`draw_buffers_1`); `MGPFramebufferState::ReadSurface` is the structural replacement.
|
||
7. `:2304-2310` — resolve-or-create the twin, materialise it if empty, `SyncToBackend(currentFBO, target)`.
|
||
|
||
### 1.4 `BackendFramebufferObject::SyncToBackend` — `Managers.cpp:7521-7737`
|
||
|
||
- `:7533` `Bind(asTarget)` first.
|
||
- Draw buffers `:7537-7585`: memcmp against `m_frontendDrawBuffers`; **applied only when
|
||
`asTarget == Draw`** (`:7551`) — the comment `:7544-7550` records the Minecraft 26.x OIT bug where a
|
||
READ-only sync would land `glDrawBuffers` on the wrong framebuffer and falsely stamp the memo.
|
||
`FramebufferAttachmentType::None` → `GL_NONE` (`:7558-7560`) — **that is the "draw-buffer None"
|
||
handling**; default-framebuffer FRONT/BACK tokens pass through unremapped (`:7564-7569`);
|
||
everything else is forced to `GL_COLOR_ATTACHMENT0 + i` (`:7571`), and `nEffectiveBuffers` is the
|
||
highest non-None index + 1 (`:7573`).
|
||
- `RecomputeBackendColorSlots` — `Managers.cpp:7432-7519`. Three passes: draw-buffer slot `s` pins
|
||
attachment `a` to backend point `s` (`:7453-7474`); survivors keep identity (`:7479-7486`); displaced
|
||
attachments are parked on the lowest free point (`:7492-7504`). Returns "moved", and each moved
|
||
attachment has its version memo forced to `~0` (`:7515-7516`). When anything moved the read-buffer memo
|
||
is dropped (`:7580-7582`). `GetBackendAttachmentType` (`:7739-7752`) is the single reader.
|
||
- Draw-target-only mask recompute `:7587-7619`: `snormClampOutputMask`, `unormClampOutputMask`,
|
||
`g_alphaWidenedDrawBufferMask`, `g_integerColorDrawBufferMask` (globals declared `Managers.h:1650`,
|
||
`:1659`, defined `Managers.cpp:7308-7309`; the shader-side masks go into
|
||
`PrgramImpl::g_snormFallbackClampOutputMask` / `g_unormFallbackClampOutputMask`).
|
||
- Read buffer `:7623-7625` → `SyncReadBufferToBackend` (`Managers.cpp:7410-7430`), which binds as READ
|
||
first (`:7427`) then `glReadBuffer`.
|
||
- **Attachment walk** `:7627-7724`: re-arm every point when `m_syncedBackendIdGeneration !=
|
||
g_attachmentBackendIdGeneration` (`:7632-7636`); per point compare
|
||
`m_syncedFrontendAttachmentVersions[i] != attachmentVersions[i]` (`:7650`); an empty colour point below
|
||
`MaxColorAttachments` is explicitly detached with `glFramebufferRenderbuffer(..., 0)` (`:7659-7666`) —
|
||
the comment `:7651-7658` says removing that detach breaks the permutation invariant.
|
||
- **Re-entrancy** `:7726-7736`: the walk itself can re-mint a texture id, so `SyncToBackend` recurses until
|
||
`g_attachmentBackendIdGeneration` is quiescent.
|
||
|
||
`SyncAttachmentObject` — `Managers.cpp:7142-7216`: the attachment id/layer/level resolution.
|
||
- Texture branch `:7145-7194`: find-or-create the texture twin, **`SyncMipmapsToBackend` only**
|
||
(`:7161` — see §2.6, the parameter gap), then three shapes:
|
||
layered → `glFramebufferTexture(target, att, id, level)` (`:7162-7165`);
|
||
3D / 2DArray / 1DArray / CubeMapArray / 2DMSArray → `glFramebufferTextureLayer(..., level, layer)`
|
||
(`:7166-7177`); otherwise bind + `glFramebufferTexture2D` with the cube-face target
|
||
(`:7178-7194`, the cube-face bind rule at `:7184-7190`).
|
||
- Renderbuffer branch `:7195-7214`: find-or-create, `SyncToBackend`, `Bind()`,
|
||
`glFramebufferRenderbuffer`.
|
||
- An empty attachment returns `true` and issues nothing (`:7215`).
|
||
|
||
Read-side helpers on the frontend READ binding: `GetReadColorAttachment` (`Managers.cpp:7280-7291`),
|
||
`IsFixedPointFallbackReadAttachment` (`:7385-7408`), `IsAlphaWidenedFallbackReadAttachment` (`:7377-7383`),
|
||
`IsAlphaWidenedColorAttachment` (`:7293-7306`), `ComputeAlphaWidenedDrawBufferMask` (`:7357-7372`),
|
||
`IsIntegerColorAttachment` (`:7344-7355`), `IsSnormFallbackAttachment` / `IsUnormFallbackAttachment`
|
||
(`:7246-7276`).
|
||
|
||
### 1.5 Binding: `BindCurrentFBO` / `ForceBindCurrentFBO` / `SyncAndBindFramebufferObject`
|
||
|
||
- `BindCurrentFBO(target)` — `DirectGLES.cpp:3206-3260`. **No fast path on the slot version** — the
|
||
comment `:3211-3216` records `KHR-GL32.packed_pixels` reading out of the previous subtest's framebuffer.
|
||
Handle arm: a plain `Find` (`:3229-3232`); legacy arm: the 64-slot `g_fboTwinLookupMemo`
|
||
(`DirectGLES.cpp:140-141`, used `:3237-3243`). Default FBO → `BindFramebufferId(..., 0)` **through the
|
||
shadow** (`:3253-3259`) — *"a raw bind here would leave the shadow claiming the previous user FBO,
|
||
false-skipping its next re-bind"*. **This is the default-FBO-bind trap from the readback-state-guards
|
||
work**; its siblings are `SyncAndBindFramebufferObject`'s early return (`:3267-3280`) and
|
||
`NoteFramebufferIdDeleted` (`Managers.cpp:7096-7105`).
|
||
- `SyncAndBindFramebufferObject(fbo, target, forceSync)` — `DirectGLES.cpp:3262-3294`; used by the DSA
|
||
clears and `BlitNamedFramebuffer`. `forceSync` → `InvalidateSyncedState()` (`Managers.cpp:7119-7140`).
|
||
- `ForceBindCurrentFBO(target)` — `DirectGLES.cpp:3296-3308`: sync+bind then stamp all four memo arrays by
|
||
hand.
|
||
|
||
Driver-binding shadow: `g_driverFBOBindings` / `g_driverFBOBindingKnown` (`Managers.cpp:7051-7056`),
|
||
`BindFramebufferId` (`:7058-7079`), `CurrentFramebufferBinding` (`:7081-7094`, the one `glGetIntegerv`
|
||
cold pin), `NoteFramebufferIdDeleted` (`:7096-7105`), `InvalidateFramebufferBindingCache`
|
||
(`:7107-7117` — also clears the three synced-FBO arrays; called from `DirectGLES.cpp:5597`, `:6112`,
|
||
`:6211`, `:10133`, `:10577`, `:11110`).
|
||
|
||
Call sites that drive the pair (`SyncCurrentFBO` then `BindCurrentFBO`): `PrepareForDraw`
|
||
(`DirectGLES.cpp:3359` + `:3363`), `Clear` (`:4511` + `:4518`), `BlitFramebuffer` (`:6433`, `:6442-6443`),
|
||
`CopyTexImage2D` (`:7176` + `:7187`), `CopyTexSubImage2D` (`:7270` + `:7282`), `ClearBufferfi/fv/iv/uiv`
|
||
(`:7866/7912/7925/7941` + `:7869/7915/7931/7944`), the four `ClearNamedFramebuffer*`
|
||
(`:7969`, `:7986`, `:8006`, `:8026` via `ForceBindCurrentFBO`), `ReadPixels` (`:9510` + `:9513`),
|
||
`GetTexImage` (`:9681`), the multisample-resolve fallback (`:6507-6508`).
|
||
|
||
### 1.6 The fragColor MRT broadcast
|
||
|
||
`PrgramImpl` `DirectGLES.cpp:3037-3057`: the memo `g_broadcastMemoFbo` / `g_broadcastMemoSlotVersion` /
|
||
`g_broadcastMemoObjectVersion` / `g_broadcastMemoValid` / `g_broadcastMemoCount`, **keyed exactly like
|
||
`SyncCurrentFBO`'s trio** and cleared by `InvalidateBroadcastMemo()` (`:3055-3057`, called from
|
||
`DestroyEGLContext` `:11113`). Derivation `DirectGLES.cpp:3079-3102`: walk `drawFBO->GetDrawBuffers()`,
|
||
`enabledDrawBuffers = i+1` for every non-`None` slot, `g_fragColorBroadcastCount = max(count,1)`.
|
||
It is **read from the frontend, deliberately not from the backend FBO sync** (`:3076-3078`), because a
|
||
program compiled against a stale count would not be relinked until the following draw. It then enters the
|
||
program-staleness condition at `:3154` (`twin->GetFragColorBroadcastCount() != g_fragColorBroadcastCount`),
|
||
i.e. **the draw-buffer array is a shader-compilation input**. P4a must keep that ordering when the FBO
|
||
state becomes a pushed `MGPFramebufferState`.
|
||
|
||
### 1.7 The other emulations that live in this surface
|
||
|
||
- **Alpha-widened three-channel attachments** (`Managers.h:1642-1659`): the stored alpha is pinned at 1.0
|
||
— clears write 1.0 (`SubstituteWidenedClearAlpha`, `Managers.h:1670-1680`, used
|
||
`DirectGLES.cpp:7917-7920`, `:7933-7936`, `:7946-7949`, and for DSA at `:7961-7963` via
|
||
`IsWidenedNamedDrawBuffer` `:7894-7907`), draws get their alpha write mask forced off
|
||
(`RenderStateImpl::SyncRenderState(forColorClear)`, contract at `DirectGLES.h:209-226`, the applied
|
||
mask memo `g_syncedColorMaskAlphaWidenMask` at `DirectGLES.cpp:2346`), and readback overwrites alpha
|
||
(`IsAlphaWidenedFallbackReadAttachment` → `forceOpaqueAlpha`, `DirectGLES.cpp:9533-9543`).
|
||
`g_integerColorDrawBufferMask` exists because `glClearBufferfv` on an integer buffer is
|
||
`INVALID_OPERATION` (`Managers.h:1652-1658`).
|
||
- **SNORM / UNORM caveat formats**: `IsSnormFallbackAttachment` / `IsUnormFallbackAttachment`
|
||
(`Managers.cpp:7246-7276`) → the two shader clamp masks; readback clamp
|
||
`applyFixedPointReadClamp` (`DirectGLES.cpp:9542`, the clamp itself `:9204-9231`).
|
||
- **Layered attachment shapes**: `attachmentObject.IsLayered()` (5 reads),
|
||
`GetTextureLayer()` (1), `GetTextureLevel()` (6), `GetTextureUploadTarget()` (4) — the three-way branch
|
||
at `Managers.cpp:7162-7194`; the layered-blit substitute at `DirectGLES.cpp:6265-6421`.
|
||
- **Blit LOD / layered blit**: `BlitFramebuffer` `DirectGLES.cpp:6423-…`; the non-zero destination array
|
||
layer is served by `glCopyImageSubData` (`:6288`, `:6354-6413`) because a driver may ignore the layer
|
||
(`:6387`). The multisample replicate path builds its own programs (`:5889-5910`, `:6105-6191`) and the
|
||
aspect split is at `:6218-6258`. `EmulateTextureLodBias` (`Utils.cpp:1749-1830`, `Utils.h:527-536`)
|
||
is the shader-side LOD-bias emulation, invoked `Managers.cpp:9855`, gated by
|
||
`ShouldAvoidExplicitLodBiasOnAngleLlvmpipe()` (`Managers.cpp:94-97`).
|
||
- **D24S8 dual-mode sampling**: `GL_DEPTH_STENCIL_TEXTURE_MODE` is forwarded per texture
|
||
(`Managers.cpp:6956-6977`; cache `m_cacheDepthStencilTextureMode`, `Managers.h:1501`), and the *second*
|
||
half — one D24S8 sampled through its own name as `STENCIL_INDEX` and through a `glTextureView` as
|
||
`DEPTH_COMPONENT` in one pass (Better Clouds) — is why `GL_ARB_texture_view` is only advertised behind
|
||
a host extension **and** `MG_Config::Features.EsprytEnableTextureView`
|
||
(`BackendObject_DirectGLES.cpp:1177-1203`; the known break is `SyncTextureViewToBackend` normalising the
|
||
view's ES internalformat independently of the storage, `:1189-1196`).
|
||
The depth/stencil **readback** emulation is `DepthStencilSamplingReadImpl`
|
||
(`DirectGLES.cpp:8117-8578`, entered from `:8617` and `:8721`), with `ScopedDepthBlitState` at `:6985`.
|
||
|
||
---
|
||
|
||
## 2. The texture object twin
|
||
|
||
### 2.1 The class
|
||
|
||
`TextureImpl::BackendTextureObject` — `Managers.h:1341-1517`. Public surface `:1343-1417`; the members
|
||
that are the real server state:
|
||
|
||
| member | line | note |
|
||
|---|---|---|
|
||
| `m_backendTextureId` | `1422` | driver name |
|
||
| `m_bufferImageSplitViewId` | `1441` | second buffer-texture name for `glBindImageTexture` (the rg32f→r32f split) |
|
||
| `m_viewSourceBackendTextureId` | `1447` | for a `glTextureView`: the ES name it was made a view OF |
|
||
| `m_contextGeneration` | `1455` | |
|
||
| `m_isInitialized`, `m_imageBindableStorageRequired`, `m_backendStorageImmutable` | `1456-1458` | |
|
||
| `m_bufferTextureUnsupportedReported` | `1463` | one-shot report latch |
|
||
| `m_prevTextureInfo` (`StateTextureBasicInfo`, `Managers.h:1320-1338`) | `1464` | format/w/h/d/levels/bufferExternalIndex/samples/fixedSampleLocations |
|
||
| `m_syncedContentVersion` | `1468` | |
|
||
| `m_syncedShapeContextId`, `m_syncedShapeGeneration`, `m_syncedShapeParamsVersion` | `1481-1483` | the cheap-gate trio |
|
||
| `m_cacheSamplerParameters`, `m_cacheLodRange` | `1484-1485` | |
|
||
| `m_cacheBorderColor` / `I` / `UI` / `Form` | `1490-1493` | **all four**, because none alone identifies the driver's border colour |
|
||
| `m_cacheSwizzleParams` | `1494-1495` | |
|
||
| `m_cacheDepthStencilTextureMode` | `1501` | |
|
||
| `m_syncedSamplerVersion`, `m_syncedTextureParamsVersion` | `1502-1503` | |
|
||
| `m_forceTextureParamsResync`, `m_forceSamplerResync` | `1507`, `1516` | the two re-mint overrides (`MGPTextureParams::ForceResync` mirrors the first) |
|
||
|
||
`IsDrawSyncClean(t, contextId, samplingGeneration)` — `Managers.h:1399-1417`: the aggregate gate that is
|
||
*exactly* the conjunction of the three per-draw sync calls' own early-outs (documented `:1383-1394`; it
|
||
must never be more permissive).
|
||
|
||
Unit binding shadow: `g_boundTexturesCache[192][TextureTargetCount]` (`Managers.h:1530-1532`, defined
|
||
`Managers.cpp:6998-7000`), `g_activeTextureUnit` (`Managers.cpp:6997`), `ActivateTextureUnit`
|
||
(`Managers.cpp:6980-6986`), `UnbindTexture` (`:6988-6995`), `Bind(target, unit=TempTextureUnit=0)`
|
||
(`Managers.cpp:4729-4743`, `TempTextureUnit` at `Managers.h:1340`).
|
||
|
||
### 2.2 `SyncTextureObjectToBackend` — `DirectGLES.cpp:1501-1577`
|
||
|
||
Find-or-create (`:1507-1512`), then a **by-value SharedPtr copy** of the twin
|
||
(`:1514-1526`) because the sync can re-enter (a texture view syncs its storage texture first) — on the
|
||
slot arm the copy is kept only to hold the twin alive. Then, in order:
|
||
`RequireImageBindableStorage` if asked (`:1528-1530`), `SyncTextureParamsToBackend` (`:1531`),
|
||
`SyncBuiltinSamplerToBackend` (`:1532`), `SyncMipmapsToBackend` (`:1533`), and — if the storage sync
|
||
re-minted the name — **both parameter pushes again** (`:1541-1544`, `NeedsParameterResync()`
|
||
`Managers.h:1397`). Handle arm re-resolves once (`:1546-1566`); legacy arm re-resolves and repairs
|
||
(`:1568-1576`).
|
||
|
||
### 2.3 The per-draw work lists and the unit-bindings epoch
|
||
|
||
`DirectGLES.cpp:1579-1990` is the block P4a rewrites. In order:
|
||
|
||
- **The identity snapshot.** `UnitBindingsSnapshot` on the push arm holds **lifetime ids, not weak_ptrs**
|
||
(`:1594-1622`; `LifetimeIdOf` `:1624-1630`), with the legacy weak_ptr fields kept under
|
||
`MOBILEGL_PIPE_LEGACY_MEMOS` (`:1616-1621`) and the arm chosen at **runtime** via
|
||
`MGB_UNIT_BINDINGS_HANDLE_ARM` (`:1632-1636`, `#undef` at `:1694`). Pull-build copy of the same two
|
||
functions at `:1696-1727`. `CaptureUnitBindings` `:1638-1664`, `UnitBindingsUnchanged` `:1666-1693`.
|
||
The design note at `:1579-1593` says explicitly what the snapshot does **not** cover (completeness
|
||
flips, high-water mark, context identity, ES context generation, the program keys).
|
||
**This is the ~115-line derivation the task names.**
|
||
- **`CurrentUnitBindingsEpoch(maxTouchedUnit)`** — `:1746-1773`, over
|
||
`g_observedUnitBindings` / `…ContextId` / `…Generation` / `…MaxUnit` / `g_unitBindingsEpoch`
|
||
(`:1730-1734`). Gate: `(contextId, bindGeneration, maxTouchedUnit)`; on a miss it re-walks and bumps
|
||
the epoch only when the bindings really moved. Stats gate `Gate::EsprytUnitBindingsEpoch` (`:1757`, `:1762`).
|
||
- **`UnitTextureSyncEntry`** — `:1801-1805`: `{const SharedPtr<ITextureObject>* slot,
|
||
ITextureObject* texture, BackendTextureObject* backend}`. **Entries borrow, never own**
|
||
(rationale `:1783-1800`), and `PairingsIntact` (`:1808-1813`) is the structural net under the keys
|
||
(a DSA by-name slot swap that never reached the bind generation would otherwise drive texture A's twin
|
||
from texture B's state).
|
||
- **`g_unitTextureSyncList`** + keys `:1814-1820`
|
||
(`Valid`, `ContextId`, `MaxUnit`, `ContextGeneration`, `Epoch`, `SamplingGeneration`).
|
||
- **`g_fboTextureSyncList`** + keys `:1825-1830`
|
||
(`Fbo`, `SlotVersion`, `ObjectVersion`, `ContextId`, `ContextGeneration`).
|
||
- **`DrawTextureSyncKeys`** `:1839-1844` and `CaptureDrawTextureSyncKeys()` `:1846-1858`
|
||
(`contextId`, `maxTouchedUnit`, `samplingGeneration`, `unitBindingsEpoch`).
|
||
- **`SyncNeccessaryTextures(keys)`** `:1860-1988`.
|
||
Unit list hit path `:1879-1901` (per entry: `IsDrawSyncClean` then the three syncs);
|
||
rebuild `:1902-1930` (skips `IsUndefinedDefaultTexture`, `:1918`).
|
||
**FBO list `:1944-1987` walks the DRAW slot only** (`:1944`), over
|
||
`currentFBO->GetAllAttachmentObjects()` with `attachment.IsTexture()` (`:1970-1976`); a null draw FBO
|
||
clears the list (`:1984-1987`). Stats gate `Gate::EsprytTextureSyncList` (`:1889`, `:1904`).
|
||
No-arg wrapper `:1990` used by every non-draw entry point.
|
||
|
||
### 2.4 `SyncTextureParamsToBackend` / `SyncBuiltinSamplerToBackend`
|
||
|
||
- `SyncBuiltinSamplerToBackend` — `Managers.cpp:6671-6780`. Gate `m_syncedSamplerVersion == version &&
|
||
!m_forceSamplerResync` (`:6684`). Multisample targets: cache and return without any GL
|
||
(`:6706-6709`). Then min/mag filter (`:6732-6744`, via `ResolveBackendMinFilter`), wrap S/T
|
||
(`:6749-6750`), wrap R only when `SupportsWrapR` (`:6751-6755`), compare func/mode (`:6756-6757`),
|
||
min/max LOD (`:6758-6765`), max anisotropy behind `SupportsTextureFilterAnisotropy` (`:6766-6775`).
|
||
**All of it via `glTexParameter*` on the texture object, after `Bind(target)` at `:6711`.**
|
||
- `SyncTextureParamsToBackend` — `Managers.cpp:6782-6978`. Gate
|
||
`m_syncedTextureParamsVersion == version && !m_forceTextureParamsResync` (`:6794`);
|
||
multisample refresh-but-skip at `:6821-6825`; `Bind(target)` `:6827`; base/max level `:6837-6847`;
|
||
swizzle with the alpha-widening substitution `:6852-6864` and the image-widening substitution
|
||
`:6865-6879`; border colour (all three forms + form, `:~6900-6954`); `GL_DEPTH_STENCIL_TEXTURE_MODE`
|
||
`:6956-6977`.
|
||
|
||
Both `Bind()` on `TempTextureUnit` (unit 0), which is why `BindCurrentTextures` has to re-establish the
|
||
real unit bindings afterwards (`DirectGLES.cpp:3687-3693`).
|
||
|
||
### 2.5 `SyncMipmapsToBackend` — `Managers.cpp:5672-…`
|
||
|
||
- Texture-view short-circuit `:5683-5686` → `SyncTextureViewToBackend`.
|
||
- **Cheap gate** `:5700-5707`: `m_isInitialized && shapeContextId && ctx live && shapeContextId ==
|
||
GetTextureContextId() && shapeGeneration == GetSamplingResolutionGeneration() && contentVersion ==
|
||
GetContentVersion() && shapeParamsVersion == GetTextureParamsVersion() && StorageType == Mipmap`.
|
||
- `IsComplete() || HasAnyDefinedMipmapLevel()` bail `:5734-5738`.
|
||
- **Second fast path** `:5751-5778`: content version unchanged + a `StateTextureBasicInfo` probe equal to
|
||
`m_prevTextureInfo` → re-stamp the cheap gate and return without the scratch bind.
|
||
- `Bind(target)` `:5780`; `currentTextureInfo` built `:5785-5792`.
|
||
- `TextureStorageType::Mipmap` `:5794-6503`:
|
||
- `needsRegeneration` `:5800`; immutable storage forces `RecreateBackendTexture()` `:5801-5804`.
|
||
- `imageWidening` = `GetImageBindableStorageWidening(format)` only when `m_imageBindableStorageRequired`
|
||
(`:5810-5813`).
|
||
- **`canAppendMipmaps`** `:5815-5827` (append-only level growth) → the `glTexImage2D/3D` append loop
|
||
`:5833-5910`.
|
||
- Full regeneration `:5912-…`: `ApplyImageBindableStorageWidening` `:5920`; multisample clamp
|
||
`:5923-5931`.
|
||
- **The per-level upload loop** `:6184-6500` — see §2.7.
|
||
- `TextureStorageType::Buffer` `:6504-…`: **calls `BufferImpl::EnsureBufferResource(buffer)` at
|
||
`:6520`** (the one place a texture sync pulls a buffer twin — and therefore the one texture path that
|
||
reaches `SyncPersistentMappedRange`, see §5), then `CallTexBuffer` / `CallTexBufferRange`
|
||
(`:6589-6598`), and mints/points `m_bufferImageSplitViewId` for the image split (`:6606-6630`,
|
||
rationale `:6533-6549`).
|
||
|
||
`SyncTextureViewToBackend` — `Managers.cpp:5579-5670`: syncs the storage owner first (`:5600-5601`,
|
||
by-value SharedPtr for the same rehash reason), short-circuits when
|
||
`m_viewSourceBackendTextureId == storageBackendTextureId` (`:5613-5616`), otherwise
|
||
`RecreateBackendTexture()` (`:5620`, which also bumps `g_attachmentBackendIdGeneration`) and calls
|
||
`ResolveTextureViewEntryPoint()` (`:5630-5634`). `StampViewSyncKeys` `:5569-5577`.
|
||
|
||
### 2.6 Where an attachment-only / image-unit / CopyImage texture gets — or misses — its parameters
|
||
|
||
**This is the exact answer the task asks for.**
|
||
|
||
| how the texture is reached | code path | gets `SyncTextureParamsToBackend` + `SyncBuiltinSamplerToBackend`? |
|
||
|---|---|---|
|
||
| bound to a sampler unit ≤ high-water mark | `SyncNeccessaryTextures` unit list, `DirectGLES.cpp:1898-1900` (hit) / `:1919-1920` → `SyncTextureObjectToBackend` (miss) | **yes** |
|
||
| attachment of the **DRAW** framebuffer | `SyncNeccessaryTextures` FBO list, `DirectGLES.cpp:1963-1965` (hit) / `:1974-1975` (rebuild) | **yes** |
|
||
| attachment of the **READ** framebuffer only | `SyncCurrentFBO` `:2304-2310` → `SyncToBackend` → `SyncAttachmentObject` `Managers.cpp:7142-7194`, which calls **`SyncMipmapsToBackend` at `:7161` and nothing else** | **NO** — storage is synced, parameters are not. `SyncNeccessaryTextures`'s attachment list reads only `GetFramebufferBindingSlotChecked(FramebufferTarget::Draw)` (`DirectGLES.cpp:1944`) |
|
||
| bound to an image unit | `SyncImageTextureBinding` → `SyncTextureObjectToBackend(imageBinding.Texture, /*imageBindable=*/true)` (`DirectGLES.cpp:2071`) | **yes**, and `RequireImageBindableStorage` additionally forces `m_forceTextureParamsResync = true` (`Managers.cpp:4787`) so the swizzle override lands |
|
||
| a `glCopyImageSubData` endpoint | `MakeGLESCopyImageEndpoint` → `SyncTextureObjectToBackend(endpoint.Texture)` (`DirectGLES.cpp:7588`); renderbuffer endpoint → `SyncRenderbufferObjectToBackend` (`:7547-7562`, `:7567`) | **yes** |
|
||
| `GenerateMipmap` target | `DirectGLES.cpp:7433` / `:7436` → `SyncTextureObjectToBackend` | **yes** |
|
||
| `GetTexImage` source | `DirectGLES.cpp:9678` `SyncNeccessaryTextures()` — i.e. only if it is unit-bound or a DRAW attachment; then a bare `Find` at `:9693` | **only incidentally** |
|
||
|
||
So today parameters ride on the *unit binding* and on the *draw* attachment set. `MGPTextureParams`
|
||
(`MGPipeTypes.h:306-318`) is per-`Res` and independent of any view, which is the fix — but the
|
||
implementer must not assume the current behaviour is complete: the READ-attachment-only case is a
|
||
pre-existing gap that P4a either preserves (bug-for-bug) or closes deliberately, and the choice must be
|
||
written down. The corresponding negative control is easy: a texture attached only as READ with a
|
||
non-default swizzle / `DEPTH_STENCIL_TEXTURE_MODE`, read back with `glReadPixels`.
|
||
|
||
### 2.7 Upload shapes — whole box vs rect vs jobs, and the PBO unpack ring
|
||
|
||
Per dirty (uploadTarget, level) in `Managers.cpp:6185-6499`:
|
||
|
||
1. Conversion fallbacks first, each of which takes the level **off** the sub-rect path by pointing
|
||
`uploadData` at a fresh buffer: `PrepareFallbackUpload` (`:6215-6217`),
|
||
`PreparePackedNormUpload` (`:6218-6220`), `PrepareImageWidenedUpload` (`:6225-6227`, defined `:5442`).
|
||
2. `subRectEligible` — `:6241-6246`: `uploadData == mipData` (unconverted) **and** a non-empty dirty
|
||
region that does not cover the whole level **and** whole-texel byte size **and** the backend upload
|
||
size equals the shadow texel size. `bpp = byteSize / texelCount` (`:6247`).
|
||
3. Union box (`GetStorageDirtyRegion`, `:6237`) vs **scatter rects** (`GetStorageDirtyRects`, `:6271-6273`,
|
||
bounded by `MipmapStorage::kMaxDirtyRects`). **`dirtyRectCount` is forced to 0 whenever the unpack ring
|
||
is available** (`:6280-6282`) — the comment records the measured +6 ms/frame Mali cliff from ~100
|
||
one-rect jobs; "One box, one job".
|
||
4. **Unpack-ring staging plan**, decided once for the level (`:6290-6353`): three shapes matching the
|
||
three branches — N rects (`:6310-6322`), one box (`:6323-6331`), whole level verbatim (`:6332-6353`).
|
||
`StageBlocksIntoUnpackRing` (`Managers.cpp:4947-5001`, `UnpackStagingBlock` `:4935-4945`) repacks
|
||
**tightly**, which is why the ring path issues no `glPixelStorei` at all — the surrounding
|
||
`ScopedDefaultUnpackState` already holds ROW_LENGTH/IMAGE_HEIGHT at 0 (`:6296-6302`).
|
||
`UnpackRingPixelOffset` `:5003-5006`.
|
||
5. `ringStaged` → `BindPixelUnpackBufferId(UnpackRingBufferId())` `:6357-6359`, restored to 0 at
|
||
`:6492-6497`.
|
||
6. **Stats** `:6360-6395`: `ByteClass::StageTexture`, `CallClass::TextureUploadEmissions`,
|
||
`TextureUploadRectEmissions` / `TextureUploadBoxEmissions`, `TextureUploadJobs` (rects → N jobs,
|
||
box → 1). *"the box/rect split is counted separately from the bytes on purpose: SSIM is blind to it"*.
|
||
7. The dispatch `:6396-6491`: 2D/CubeMap `glTexSubImage2D` in three shapes (`:6399-6431`),
|
||
3D/2DArray/CubeMapArray `glTexSubImage3D` in three shapes (`:6438-6485`).
|
||
|
||
The ring itself is `BufferImpl`'s: `g_unpackRing` (`Managers.cpp:866-872`), `kUnpackRingInitialBytes`
|
||
4 MiB / `kUnpackRingMaxBytes` 64 MiB / alignment 64 (`:803-810`), `UnpackRingAvailable`
|
||
(`:3466-3469`, honours `MG_Config::Features.EsprytDisableUnpackRing`), `UnpackRingAllocate` (`:3471-3476`),
|
||
`UnpackRingMappedPtr`/`BufferId`/`MaxBytes` (`:3478-3480`), `UnpackRingOnPresent` (`:3482`),
|
||
`ResetRingForNewContext` (`:2577`). `ScopedDefaultUnpackState` `Managers.cpp:4851-4880`.
|
||
|
||
Widening helpers: `GetWidenableClientComponentCount` / `IsIntegerWidenableFormat` /
|
||
`PrepareChannelWidenedUpload` / `PreparePackedIntWidenedUpload` (`Managers.h:1292-1318`);
|
||
`ShouldUseCaveatTextureFormat`, `BackendTextureFormatAddsAlpha`, `GetImageBindableStorageWidening`
|
||
(`Utils.h:63`, `:69`, `:130-161`; `Utils.cpp:245`, `:270`, `:280-…`).
|
||
Target mapping: `MapToBackendTextureTarget` (`Managers.h:1243-1253`), `GetBackendUploadSize`
|
||
(`:1273-1278` — 1D arrays move layers from y to z).
|
||
|
||
### 2.8 Image units and the format recast
|
||
|
||
`SyncImageTextureBinding(unit)` — `DirectGLES.cpp:2057-2135`: high-water mark `g_imageUnitHighWaterMark`
|
||
(`:2055`), `TrackWritableImageBufferUnit` (`:2062`, counter at `:2040-2047`), unbind path `:2066-2069`,
|
||
`SyncTextureObjectToBackend(tex, true)` `:2071`, layered/layer resolution via
|
||
`SupportsLayeredImageBinding` (`:2072-2074`, rationale `:1992-1999`), the buffer split view
|
||
(`:2120-2124`), and the **bind-format recast** (`:2126-2132`, rationale `:2075-2083`: a `GL_RG32F` bind
|
||
is `INVALID_VALUE` on 19/26 non-core formats on Adreno and 25 on both Malis).
|
||
`SyncImageTextureBindings()` `:2169-2179`; the per-draw gate
|
||
`SyncImageTextureBindingsForDraw(keys)` `:2202-2214`, keyed on `(contextId, samplingGeneration,
|
||
g_backendContextGeneration)` with the deliberate note (`:2195-2201`) that the key is the frontend
|
||
sampling-resolution generation and **not** the backend re-mint counter.
|
||
`MarkWritableImageBufferTexturesGpuWritten()` `:2149-2167`.
|
||
|
||
### 2.9 Re-mint and the FBO coupling
|
||
|
||
`RecreateBackendTexture()` — `Managers.cpp:4790-4840`: `ScratchFBOImpl::NoteTextureIdDeleted`
|
||
(`:4792`), **`++FramebufferImpl::g_attachmentBackendIdGeneration`** (`:4797`), delete under the matching
|
||
context generation (`:4798-4800`), scrub `g_boundTexturesCache` (`:4801-4807`), regen (`:4810`), reset
|
||
`m_isInitialized` / `m_backendStorageImmutable` / `m_prevTextureInfo` (`:4818-4820`), reset **every**
|
||
parameter cache and set both force flags (`:4829-4839`).
|
||
`RequireImageBindableStorage` — `Managers.cpp:4752-4788`: sticky, clears `m_isInitialized`,
|
||
**re-marks every already-uploaded level dirty** (`:4769-4779`), sets `m_forceTextureParamsResync`
|
||
(`:4787`). `g_attachmentBackendIdGeneration` is defined at `Managers.cpp:7763`.
|
||
Destructor `Managers.cpp:4693-4727` (note the `/sdcard/MG/exp_leak_texture_deletes` experiment latch at
|
||
`:4711-4718` — an existing temp-experiment hook in the destructor).
|
||
|
||
### 2.10 CopyImage
|
||
|
||
`CopyImageSubData` — `DirectGLES.cpp:7696-…`; endpoint resolution `MakeGLESCopyImageEndpoint`
|
||
`:7564-7603` (renderbuffer at `:7566-7573`, texture at `:7587-7602`, with the explicit *no* axis remap
|
||
for 1D arrays at `:7592-7598`); `GLESCopyImageEndpoint` at `:7530-7542`;
|
||
`GetCopyImageEndpointFormat` `:7605-7608`; `CanMirrorCopyImageShadow` `:7613-7617` and the CPU-shadow
|
||
mirror below it (`:7619-…`, `:7790` "the CPU shadow is what glGetTexImage answers from, so it has to
|
||
follow the same move"). The layered-blit substitute reuses `glCopyImageSubData` at
|
||
`DirectGLES.cpp:6288`, `:6406`.
|
||
|
||
### 2.11 GenerateMipmap
|
||
|
||
`GenerateMipmap(target)` — `DirectGLES.cpp:7417-7462`.
|
||
`EnsureGenerateMipmapStorageAllocated` (`:6707-6752`) for R11F_G11F_B10F / depth-only / RGB16F / RGB32F;
|
||
`GenerateThreeChannelFloatMipmapOnCpu` (`:7432-7435`, filtered on the CPU so dirty levels ride down with
|
||
the sync); `GenerateDepthTexture2DMipmap` (`:7439`), `GenerateColorTexture2DMipmap` (`:7444`);
|
||
`ScopedDetachedTextureFramebufferAttachments` (`:7453`) then `ScopedCompleteFramebufferBinding` (`:7455`)
|
||
then `glGenerateMipmap` (`:7460`).
|
||
|
||
---
|
||
|
||
## 3. The renderbuffer twin
|
||
|
||
`RenderbufferImpl::BackendRenderbufferObject` — `Managers.h:2235-2255`. Members:
|
||
`m_backendRBOId`, `m_contextGeneration`, `m_isInitialized`, `m_cacheInternalFormat`, `m_cacheWidth`,
|
||
`m_cacheHeight`, `m_cacheSamples` (`:2248-2254`).
|
||
|
||
- ctor `Managers.cpp:10585-10597` (`glGenRenderbuffers`, stamps `g_backendContextGeneration`).
|
||
- dtor `Managers.cpp:10599-10612`; no binding shadow exists (`:10606-10607` — `Bind()` always issues).
|
||
- `Bind()` `Managers.cpp:10614-10619` — unconditional `glBindRenderbuffer`.
|
||
- `SyncToBackend` `Managers.cpp:10621-10693`: **storage respecify is memoised on the four-field cache**
|
||
(`:10634-10640`); otherwise `Bind()`, `GenerateRenderbufferFormatInfo` (`:10650`),
|
||
`ClampSamplesToBackendSupport` for the multisample form (`:10664-10668`) or
|
||
`glRenderbufferStorage` (`:10670-10671`), and an explicit `GL_OUT_OF_MEMORY` probe that reports
|
||
`ErrorCode::OutOfMemory` to the application (`:10673-10683`) — the deferred-allocation model means the
|
||
error lands on whatever entry point triggered the sync (`:10652-10658`).
|
||
- Table `Managers.cpp:10695-10696`. Reached from `SyncAttachmentObject` (`Managers.cpp:7195-7213`),
|
||
from `SyncRenderbufferObjectToBackend` (`DirectGLES.cpp:7547-7562`, used by CopyImage), and ticked by
|
||
`SyncCurrentFBO` (`DirectGLES.cpp:2235`).
|
||
- **Lifetime id / death**: `RenderbufferObject` raises `NotifyStateObjectDestroyed`; the consumer arm is
|
||
`Managers.cpp:208-210`.
|
||
|
||
Frontend members read: `GetInternalFormat` (7 sites), `GetExternalIndex` (4), `GetWidth`, `GetHeight`,
|
||
`GetSamples` (2 each).
|
||
|
||
---
|
||
|
||
## 4. Readback paths, and what a server without the frontend address space needs
|
||
|
||
### 4.1 `ReadPixels` — `DirectGLES.cpp:9488-9636`
|
||
|
||
`IsLegacyNativeReadPixelsFormat` / `…Type` / `IsSupportedDepthStencilReadPixelsPair`
|
||
(`:9455-9486`) decide `useNativeReadback`; `GetReadbackChannelMapping` + `GetReadbackDstPixelSize`
|
||
decide `convertible` (`:9497-9499`). Then:
|
||
`SyncNeccessaryTextures()` `:9507`, `SyncCurrentFBO()` `:9510`, `BindCurrentFBO(Read)` `:9513`,
|
||
`ScopedPackState packParamsScope(PackStateFromContext())` `:9516`,
|
||
`glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)` `:9518-9524`.
|
||
`packSwapBytes` `:9530`, `forceOpaqueAlpha = IsAlphaWidenedFallbackReadAttachment()` `:9533`,
|
||
`nativeFastPair` `:9537-9539`; conversion path `:9540-9550`; depth/stencil helpers `:9555-9560+`;
|
||
the pack-PBO resolution `GLuint packBufferId` `:9575-9585` (**via
|
||
`BufferImpl::EnsureBufferResource(pixelPackBufferObject)` at `:9577`**), `ScopedPixelPackBuffer` `:9589`,
|
||
`glReadPixels` `:9593`, the retry-on-error via conversion `:9594-9617`, and the
|
||
**PBO → frontend shadow writeback** `:9618-9634` (`WritebackFromBackend` `:9626` +
|
||
`BumpBufferMutationEpoch()` `:9628`).
|
||
|
||
### 4.2 `GetTexImage` — `DirectGLES.cpp:9655-…`
|
||
|
||
`IsNativeGetTexImagePair` `:9640-9653`; `SyncNeccessaryTextures()` `:9678`; `SyncCurrentFBO()` `:9681`;
|
||
resolves the texture off the **active unit's binding slot** (`:9683-9691`) and does a bare
|
||
`g_backendTextureObjects.Find` (`:9693-9702`) — it does **not** create a twin, it bails.
|
||
Then the cross-FBO resolve barrier note (`:9704-9709`), widening probes at `:9810`, `:9818`,
|
||
the temp-FBO + `ReadPixelsViaFormatConversion` route (`:9933`, `:9956`), the pack-PBO resolution
|
||
`:10001-10011` and the PBO writeback `:10029-10043`.
|
||
|
||
**The shadow fallback**: `GetTexImageViaShadowConversion` — `DirectGLES.cpp:9244-9300+`. Valid *only*
|
||
while the CPU shadow is authoritative, which holds for non-renderable formats (`:9246-9247`). Reads
|
||
`textureMipmapObject->MapMipmapData(uploadTarget, level)` (`:9268`), takes the raw-packed shortcut when
|
||
`IsRawPackedPixelTransfer` (`:9277-9287`, so an RGB9_E5 shared exponent is not canonicalised), else
|
||
`DecodeShadowDataToWideRGBA` (`:9292-9297`) then `ReadbackImpl::StoreWideRowsToClient`.
|
||
|
||
`ReadPixelsViaFormatConversion` — `DirectGLES.cpp:9015-9242`; the fixed-point read clamp `:9204-9231`.
|
||
`ReadbackImpl` client-store walks: `Utils.cpp:1973-2404`, `StoreWideRowsToClient` `Utils.cpp:2362`,
|
||
`StorePackedWordsToClient` `Utils.cpp:2387` (declarations `Utils.h:188-246`); the PBO writeback inside
|
||
the shared row walk is `Utils.cpp:2343-2352`.
|
||
`StoreReadbackRowsToClient` (depth/stencil destination walk) — `DirectGLES.cpp:8040-8073`, PBO writeback
|
||
`:8062`, epoch bump `:8068`.
|
||
|
||
### 4.3 The scoped driver-state guards (the readback-state-guards architecture)
|
||
|
||
`DirectGLES.cpp:6545-6644` and `:6795-6900+`:
|
||
`ScopedFramebufferBinding` `:6555-6573` (saves/restores through `CurrentFramebufferBinding` +
|
||
`BindFramebufferId`, never `glGetIntegerv`), `ScopedPackState` `:6576-6588`, `PackStateFromContext`
|
||
`:6592-6596`, `ScopedPixelPackBuffer` `:6601-6607` (always returns the binding to 0),
|
||
`ScopedScissorDisable` `:6612-6626` (reads the render-state shadow), `TempFBOBinder` `:6631-6644`,
|
||
`ScopedCompleteFramebufferBinding` `:6795-6803`,
|
||
`ScopedDetachedTextureFramebufferAttachments` `:6805-…` — **the one direct-iteration site over a twin
|
||
table**: push arm uses `g_backendFramebufferObjects.ForEachLive` (`:6877-6884`), legacy arm walks
|
||
`begin()/end()` and must test `stateRef.expired()` by hand (`:6885-6895`), pull arm keeps the pre-P2 walk
|
||
verbatim (`:6896-6899`). `SlotTables.h:61-68` says the per-entry `weak_ptr` survives **only** for this
|
||
caller. `ScopedDepthBlitState` `:6985`.
|
||
`PixelStoreImpl` (PACK shadow): `Managers.h:1792-1806`, `Managers.cpp:8004-8054`
|
||
(`ApplyPackState` `:8019`).
|
||
`ScratchFBOImpl`: `Managers.h:1734-1781`, `Managers.cpp:7767-8002`
|
||
(`EnsureId` `:7833`, `EnsureColorAttachment2D` `:7847`, `EnsureCompleteTinyFramebufferId` `:7960`,
|
||
`NoteTextureIdDeleted` `:7984`).
|
||
|
||
### 4.4 What a server without the frontend address space needs
|
||
|
||
Enumerated so the implementer does not have to re-derive it:
|
||
|
||
1. **A destination that is not a client pointer.** Every readback today writes either into the
|
||
application's `void* pixels` or into a frontend `BufferObject`'s CPU shadow via
|
||
`WritebackFromBackend` (four sites, §5). `MGPReadbackInfo` (`MGPipeTypes.h:748-756`) already carries
|
||
`DstOffset`/`DstSize` and is `kReplySlot`, so the reply slot is the destination — but the
|
||
**pack-PBO** case is different: the driver writes into the *backend* buffer store and the frontend
|
||
shadow then has to be refreshed. That refresh is `WritebackFromBackend` + `BumpBufferMutationEpoch`,
|
||
both frontend calls, and is P4a's hardest readback item.
|
||
2. **PACK pixel-store parameters as payload.** `PackStateFromContext` (`DirectGLES.cpp:6592-6596`) and
|
||
the row walks read `MGB_CTX->GetPixelStoreParameters(false)` directly
|
||
(`DirectGLES.cpp:9530`, `:8042`, `Utils.cpp:~2300`). `SetPixelPackState`/`MGPPixelPackState` exists
|
||
in the catalogue (`PipeCalls.def:126`) — P4a has to actually push it, including the four ES-less
|
||
fields (`IMAGE_HEIGHT`, `SKIP_IMAGES`, `SWAP_BYTES`, `LSB_FIRST`; `Managers.h:1788-1791` says they are
|
||
honoured on the CPU from frontend state).
|
||
3. **The resolved read surface**, not the read-buffer enum: `MGPFramebufferState::ReadSurface`.
|
||
The three helpers `IsFixedPointFallbackReadAttachment`, `IsAlphaWidenedFallbackReadAttachment`,
|
||
`GetReadColorAttachment` (`Managers.cpp:7280-7408`) all currently walk
|
||
`MGB_CTX->GetFramebufferBindingSlot(Read)`; with the inline `InternalFormat` on `MGPSurface`
|
||
(`MGPipeTypes.h:344`) they become pure functions of the pushed record.
|
||
4. **The texture CPU shadow.** `GetTexImageViaShadowConversion` reads `MapMipmapData`
|
||
(`DirectGLES.cpp:9268`) and the CopyImage mirror writes it (`DirectGLES.cpp:~7790`,
|
||
`MarkStorageDirty`). A split server has no shadow. Either the shadow stays client-side and the server
|
||
answers only from the driver, or a mirror is added — this is the texture analogue of P8's index host
|
||
mirror and should be recorded, not solved, in P4a.
|
||
5. **Error reporting.** `BackendRenderbufferObject::SyncToBackend` calls `MGB_CTX->RecordError`
|
||
(`Managers.cpp:10677-10682`); `GenerateMipmap` calls `RecordGLError` (`DirectGLES.cpp:7461`). Both
|
||
need the reverse channel.
|
||
|
||
---
|
||
|
||
## 5. `SyncPersistentMappedRange` / `SyncGpuWrites` sites still touching textures
|
||
|
||
Full census in `MG_Backend/DirectGLES` (the grep is exhaustive):
|
||
|
||
| site | file:line | texture-related? |
|
||
|---|---|---|
|
||
| `drawBuffer->SyncPersistentMappedRange()` (indirect) | `DirectGLES.cpp:301`, `:5117-5118`, `:5219-5220` | no (indirect buffers) |
|
||
| `indexBuffer->SyncPersistentMappedRange(); SyncGpuWrites()` | `DirectGLES.cpp:4863-4864`, `MultiDraw.cpp:511-512` | no |
|
||
| `bufferObject->SyncPersistentMappedRange()` inside the ensure path | `Managers.cpp:2799`, `:2976` (D-N note `:2703-2718`) | **reached from the texture path**: `SyncMipmapsToBackend`'s buffer-texture branch calls `BufferImpl::EnsureBufferResource(buffer)` at `Managers.cpp:6520` |
|
||
| `bufferObject->SyncGpuWrites()` | `Managers.cpp:4424`, `:4549` (fp64 narrowing; ID-15 M-3 is the RULED P3a deviation) | no |
|
||
| `MarkBufferGpuWritten` / `MarkGpuWritten` for writable image **buffer textures** | `DirectGLES.cpp:2162-2164` (from `MarkWritableImageBufferTexturesGpuWritten`, `:2149-2167`) | **yes — the only texture-driven GPU-write announcement** |
|
||
| SSBO / atomic-counter marks | `DirectGLES.cpp:506-510`, `:556-558` | no |
|
||
| `WritebackFromBackend` (pack PBO) | `DirectGLES.cpp:8062`, `:9626`, `:10036`, `Utils.cpp:2343` | **yes — these are the readback writebacks**, each paired with `BumpBufferMutationEpoch()` (`:8068`, `:9628`, `:10038`, `Utils.cpp:2351`) |
|
||
| `WritebackFromBackend` (XFB capture) | `DirectGLES.cpp:1017`, `:1125` | no |
|
||
| `Ops_ReadbackFromGpu` | `Managers.cpp:1726-1761`, tracked wrapper `:1828-1829`, table row `:2300` | no |
|
||
|
||
So for P4a: **one** texture-owned `SyncPersistentMappedRange` reach (the buffer-texture backing store, via
|
||
`EnsureBufferResource`), **one** texture-owned GPU-write mark (writable image buffer textures), and
|
||
**four** readback writebacks that are texture/framebuffer-triggered but land on buffers.
|
||
`MarkBufferGpuWritten` is `Managers.cpp:2491-2520` (declared `Managers.h:753-763`); the
|
||
`BufferImpl::CurrentBufferMutationEpoch` / `BumpBufferMutationEpoch` contract is documented at length in
|
||
`Managers.h:558-593` — note it explicitly lists *"every pack-PBO `WritebackFromBackend` site
|
||
(glReadPixels/glGetTexImage)"* (`Managers.h:579`) as a bumping path.
|
||
|
||
---
|
||
|
||
## 6. Tests and traces
|
||
|
||
### 6.1 Unit tests (label `unit`)
|
||
|
||
- **`MG_Test/SanityTest.cpp`** (4378 lines) — the slot-table suite `DirectGLESSlotTable`:
|
||
`ARecycledSlotIsANewHandleAndTheStaleOneResolvesToNothing` `:3316`,
|
||
`RepeatedLookupsOfALiveObjectKeepOneHandle` `:3358`,
|
||
`ANegativeLookupIsNotCachedAcrossAnotherHoldersAcquire` `:3387`,
|
||
`FindNeverMutatesTheTable` `:3416`,
|
||
`AWholeTableSavesResetsAndRestores` `:3450`,
|
||
`TwoTablesOfTheSameKindShareOneSlotAndKeepTheirOwnTwin` `:3491`,
|
||
`OneDeathNoticeDropsTheTwinInEveryHolderOfTheKind` `:3537`,
|
||
`AnnouncedDeathKeepsObjectChurnFromAccumulatingWithoutASweep` `:3596`,
|
||
`GetOrCreateToleratesANullStateObject` `:3635`,
|
||
`AnAnnouncedDeathReturnsTheSlotWithoutASweep` `:3662`,
|
||
`EveryReKeyedObjectClassAnnouncesItsOwnDeath` `:3711`,
|
||
`TheHandleArmInstallsTheDeathNoticeConsumer` `:3777`,
|
||
`TheTwinRegistryCasesInThisBinaryRunOnTheHandleArm` `:3795`,
|
||
**`EverySwitchedOverKindResolvesItsTwinThroughTheHandleArm` `:3878`** — drives Texture, Framebuffer,
|
||
Renderbuffer, SamplerCso, ShaderCso, VertexElementsCso through
|
||
`ExpectTheHandleArmDrivesThisKind` (`:3888-3899`) and then the seventh (Buffer) table by hand
|
||
(`:3908-3958`). **This is the case P4a extends** for the kinds it converts to `GetOrCreate(handle)`.
|
||
`ASavedCopyOfARealRegistryDropsTheTwinOnTheSameNotice` `:3963`,
|
||
`AnArmlessKnobCombinationStopsInsteadOfSkippingTheLane` `:4004`,
|
||
`TheArmlessCasesLeaveTheLogPathAndTheConfigAsTheyFoundThem` `:4061`.
|
||
Fixture `ScopedDirectGLESTextureBindings` `:218-260` — **the second live holder of kind Texture**
|
||
(saves `g_backendTextureObjects` by value at `:224`, resets with `= {}` at `:228`, restores at `:242`),
|
||
used at `:353`, `:404`, `:2699`, `:3065`. `EsprytSlotArmEnvironment` `:90-103`.
|
||
- **`MG_Test/Framebuffer/FramebufferTest.cpp`** — 60+ `FramebufferTest.*` cases; the ones P4a's
|
||
framebuffer record must keep green: `NamedFramebufferTextureAttachesWithoutChangingBindings` `:244`,
|
||
`FramebufferTextureBumpsAttachmentVersionOnlyOnce` `:289`,
|
||
`ReadPixelsAllowsPersistentMappedPixelPackBuffer` `:310`,
|
||
`NamedFramebufferDrawBuffersDoNotModifyDefaultFramebuffer` `:446`,
|
||
`DefaultFramebufferProvidesTextureAttachmentsForFrontAndBackAliases` `:492`,
|
||
`FramebufferTexture3DAttachesSliceWithLayerTracking` `:825`,
|
||
`ThreeChannelColorAttachmentsAreCompleteThroughTheWidenedSubstitution` `:961`,
|
||
`TwoAttachmentCompositeFramebufferMatchesIrisComplementaryPass` `:972`,
|
||
**`WidenedDrawBufferIsIdentifiedPerDrawBufferSlotNotPerAttachmentPoint` `:1194`**,
|
||
`DrawIntoAWidenedDrawBufferReachesTheDriverWithAlphaWritesMaskedOff` `:1227`,
|
||
`ClearIntoAWidenedDrawBufferKeepsAlphaWritableAndSubstitutesOne` `:1263`,
|
||
plus the `PackedReadbackEncodeTest.*` block `:645-769` (pure readback encoders) and
|
||
`ReadPixelsRejectsIntegerFormatMismatchWithReadBuffer` `:785`.
|
||
- **`MG_Test/Texture/TextureTest.cpp`** — 192 cases. Directly load-bearing for P4a:
|
||
`DepthStencilTextureModeIsBackendVisibleThroughTheParamsVersion` `:2609`,
|
||
`ShadowReadbackRefusesALayoutItCannotProduceInsteadOfOverrunningTheBuffer` `:1017`,
|
||
`ShadowReadbackRefusesAPackStateItCannotHonour` `:1058`,
|
||
`GetTexImageReadsALevelWhoseLowerLevelsWereNeverDefined` `:1971`,
|
||
`GetTexImageOfOneCubeFacePacksIntoAOneFacePixelPackBuffer` `:2096`,
|
||
`CopyImageSubDataRejectsALevelTheTextureDoesNotHave` `:2531`,
|
||
`TextureStorage2DMultisampleTracksNamedObjectState` `:1921`,
|
||
the border-colour trio `:610`, `:656`, `:698`, and the compressed-upload/PBO block `:2657-2812`.
|
||
`MG_Test/Texture/TextureViewTest.cpp`, `MG_Test/Texture/VkClearManagerTest.cpp`.
|
||
- `MG_Test/Backend/DirectGLES/` holds only `BaseInstanceInjectionTest.cpp`,
|
||
`EsslShaderPassTest.cpp`, `ViewportIndexRoutingTest.cpp` — **there is no backend-side framebuffer or
|
||
texture unit test today**; P4a's emitter tests belong in `MG_Test/Pipe/` beside
|
||
`ResourceEmitTest.cpp` and `VertexInputEmitTest.cpp` (the P3a shape), which is also where the
|
||
`PipeCatalogueTest.cpp` size pins live.
|
||
|
||
### 6.2 Integration scenarios (label `integration-gpu` / `integration-verify`)
|
||
|
||
Registered in `MG_IntegrationTest/CMakeLists.txt`; the framebuffer/texture/renderbuffer set with its
|
||
case names:
|
||
|
||
- **`HandleRecycleScenario`** (`:979-1015+`, four+ pinned lanes): `TheReproducerRecyclesEveryName`,
|
||
`AVertexArrayAtARecycledAddressDoesNotInheritItsPredecessorsVertexInput`,
|
||
`ABufferAtARecycledAddressDoesNotInheritItsPredecessorsContents`,
|
||
**`ATextureAtARecycledAddressDoesNotInheritItsPredecessorsTwin`** (`Scenarios/HandleRecycleScenario.cpp:869`),
|
||
**`AFramebufferAtARecycledAddressDoesNotInheritItsPredecessorsTwin`** (`:920`),
|
||
`DestroyedVertexArraysReturnTheirVertexElementsSlots`.
|
||
P4a's G8 analogue: add a renderbuffer case and a sampler case here.
|
||
- `LayeredAttachmentShapeScenario` (8 cases: `LayeredThreeDColorAttachmentReachesEverySlice`,
|
||
`NonLayeredThreeDSliceAttachmentWritesOnlyThatSlice`,
|
||
`LayeredCubeMapArrayColorAttachmentReachesEveryLayerFace`,
|
||
`LayeredCubeMapArrayDepthAttachmentGatesEveryLayerFace`,
|
||
`LayeredCubeMapArrayDepthStencilAttachmentGatesEveryLayerFace`,
|
||
`LayeredCubeMapClearMaterialisedBySamplingReachesEveryFace`,
|
||
`LayeredOneDArrayClearMaterialisedBySamplingReachesEveryLayer`,
|
||
`CubeMapFaceReadbackAnswersTheFaceItWasAskedFor`) — **the direct gate on `MGPSurface`'s
|
||
`Layered`/`Level`/`Layer`/`UploadTarget`.**
|
||
- `LayeredTextureReadbackScenario` (2), `LayeredAttachmentBarrierScenario`.
|
||
- `ThreeChannelAttachmentScenario` (`ThreeChannelColorAttachmentsReportComplete`,
|
||
`WidenedAttachmentReadsBackOpaqueWhileItsNeighbourKeepsItsAlpha`,
|
||
`DstAlphaBlendingSeesOneInAWidenedAttachment`) — the alpha-widening triple.
|
||
- `SnormAttachmentScenario`, `RenderbufferBlendFormatScenario` (3 cases — the only renderbuffer-specific
|
||
scenario), `DepthStencilReadbackScenario` + `…MatrixScenario` + `…AttachmentShapeScenario`
|
||
(one lane, `TEST_FILTER "DepthStencilReadback*Scenario.*"` at `:639`),
|
||
`PackedWordReadbackScenario`, `PixelStoreSweepScenario`,
|
||
`ClearThenReadPixelsScenario` (5 cases, incl. `ABlitIntoTheDefaultFramebufferSurvivesAnEarlierClear`
|
||
and `AMultisampleResolveBlitIntoTheDefaultFramebufferKeepsItsOrientation`),
|
||
`ClearTexImageUndefinedLevelZeroScenario`,
|
||
`CopyImageLevelRangeScenario` / `CopyImageLayeredScenario` / `CopyImagePacked16Scenario` (own lane `:806`),
|
||
`TextureViewScenario` (8 cases, incl. `BetterCloudsCoveragePipeline` and `CoherencyIsBidirectional`),
|
||
`BufferTextureScenario`, `ImageSizeAfterRespecScenario`
|
||
(`ADrawSeesTheNewSizeOfARespecifiedImageTexture` — the `RecreateBackendTexture` /
|
||
`g_attachmentBackendIdGeneration` gate), `SampledSetStalenessScenario` (2 cases — the
|
||
sampling-resolution-generation gate), `ImageTargetKindScenario`, `ImageLoadStoreSsoScenario`,
|
||
`NonCoreImageFormatScenario`, `FormatlessImageBakeScenario`, `ImageFormatQualifierScenario`,
|
||
`UnboundImageDescriptorScenario`, `IntegerBorderColorScenario` (carries a known ASan
|
||
stack-buffer-overflow, ID-21 follow-up), `FragmentOutputArrayIndexScenario`,
|
||
`OrientationScenario`, `FragCoordOriginScenario`, `IterationRP*Scenario` ×3, `GuiBatchScenario`.
|
||
- `ResourceSubsystemControlScenario.ClearingTheP3aBitsStopsTheEmissionsAndNotThePixels` — the G12
|
||
template P4a copies for its own bits; `CsoContentAddressingScenario`; `PipeVerifyArmingScenario`.
|
||
|
||
### 6.3 Traces most sensitive to this surface
|
||
|
||
From `tools/trace_replay/trace_cases.json` (40 cases; 79 desktop arms):
|
||
|
||
- **`improved-transparency-minecraft-26.3`** — the OIT scratch-clear-FBO / draw-buffers-`None` /
|
||
read-buffer bug family lives here (`Managers.cpp:7544-7550`). First trace to break on a draw-buffer or
|
||
read-buffer regression.
|
||
- **`minecraft-1.21.4-fabric-iris-*-in-world`** (17 shader packs: bsl, complementary-reimagined,
|
||
complementary-unbound, sundial-lite, iterationt, iterationrp, photon-v1.1/v1.3b, mellow, nostalgia,
|
||
bliss, chocapic-v6-lite, super-duper-vanilla, makeup-ultrafast, derivative-main, …) — heavy MRT,
|
||
attachment relocation and `glDrawBuffers` permutations; `TwoAttachmentCompositeFramebufferMatchesIrisComplementaryPass`
|
||
is the unit-test shadow of this. `iterationrp` has no DirectGLES arm (79 = 40×2 − 1).
|
||
- **`minecraft-1.21.4-rd12-odinlite-in-world`** — the VAO/buffer-heavy fixture that cost P3a +30%/+27%
|
||
(ID-19/ID-24); also the one that aborts on Xiaomi/Magma pre-existing (ID-23). Texture-upload traffic
|
||
is the other half of its frame.
|
||
- **`minecraft-1.21.4-fabric-sodium-in-world`** — sub-ms frames, the per-call overhead canary.
|
||
- **`minecraft-1.21.1-neoforge-create-instancing-in-world`** (2-frame fixture, not a benchmark) and
|
||
**`create-indirect`** (pre-existing `dev` red, BRIEF-P3A.md's first caveat — keep it in the desktop
|
||
SSIM corpus, out of the device A/B).
|
||
- `minecraft-1.21.4-fabric-{rei,xaero-minimap,xaero-world-map,journeymap,modernui}-*` and
|
||
`…-normal-world` — GUI/atlas-heavy, i.e. the `glTexSubImage` union-box vs scatter-rect shape and the
|
||
unpack ring; a shape regression there is invisible to SSIM and shows only in
|
||
`TextureUploadBoxEmissions` / `TextureUploadRectEmissions` / `TextureUploadJobs`
|
||
(`Managers.cpp:6385-6394`).
|
||
- `minecraft-1.17-main-menu-854`, `minecraft-1.21.4-fabric-iris-bsl-esc-menu-854`, `OpenRA` — default
|
||
framebuffer / blit-to-default paths.
|
||
|
||
---
|
||
|
||
## 7. Working notes for the P4a implementer
|
||
|
||
1. **Seven→ten tables.** P3a converted only the Buffer kind to a call-carried handle
|
||
(`Managers.h:690-692`). Framebuffer, Texture, Renderbuffer (and Sampler) still mint inside
|
||
`MG_Backend` through `TwinRegistry::GetOrCreate(StatePtr)` (`Managers.h:326-372`) and still depend on
|
||
the shared death notice (`Managers.cpp:199-245`). The buffer family's shape — client mints →
|
||
`GetOrCreate(handle)` → `ResourceDestroy` retires → **client** frees the slot (`SlotTables.h:337-361`,
|
||
`Managers.cpp:233-243`) — is the template. Note the `GetOrCreate(handle)` asymmetry: forward
|
||
generations recycle, **backward generations are refused** (`SlotTables.h:301-321`).
|
||
2. **`ForEachLive` is the blocker for dropping `Entry::stateRef` on Framebuffer.**
|
||
`ScopedDetachedTextureFramebufferAttachments` (`DirectGLES.cpp:6805-6899`) is the only
|
||
direct-iteration site and it needs the frontend `FramebufferObject` to read its attachment array. A
|
||
handle-keyed FBO table has no `stateRef` (`SlotTables.h:266-269`), so that walk has to be re-expressed
|
||
over the applier's `MGPFramebufferState` records before the FBO table can lose its `StatePtr`.
|
||
3. **The `Color[8]` / `DrawBuffers[8]` vs 32 mismatch** (§0) has to be ruled on before any emitter is
|
||
written; `MAX_COLOR_ATTACHMENT_SLOTS` is 32 at `Managers.h:1579-1581` and `RecomputeBackendColorSlots`
|
||
is 32-wide (`Managers.cpp:7443-7448`).
|
||
4. **`ContentHash` suppression on `SetFramebufferState`** must not swallow the `fragColorBroadcastCount`
|
||
input (§1.6): the broadcast count is read from the frontend *before* the FBO sync precisely so a
|
||
program relinks in the same draw. If the FBO record is suppressed, the broadcast count must still be
|
||
derivable — either it becomes a field of the record or it stays a separate emission.
|
||
5. **`ForceResync` on `MGPTextureParams`** (`MGPipeTypes.h:312-313`) is already the wire spelling of
|
||
`m_forceTextureParamsResync`; there is **no** wire spelling of `m_forceSamplerResync`
|
||
(`Managers.h:1508-1516`), and the failure mode it guards is not mis-filtering but an *incomplete*
|
||
texture that samples `(0,0,0,1)`. That gap is P4a's third contract ruling.
|
||
6. **G5-style byte-identity.** P3a's G5 protects eleven buffer-pool/ring functions
|
||
(ID-11/ID-13/ID-15). The P4a analogue candidates, if the phase wants one, are the unpack-ring
|
||
staging (`StageBlocksIntoUnpackRing`, `Managers.cpp:4947-5001`), `RecomputeBackendColorSlots`
|
||
(`Managers.cpp:7432-7519`) and `ScratchFBOImpl`'s attachment shadow (`Managers.cpp:7767-8002`) —
|
||
all three are dense, well-tested and have nothing to gain from being rewritten.
|
||
7. **Exit order (ID-18/ID-21).** `EnsureProcessTeardownSentinel` / `InProcessTeardown`
|
||
(`Managers.cpp:171-180`) gate every twin destructor (`:3556`, `:4693`, `:7026`, `:8085`, `:10432`,
|
||
`:10600`), and `OnFrontendStateObjectDestroyed` drops notices during teardown (`:200`). Any new
|
||
MGPipe singleton P4a adds must be heap-constructed and intentionally leaked, like the four
|
||
`fde5fda3` made never-destroyed.
|