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.
811 lines
55 KiB
Markdown
811 lines
55 KiB
Markdown
# P4a scout — Espryt's sampler / image / program surface
|
|
|
|
Tree read: `C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg` at
|
|
`feat/disaggregated@37da3c3a` (P3a closed, ID-24). Every line number below was opened in that tree.
|
|
Paths are repo-relative under `MobileGL/` unless written out in full.
|
|
|
|
House shape assumed from `~/w7/notes/p3a/BRIEF-P3A.md` §A/§C and `INTEGRATOR-DECISIONS.md` ID-1..24:
|
|
a phase is 4-5 packages (contract → wire → client → backend → gates), the contract commit is tagged
|
|
and frozen, G1 (pull build symbol-identical, admitted-resize set EMPTY) is the hardest gate, and every
|
|
backend edit lives inside `#if MOBILEGL_PIPE_PUSH`.
|
|
|
|
---
|
|
|
|
## 0. The one-paragraph orientation
|
|
|
|
P3a converted **one** family (buffers) to a handle-addressed op table and **one** CSO (vertex
|
|
elements) to an applier record. P4a's sampler/program half is structurally *easier* in one way and
|
|
*harder* in three:
|
|
|
|
* **Easier**: the payloads already exist and are already in the verify comparator's list.
|
|
`MGPSamplerDesc` (`MG_Pipe/MGPipeTypes.h:283-287`), `MGPSamplerView` (`:293-304`),
|
|
`MGPTextureParams` (`:307-318`), `MGPProgramDesc` (`:323-335`), `MGPBoundView`/`MGPSamplerViews`
|
|
(`:432-444`), `MGPSamplerStates` (`:447-451`), `MGPImageView`/`MGPShaderImages` (`:453-468`),
|
|
`MGPGlobalConstants` (`:507-513`). All twelve are already in `MGP_VERIFY_PAYLOAD_LIST`
|
|
(`MG_Pipe/PipeFields.def:314-336`) with their `MGP_FIELDS_*` tables (`:74-88`, `:108-134`).
|
|
The catalogue rows exist too and are `kNone`/`kHasBlob`/`kVarTail` today
|
|
(`MG_Pipe/PipeCalls.def:104-110`, `:117-119`, `:127`, `:134-135`).
|
|
* **Harder 1**: sampler state has **two** carriers in MobileGL (a `SamplerObject` bound to a unit,
|
|
and a `SamplerObject` *owned by every texture object*), and Espryt pushes them through **two
|
|
different driver entry-point families** (`glSamplerParameter*` vs `glTexParameter*`), with two
|
|
independent caches. §1.
|
|
* **Harder 2**: the shader CSO's wire form is SPIR-V + the whole `LinkArtifacts`+`SpirvArtifacts`
|
|
archive (`ARCHITECTURE.md:259-266`), but the archive **has no serializer yet** — only the field
|
|
tables. §3.9.
|
|
* **Harder 3**: three of the four things Espryt's program twin is keyed on are **not program state
|
|
at all** (draw-FBO clamp masks, fragColor broadcast count, live image-unit formats, patch
|
|
parameters). That is the `st_variant`-style lazy specialisation `ARCHITECTURE.md:264` names, and
|
|
it is what stops `create_shader_state` from being self-contained. §3.4.
|
|
|
|
---
|
|
|
|
## 1. Samplers
|
|
|
|
### 1.1 `SamplerParameters` and `BorderColorForm` — the value type
|
|
|
|
`MG_Pipe/MGPipeValueTypes.h:462-486`. 15 members, `sizeof == 100`, trivially copyable
|
|
(`:615` static_assert; pinned again in `MG_Test/Pipe/PipeCatalogueTest.cpp:131` and `:138`):
|
|
|
|
```
|
|
wrapS/wrapT/wrapR SamplerWrapMode (enum class, 4 B each) default Repeat
|
|
minFilter SamplerFilterMode default Nearest
|
|
magFilter SamplerFilterMode default Linear
|
|
mipmapMode SamplerMipmapMode default Linear
|
|
minLod/maxLod Float -1000 / +1000
|
|
lodBias Float 0
|
|
maxAnisotropy Float 1
|
|
compareFunc SamplerCompareFunc LessEqual (NOT Never - table 23.18)
|
|
compareMode SamplerCompareMode None
|
|
borderColor FloatVec4 (0,0,0,0)
|
|
borderColorI IntVec4
|
|
borderColorUI UintVec4
|
|
borderColorForm BorderColorForm : Uint8 Float
|
|
```
|
|
|
|
`BorderColorForm` is declared at `:449-460` with the rationale verbatim: all three representations
|
|
are *always* numerically populated, so the value alone cannot say which driver entry point to use.
|
|
`MGPipeTypes.h:279-282` says the struct "crosses byte for byte INCLUDING borderColorForm".
|
|
|
|
> **TRAP (P4a must fix in the contract commit).** `SamplerParameters` has **3 bytes of trailing
|
|
> padding** (96 bytes of members + 1 byte `borderColorForm` = 97, padded to 100). There is **no
|
|
> `MGP_FIELDS_SamplerParameters`** in `PipeFields.def` (grep: only `RenderStateParameters` at `:233`
|
|
> and `PixelStoreParameters` at `:253` of the value structs), and `SamplerParameters` is **not** in
|
|
> `MGP_VERIFY_PAYLOAD_LIST`. `MGPSamplerDesc::Parameters` is an `MGPBlobRef`, so under
|
|
> `MOBILEGL_PIPE_VERIFY` the blob is compared as bytes and the padding false-differs; and any
|
|
> content hash taken over those 100 bytes hashes uninitialised padding, which would mint a fresh
|
|
> sampler CSO per call. This is exactly the shape the P3a brief's G7 negative control exists for on
|
|
> the vertex side. Add the field table + the verify-list row, and hash/compare field-wise.
|
|
|
|
### 1.2 The `SamplerCso` slot table (P2)
|
|
|
|
`MG_Backend/DirectGLES/Managers.h:2228-2229`:
|
|
|
|
```cpp
|
|
extern TwinRegistry<MG_State::GLState::SamplerObject, BackendSamplerObject, MG_Pipe::MGPipeKind::SamplerCso>
|
|
g_backendSamplerObjects;
|
|
```
|
|
|
|
defined at `Managers.cpp:10583`. `TwinRegistry` is the alias at `Managers.h:1188-1195` that swallows
|
|
the kind parameter in the pull build (G1); the push arm holds a
|
|
`BackendSlotTable<SamplerObject, BackendSamplerObject, SamplerCso>` member at `Managers.h:538`, and
|
|
`StateBackendObjectRegistry::GetOrCreate` dispatches to it at `Managers.h:325-332` when
|
|
`EsprytSlotTablesEnabled()` (`SlotTables.h:132-135`).
|
|
|
|
The handle-keyed overloads P4a needs already exist and are **unused for this kind**:
|
|
`BackendSlotTable::GetOrCreate(MGPipeHandle)` (`SlotTables.h:278-326` — forward Gen = recycle+reset,
|
|
**backward Gen = refuse**, `kMaxHandleSlot = 1<<20` at `:148`), `FindByHandle` (`:376-382`),
|
|
`ReleaseByHandle` (`:350-361`), `LiveGenAt` (`:331-335`).
|
|
|
|
**Death today is the shared notice, not a call.** `Managers.cpp:199-247`
|
|
`OnFrontendStateObjectDestroyed` switches on kind; `MGPipeKind::SamplerCso` →
|
|
`SamplerImpl::g_backendSamplerObjects.DestroyByLifetimeId(lifetimeId)` (`Managers.cpp:211-213`),
|
|
which forwards to `BackendSlotTable::OnFrontendObjectDestroyed` (`Managers.h:442-446`,
|
|
`SlotTables.h:416-430`). The raiser is `SamplerObject`'s push-only destructor
|
|
(`MG_State/GLState/SamplerState/SamplerObject.h:18-24` — declared only under `MOBILEGL_PIPE_PUSH`
|
|
so the pull build keeps its implicit dtor, G1). The ops table is installed at `Managers.cpp:247-250`
|
|
+ `:326`. **P4a decision owed**: does `SamplerCso` follow buffers (its own `DeleteSamplerState` call,
|
|
client frees the slot — the `default:` comment at `Managers.cpp:236-246` spells the ordering rule) or
|
|
does it keep the shared notice? The catalogue already has `DeleteSamplerState`
|
|
(`PipeCalls.def:105`), and the P3a `VertexElementsCso` case (`Managers.cpp:225-236`) is the
|
|
precedent for the **hybrid**: the client speaks the whole death *and* the backend keeps a notice
|
|
consumer for the driver object, with a documented double-release that is a no-op.
|
|
|
|
### 1.3 `BackendSamplerObject` — the twin
|
|
|
|
Declared `Managers.h:2205-2223`, defined `Managers.cpp:10416-10584`.
|
|
|
|
| member | line | note |
|
|
|---|---|---|
|
|
| `m_backendSamplerId` | `Managers.h:2218` | `glGenSamplers` in ctor `Managers.cpp:10417-10429` |
|
|
| `m_contextGeneration` | `:2219` | vs `g_backendContextGeneration`; dtor refuses to delete a foreign name |
|
|
| `m_isInitialized` | `:2220` | |
|
|
| `m_cacheSamplerParameters` | `:2221` | the whole 100-byte struct, as the last-pushed shadow |
|
|
| `m_syncedSamplerVersion` | `:2222` | `Uint16`, vs `SamplerObject::GetVersion()` |
|
|
|
|
`SyncToBackend` (`Managers.cpp:10451-10556`): version early-out at `:10461-10465`; then a
|
|
per-field diff against `m_cacheSamplerParameters`:
|
|
|
|
* min-filter + mipmap-mode fused, through `ResolveBackendMinFilter(params, ShouldAvoidSamplerMipmapMinFilterOnAngleLlvmpipe())`
|
|
(`Managers.cpp:10482-10490`; the resolver is `Managers.cpp:100-117` and collapses the four
|
|
`*_MIPMAP_*` filters to `GL_NEAREST`/`GL_LINEAR` on ANGLE-on-llvmpipe);
|
|
* mag filter `:10491-10497`;
|
|
* `SYNC_SAMPLER_PARAM_IF_CHANGED` macro (`:10475-10480`) for wrapS/T/R, compareFunc, compareMode
|
|
(`:10498-10502`);
|
|
* minLod / maxLod `:10503-10510`;
|
|
* maxAnisotropy behind `g_GLESCapabilities.SupportsTextureFilterAnisotropy`, cache updated even when
|
|
the call is skipped `:10511-10517`;
|
|
* **border colour**, `:10518-10553`: gated on `SupportsTextureBorderClamp`, branches on
|
|
`borderColorForm` to `glSamplerParameterIiv` / `Iuiv` / `fv`, with the entry-point null checks;
|
|
the redundancy filter compares **all four** (float, int, uint, form) `:10518-10521`.
|
|
* **`lodBias` is never pushed here.** ES has no `GL_TEXTURE_LOD_BIAS`; it is folded into the shader
|
|
as a uniform — see §3.5.
|
|
|
|
`Bind(unit)` (`:10558-10566`) dedups against `g_boundSamplersCache[unit]` and calls `glBindSampler`.
|
|
`UnbindSampler(unit)` (`:10575-10581`) is the symmetric zero-bind. Destructor
|
|
(`:10431-10449`) scrubs **every** row of `g_boundSamplersCache` that names `this` before deleting —
|
|
the recycled-heap-address defence that `{slot,gen}` is supposed to retire.
|
|
|
|
### 1.4 The unit shadow
|
|
|
|
`Array<BackendSamplerObject*, TextureState::MAX_TEXTURE_IMAGE_UNITS> g_boundSamplersCache`
|
|
— declared `Managers.h:2226-2227`, defined `Managers.cpp:10582`. `MAX_TEXTURE_IMAGE_UNITS` is **192**
|
|
(`MGPipeTypes.h:429-431` states it: "one Array of MAX_TEXTURE_IMAGE_UNITS = 192", merged unit space,
|
|
no stage dimension — which is why `PipeCalls.def:194-196` deliberately drops the stage dimension of
|
|
`set_sampler_views`).
|
|
|
|
This shadow is a **raw twin pointer per unit** and is read by three memos as a proof-of-driver-state
|
|
(§1.5, §1.6). Under a split it cannot be a pointer; it becomes the applier's own per-unit
|
|
`MGPipeHandle` array. That is the single largest mechanical change on the sampler side.
|
|
|
|
### 1.5 Per-unit sampler walk — `BindCurrentUnitSamplers`
|
|
|
|
`MG_Backend/DirectGLES/DirectGLES.cpp:3593-3626`, called last from `BindCurrentTextures`
|
|
(`DirectGLES.cpp:3752`).
|
|
|
|
Two memos stack here:
|
|
|
|
1. **`UnitSamplerLookupMemo`** (`DirectGLES.cpp:3522-3535`), one row per unit (192 rows). Push arm
|
|
stores `MGPipeHandle frontendHandle` (`:3525`); legacy arm stores a `WeakPtr<SamplerObject>`
|
|
under `MOBILEGL_PIPE_LEGACY_MEMOS` (`:3528-3530`). `ResolveUnitSamplerBackend`
|
|
(`:3537-3572`): push arm probes `g_backendSamplerObjects.HandleOf(sampler)` then `FindByHandle`
|
|
(`:3543-3556`); legacy arm uses `OwnerEquals` + `Find` (`:3558-3568`). **A miss is never cached**
|
|
(`:3550-3553`) because the program pass creates the twin later in the same draw.
|
|
`SlotTables.h:556-561` records that this caller *thrashes* the table's single-entry
|
|
lifetime-id memo (a different sampler per unit) — a known, unpaid perf item.
|
|
2. **The walk memo** (`DirectGLES.cpp:3588-3593`): `g_unitSamplerWalkContextId`,
|
|
`…Epoch`, `…MaxUnit`, `…ContextGeneration`, `…Valid`, plus
|
|
`g_unitSamplerWalkRows` — a **full 192-entry copy of `g_boundSamplersCache`**. The replay gate
|
|
(`:3597-3604`) is keys-equal **AND** `memcmp` of the first `maxTouchedUnit+1` rows. The
|
|
invalidation argument is written out at `:3575-3587`.
|
|
|
|
The walk itself (`:3605-3616`): for every unit ≤ `maxTouchedUnit`, take
|
|
`MGB_CTX->GetTextureUnitObject(unit).GetSamplerObject()`; bind its twin, or `UnbindSampler(unit)`.
|
|
|
|
### 1.6 `SamplerPassMemo` — the program-side sampler pass
|
|
|
|
Declared `Managers.h:1913-1924` (with the six-bullet invalidation enumeration at `:1895-1912`),
|
|
member at `Managers.h:2117`, accessor `Managers.h:2001`.
|
|
|
|
```cpp
|
|
struct SamplerPassMemo { // Managers.h:1913
|
|
static constexpr SizeT kMaxEntries = 16; // :1914
|
|
Bool valid; Uint8 count;
|
|
Uint64 contextId, unitBindingsEpoch, samplingGeneration;
|
|
Uint32 backendStateVersion; Uint textureContextGeneration;
|
|
Array<Uint8, 16> units; // :1922
|
|
Array<BackendSamplerObject*, 16> rows; // :1923 - driver-state proof
|
|
};
|
|
```
|
|
|
|
Consumed in `BindCurrentProgramWithResources` at `DirectGLES.cpp:3922-4038`: the clean test is the
|
|
five keys (`:3936-3941`) plus a per-entry row compare against `g_boundSamplersCache`
|
|
(`:3942-3950`). A pass touching >16 units, or an out-of-range unit, simply never memoises
|
|
(`:3956-3966`). Reset on relink at `Managers.cpp:9258`.
|
|
|
|
The pass body (`:3954-4030`) does five things per `SamplerUniformBinding`:
|
|
|
|
1. resolve the frontend unit — `currentProgram->GetUniformSamplerOrImageUnitIndex(frontendLocation)`
|
|
(`:3958-3960`);
|
|
2. `glUniform1i(backendLocation, unit)` if it moved (`:3971-3974`);
|
|
3. the **LOD-bias uniform** (`:3982-4000`) — sampler-object bias wins, else the *texture's own*
|
|
sampler's bias, looked up through `SamplerUniformTextureTarget(uniformType)`
|
|
(`DirectGLES.cpp:3553`ff mapping table, i.e. `DirectGLES.cpp` right after
|
|
`NeedsRawDepthFetchSampler`);
|
|
4. the **raw-depth-fetch substitution** (`:4001-4010`);
|
|
5. otherwise `SyncToBackend` + `Bind(unit)` on the unit's sampler object, creating the twin if
|
|
`ResolveUnitSamplerBackend` missed (`:4011-4026`), or `UnbindSampler(unit)` (`:4027-4029`).
|
|
|
|
**This is the only site in Espryt that ever creates a `BackendSamplerObject`**
|
|
(`DirectGLES.cpp:4014-4020`) — there is no eager `glGenSamplers` at `glGenSamplers` time.
|
|
`create_sampler_state` under P4a inverts that: the CSO is minted and emitted at the frontend call,
|
|
and the draw path only binds. Say so in the brief; it changes when `glGenSamplers` runs on the
|
|
driver and therefore what `MGLOG_E_ONCE("Failed to generate sampler object.")` at
|
|
`Managers.cpp:10424` can report.
|
|
|
|
### 1.7 The raw-depth-fetch sampler
|
|
|
|
`DirectGLES.cpp:61-62` (two file-static `SharedPtr`s), `:207-219` `GetRawDepthFetchSampler()`,
|
|
`:221-235` `NeedsRawDepthFetchSampler()`.
|
|
|
|
It **constructs a frontend `MG_State::GLState::SamplerObject(0)`** inside the backend
|
|
(`:209-214`) purely to reuse `BackendSamplerObject::SyncToBackend`'s signature — nearest/nearest,
|
|
no mipmaps, compare mode off, compare func Always. `ARCHITECTURE.md:322` names it explicitly:
|
|
|
|
> Espryt 的小号同类:`g_rawDepthFetchSamplerState` → 后端原生 sampler。
|
|
|
|
so P4a is expected to replace it with a backend-native sampler (a `glGenSamplers` id + a literal
|
|
parameter push), not to route it through a CSO. `NeedsRawDepthFetchSampler` reads
|
|
`IsDepthFormatInternalFormat(textureFormat)` and three sampler params (compareMode, minFilter,
|
|
mipmapMode, magFilter) — under a split those come from the **resolved sampler view + the sampler
|
|
CSO's parameters**, both of which the applier will hold. `ARCHITECTURE.md:206` already assigns this
|
|
post-processing to the **server**, acting on the already-resolved set:
|
|
|
|
> 两处后端特定后处理留在 server、作用于已解析集合:Espryt 的 raw-depth-fetch sampler 替换、Magma 的 feedback-loop 检测。
|
|
|
|
### 1.8 Texture-object sampling state — the *second* carrier
|
|
|
|
Every `ITextureObject` owns a `SamplerObject` (`GetSamplerObject()`), and Espryt pushes it with
|
|
`glTexParameter*` in `BackendTextureObject::SyncBuiltinSamplerToBackend`
|
|
(`Managers.cpp:6671-6781`):
|
|
|
|
* version gate on `samplerObject->GetVersion()` **plus** `m_forceSamplerResync`
|
|
(`:6683-6690`; the flag's rationale is `Managers.h:1508-1519` — a driver re-mint loses every
|
|
pushed parameter, and an incomplete texture samples (0,0,0,1));
|
|
* multisample targets take **no** parameters at all — the cache is stamped and the function returns
|
|
(`:6706-6710`);
|
|
* min/mag filter through the same `ResolveBackendMinFilter`, but with `IsAngleLlvmpipeRenderer()`
|
|
rather than the sampler-object predicate (`:6732-6744`) — **an asymmetry to preserve**;
|
|
* `SYNC_TEX_SAMPLER_PARAM_IF_CHANGED` (`:6719-6731`) for wrapS/T, wrapR **only when
|
|
`SupportsWrapR(targetInternal)`** (`:6752-6757`), compareFunc, compareMode;
|
|
* minLod/maxLod/maxAnisotropy `:6758-6776`;
|
|
* **no border colour here.** The texture's border colour is pushed from
|
|
`SyncTextureParamsToBackend` instead (`Managers.cpp:6911-6954`), reading
|
|
`stateTextureObject->GetBorderColorForm()/GetBorderColor()/…I()/…UI()` and comparing against four
|
|
separate members `m_cacheBorderColor{,I,UI,Form}` (`Managers.h:1488-1493`). That split matters:
|
|
the border colour is *sampler* state per GL 4.6 table 23.18 (`MGPipeValueTypes.h:478-483`) but
|
|
Espryt pushes it on the **texture params** version, not the sampler version.
|
|
|
|
So a P4a `MGPSamplerDesc` covers the *sampler object* half; the texture's own sampling state is
|
|
`set_texture_params` (`MGPTextureParams`, `MGPipeTypes.h:307-318`) **plus** a second sampler CSO or
|
|
an inline parameter block — and today `MGPTextureParams` carries `BaseLevel/MaxLevel/Swizzle/
|
|
DepthStencilMode/ForceResync/MinLod/MaxLod/LodBias` and **no filter, no wrap, no border colour, no
|
|
compare mode**. That is a real gap between the payload and what
|
|
`SyncBuiltinSamplerToBackend` + the border block actually push. Resolve it in the contract commit —
|
|
either widen `MGPTextureParams` (it is 32 B today, `MGP_ASSERT_POD` at `:318`) or give every
|
|
texture object an implicit sampler CSO.
|
|
|
|
### 1.9 Seamless cube maps — a non-item, state it so nobody looks for it
|
|
|
|
`GL_TEXTURE_CUBE_MAP_SEAMLESS` is **not** sampler state in this codebase. It is
|
|
`RenderStateParameters::TextureCubeMapSeamlessEnabled` (`MGPipeValueTypes.h:305`, packed into the
|
|
alignment hole so `sizeof(RenderStateParameters)` stays 1168 — `:292-303`), lives in the **P1
|
|
pipeline chunk** (`MG_Pipe/MGPipeRenderStateSpans.h:64-67`), and therefore already crosses as part
|
|
of `create_render_state`/`bind_render_state` since P2. `PipeApply.cpp:72` and `:370` carry it in the
|
|
capability list. **`grep -rn CubeMapSeamless MG_Backend/` returns nothing** — no backend reads it
|
|
(ES 3.0+ cube sampling is unconditionally seamless), and the frontend answers `glIsEnabled` from
|
|
`RenderState.cpp:340` / `:420`. Nothing for P4a to do.
|
|
|
|
### 1.10 Complete list of sampler-state reads from backend code
|
|
|
|
| site | file:line | reads |
|
|
|---|---|---|
|
|
| `DirectGLES.cpp:227` | raw-depth probe | `samplerObject->GetAllSamplerParameters()` (compareMode, minFilter, mipmapMode, magFilter) |
|
|
| `DirectGLES.cpp:3607` | unit walk | `GetTextureUnitObject(unit).GetSamplerObject()` |
|
|
| `DirectGLES.cpp:3986` | LOD bias | `samplerObject->GetLodBias()` |
|
|
| `DirectGLES.cpp:3993-3995` | LOD bias fallback | `boundTexture->GetSamplerObject()->GetLodBias()` |
|
|
| `DirectGLES.cpp:4004-4008` | raw-depth gate | unit sampler, else `texture2D->GetSamplerObject()`; `texture2D->GetFormat()` |
|
|
| `DirectGLES.cpp:3468-3474` | completeness | `textureUnit.GetSamplerObject()` else `textureObject->GetSamplerObject()`, then `MG_State::GLState::SamplesAsIncompleteTexture` (`MG_State/GLState/TextureState/TextureObject.cpp:489-512`) |
|
|
| `Managers.cpp:6705` | texture built-in sampler | `samplerObject->GetAllSamplerParameters()` |
|
|
| `Managers.cpp:6683` | version gate | `samplerObject->GetVersion()` |
|
|
| `Managers.cpp:10473` | sampler object | `stateSamplerObject->GetAllSamplerParameters()` |
|
|
| `Managers.cpp:10461`,`:10471`,`:10502` | ids/versions | `GetVersion()`, `GetExternalIndex()` |
|
|
| `Managers.cpp:6925`,`:6932`,`:6937`,`:6942` | border | `GetBorderColorForm/I/UI/…` on the **texture object** |
|
|
| `Managers.cpp:4838` | reset | `m_cacheSamplerParameters = SamplerParameters{}` (on backend texture re-mint) |
|
|
|
|
`DirectVulkan` uses the same helpers (`MG_Backend/DirectVulkan/Renderer/UniformManager.cpp:515`,
|
|
`:828`, `:857`, `:1676`) — a reminder that `SamplesAsIncompleteTexture`/`IsUndefinedDefaultTexture`
|
|
resolution is shared and, per `ARCHITECTURE.md:206`, moves **client-side** in P4a
|
|
("~40 行搬迁"), leaving only the two backend-specific post-processings on the server.
|
|
|
|
---
|
|
|
|
## 2. Images (`set_shader_images`' counterpart)
|
|
|
|
### 2.1 The payload that exists
|
|
|
|
`MGPImageView` (`MGPipeTypes.h:453-462`): `Res, Unit, InternalFormat, Layer, Level:Uint16,
|
|
Layered:Uint8, Access:Uint8` (24 B). `MGPShaderImages` (`:464-468`): `Start, Count, ContentHash`
|
|
var-tail header. Fields tables `PipeFields.def:117-121`.
|
|
Catalogue row `PipeCalls.def:119` — `kCtxState, kVarTail`.
|
|
Coverage row `Coverage.def:85` maps `GetImageTextureBinding → SetShaderImages`.
|
|
Dirty bit `MGPipeDirty::NewShaderImages` (`MG_Impl/Pipe/Tracker.h:79`), computed at
|
|
`Tracker.h:311-312` as `mix(mix(textureContent, textureParams), programImageUnitVersion)` — already
|
|
live, just not emitted (`kMGPipeDirtyEmittedAtP3a` at `Tracker.h:104-107` stops at bit 10).
|
|
|
|
### 2.2 The single funnel: `SyncImageTextureBinding`
|
|
|
|
`DirectGLES.cpp:2057-2135`. Everything about an image unit passes through here — the file says so at
|
|
`:2019-2021` ("`glBindImageTextures` is a frontend loop over `glBindImageTexture`, and the whole-sweep
|
|
`SyncImageTextureBindings` goes through it too"). It:
|
|
|
|
* maintains `g_writableImageBufferUnits[192]` + `g_writableImageBufferUnitCount`
|
|
(`:2029-2031`, tracker `:2038-2054`, predicate `:2033-2036`);
|
|
* maintains `g_imageUnitHighWaterMark` (`:2055`, bumped `:2063-2065`);
|
|
* unbinds with `glBindImageTexture(unit, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8)` `:2066-2069`;
|
|
* calls `SyncTextureObjectToBackend(texture, /*imageBindableStorageRequired=*/true)` `:2071` —
|
|
**this is the call that forces immutable storage and can re-mint the ES texture name**;
|
|
* normalises `layered`/`layer` through `SupportsLayeredImageBinding` (`:1992-2013`, and note the
|
|
rule: it asks the **backend** target after `MapToBackendTextureTarget`, and `layer` is forced to 0
|
|
for a non-layerable target — Adreno took a stray layer index literally);
|
|
* rewrites the bind **format**: buffer textures take the *split* view
|
|
(`GetImageBindableBufferSplitFormat`, `backendTexture->GetBufferImageSplitViewId()`,
|
|
`:2126-2138`), everything else takes the *widening*
|
|
(`GetImageBindableStorageWidening`, `:2139-2145`);
|
|
* finally `glBindImageTexture(unit, bindTextureId, Level, layered, layer, Access, bindFormat)` `:2133`.
|
|
|
|
`MarkWritableImageBufferTexturesGpuWritten` (`DirectGLES.cpp:2147-2171`) is the GPU-write
|
|
announcement for buffer textures on writable image units; under push it already routes through
|
|
`BufferImpl::MarkBufferGpuWritten` (`:2166-2169`), i.e. the P3a reverse channel.
|
|
|
|
`SyncImageTextureBindings` (`:2173-2185`) sweeps `min(192, MaxImageUnits)` units.
|
|
|
|
### 2.3 The image sweep memo
|
|
|
|
`DirectGLES.cpp:2190-2193` (`g_imageSweepContextId`, `g_imageSweepSamplingGeneration`,
|
|
`g_imageSweepBackendContextGeneration`, `g_imageSweepValid`), gate
|
|
`SyncImageTextureBindingsForDraw` at `:2202-2214`. Two properties P4a must keep:
|
|
|
|
* the `g_imageUnitHighWaterMark == 0` early-out (`:2203`) is what makes every Minecraft draw pay one
|
|
integer test;
|
|
* the gate key is deliberately the **frontend** sampling-resolution generation and **not** a
|
|
backend re-mint counter, because a texture bound *only* to an image unit is re-minted *inside* the
|
|
sweep (`:2195-2201`). A server-side epoch would be bumped after the gate had already declined.
|
|
|
|
Called from the draw path at `DirectGLES.cpp:3355`.
|
|
|
|
### 2.4 The photon canary — image binding semantics
|
|
|
|
Memory `flywheel-indirect-lessons` item 5 (and item 2) applies directly, and its mechanism is in
|
|
this tree:
|
|
|
|
* `RebindImageUniformsToFrontendUnits` (`Managers.cpp:602-651`) rewrites every image uniform
|
|
declaration's `layout(binding = N)` to the **frontend-tracked unit**
|
|
(`GetUniformLocation` → `GetUniformSamplerOrImageUnitIndex`, `:627-635`), because glslang
|
|
auto-assigns a binding when desktop GL source omits one while the app picks the unit with
|
|
`glUniform1i` (illegal on ES image uniforms). Called at `Managers.cpp:9800`, and the ordering
|
|
constraints against `BakeImageFormatQualifiers` / `RemapImageArrayElementUnits` /
|
|
`SplitReadWriteImageUniforms` / `RemoveLayoutBinding` are spelled out at `:9801-9851`.
|
|
* `IsImageUniformType` (`Managers.h:1826-1867`) is the **33-entry** contiguous block
|
|
`GL_IMAGE_1D`..`GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY`; the header records that the earlier
|
|
15-entry list caused two bugs (a non-baked non-core format losing the stage, and an image uniform
|
|
assigned with `glUniform1i`).
|
|
* `CacheResourceLocations` skips image uniforms entirely (`Managers.cpp:10316-10321`).
|
|
|
|
**The canary itself** (from the memory note, still the rule): real-device Flywheel testing does NOT
|
|
cover Iris packs; `photon-v1.3b` on llvmpipe retrace is what catches an image/SSBO binding-semantics
|
|
regression. `MEASUREMENTS`/roadmap caveat: photon is broken on Adreno (memory
|
|
`photon-broken-on-adreno`) — run it on llvmpipe, not on the phone. P4a moves the image-unit
|
|
resolution to `set_shader_images` records and the *format bake* stays server-side (the format the
|
|
shader was built against is live `glBindImageTexture` state, §3.4), so this is precisely the change
|
|
the canary exists for. **Recommend the brief pin `photon-v1.3b` desktop retrace as a named G3b
|
|
case for P4a.**
|
|
|
|
### 2.5 Format-less image bake (the fourth staleness key)
|
|
|
|
`ImageFormatBakeInputs` (`Managers.h:2170-2199`) + `CollectImageFormatBakeInputs`
|
|
(`Managers.cpp:8327-8390`): walks the program's uniform reflection
|
|
(`GetMaxUniformLocation`/`GetUniformName`/`GetUniformType`/`GetUniformTypeFacts`, `:8346-8351`),
|
|
resolves each format-less image's unit (`GetUniformSamplerOrImageUnitIndex`, `:8377`), reads the
|
|
**live** bound format (`BoundImageUnitFormat`, `Managers.cpp:8155-8161`) and mixes a signature
|
|
(`MixImageUnitFormat`, `:8163`). Stored on the twin as `m_formatlessImageUnits` +
|
|
`m_imageUnitFormatSignature` (`Managers.h:2118-2119`, written `Managers.cpp:9287-9288`) and
|
|
re-checked per draw by `ImageUnitFormatsStillMatch()` (`Managers.cpp:8544-8547`, declared
|
|
`Managers.h:2030`), consumed in the rebuild condition at `DirectGLES.cpp:3164`.
|
|
`FormatlessImageBakeScenario` and `NonCoreImageFormatScenario` are its itests.
|
|
|
|
---
|
|
|
|
## 3. Programs
|
|
|
|
### 3.1 The `ShaderCso` slot table and the composite band
|
|
|
|
`Managers.h:2130-2131` / `Managers.cpp:8065`:
|
|
`TwinRegistry<ProgramObject, BackendProgramObjectImpl, MGPipeKind::ShaderCso> g_backendProgramObjects`.
|
|
|
|
Slot space: `MGPipeHandles.h:84-92` reserves the **top 1/16** of the `ShaderCso` space
|
|
(`kMGPipeShaderCsoSlotLimit = 1<<20`, `kMGPipeShaderCsoCompositeSlotBase = limit - limit/16`) for
|
|
**program-pipeline composites**, with `MGPipeIsCompositeShaderSlot(slot)` at `:90-92`. That band is
|
|
allocated and asserted but **nothing mints into it yet** — P4a's composite resolver does.
|
|
|
|
Resolution today, `PrgramImpl::SyncCurrentProgram` at `DirectGLES.cpp:3104-3128`: push arm
|
|
`g_backendProgramObjects.Find` → `GetOrCreate` (`:3106-3113`); legacy arm goes through
|
|
`g_programTwinLookupMemo` (`DirectGLES.cpp:134-135`, an 8-entry `TwinLookupMemo` declared at
|
|
`DirectGLES.cpp:93`), `:3115-3127`. A per-draw stash `g_currentDrawFrontendProgram` /
|
|
`g_currentDrawBackendProgram` is written at `:3202-3203` and read at `DirectGLES.cpp:3775-3778`
|
|
and `:4046-4052`.
|
|
|
|
### 3.2 The rebuild condition — nine clauses, and where each one comes from
|
|
|
|
`DirectGLES.cpp:3148-3200`. `twin->SyncToBackend(currentProgram)` runs when **any** of:
|
|
|
|
| clause | line | source of truth |
|
|
|---|---|---|
|
|
| no backend program id | 3148 | |
|
|
| `GetSyncedLinkVersion() != GetLinkVersion()` | 3150 | program state |
|
|
| `GetSyncedImageUnitVersion() != GetImageUnitVersion()` | 3151 | program state |
|
|
| snorm clamp mask ≠ `g_snormFallbackClampOutputMask` | 3152 | **draw FBO** |
|
|
| unorm clamp mask ≠ `g_unormFallbackClampOutputMask` | 3153 | **draw FBO** |
|
|
| `GetFragColorBroadcastCount() != g_fragColorBroadcastCount` | 3154 | **draw FBO**, memoised at `:3078-3101` |
|
|
| SSBO binding signature ≠ `ComputeShaderStorageBlockBindingSignature(*program)` | 3155-3156 | program state |
|
|
| `!ImageUnitFormatsStillMatch()` | 3164 | **live image-unit state** |
|
|
| patch-vertices/outer/inner mismatch (bitwise compare, NaN-safe — `:3183-3186`) | 3188-3196 | **context state** (`MGB_CTX->GetPatchVertices()` etc.) |
|
|
|
|
The comment at `:3187-3199` explains why the `gl_PerVertex` member set needs **no** clause on this
|
|
backend (it mirrors the neighbouring stages' emitted ESSL) while DirectVulkan does.
|
|
|
|
`GetSyncedLinkVersion()` call sites: `DirectGLES.cpp:3150`, `:4110`, `:7857`; written once at
|
|
`Managers.cpp:10234`; declared `Managers.h:2004`.
|
|
|
|
### 3.3 The `BackendProgramObjectImpl` surface
|
|
|
|
Class `Managers.h:1873-2122`. Public accessors that P4a's `MGPProgramDesc`/applier must reproduce
|
|
or replace: `GetIndirectParamsBinding` (`:1959`), `GetBackendProgramId` (`:1960`),
|
|
`IsBackendProgramUsable` (`:1964`), `GetBackendGlobalUBOId` (`:1965`),
|
|
`GetSnormFallbackClampOutputMask`/`GetUnormFallbackClampOutputMask` (`:1966-1967`),
|
|
`GetFragColorBroadcastCount` (`:1968`), `GetShaderStorageBlockBindingSignature` (`:1973`),
|
|
`GetAtomicCounterBindings` + `GetAtomicCounterEsslBindingTop` (`:1979-1980`),
|
|
`GetPassthroughTessControl{PatchVertices,OuterLevel,InnerLevel}` (`:1986-1998`),
|
|
`HasGlobalUboBlock` / `GetUniformBlockBackendIndices` / `GetSamplerUniformBindings` (`:1997-1999`),
|
|
`GetLastUploadedGlobalUboVersion`/`Set…` (`:2000-2001`), `GetGlobalUboBackendBlockSize` (`:1999`),
|
|
`GetGlobalUboRingAllocation` (`:2000`), `GetSamplerPassMemo` (`:2001`),
|
|
`ReadsDrawID`/`ReadsBaseVertex`/`RoutesViewportIndex` (`:1946-1958`),
|
|
`SetBaseInstance`/`SetBaseInstanceWordIndex`/`SetDrawID`/`SetBaseVertex`/`SetViewportPassMask`
|
|
(`:1939-1952`). Private state at `:2066-2121`.
|
|
|
|
`SamplerUniformBinding` (`Managers.h:1880-1891`): `frontendLocation, backendLocation, uniformType,
|
|
lastAssignedUnit, lodBiasLocation, lastAssignedLodBias`. Built once per link in
|
|
`CacheResourceLocations` (`Managers.cpp:10264-10345`), which also does the array-element
|
|
subscripting fix (`SubscriptUniformNameForElement`, `Managers.cpp:10245-10256` — reflection names an
|
|
array after its first element at every location it spans, so `goku[7]` needed its own
|
|
`glGetUniformLocation`).
|
|
|
|
### 3.4 `SyncToBackend` — what it reads from the `ProgramObject`
|
|
|
|
`Managers.cpp:9230-10240`. The complete read set with line numbers (the P4a archive must supply
|
|
every one of these, or the call must carry it):
|
|
|
|
```
|
|
9241 GetExternalIndex 9248 GetLinkStatus / GetSpirvStatus
|
|
9268 GetShaderStorageBlockBindingOverrides (-> SpvcSession::SetShaderStorageBlockBinding)
|
|
9335 GetLinkedShaderStages 9336 GetGeneratedSpirv 9347 PointSizeDemoted
|
|
9357 GetLinkedShaderSnapshot 9366 GetSpirvValidationEnabled
|
|
9375 GetTransformFeedbackVaryings 10017 GetTransformFeedbackVaryingCount
|
|
10067 GetTransformFeedbackBufferMode 10217 GetUBOSize
|
|
10234 GetLinkVersion 10235 GetImageUnitVersion
|
|
10290 GetActiveUniformBlocksCount 10295 GetUniformBlockName
|
|
10310 GetMaxUniformLocation 10312 GetUniformName 10314 GetUniformType
|
|
8119 GetShaderStorageBlockBindingOverrides (signature)
|
|
8346-8351 GetMaxUniformLocation/GetUniformName/GetUniformType/GetUniformTypeFacts
|
|
8377 GetUniformSamplerOrImageUnitIndex 8506/8513 GetUniformLocation / UniformLocationsAliasSameUniform
|
|
622/627 (RebindImageUniformsToFrontendUnits) GetUniformLocation / GetUniformSamplerOrImageUnitIndex
|
|
```
|
|
|
|
`ARCHITECTURE.md:264` names the **eight extra inputs** the backend program depends on beyond the
|
|
artefacts, and the list above matches: draw-FBO snorm/unorm clamp masks, fragColor broadcast count,
|
|
storage-block binding signature, atomic-counter set, live image formats, patch parameters (+ Magma's
|
|
default-FB height and XFB layout). `create_shader_state` publishes the **artefacts**; the server
|
|
specialises at verb time. That is the design; nothing in this tree contradicts it.
|
|
|
|
### 3.5 Sampler/LOD-bias/UBO/atomic-counter binding at draw time
|
|
|
|
`BindCurrentProgramWithResources` (`DirectGLES.cpp:3765-4041`), in order:
|
|
|
|
1. `backendProgram.Use()` (`:3781`; the impl is `Managers.cpp:10347-10390`, with the
|
|
`m_rebindAfterRelink` rule — a relink replaces the executable behind an unchanged GL name);
|
|
2. **global UBO / default uniform block** (`:3786-3856`): `GetUBOSize()`, `GetUBOContentVersion()`,
|
|
`MapUBO()`; preferred path is the shared persistent-mapped **UBO ring**
|
|
(`UboRingAvailable/UboRingAllocate/UboRingMappedPtr/UboRingBufferId`, `:3805-3831`) with a
|
|
per-program `UboRingAllocation` keyed `{contentVersion, ringGeneration, frameSerial, offset}`
|
|
(`Managers.h:2003`, `BufferImpl::UboRingAllocation`), and a `glBufferSubData` fallback
|
|
(`:3833-3852`). Bytes are accounted to `PipeStats::ByteClass::StageUboGlobal`
|
|
(`MG_Util/Metrics/PipeStats.h:55-57`). **This is `set_global_constants`'
|
|
(`MGPGlobalConstants{ShaderCso, Version, Blob}`, `MGPipeTypes.h:507-513`, "covers the DEFAULT
|
|
UNIFORM BLOCK only (D6)") counterpart** — the ring is on `ARCHITECTURE.md:315`'s
|
|
do-not-touch list, so the P4a shape is: the client emits the block image + version, the server
|
|
keeps the ring exactly as-is;
|
|
3. **named UBOs** (`:3857-3910`): `blockBackendIndices` from the link cache,
|
|
`currentProgram->GetUniformBlockBinding(i)`, `MGB_CTX->GetBufferBindingPoint(Uniform, binding)`,
|
|
then the P3a buffer clean-probe (`GetBufferResource`/`IsBufferDrawClean`/`EnsureBufferResource`,
|
|
`:3884-3894`) and `BindBufferBaseCached`/`BindBufferRangeCached`. This is
|
|
`set_shader_buffers(Uniform)` — **P4b, not P4a** (dirty bits 15-17, `Managers.h:717-723` says so
|
|
explicitly);
|
|
4. **atomic counters** (`:3912-3921`) via `SyncAtomicCounterBuffers(bindings, esslBindingTop)`;
|
|
5. the sampler pass (§1.6).
|
|
|
|
### 3.6 The SPIR-V → ESSL transpile: what is per-program and what is per-context
|
|
|
|
* **`SpvcSession` is per STAGE, per transpile, and stack-allocated**:
|
|
`MG_Util/ShaderTranspiler/SpvcSession.h:89-105` (move-only, non-copyable), constructed at
|
|
`Managers.cpp:9019-9020` inside `TranspileSpirvToEssl`. Nothing persists it. Its mutators that
|
|
carry program state: `SetShaderStorageBlockBinding` (`SpvcSession.h:120`, called
|
|
`Managers.cpp:9037-9039`), `SetAtomicCounterBlockBindings` (`SpvcSession.h:135`, called
|
|
`Managers.cpp:9060-9061`, and **both halves are memo state** — the top is key, the returned
|
|
bindings are payload, `:9051-9059`), `SetVertexAttribLocation` (`:109`),
|
|
`DropDefaultFragmentOutputColorIndex` (`:154`), `RelaxReadWriteExclusiveStorageBuffers` (`:175`),
|
|
`ReflectTransformFeedbackCaptures` (`:189`), `StripTransformFeedbackDecorations` (`:201`),
|
|
`SetEntryPoint` (`:202`), `SetSpecializationConstants` (`:210`).
|
|
* **The L2 ESSL translation cache is per PROCESS**: `GetEsslTranslationCache()`
|
|
(`MG_Util/ShaderTranspiler/TranslationCache.h:698`), consulted at `Managers.cpp:9664-9720`.
|
|
Key inputs `EsslTranslationKeyInputs` (`TranslationCache.h:635-689`) mix **driver capability bits**
|
|
(`viewportIndexLoweringArmed`, `supportsNoperspectiveInterpolation`,
|
|
`supportsExtendedImageFormats`, the four sample-count limits) with **per-program/per-context**
|
|
inputs (`xfbCaptureBlockNames`, `glFormatByUniformName`, `storageBlockBindingOverrides`,
|
|
the two block-rename maps, the two location-strip flags, `atomicCounterEsslBindingTop`,
|
|
`esslVersion`, `enableSpirvValidation`). Payload `EsslTranslationResult` (`:618-632`) carries the
|
|
ESSL **plus** `flattenedXfbBlockNames` **plus** `atomicCounterGlBindings` — the header records
|
|
why dropping either is a silent-wrong-output bug. `Managers.cpp:8556-8558` states the invariant:
|
|
*every* read inside `TranspileSpirvToEssl` must appear in `BuildEsslTranslationKey`.
|
|
`MG_Test/ShaderTranspiler/TranslationCacheTest.cpp` is its unit test.
|
|
* **`InvalidateCompileEnv()` is a forwarded (F-class) `PipeInputs` accessor**
|
|
(`MG_Backend/MGPipe/PipeInputs.h:573`), i.e. already a reverse call rather than a state read.
|
|
|
|
`ARCHITECTURE.md:258-260`: glslang stays entirely on the **client**, SPIRV-Cross
|
|
(`TranspileSpirvToEssl`) entirely on the **server**, file-level cut. No `MOBILEGL_IPC_PROGRAM`
|
|
switch, no server-side compile pool.
|
|
|
|
### 3.7 What a shader CSO on the wire must carry
|
|
|
|
`MGPipeTypes.h:320-335`:
|
|
|
|
```cpp
|
|
struct MGPProgramDesc { // 192 B
|
|
MGPipeHandle Cso;
|
|
Uint32 StageMask; // == GetLinkedShaderStages()
|
|
Uint32 GlobalUboSize;
|
|
Uint32 ReservedNumSamplesOffset;
|
|
Uint8 SpirvStatus, NativeFloat64, PointSizeDemoted, EnableSpirvValidation;
|
|
MGPBlobRef Spirv[6]; // per stage
|
|
MGPBlobRef Reflection; // the whole LinkArtifacts + SpirvArtifacts archive
|
|
};
|
|
```
|
|
|
|
`ARCHITECTURE.md:259` enumerates what the archive **must** cover: the four `ResourceReflection`
|
|
vectors (each with its `TypeFacts`), `uniformSamplerOrImageUnitIndex`, `uniformBlockBinding`,
|
|
`shaderStorageBlockBinding` (by name), `explicitOpaqueUniformBindings`,
|
|
`xfbVaryings/xfbStrides/xfbPackedStride/xfbNeedsScatteredCapture`, `computeLocalSize`, the GS/TCS/TES
|
|
facts, `usesReservedNumSamples`, `uniformOffsets`; and `XfbVarying` carries two spellings (GL name
|
|
+ block instance/member/element).
|
|
|
|
### 3.8 `ProgramArtifacts.h` — the archive as it stands
|
|
|
|
`MG_State/GLState/ProgramState/ProgramArtifacts.h`, 576 lines, extracted at P0.5. Purity is asserted
|
|
by `check_include_closure.py`'s `artifacts-header` probe (`:16-19`), and the header notes that it is
|
|
glslang-free **by symbol**, not textually (`:10-15`).
|
|
|
|
* `TypeFacts` (`:31-59`, 20 fields, POD, `sizeof == 44` pinned at `:552`);
|
|
* `ResourceReflection` (`:63-86`, 14 fields, `sizeof == 128` on libstdc++);
|
|
* `XfbVarying` (`:95-121`, 11 fields, `sizeof == 128`);
|
|
* `LinkArtifacts` (`:160-345`, **58 members**, `sizeof == 1056`), including the live
|
|
`SharedPtr<glslang::TProgram> program` at `:165` that is **null for every archived instance by
|
|
construction** and is the one member `VisitFields` deliberately omits (`:471-474`);
|
|
* `SpirvArtifacts` (`:365-402`, 8 fields, `sizeof == 88`): `generatedSpirv`,
|
|
`enableSpirvValidation`, `uniformOffsets`, `globalUboScratch`, `reservedNumSamplesOffset`,
|
|
`spirvStatus`, `nativeFloat64`, `pointSizeDemoted`.
|
|
* **The visitor tables**: `VisitFields(TypeFacts)` `:408-434`, `(ResourceReflection)` `:436-453`,
|
|
`(XfbVarying)` `:455-469`, `(LinkArtifacts)` `:471-535` (57 fields), `(SpirvArtifacts)` `:537-547`.
|
|
Contract at `:404-407`: `v(const char* name, Field&)`, `Field` const when `Self` is const, so **one
|
|
table serves both directions**.
|
|
* **Trip wires** `:549-575`: `sizeof` static_asserts, pinned **per STL** — libstdc++ numbers are in
|
|
the tree, the libc++/NDK branch at `:565-566` is **inert until the integrator pins it**
|
|
(`MGL_ARTIFACT_SIZES_LIBCXX_PINNED`). `MG_Test/Program/ProgramArtifactsTest.cpp` records every
|
|
`sizeof` as a ctest property on every platform.
|
|
|
|
> **The serializer does not exist.** Grep finds no writer/reader over these tables — only the tables
|
|
> and the size asserts. Every `VisitFields` comment says "(and its serializer when one exists)".
|
|
> **P4a's contract package owns writing it**, and the NDK `sizeof` pin at `:565-566` is a
|
|
> gate the integrator must close in the same phase, or an Android build silently has no trip wire.
|
|
|
|
### 3.9 Program-pipeline composites (`ProgramPipelineScenario`)
|
|
|
|
`GLContext::GetProgramForDraw()` (`MG_State/GLState/Core.h:190`, `Core.cpp:609`) already flattens a
|
|
pipeline into one hidden composite `ProgramObject` **entirely in the frontend**, which is why
|
|
`ARCHITECTURE.md:212` says the tracker pushes **one** handle out of the composite band and the
|
|
server never learns it is a composite. Two signatures decide the work
|
|
(`MG_State/GLState/ProgramState/ProgramPipelineObject.h`):
|
|
|
|
* `ComputeDrawProgramSignature` (`:97-107`) — `{lifetimeId, GetLinkVersion()}` per graphics stage;
|
|
the header (`:75-85`) records that keying on `GetBackendStateVersion()` instead made the SSO
|
|
conformance loop rebuild the composite (glslang + SPIR-V + spirv-opt) **on every draw**;
|
|
* `ComputeUniformMirrorVersions` (`:125-141`) — the per-stage refresh gate, mixing
|
|
`backendStateVersion<<32 | uboContentVersion` and `blockBindingVersion<<32 | uniformWriteSetVersion`.
|
|
|
|
`DirectGLES.cpp:3157-3163` records the accident this removed: `glUniform1i` on an image uniform used
|
|
to bump the composite's cache key and therefore produce a whole new twin.
|
|
|
|
`ProgramPipelineScenario.cpp:1-28` lists the sixteen conformance cases (compute_shader.*,
|
|
shader_image_load_store.advanced-sso-*, shader_storage_buffer_object.basic-syntaxSSO /
|
|
basic-noBindingLayout) that depend on the flattening. `MG_Test/Program/ProgramPipelineCompositeTest.cpp`
|
|
is the unit-level pin.
|
|
|
|
### 3.10 The "CompositeResolver"
|
|
|
|
**It does not exist in the tree.** `grep -rn CompositeResolver MobileGL/` returns nothing; the only
|
|
two mentions are `docs/Disaggregated/ARCHITECTURE.md:562` (the P2+ file list for `MG_Impl/Pipe/`,
|
|
which today holds `CsoCache.h, PipeFill.cpp, PipeFill.h, ResourceTracker.h, SetHashSuppressor.h,
|
|
SlotAllocator.{h,cpp}, Tracker.h, VertexInputEmit.h`) and `ROADMAP.md:20` (P4a's own row).
|
|
|
|
From `ARCHITECTURE.md:212` and `MGPipeHandles.h:84-92`, what it composes is **program + program**:
|
|
the pipeline's per-stage `ProgramObject`s → one `ShaderCso` handle out of the reserved top-1/16 band,
|
|
with the pipeline cache's eviction releasing the slot, bumping `gen` and emitting
|
|
`delete_shader_state`. It is **not** a framebuffer/sampler composer — those are separate
|
|
(`set_framebuffer_state`, `bind_sampler_states`). Its side benefit, stated at `:212`: the blocking
|
|
`JoinLinkAndSpirv()` leaves the server's draw path. Model it on `MG_Impl/Pipe/CsoCache.h`
|
|
(`kMGPipeCsoCacheCapacity = 64` at `:53`, `Acquire` at `:74`, `Counters` at `:57-69`, LRU eviction,
|
|
and the `kMGPipeBehaviourNoCsoContentAddressing` negative control at `:30-37`).
|
|
|
|
---
|
|
|
|
## 4. Emulations that touch programs — which stay frontend-side
|
|
|
|
`ARCHITECTURE.md:216` is the rule: *驱动表达不了的变换在 tracker 里 lowering,硬件/驱动强加的变换在
|
|
driver 里 lowering*, and the per-emulation table is `:221-243`.
|
|
|
|
| emulation | code | verdict |
|
|
|---|---|---|
|
|
| **fragColor broadcast** | `PrgramImpl::BroadcastLegacyFragColor` (`Utils.h:260`, `Utils.cpp:506`), called `Managers.cpp:9854`; the count is `g_fragColorBroadcastCount` (`Managers.h:2129`) memoised from the draw FBO's `GetDrawBuffers()` at `DirectGLES.cpp:3078-3101`, snapshotted onto the twin at `Managers.cpp:9261` | **SERVER.** It is a post-emission ESSL rewrite (`ARCHITECTURE.md:315` do-not-touch: "SPIRV-Cross 会话与 post-emission ESSL 重写"), keyed on framebuffer state the server already holds after `set_framebuffer_state`. |
|
|
| **noperspective** | `ShaderCompiler::EmulateNoPerspectiveForEssl`, `Managers.cpp:8789-8792`, armed on `!g_GLESCapabilities.SupportsNoperspectiveInterpolation` (`:8788`); the arming bit is L2 key material (`TranslationCache.h:661`) | **SERVER.** Driver-capability-armed SPIR-V pass; the client cannot know the arming. |
|
|
| **fp64 demotion** | `DemoteFloat64Pass`, decided **per program at link** and recorded as `SpirvArtifacts::nativeFloat64` (`ProgramArtifacts.h:378-390`); read by `BackendObject.h:496`, `Managers.cpp:3522`/`:3924`, DirectVulkan `VertexInputStateFactory.cpp:253`/`:494`, and by the frontend uniform path `GL_Program.cpp:1319`/`:1643`/`:1784` | **CLIENT (already).** The demotion happens before the archive exists; the flag rides in `MGPProgramDesc::NativeFloat64` (`MGPipeTypes.h:329`) so the server knows the layout. The **vertex-side** fp64 narrowing is server-side (`ARCHITECTURE.md:233`), and ID-15's M-3 ruled the P3a `SyncGpuWrites` gap on that stream a deviation — **P4a inherits that open item**. `DoublePrecisionScenario` is the gate. |
|
|
| **XFB scatter** | `XfbImpl` in `DirectGLES.cpp:895-1230`: `XfbCaptureTarget` `:895`, `scattered/scatterProgram/scatterCapacityVertices` `:922-924`, `g_scatterBufferId` `:930`, `ReadbackCapturedRanges` `:987`, `BindScatterCaptureBuffer` `:1032`, `ScatterCapturedRecords` `:1068-1140`, armed at `StartPendingTransformFeedback` `:1210` off `program->NeedsScatteredTransformFeedbackCapture()` (`ProgramObject.h:1227` → `xfbNeedsScatteredCapture`) | **CLIENT** — `ARCHITECTURE.md:239` and §8.5. The read-modify-write moves to the client. Note `Managers.h:576-580`: `ReadbackCapturedRanges` and the scatter path are two of the backend-initiated shadow writebacks that bump the buffer-mutation epoch **without** an op — P3a's epoch contract already names them, and a P4a move must not orphan those bumps. |
|
|
| **texture LOD bias** | `EmulateTextureLodBias` (`Utils.h:536`, called `Managers.cpp:9855`) + the per-draw `glUniform1f` at `DirectGLES.cpp:3982-4000` | **SERVER** (post-emission rewrite), but the *value* is sampler state and must arrive with the sampler CSO / sampler view. `MGPTextureParams::LodBias` exists (`MGPipeTypes.h:317`) but `SamplerParameters::lodBias` (`MGPipeValueTypes.h:470`) is the one the pass reads. |
|
|
| **image format bake / widen / split / array remap** | §2.5, plus `BakeImageFormatQualifiers` (`Utils.h:313`), `RemapImageArrayElementUnits` (`:365`), `SplitReadWriteImageUniforms` (`:519`), `RemoveLayoutBinding` (`:314`) | **SERVER.** `ARCHITECTURE.md:235`: image-bindable 存储加宽/拆分 → server. |
|
|
| **pass-through TCS synthesis** | `AttachPassthroughTessControlStage` (`Managers.h:2041-2044`), `BuildPassthroughTessControlEssl` (`Utils.h:408`) | **SERVER**, keyed on `set_patch_state` (already P2). |
|
|
|
|
Everything in this table except fp64-demotion and XFB scatter stays on the server, which is why the
|
|
shader CSO's payload is SPIR-V + archive and **not** ESSL.
|
|
|
|
---
|
|
|
|
## 5. Tests
|
|
|
|
### 5.1 Integration scenarios that already exercise this surface
|
|
|
|
All rows are in `MG_IntegrationTest/CMakeLists.txt:56-135`.
|
|
|
|
*Samplers / border colour*: `IntegerBorderColorScenario` (`:127`) — the `borderColorForm` scenario;
|
|
its header (`Scenarios/IntegerBorderColorScenario.cpp:9-33`) documents the exact Espryt failure
|
|
(1132396544 = the bits of 255.0f read through an integer sampler) and the Vulkan
|
|
`VK_EXT_custom_border_color` requirement. **ID-21 records a pre-existing ASan stack-buffer-overflow
|
|
in this scenario** — a test bug, and P4a will be running it a lot.
|
|
Also `SampledSetStalenessScenario` (`:65`), `TextureViewScenario` (`:114`),
|
|
`ClearTexImageUndefinedLevelZeroScenario` (`:128`).
|
|
|
|
*Images*: `ImageLoadStoreSsoScenario` (`:88`), `ImageTargetKindScenario` (`:89`),
|
|
`ImageFormatQualifierScenario` (`:90`), `NonCoreImageFormatScenario` (`:91`),
|
|
`ImageSizeAfterRespecScenario` (`:92`), `FormatlessImageBakeScenario` (`:100`),
|
|
`UnboundImageDescriptorScenario` (`:126`), `AtomicCounterScenario` (`:119`).
|
|
|
|
*Programs*: `ProgramPipelineScenario` (`:87`), `PostLinkAttachScenario` (`:99`),
|
|
`RelinkStageSetScenario` (`:124`), `AsyncCompileScenario` (`:61`, with two pinned-config lanes at
|
|
`:679-753`), `PipelineFailureScenario` (`:68`), `SpirvShaderBinaryScenario` (`:123`),
|
|
`UniformInitializerScenario` (`:82`), `Glsl420DeclarationScenario` (`:94`),
|
|
`IoBlockNameCollisionScenario` (`:95`), `UnlocatedIoBlockScenario` (`:96`, plus a location-strip
|
|
pinned-on lane at `:647-670`), `PointSizeDemotionScenario` (`:109`, pinned lanes `:814-834`),
|
|
`SsboDeclarationFormScenario` (`:93`), `SsboArrayLengthScenario` (`:80`),
|
|
`SsboArrayDynamicIndexScenario` (`:121`), `DoublePrecisionScenario` (`:81`),
|
|
`ViewportArrayScenario` (`:79` + negative control `:768`), the five XFB scenarios (`:104-108`),
|
|
`TessellationDrawModeScenario`/`GeometryDrawModeScenario` (`:97-98`).
|
|
|
|
*The P2/P3a machinery scenarios P4a extends*: `HandleRecycleScenario` (`:133`, six pinned arms at
|
|
`:979-1024`), `CsoContentAddressingScenario` (`:134`, four lanes `:1086-1113`),
|
|
`ResourceSubsystemControlScenario` (`:135`, two lanes `:1160-1169`),
|
|
`PipeVerifyArmingScenario` (`:131`), `PoisonOmissionScenario` (`:132`).
|
|
|
|
`HandleRecycleScenario` today covers exactly two kinds — buffer
|
|
(`ABufferAtARecycledAddressDoesNotInheritItsPredecessorsContents`, header `:14`) and VAO
|
|
(`AVertexArrayAtARecycledAddressDoesNotInheritItsPredecessorsVertexInput`, `:568`). **P4a's G8
|
|
analogue is: add a texture case, a sampler case and a program case, each with `.Handles`/`.Legacy`/
|
|
`.AbaControl` arms**, and record the red-before evidence on the contract tree exactly as P3a did.
|
|
|
|
### 5.2 Unit tests
|
|
|
|
`MG_Test/Pipe/`: `PipeCatalogueTest.cpp` (payload sizes — `sizeof(SamplerParameters)==100` at
|
|
`:131`, trivially-copyable at `:138`), `CsoCacheTest.cpp`, `TrackerTest.cpp`, `SlotAllocatorTest.cpp`,
|
|
`PipeInputsTest.cpp`, `RenderStateSpansTest.cpp`, and P3a's two emitter tests
|
|
`ResourceEmitTest.cpp` / `VertexInputEmitTest.cpp` (the shape P4a's `SamplerEmitTest` /
|
|
`ProgramEmitTest` / `ImageEmitTest` should copy; note ID-14 — `ResourceEmitTest`'s `ApplierGuard`
|
|
is the fixture-scoping pattern, and `MG_Test/Buffer/BufferTest.cpp` needed the same treatment).
|
|
|
|
`MG_Test/ShaderTranspiler/`: `SpirvPassTest.cpp`, `TranslationCacheTest.cpp`,
|
|
`WidenImageFormatsTest.cpp`, `FlattenAtomicCounterBlockTest.cpp`, `FlattenXfbInterfaceBlocksTest.cpp`,
|
|
`UniquifyIoBlockNamesTest.cpp`, `StripIoBlockLocationsTest.cpp`, `LowerViewportIndexTest.cpp`,
|
|
`DemoteFloat64Test.cpp`, `DemotePointSizeTest.cpp`, `LegalizeResourceArrayIndexTest.cpp`,
|
|
`ClampMultisampleFetchTest.cpp`, `GlslangCaptureTest.cpp`, `DeriveNumSubgroupsTest.cpp`,
|
|
`EmulateSubgroupsTest.cpp`, `FixIterationRP*Test.cpp`, `FlattenFloat64StorageBlockTest.cpp`.
|
|
`EmulateNoPerspectiveForEssl` is tested from `MG_Test/Program/ProgramUtilTest.cpp:1344`, `:1385`,
|
|
`:1439`, `:1465` (not from the ShaderTranspiler directory).
|
|
|
|
`MG_Test/Program/`: `ProgramArtifactsTest.cpp` (**the archive's size-and-field pin — P4a's serializer
|
|
test belongs beside it**), `ProgramPipelineCompositeTest.cpp`, `ProgramInterfaceTest.cpp`,
|
|
`ProgramTest.cpp`, `ProgramUtilTest.cpp`, `AsyncLinkTest.cpp`, `AsyncSpirvPhaseTest.cpp`
|
|
(reads `GetLinkVersion()` at `:695`/`:701`), `AsyncCompileTest.cpp`, `AsyncTeardownTest.cpp`,
|
|
`OptimisticStatusTest.cpp`, `ParallelShaderCompileTest.cpp`, `ShaderCompileAdoptionTest.cpp`,
|
|
`TessellationLinkTest.cpp`, `XfbBlockVaryingTest.cpp`, `XfbFrontendOrderInvarianceTest.cpp`.
|
|
|
|
### 5.3 `PipeInputs` accessors for programs
|
|
|
|
`MG_Backend/MGPipe/PipeInputs.h`. O-class storage: `m_programForDispatch`, `m_programForDraw`,
|
|
`m_transformFeedbackProgram` (`:683-685`), listed in `MGP_INPUT_STORAGE_LIST` at `:108-109`, `:122`,
|
|
with accessors at `:534-548`. F-class (forwarded, sticky, no poison check — the declared exception
|
|
at `:558-568`): `GetProgramObject(Uint index)` `:570`, `ValidateProgramName(Uint index)` `:574`,
|
|
plus `GetTextureObject` `:571` and `InvalidateCompileEnv` `:573`.
|
|
`Coverage.def:99-101` maps `GetProgramForDispatch→SetDispatchProgram`,
|
|
`GetProgramForDraw→SetDrawProgram`, `GetProgramObject→CreateShaderState`, and `:127`/`:146-149`
|
|
records `GetProgramObject`/`ValidateProgramName` as client-resolved name lookups.
|
|
|
|
`gPipeInputs` is **leak-at-exit storage** (`PipeInputs.h:688-700`, ID-18/ID-21): its O-class
|
|
`SharedPtr`s reach `~BufferObject` → the slot allocator at exit. P4a adds **more** frontend
|
|
`SharedPtr` members if it is careless — sampler/program handles must be `MGPipeHandle`, not
|
|
`SharedPtr`, or the exit-order closure has to be re-litigated.
|
|
|
|
---
|
|
|
|
## 6. Traps and open items for the P4a brief
|
|
|
|
1. **`SamplerParameters` has 3 bytes of trailing padding and no field table.** §1.1. Contract commit
|
|
must add `MGP_FIELDS_SamplerParameters` + the `MGP_VERIFY_PAYLOAD_LIST` row, and the sampler CSO's
|
|
content hash must be field-wise. A memcmp-based hash mints a fresh CSO per call.
|
|
2. **`MGPTextureParams` cannot express what `SyncBuiltinSamplerToBackend` pushes.** §1.8 — no filter,
|
|
wrap, compare mode or border colour. Decide in the contract: widen the payload (it is 32 B with
|
|
an `MGP_ASSERT_POD`) or give each texture an implicit sampler CSO.
|
|
3. **The archive has no serializer, and the libc++/NDK `sizeof` pin is inert.**
|
|
`ProgramArtifacts.h:565-566`. Both are contract-package work; the NDK pin must land in the same
|
|
phase or the Android build has no trip wire on a 58-member struct.
|
|
4. **`g_boundSamplersCache` is a raw twin pointer per unit and is load-bearing for three memos.**
|
|
§1.4-1.6. It becomes a per-unit handle array in the applier; the `memcmp` replay proofs
|
|
(`DirectGLES.cpp:3597-3604`, `:3942-3950`) have to be re-derived against that.
|
|
5. **The sampler twin is created lazily, only from the program pass** (`DirectGLES.cpp:4014-4020`).
|
|
`create_sampler_state` inverts the timing of `glGenSamplers`.
|
|
6. **Three of the program twin's nine staleness keys are not program state.** §3.2/§3.4. The
|
|
`st_variant` specialisation is the design (`ARCHITECTURE.md:264`); a brief that treats
|
|
`create_shader_state` as self-contained will produce a per-draw rebuild.
|
|
7. **`g_rawDepthFetchSamplerState` constructs a frontend `SamplerObject` inside `MG_Backend`**
|
|
(`DirectGLES.cpp:209`). `ARCHITECTURE.md:322` names its removal as P4a work. The purity gate G13
|
|
(`grep -rc pGLContext MG_Backend` empty) does not catch it — it is a type use, not `pGLContext`.
|
|
8. **The image sweep's gate must stay keyed on the frontend generation** (`DirectGLES.cpp:2195-2201`)
|
|
— a server-side epoch is bumped too late by construction.
|
|
9. **`SlotTables.h` still mints handles from inside `MG_Backend`** for the six P2 kinds
|
|
(`:70-77` records the debt; `GetOrCreate(StatePtr)` at `:209-252` calls
|
|
`MGPipeSlots().Acquire`). P3a discharged it for `Buffer` only. P4a discharges it for
|
|
`Texture`, `Framebuffer`, `SamplerCso`, `ShaderCso` — four of the remaining five — and should say
|
|
whether `Renderbuffer` follows in the same phase (its `resource_*` conversion is in the
|
|
`ROADMAP.md:20` cell) or is left for later.
|
|
10. **`ForEachLive` needs `Entry::stateRef`, and a handle-keyed entry has none** (`SlotTables.h:266-269`,
|
|
`:443-463`). The one direct-iteration site is `ScopedDetachedTextureFramebufferAttachments`, which
|
|
walks **texture** twins (`SlotTables.h:61-64`) — i.e. exactly a kind P4a converts. That iteration
|
|
has to be re-expressed before the texture table goes handle-keyed, or it silently walks nothing.
|
|
11. **The photon canary.** §2.4 — pin `photon-v1.3b` (llvmpipe retrace, **not** Adreno) as a named
|
|
P4a case; it is the only thing that has ever caught an image-binding-semantics regression.
|
|
12. **`ProgramPipelineObject`'s composite band is allocated but unused** (`MGPipeHandles.h:84-92`).
|
|
The composite resolver mints into it, and the pipeline cache's eviction is what frees the slot,
|
|
bumps `gen` and emits `delete_shader_state` (`ARCHITECTURE.md:212`).
|
|
13. **Inherited from P3a**: ID-15 M-3 (the fp64 stream's dropped `SyncGpuWrites`) is still a ruled
|
|
deviation; ID-21's `IntegerBorderColorScenario` ASan stack-buffer-overflow is still open and sits
|
|
squarely in P4a's sampler test set; ID-19's per-draw vertex-input emission cost (+699/+422
|
|
ns/draw) is on the optimisation list and P4a's per-unit sampler/image emission is the same shape
|
|
of risk on VAO/texture-heavy frames (rd12 was +30%/+27%).
|
|
14. **Subsystem bits.** `MGPipe.h:72-95` has bits 0-8 used and
|
|
`kMGPipeSubsystemsMigratedAtP3a = 0x1ff`. P4a needs its own bits (framebuffer / texture+sampler /
|
|
program are three natural ones) and a `kMGPipeSubsystemsMigratedAtP4a`, plus the arm-resolution
|
|
treatment at `Managers.cpp:2352-2400` (`ClassifyPipeSubsystemArm`, `StopOnArmlessPipeSubsystem`)
|
|
— including the **dependency refusal** pattern (bit 8 requires bit 7, `Managers.cpp:2395-2400`):
|
|
a program/sampler bit will require the texture bit for the same reason.
|
|
15. **Dirty bits.** `Tracker.h:70-83` already computes `NewShader`, `NewShaderBindings`,
|
|
`NewGlobalConstants`, `NewFramebuffer`, `NewSamplerViews`, `NewSamplers`, `NewShaderImages`
|
|
(values at `:246-262` and `:305-312`) — P4a only has to **emit** them and extend
|
|
`kMGPipeDirtyEmittedAtP3a` (`:104-107`) into a `…AtP4a`. `MGPipeSubsystemForDirty` (`:128`ff)
|
|
is where each new bit's gate goes.
|
|
16. **Registration.** `MGPipeSetResourceOps` is installed from `BufferImpl::RegisterBufferBackendOps`
|
|
(`Managers.cpp:2529-2551`, called `BackendObject_DirectGLES.cpp:849` and
|
|
`DirectGLES.cpp:10568`) — ID-8's accepted deviation. P4a's new op tables should get their own
|
|
registration entry point rather than piling onto the buffer one.
|