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
52 KiB
Markdown
811 lines
52 KiB
Markdown
# Scout: render state → CSO + dynamic state (P2 groundwork)
|
||
|
||
Worktree `C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg`, branch
|
||
`feat/disaggregated` @ `e7a6a72f`. Every line number below was opened in this worktree at that
|
||
commit. Paths are repo-relative to that root unless absolute.
|
||
|
||
---
|
||
|
||
## 1. `RenderStateParameters` as it is today
|
||
|
||
File: `MobileGL/MG_Pipe/MGPipeValueTypes.h`, `struct RenderStateParameters` at **line 239**,
|
||
closing brace **line 387**. It moved here from `MG_State/GLState` in P0.5; the MG_State headers
|
||
now include this file, so every old spelling still compiles (header comment lines 17-25).
|
||
|
||
### 1.1 Trip wires around it (`MGPipeValueTypes.h:539-544`)
|
||
|
||
```
|
||
539 static_assert(std::is_trivially_copyable_v<RenderStateParameters>);
|
||
540 static_assert(std::is_standard_layout_v<RenderStateParameters>); // offsetof legality
|
||
541 static_assert(sizeof(RenderStateParameters) == 1168, "...MGL_RESIDUAL_BLOCK_SIZE and the Espryt spans depend on it");
|
||
543 static_assert(offsetof(RenderStateParameters, BlendStates) < offsetof(RenderStateParameters, LogicOp));
|
||
544 static_assert(std::tuple_size_v<decltype(RenderStateParameters::BlendStates)> == kMGMaxDrawBuffers);
|
||
```
|
||
|
||
Supporting element sizes, also asserted in the same file: `PerBufferBlendState` = 28
|
||
(`:537`), `StencilFaceState` = 28 (`:538`), `PixelStoreParameters` = 28 (`:536`).
|
||
`kMGMaxDrawBuffers = 8` at `:30`; `MAX_VIEWPORTS = 16` is a `static constexpr` member at
|
||
`:244` (no storage).
|
||
|
||
Vector sizes (from `MobileGL/MG_Util/Math/VectorTypes.h:16-18` — `VecBase` is a bare
|
||
`Array<T,N>`, `:215-224` for the aliases): `FloatVec4`/`IntVec4` = 16, `FloatVec2` = 8,
|
||
`BoolVec4` = 4 (`Bool` is 1 byte), and enum classes default to a 4-byte underlying type.
|
||
|
||
### 1.2 Every member, in declaration order, with computed offset/size
|
||
|
||
Offsets below are derived by hand from the above sizes and align-4 layout; they sum to exactly
|
||
**1168**, which is the `static_assert` at `:541`, so the derivation is self-checking. **P2 must
|
||
still compute them with `offsetof` in `MGPipeRenderStateSpans.cpp`** — `scripts/gen_pipe.py:72-74`
|
||
explicitly refuses to guess them in python.
|
||
|
||
| # | Member | Decl line | Off | Size | Class today | Notes |
|
||
|---|--------|-----------|-----|------|-------------|-------|
|
||
| 1 | `Array<FloatVec4,16> Viewports` | 255 | 0 | 256 | **dynamic** | float state since GL 4.1; index 0 is what `glViewport`/`GetIntegerv` address |
|
||
| 2 | `Float LineWidth` | 256 | 256 | 4 | **dynamic** | `VK_DYNAMIC_STATE_LINE_WIDTH` |
|
||
| 3 | `Float PointSize` | 257 | 260 | 4 | **dynamic** | Espryt head span, ES 1.x only |
|
||
| 4 | `Uint PatchVertices` | 259 | 264 | 4 | **pipeline** | in G7 list |
|
||
| 5 | `FloatVec4 PatchDefaultOuterLevel` | 265 | 268 | 16 | **pipeline** | in G7 list; baked into synthesized TCS |
|
||
| 6 | `FloatVec2 PatchDefaultInnerLevel` | 266 | 284 | 8 | **pipeline** | in G7 list |
|
||
| 7 | `Float PolygonOffsetFactor` | 267 | 292 | 4 | **dynamic** | `VK_DYNAMIC_STATE_DEPTH_BIAS` |
|
||
| 8 | `Float PolygonOffsetUnits` | 268 | 296 | 4 | **dynamic** | idem |
|
||
| 9 | `Float PolygonOffsetClamp` | 272 | 300 | 4 | *neither* | **no backend reads it** (grep: 0 hits under `MG_Backend/`) |
|
||
| 10 | `GLenum ClipOrigin` | 276 | 304 | 4 | *neither* | getter-only; only reader is `MG_Impl/GLImpl/Getter/GL_Getter.cpp:1541` |
|
||
| 11 | `GLenum ClipDepthMode` | 277 | 308 | 4 | *neither* | idem, `GL_Getter.cpp:1544` |
|
||
| 12 | `Array<PerBufferBlendState,8> BlendStates` | 280 | **312** | 224 | **pipeline** | span boundary: `offsetof(BlendStates)` = Espryt `kBlendSpanBegin` |
|
||
| 13 | `LogicOperation LogicOp` | 281 | **536** | 4 | **pipeline** | span boundary: `offsetof(LogicOp)` = Espryt `kBlendSpanEnd` |
|
||
| 14 | `Bool DepthTestEnabled` | 284 | 540 | 1 | **pipeline** | |
|
||
| 15 | `DepthTestFunc DepthFunc` | 285 | 544 | 4 | **pipeline** | |
|
||
| 16 | `Bool DepthMask` | 286 | 548 | 1 | **pipeline** | |
|
||
| 17 | `Array<BoolVec4,8> ColorMasks` | 290 | 549 | 32 | **pipeline** | align 1; note the odd offset |
|
||
| 18 | `FloatVec4 ClearColor` | 293 | 584 | 16 | **dynamic** | |
|
||
| 19 | `Float ClearDepth` | 294 | 600 | 4 | **dynamic** | |
|
||
| 20 | `Uint32 ClearStencil` | 295 | 604 | 4 | **dynamic** | |
|
||
| 21 | `FloatVec4 BlendColor` | 296 | 608 | 16 | **dynamic** | `VK_DYNAMIC_STATE_BLEND_CONSTANTS` |
|
||
| 22 | `Array<FloatVec2,16> DepthRanges` | 303 | 624 | 128 | **dynamic** | rides `VkViewport::min/maxDepth` |
|
||
| 23 | `Float SampleCoverageValue` | 304 | 752 | 4 | *see §2 conflict* | Espryt-only; `SetSampleCoverage` bumps the **pipeline** version but the field is NOT in the G7 list |
|
||
| 24 | `Bool SampleCoverageInvert` | 305 | 756 | 1 | idem | idem |
|
||
| 25 | `Uint32 SampleMaskValue` | 306 | 760 | 4 | **pipeline** | in G7 list |
|
||
| 26 | `Float MinSampleShadingValue` | 310 | 764 | 4 | **pipeline** | in G7 list |
|
||
| 27 | `Array<StencilFaceState,2> StencilStates` | 311 | 768 | 56 | **split** | `.Func`/`.FailOp`/`.PassDepthFailOp`/`.PassDepthPassOp` = pipeline; `.Ref`/`.ValueMask`/`.WriteMask` = dynamic — the one member that straddles the split |
|
||
| 28 | `Bool CullFaceEnabled` | 314 | 824 | 1 | **pipeline** | |
|
||
| 29 | `CullFaceMode CullFaceModeSetting` | 315 | 828 | 4 | **pipeline** | |
|
||
| 30 | `FrontFaceMode FrontFaceModeSetting` | 316 | 832 | 4 | *Espryt-only* | Magma never reads it (see §4.4) |
|
||
| 31 | `ProvokingVertexMode ProvokingVertexModeSetting` | 317 | 836 | 4 | *unhashed pipeline* | Magma reads it at `VulkanRenderer.cpp:12768` but it is not in the hash |
|
||
| 32 | `GLenum LineSmoothHint` | 320 | 840 | 4 | *neither* | no backend reader |
|
||
| 33 | `GLenum PolygonSmoothHint` | 321 | 844 | 4 | *neither* | |
|
||
| 34 | `GLenum TextureCompressionHint` | 322 | 848 | 4 | *neither* | |
|
||
| 35 | `GLenum FragmentShaderDerivativeHint` | 323 | 852 | 4 | *neither* | |
|
||
| 36 | `Float PointFadeThresholdSize` | 326 | 856 | 4 | *neither* | no backend reader |
|
||
| 37 | `GLenum PointSpriteCoordOrigin` | 327 | 860 | 4 | *neither* | |
|
||
| 38 | `GLenum ClampReadColor` | 330 | 864 | 4 | **dynamic** | read at `VulkanRenderer.cpp:2772`; Coverage.def maps it to `SetDynamicState` |
|
||
| 39 | `GLenum PolygonModeFront` | 334 | 868 | 4 | **pipeline** | in G7 list |
|
||
| 40 | `GLenum PolygonModeBack` | 335 | 872 | 4 | *neither* | never read by a backend (Espryt pushes `GL_FRONT_AND_BACK` with the **front** value at `DirectGLES.cpp:2513`) |
|
||
| 41 | `Uint32 PrimitiveRestartIndex` | 339 | 876 | 4 | *per-verb* | Coverage.def maps it to `DrawVbo`, not to a CSO |
|
||
| 42 | `Bool ColorLogicOpEnabled` | 342 | 880 | 1 | **pipeline** | |
|
||
| 43 | `Bool DebugOutputEnabled` | 343 | 881 | 1 | *neither* | |
|
||
| 44 | `Bool DebugOutputSynchronousEnabled` | 344 | 882 | 1 | *neither* | |
|
||
| 45 | `Bool DitherEnabled` | 345 | 883 | 1 | *Espryt-only* | `SYNC_CAPABILITY(Dither, …)` `DirectGLES.cpp:2112` |
|
||
| 46 | `Bool LineSmoothEnabled` | 346 | 884 | 1 | *neither* | |
|
||
| 47 | `Bool MultisampleEnabled` | 347 | 885 | 1 | **pipeline** | in G7 list (feeds `ResolveEffectiveSampleMask`) + Espryt `:2113` |
|
||
| 48 | `Bool PolygonOffsetFillEnabled` | 348 | 886 | 1 | **pipeline** | |
|
||
| 49 | `Bool PolygonOffsetLineEnabled` | 349 | 887 | 1 | *neither* | |
|
||
| 50 | `Bool PolygonOffsetPointEnabled` | 350 | 888 | 1 | *neither* | |
|
||
| 51 | `Bool PolygonSmoothEnabled` | 351 | 889 | 1 | *neither* | |
|
||
| 52 | `Bool PrimitiveRestartEnabled` | 352 | 890 | 1 | **pipeline** | |
|
||
| 53 | `Bool PrimitiveRestartFixedIndexEnabled` | 353 | 891 | 1 | **pipeline** | |
|
||
| 54 | `Bool RasterizerDiscardEnabled` | 354 | 892 | 1 | **pipeline** | |
|
||
| 55 | `Bool SampleAlphaToCoverageEnabled` | 355 | 893 | 1 | *Espryt-only* | `SYNC_CAPABILITY` `:2114`; Magma has **no** `alphaToCoverageEnable` at all |
|
||
| 56 | `Bool SampleAlphaToOneEnabled` | 356 | 894 | 1 | *neither* | not even in Espryt's macro list |
|
||
| 57 | `Bool SampleCoverageEnabled` | 357 | 895 | 1 | *Espryt-only* | `SYNC_CAPABILITY` `:2115` |
|
||
| 58 | `Bool SampleMaskEnabled` | 358 | 896 | 1 | **pipeline** | in G7 list |
|
||
| 59 | `Bool SampleShadingEnabled` | 359 | 897 | 1 | **pipeline** | in G7 list |
|
||
| 60 | `Bool StencilTestEnabled` | 360 | 898 | 1 | **pipeline** | |
|
||
| 61 | `Bool ProgramPointSizeEnabled` | 361 | 899 | 1 | *neither* | |
|
||
| 62 | `Uint32 ScissorTestEnabledMask` | 367 | 900 | 4 | *conflict* | comment `:362-367` says it **does** bump the pipeline version (Espryt turns it into a real `glEnable`), yet it is a **dynamic** input on Magma (`DynamicTailKey::scissorEnabled`) and is absent from the G7 list |
|
||
| 63 | `Array<IntVec4,16> ScissorBoxes` | 368 | 904 | 256 | **dynamic** | |
|
||
| 64 | `Uint32 ScissorBoxWrittenMask` | 380 | 1160 | 4 | **dynamic** | deliberately beside `ScissorBoxes` so it lands in Espryt's tail span (`:376-379`) |
|
||
| 65 | `Uint32 ClipDistanceEnabledMask` | 386 | 1164 | 4 | **dynamic** | `RenderState.cpp:374-378` documents "deliberately NOT `BumpVersions()`" |
|
||
|
||
Total = **1168** ✓.
|
||
|
||
The three Espryt spans therefore are: **head `[0, 312)`**, **blend `[312, 536)`**,
|
||
**tail `[536, 1168)`**.
|
||
|
||
### 1.3 The `FramebufferSrgb` / `DepthClamp` storage hole
|
||
|
||
`CapabilityInput::DepthClamp` (`MGPipeValueTypes.h:182`) and `CapabilityInput::FramebufferSrgb`
|
||
(`:185`) exist in the enum and in all three converters
|
||
(`MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp:261,267`;
|
||
`MGToGL/…:261,267`; `MGToStr/…:243,249`) — but **`RenderStateParameters` has no
|
||
`FramebufferSrgbEnabled` / `DepthClampEnabled` field at all.**
|
||
|
||
Consequence chain:
|
||
- `RenderState::SetCapability` (`MobileGL/MG_State/GLState/RenderState/RenderState.cpp:308-385`)
|
||
falls through to `default: break;` at `:381-382` for both — the `glEnable` is silently swallowed,
|
||
no version bumps.
|
||
- `RenderState::IsCapabilityEnabled` (`:387-432`) falls to `default: return false;` at `:429-430`.
|
||
- Under push, `MG_Impl/Pipe/PipeFill.cpp:229-235` copies the whole capability array including
|
||
FramebufferSrgb — the comment at `:230` says it "copies today's constant false".
|
||
- **Six backend read points are hard-constant `false` today**, all for `FramebufferSrgb`
|
||
(`DepthClamp` has zero backend readers):
|
||
1. `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:2168` (the sRGB-write block in SyncRenderState)
|
||
2. `MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp:58`
|
||
3. `MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp:614`
|
||
4. `…/VkRenderPassManager.cpp:965-968`
|
||
5. `…/VkRenderPassManager.cpp:1112`
|
||
6. `MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp:953`
|
||
- This is called out as an open question in `MobileGL/MG_Pipe/generated/PipeSpanTable.inc:24-26`
|
||
and in `docs/Disaggregated/ARCHITECTURE.md:189` (the doc's proposal: give both real storage and
|
||
put `FramebufferSrgb` on the **pipeline** side, because it changes the interpretation of the
|
||
attachment/blend). `docs/Disaggregated/ROADMAP.md:18` lists "补 `FramebufferSrgb`/`DepthClamp`
|
||
存储" as P2 scope.
|
||
|
||
Note the size trap: adding two `Bool`s changes `sizeof(RenderStateParameters)` away from 1168 and
|
||
breaks `MGPipeValueTypes.h:541`, `MGL_RESIDUAL_BLOCK_SIZE` (`MG_Pipe/MGPipeTypes.h:536`, which
|
||
"only ever goes DOWN" — a growth is a deliberate build break, `:529-535`), and
|
||
`MG_Test/Pipe/PipeCatalogueTest.cpp:111-112`. There are 7 free padding bytes after
|
||
`ProgramPointSizeEnabled` at offsets 900… no — offsets 880-899 are 20 packed bools with **no**
|
||
padding, and 549+32=581 → 584 leaves 3 free bytes after `ColorMasks`. Placing the two new bools
|
||
in that 3-byte hole (i.e. declaring them right after `ColorMasks` and before `ClearColor`) keeps
|
||
`sizeof` at 1168 but would move them into the tail span and change no offsets — worth checking
|
||
against the field-order load-bearing note in `ARCHITECTURE.md:183`.
|
||
|
||
### 1.4 The mirror field list
|
||
|
||
`MobileGL/MG_Pipe/PipeFields.def:230-247` — `MGP_FIELDS_RenderStateParameters(F)` names all 65
|
||
members in the same order. `gen_pipe.py` asserts every direct data member appears here
|
||
(`PipeFields.def:224-228`), so **a new member without a row here fails the pipe gates**. That is
|
||
the enforcement point for the FramebufferSrgb/DepthClamp addition.
|
||
|
||
---
|
||
|
||
## 2. Frontend `RenderState` — every public setter and which version it bumps
|
||
|
||
Files: `MobileGL/MG_State/GLState/RenderState/RenderState.h` (180 lines),
|
||
`MobileGL/MG_State/GLState/RenderState/RenderState.cpp` (973 lines).
|
||
|
||
State: `Uint16 m_version` (`RenderState.h:164`), `Uint16 m_pipelineStateVersion` (`:171`,
|
||
with the rationale comment at `:165-170`), `RenderStateParameters m_parameters` (`:172`),
|
||
plus two `PixelStoreParameters` (`:175-176`) that live **outside** the parameter block.
|
||
`BumpVersions()` is the private inline at `RenderState.h:159-162` — `++m_version;
|
||
++m_pipelineStateVersion;`.
|
||
|
||
Readers: `GetVersion()` `:20`/`.cpp:53`, `GetPipelineStateVersion()` `:22`/`.cpp:57`,
|
||
`GetAllParameters()` `:23`/`.cpp:61`.
|
||
|
||
Constructor `RenderState::RenderState()` `.cpp:37-51` — all-true `ColorMasks`, `(0,1)`
|
||
`DepthRanges`; the viewport/scissor all-zero default deliberately means "never written".
|
||
|
||
### 2.1 Group A — setters that bump **both** versions (`BumpVersions()` / `SET_CAPABILITY`)
|
||
|
||
This is the list the G7 consistency test must enumerate on the "pipeline subset hash MUST move"
|
||
side.
|
||
|
||
| Setter | .cpp lines |
|
||
|---|---|
|
||
| `SetPolygonMode(front, back)` | 174-179 |
|
||
| `SetPatchVertices` | 210-215 |
|
||
| `SetPatchDefaultOuterLevel` | 229-234 (bitwise-equal guard, `:225-228`) |
|
||
| `SetPatchDefaultInnerLevel` | 240-245 |
|
||
| `SetCapability` — the 22 `SET_CAPABILITY` arms | macro 309-314, arms 317-338 |
|
||
| `SetCapability(Blend)` | 339-348 |
|
||
| `SetCapability(ScissorTest)` | 353-359 |
|
||
| `SetCapabilityIndexed(ScissorTest, i)` | 442-453 |
|
||
| `SetCapabilityIndexed(Blend, i)` | 455-469 |
|
||
| `SetBlendFunc` | 497-513 |
|
||
| `SetBlendFuncIndexed` | 523-539 |
|
||
| `SetBlendEquation` | 553-565 |
|
||
| `SetBlendEquationIndexed` | 572-584 |
|
||
| `SetLogicOp` | 595-600 |
|
||
| `SetDepthFunc` | 607-612 |
|
||
| `SetDepthMask` | 618-623 |
|
||
| `SetStencilOp` | 652-664 |
|
||
| `SetColorMask` | 671-681 |
|
||
| `SetColorMaskIndexed` | 688-692 |
|
||
| `SetSampleCoverage` | 778-784 |
|
||
| `SetSampleMaskValue` | 794-799 |
|
||
| `SetMinSampleShadingValue` | 805-813 (explicit comment: Vulkan bakes `minSampleShading`) |
|
||
| `SetCullFaceMode` | 882-887 |
|
||
| `SetFrontFaceMode` | 893-898 |
|
||
| `SetProvokingVertexMode` | 904-909 |
|
||
|
||
The 22 `SET_CAPABILITY` names, in source order (`.cpp:317-338`): `ColorLogicOp`, `DebugOutput`,
|
||
`DebugOutputSynchronous`, `DepthTest`, `CullFace`, `Dither`, `LineSmooth`, `Multisample`,
|
||
`PolygonOffsetFill`, `PolygonOffsetLine`, `PolygonOffsetPoint`, `PolygonSmooth`,
|
||
`PrimitiveRestart`, `PrimitiveRestartFixedIndex`, `RasterizerDiscard`, `SampleAlphaToCoverage`,
|
||
`SampleAlphaToOne`, `SampleCoverage`, `SampleMask`, `SampleShading`, `StencilTest`,
|
||
`ProgramPointSize`.
|
||
|
||
### 2.2 Group B — setters that bump **`m_version` only**
|
||
|
||
| Setter | .cpp lines |
|
||
|---|---|
|
||
| `SetViewport` (broadcasts all 16) | 69-79 |
|
||
| `SetViewportIndexed` | 90-99 |
|
||
| `SetLineWidth` | 109-114 |
|
||
| `SetHint` | 120-132 |
|
||
| `SetPointFadeThresholdSize` | 144-148 |
|
||
| `SetPointSpriteCoordOrigin` | 154-158 |
|
||
| `SetClampReadColor` | 164-168 |
|
||
| `SetPrimitiveRestartIndex` | 189-193 |
|
||
| `SetPointSize` | 199-204 |
|
||
| `SetPolygonOffset` (delegates to Clamped) | 251-258 |
|
||
| `SetPolygonOffsetClamped` | 268-277 |
|
||
| `SetClipControl` | 283-289 |
|
||
| `SetCapability(ClipDistance0..7)` | 360-380 (explicit "deliberately NOT BumpVersions", `:374-377`) |
|
||
| `SetStencilMask` | 644-650 |
|
||
| `SetClearColor` | 699-704 |
|
||
| `SetClearDepth` | 710-715 |
|
||
| `SetClearStencil` | 721-726 |
|
||
| `SetBlendColor` | 732-737 |
|
||
| `SetDepthRange` (broadcasts all 16) | 745-753 |
|
||
| `SetDepthRangeIndexed` | 759-768 |
|
||
| `SetScissorBox` | 918-941 (also flips `ScissorBoxWrittenMask`; the transition itself counts as a change, `:925-939`) |
|
||
| `SetScissorBoxIndexed` | 947-962 |
|
||
|
||
### 2.3 Group C — the one conditional setter
|
||
|
||
`SetStencilFunc(face, func, ref, mask)` `.cpp:629-642`: always `++m_version` (`:640`), and
|
||
`++m_pipelineStateVersion` **only if `Func` moved** (`:636`, `:641`). Comment `:633-635`: `Ref`
|
||
and `ValueMask` are `VK_DYNAMIC_STATE_STENCIL_REFERENCE`/`_COMPARE_MASK`. This is the setter that
|
||
proves the split is per-field, not per-setter — a G7 test that walks setters must special-case it
|
||
(drive it twice: once changing only `Ref`, once changing `Func`).
|
||
|
||
### 2.4 Group D — setters that bump **nothing**
|
||
|
||
`SetPixelStoreParam` `.cpp:820-848` (macro `:821-825`). Pixel-store state is not in the parameter
|
||
block and moves neither counter; it becomes `set_pixel_pack_state` (Coverage.def).
|
||
|
||
### 2.5 Conflicts the G7 test will surface immediately
|
||
|
||
The G7 invariant per `docs/Disaggregated/ARCHITECTURE.md:186` and
|
||
`generated/PipeSpanTable.inc:15-20` is: **pipeline-subset hash moves ⟺ `m_pipelineStateVersion`
|
||
moves.** Against today's tree that invariant is **already false in at least six ways**, all in the
|
||
"version moved but hash did not" direction:
|
||
|
||
1. `SetSampleCoverage` (`.cpp:778-784`) bumps the pipeline version, but neither
|
||
`SampleCoverageValue` nor `SampleCoverageInvert` is in `kMGPipePipelineStateMembers`.
|
||
2. `SetFrontFaceMode` (`.cpp:893-898`) bumps it; `FrontFaceModeSetting` is not in the list, and
|
||
(see §4.4) Magma does not read it at all.
|
||
3. `SetProvokingVertexMode` (`.cpp:904-909`) bumps it; `ProvokingVertexModeSetting` is not in the
|
||
list. `Coverage.def:74-77` already flags this ("Not in ComputePipelineStateHash today even
|
||
though Vulkan makes it pipeline state; recorded here so the G7 chunk table has to answer for
|
||
it before it freezes").
|
||
4. `SetCapability(ScissorTest)` / `SetCapabilityIndexed(ScissorTest, …)` bump it;
|
||
`ScissorTestEnabledMask` is not in the list and is a **dynamic** input on Magma
|
||
(`DynamicTailKey::scissorEnabled`).
|
||
5. Eleven of the 22 `SET_CAPABILITY` names are absent from the list: `DebugOutput`,
|
||
`DebugOutputSynchronous`, `Dither`, `LineSmooth`, `PolygonOffsetLine`, `PolygonOffsetPoint`,
|
||
`PolygonSmooth`, `SampleAlphaToCoverage`, `SampleAlphaToOne`, `SampleCoverage`,
|
||
`ProgramPointSize`.
|
||
6. `SetPolygonMode(front, back)` bumps it even when only `PolygonModeBack` moved;
|
||
`PolygonModeBack` is not in the list (and no backend reads it).
|
||
|
||
Directionally the other way there is nothing: every one of the 24 listed members is written only
|
||
by a Group-A setter (`StencilStates` is the exception, and its dynamic third is written by
|
||
`SetStencilMask`/`SetStencilFunc`'s `Ref`, which correctly do **not** bump the pipeline version).
|
||
|
||
**Implication for P2**: either the chunk table grows past the 24 hashed members (making the
|
||
pipeline subset a superset of what Magma hashes), or several Group-A setters must be demoted to
|
||
`++m_version`. Demoting is behaviour-changing for Espryt, which pushes `Dither`,
|
||
`SampleAlphaToCoverage`, `SampleCoverage` and the scissor-test enable through
|
||
`SYNC_CAPABILITY` — but Espryt gates on `m_version`, not the pipeline version, so demotion is
|
||
safe there. Decide this before the chunk table freezes.
|
||
|
||
---
|
||
|
||
## 3. Espryt's consumer — `SyncRenderState`
|
||
|
||
File: `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp` (10639 lines total).
|
||
`namespace RenderStateImpl` spans **1983-2688**; `void SyncRenderState(Bool forColorClear)`
|
||
is **2024-2687** (664 lines of body). `docs/Disaggregated/ROADMAP.md:47` and
|
||
`ARCHITECTURE.md:188` state the contract as "`SyncRenderState`(693 行)一行未动" — the exact count
|
||
differs by how the file-scope shadows are counted, but the contract is unambiguous: **nothing in
|
||
1983-2688 may move**. Declaration: `DirectGLES.h:225`.
|
||
|
||
### 3.1 The file-scope shadow state (1984-2017)
|
||
|
||
```
|
||
1984 static Uint16 g_syncedRenderStateVersion = 0;
|
||
1985 static Bool g_hasSyncedRenderState = false;
|
||
1986 static RenderStateParameters g_syncedRenderStateParameters; // the byte shadow
|
||
1987 static IntVec4 g_syncedBackendViewport = (-1,-1,-1,-1); // RESOLVED viewport
|
||
1990 static IntVec4 g_syncedBackendScissorBox = (-1,-1,-1,-1); // RESOLVED scissor
|
||
1993 static Bool g_syncedSrgbFramebufferWrites = true;
|
||
2003 static Bool g_forceFullRenderStateResync = true; // see 1994-2002
|
||
2011 static Uint32 g_syncedColorMaskAlphaWidenMask = 0; // NOT derivable from the block, 2006-2010
|
||
2016 static Array<PerBufferBlendState, MAX_DRAW_BUFFERS> g_dualSourceDeclinedBlendStates;
|
||
2018 void InvalidateSyncedRenderState() // 2018-2023
|
||
```
|
||
|
||
`InvalidateSyncedRenderState` callers: `DirectGLES.cpp:3902`, `DirectGLES.cpp:10079`
|
||
(MakeCurrent), `MG_Test/Framebuffer/FramebufferTest.cpp:1171,1179`.
|
||
|
||
### 3.2 The version early-out (2028-2046)
|
||
|
||
`Uint16 currentRenderStateVersion = MGB_CTX->GetRenderStateParametersVersion();` at **2028** —
|
||
this is the **only** MGPipe accessor call on the hit path. `forceFullPush` is read and cleared at
|
||
2029-2030. `appliedWidenMask` / `colorMaskWidenDirty` 2035-2036 (these are *not* frontend state;
|
||
they come from `FramebufferImpl::g_alphaWidenedDrawBufferMask` and from `forColorClear`). The
|
||
early-out itself is 2037-2046, and it is "Gate 1 of section 2.3.1"
|
||
(`PipeStats::Gate::EsprytRenderState`, 2041-2044).
|
||
|
||
**Under a CSO scheme the server must keep this shape**: the working `RenderStateParameters` block
|
||
and a `Uint16` that increases whenever any chunk is scattered in. `MGPBindRenderState` carries
|
||
both counters (`MGPipeTypes.h:225-230`: `Version`, `PipelineVersion`), which is exactly what feeds
|
||
line 2028.
|
||
|
||
### 3.3 The three-segment memcmp (2070-2083)
|
||
|
||
```
|
||
2070 static_assert(std::is_trivially_copyable_v<RenderStateParameters>, "span memcmp/memcpy below treats the parameter block as raw bytes");
|
||
2072 constexpr SizeT kBlendSpanBegin = offsetof(RenderStateParameters, BlendStates); // 312
|
||
2073 constexpr SizeT kBlendSpanEnd = offsetof(RenderStateParameters, LogicOp); // 536
|
||
2074 currentBytes = (const unsigned char*)¶meters;
|
||
2075 syncedBytes = (const unsigned char*)&g_syncedRenderStateParameters;
|
||
2076 headSpanDirty = !g_hasSyncedRenderState || memcmp(cur, syn, kBlendSpanBegin) != 0;
|
||
2078 blendSpanDirty = !g_hasSyncedRenderState || memcmp(cur+Begin, syn+Begin, End-Begin) != 0;
|
||
2081 tailSpanDirty = !g_hasSyncedRenderState || memcmp(cur+End, syn+End, sizeof(RSP)-End) != 0;
|
||
```
|
||
|
||
Rationale comment 2058-2069 (the Blaze3D per-batch `GL_BLEND` toggle). Note 2064-2066: memcmp can
|
||
false-DIFFER on padding but never false-match, and the write-back below makes the shadow
|
||
byte-identical including padding.
|
||
|
||
The `offsetof(BlendStates) < offsetof(LogicOp)` assertion in `MGPipeValueTypes.h:543` exists
|
||
solely to keep these two constants ordered.
|
||
|
||
### 3.4 The gated blocks, in file order
|
||
|
||
| Lines | Gate | What it pushes |
|
||
|---|---|---|
|
||
| 2085-2097 | *ungated* | resolved viewport (`MGB_CTX->GetViewport()` at 2085, surface-size fallback 2086-2092, shadow compare 2093-2097) |
|
||
| 2101-2139 | `tailSpanDirty` | `SYNC_CAPABILITY` macro (2102-2109) × 11 caps (2110-2120) + scissor-test bit 0 (2131-2138) |
|
||
| 2141-2161 | `tailSpanDirty && SupportsClipDistance` | `ClipDistanceEnabledMask` → 8 `glEnable/glDisable` |
|
||
| 2163-2175 | *ungated* | `GL_FRAMEBUFFER_SRGB` — reads `MGB_CTX->IsCapabilityEnabled(FramebufferSrgb)` at **2168** (constant false today) |
|
||
| 2177-2187 | `tailSpanDirty` | primitive restart → `GL_PRIMITIVE_RESTART_FIXED_INDEX` |
|
||
| 2196-2410 | `blendSpanDirty` | the whole blend block, incl. the dual-source-decline path 2200-2264 |
|
||
| 2412-2422 | `tailSpanDirty` | `glDepthFunc`, `glDepthMask`, `glDepthRangef(DepthRanges[0])` |
|
||
| 2424-2448 | `tailSpanDirty` | stencil: `glStencilFuncSeparate` / `glStencilMaskSeparate` / `glStencilOpSeparate` × 2 faces |
|
||
| 2450-2505 | `tailSpanDirty \|\| colorMaskWidenDirty` | colour mask (broadcast vs. `glColorMaski`), widen-mask doctoring 2462-2468 |
|
||
| 2507-2515 | `tailSpanDirty` | polygon mode (NV/ANGLE ext; front value only) |
|
||
| 2517-2532 | `tailSpanDirty` | clear colour/depth/stencil + `glBlendColor` |
|
||
| 2534-2543 | `tailSpanDirty` | `glCullFace`, `glFrontFace` |
|
||
| 2545-2585 | `tailSpanDirty` | resolved scissor (`ScissorBoxWrittenMask` bit 0 → surface fallback 2571-2577; shadow compare 2580-2584). The long comment 2545-2569 is the `scissor_zero_dimension` history |
|
||
| 2587-2596 | `tailSpanDirty` | `glLogicOp` (null-checked) |
|
||
| 2598-2603 | `headSpanDirty` | `glPolygonOffset` |
|
||
| 2605-2609 | `headSpanDirty` | `glLineWidth` |
|
||
| 2611-2617 | `headSpanDirty` | `glPointSize` (null-checked) |
|
||
| 2619-2625 | `tailSpanDirty` | `glSampleCoverage` |
|
||
| 2627-2632 | `tailSpanDirty` | `glSampleMaski` |
|
||
| 2634-2655 | `tailSpanDirty` | sample shading enable + `glMinSampleShading` |
|
||
|
||
`SYNC_CAPABILITY`'s 11 GL caps (2110-2120): `DepthTest`, `ColorLogicOp`, `Dither`, `Multisample`,
|
||
`SampleAlphaToCoverage`, `SampleCoverage`, `SampleMask`, `PolygonOffsetFill`, `RasterizerDiscard`,
|
||
`StencilTest`, `CullFace`. Note `SampleAlphaToOne` is **absent**.
|
||
|
||
### 3.5 The write-back (2657-2686)
|
||
|
||
```
|
||
2657 g_syncedRenderStateVersion = currentRenderStateVersion;
|
||
2662 syncedBytesMut = (unsigned char*)&g_syncedRenderStateParameters;
|
||
2663 if (headSpanDirty) memcpy(syncedBytesMut, currentBytes, kBlendSpanBegin);
|
||
2666 if (blendSpanDirty) memcpy(syncedBytesMut+Begin, currentBytes+Begin, End-Begin);
|
||
2677 …then re-stamp the dual-source-DECLINED draw buffers from g_dualSourceDeclinedBlendStates (2677-2680)
|
||
2682 if (tailSpanDirty) memcpy(syncedBytesMut+End, currentBytes+End, sizeof(RSP)-End);
|
||
2686 g_hasSyncedRenderState = true;
|
||
```
|
||
|
||
Comment 2658-2661 explains the byte copy (padding included) and 2669-2676 explains why the
|
||
declined blend states are re-stamped.
|
||
|
||
### 3.6 What must NOT move, precisely
|
||
|
||
- The `const RenderStateParameters&` binding at **2056** — the server must hand `SyncRenderState`
|
||
a reference to a whole 1168-byte block, not chunks.
|
||
- The `Uint16` version read at **2028** and the early-out at **2037-2046**.
|
||
- The two `offsetof` constants at **2072-2073** and the three memcmps at **2076-2083**.
|
||
- The memcpy write-back at **2662-2685**.
|
||
- The two *resolved* shadows (`g_syncedBackendViewport` 1987, `g_syncedBackendScissorBox` 1990) —
|
||
those are backend-derived values, unrelated to the CSO split.
|
||
|
||
The Espryt head/blend/tail split and the pipeline/dynamic split are **orthogonal** and coexist —
|
||
`ARCHITECTURE.md:188` states this explicitly. In particular a pipeline chunk (`BlendStates`,
|
||
`ColorMasks`, `StencilStates`) and a dynamic chunk (`ScissorBoxes`, `ClearColor`,
|
||
`StencilStates.Ref`) both land inside Espryt's **tail** span, and `StencilStates` lands in the
|
||
tail for both halves.
|
||
|
||
### 3.7 `SyncRenderState` call sites (Espryt)
|
||
|
||
`DirectGLES.cpp` 2996, 4123 (`forColorClear = mask & GL_COLOR_BUFFER_BIT`), 6031, 6066, 6693,
|
||
6787, 7380, 7426, 7439, 7455, 7471, 7491, 7508, 7528; plus
|
||
`MG_Test/Framebuffer/FramebufferTest.cpp` 1238, 1271, 1283, 1323, 1356.
|
||
|
||
---
|
||
|
||
## 4. Magma's consumer
|
||
|
||
File: `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp` unless noted.
|
||
|
||
### 4.1 `ComputePipelineStateHash` — 4821-4910
|
||
|
||
Signature `(Uint32 colorAttachmentCount, VkSampleCountFlagBits rasterizationSamples) const`;
|
||
declaration `VulkanRenderer.h:849-850`. Mixer `CombinePipelineStateWord` at **4776-4778**
|
||
(boost-style, seed `0x243F6A8885A308D3` at 4848). Enumeration comment **4780-4794** — this is the
|
||
list `scripts/gen_pipe.py:75-99` was copied from, and it names itself as the contract
|
||
("any new GL-state read there must be added here").
|
||
|
||
One bulk fetch at **4829** (`const RenderStateParameters& p = MGB_CTX->GetRenderStateParameters();`,
|
||
rationale 4823-4828 — replaced ~17 accessor calls).
|
||
|
||
Fields hashed, in order:
|
||
|
||
| Lines | Field(s) |
|
||
|---|---|
|
||
| 4831-4840 | `capabilityBits` bits 0-9: `CullFaceEnabled`, `DepthTestEnabled`, `PolygonOffsetFillEnabled`, `RasterizerDiscardEnabled`, `ColorLogicOpEnabled`, `StencilTestEnabled`, `PrimitiveRestartEnabled`, `PrimitiveRestartFixedIndexEnabled`, `DepthMask`, `SampleShadingEnabled` |
|
||
| 4846-4847 | bit 10 = **effective** sample-mask enable, via `ResolveEffectiveSampleMask` |
|
||
| 4852-4856 | `MinSampleShadingValue`, hashed by **bits** (`memcpy`), not value |
|
||
| 4863 | `ResolveEffectiveSampleMask(...)` word (i.e. `SampleMaskValue` gated) |
|
||
| 4864 | `PatchVertices` |
|
||
| 4871-4878 | `PatchDefaultOuterLevel[0..3]`, `PatchDefaultInnerLevel[0..1]`, all `bit_cast` |
|
||
| 4879 | `PolygonModeFront` |
|
||
| 4880 | `CullFaceModeSetting` |
|
||
| 4881 | `DepthFunc` |
|
||
| 4882 | `LogicOp` |
|
||
| 4885-4891 | `StencilStates[0]` then `[1]`: `FailOp \| PassDepthPassOp<<16 \| PassDepthFailOp<<32 \| Func<<48` — **`Ref`/`ValueMask`/`WriteMask` deliberately excluded** |
|
||
| 4895-4908 | per draw buffer `i < colorAttachmentCount`: `BlendStates[i].Enabled`, `ColorMasks[independentBlend ? i : 0]` rgba, then the four factors and two equations |
|
||
|
||
`ResolveEffectiveSampleMask` is **4813-4819** and reads
|
||
`IsCapabilityEnabled(Multisample)` (4816), `IsCapabilityEnabled(SampleMask)` (4817) and
|
||
`GetRenderStateParameters().SampleMaskValue` (4818). Comment 4795-4812 explains the GL-vs-Vulkan
|
||
rule; both the hash and the payload go through it so they cannot disagree (4811-4812).
|
||
|
||
**Not hashed but pipeline-relevant** (already flagged in `PipeSpanTable.inc:27-29`):
|
||
`ProvokingVertexModeSetting`, `FrontFaceModeSetting`, `ClipOrigin`, `ClipDepthMode`.
|
||
|
||
### 4.2 `GetOrCreatePipeline` — 4951 onward; the memo
|
||
|
||
- Signature 4951-4958: `(mode, program, programObj, transformFlags, vao, renderPassEntry, primitiveRestartEnable)`.
|
||
- `vertexLayoutHash` 4976-4977, `renderPassHash` 4978.
|
||
- **Version → hash gate** 4985-4995: `MGB_CTX->GetPipelineStateVersion()` at **4985**, then the
|
||
cached-hash key `(version, colorAttachmentCount, sampleCount)` (4986-4994). Comment 4979-4984 is
|
||
the "glViewport must not evict a good VkPipeline" argument.
|
||
- **Memo probe** 4997-5013: linear scan comparing `mode`, `programHash`, `vertexInputHash`,
|
||
`renderPassHash`, `pipelineStateHash`, `primitiveRestartEnable`, `transformFlags`. Gate 5 stats
|
||
at 5005-5009.
|
||
- **Memo storage**: `VulkanRenderer.h:818-841` — `struct PipelineMemoEntry` (818-837),
|
||
`kPipelineMemoSize = 8` (838), `m_pipelineMemo[8]` (839), `m_pipelineMemoCount` (840),
|
||
`m_pipelineMemoNext` (841, round-robin). Cached hash: `m_pipelineStateHashVersion` (855),
|
||
`m_pipelineStateHashColorCount` (856), `m_pipelineStateHashSampleCount` (860),
|
||
`m_pipelineStateHash` (861), `m_pipelineStateHashValid` (862).
|
||
`InvalidatePipelineMemo()` at 881-885 clears all three.
|
||
Insertion at `VulkanRenderer.cpp:5665-5675`.
|
||
- Payload build reads (all `MGB_CTX->`): capabilities 5173-5182 (`CullFace`, `DepthTest`,
|
||
`PolygonOffsetFill`+`DrawModeUsesPolygonFill`, `RasterizerDiscard`, `ColorLogicOp`+
|
||
`m_logicOpFeatureEnabled`, `StencilTest`); FBO depth/stencil gating 5186-5199; stencil faces
|
||
5200-5201; `GetPolygonModeFront()` 5203; the payload initializer 5276-5332 with
|
||
`IsCapabilityEnabled(SampleShading)` 5288, `GetMinSampleShadingValue()` 5289,
|
||
`ResolveEffectiveSampleMask` 5291, `GetPatchVertices()` 5295, `GetCullFaceMode()` 5299,
|
||
**`.frontFace = VK_FRONT_FACE_CLOCKWISE` hardcoded at 5301**, `GetDepthMask()` 5313,
|
||
`GetDepthFunc()` 5318, `GetLogicOp()` 5319; the tessellation defaults 5355-5356; the per-colour-
|
||
attachment blend/mask loop 5429-5448 (`GetBlendFuncIndexed` 5436, `GetBlendEquationIndexed` 5437,
|
||
`IsCapabilityEnabledIndexed(Blend, i)` 5438, `GetColorMaskIndexed` 5443). PipeStats constant of
|
||
15 reads at 5258-5275 (with its own inventory).
|
||
- `ResolvePrimitiveRestartEnable` 4936-4949 — bulk fetch at 4941, reads
|
||
`PrimitiveRestartFixedIndexEnabled`, `PrimitiveRestartEnabled`, `PrimitiveRestartIndex`. It is a
|
||
**draw** input, not a CSO input (the answer depends on whether the draw is indexed and on the
|
||
index type; see `VulkanRenderer.h:830-835`).
|
||
- `PipelineFactory::GetOrCreatePipeline` (the second, hash-keyed level) is
|
||
`MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp:252-…`, declared
|
||
`PipelineFactory.h:132`; frame-boundary bookkeeping at `PipelineFactory.h:184`.
|
||
|
||
### 4.3 `ApplyDynamicDrawStateTail` — 5902-6004, and its shadow
|
||
|
||
`struct DynamicStateShadow` is the anonymous-namespace type at `VulkanRenderer.cpp:300-397`,
|
||
instance `static DynamicStateShadow g_dynamicStateShadow;` at **398**.
|
||
|
||
- Per-state shadows 300-317 (viewport, scissor, blend constants, depth bias, line width, the six
|
||
stencil words).
|
||
- Whole-tail gate fields 328-332: `dynamicTailValid`, `dynamicTailParamsVersion`,
|
||
`dynamicTailExtentX/Y`, `dynamicTailIsDefaultFbo`. Rationale 318-327.
|
||
- **`struct DynamicTailKey` 355-395** with `operator==` 372-394. This is the ready-made shape of
|
||
the dynamic chunk set: `viewport[4]`, `depthRange[2]`, `blendColor[4]`, `polygonOffsetFactor`,
|
||
`polygonOffsetUnits`, `lineWidth`, `stencilValueMask[2]`, `stencilWriteMask[2]`, `stencilRef[2]`,
|
||
`scissorBox[4]`, `extentX`, `extentY`, `preTransform`, `scissorEnabled`, `isDefaultFbo`.
|
||
- **The complete input inventory is spelled out at 341-354**, one line per reader:
|
||
`ApplyGLViewportState` → `Viewports[0]`, `DepthRanges[0]`; `ApplyBlendConstants` → `BlendColor`;
|
||
`ApplyPolygonOffsetState` → `PolygonOffsetUnits`, `PolygonOffsetFactor`; `ApplyLineWidthState`
|
||
→ `LineWidth`; `ApplyStencilState` → `StencilStates[0..1].{ValueMask, WriteMask, Ref}`;
|
||
scissor rect → `ScissorTestEnabledMask` bit 0, `ScissorBoxes[0]`. The caveat at 351-354:
|
||
`ApplyLineWidthState` also clamps against device limits, which are not in the key.
|
||
|
||
Function body:
|
||
- multi-viewport arm 5905-5916 (delegates to `ApplyMultiViewportDynamicState` 5878-5900, which
|
||
invalidates the single-element shadow at 5896-5899).
|
||
- **version gate** 5920-5930: `MGB_CTX->GetRenderStateParametersVersion()` at **5920**, compared
|
||
with extent + `isDefaultFbo`; Gate 6 stats 5925-5928.
|
||
- **value gate**: one bulk fetch at **5944**, key built 5945-5976, compared 5977-5982 (a hit
|
||
re-arms the cheap version gate at 5980).
|
||
- the six applies 5983-5997, shadow update 5998-6003.
|
||
|
||
The five `Apply*` helpers (each with its own per-state shadow early-out):
|
||
`ComputeGLViewport` 451-505 (`GetViewportIndexed(index)` 464, `GetDepthRangeIndexed(index)` 469),
|
||
`ApplyGLViewportState` 507-521, `ApplyBlendConstants` 523-543 (`GetBlendColor()` 524),
|
||
`ApplyPolygonOffsetState` 556-568 (`GetPolygonOffsetUnits()` 557, `GetPolygonOffsetFactor()` 558),
|
||
`ApplyLineWidthState` 570-589 (`GetLineWidth()` 571, device clamp 572-581),
|
||
`ApplyStencilState` 649-677 (`GetStencilState(Front/Back)` 650-651 → six `vkCmdSetStencil*`
|
||
671-676). Scissor helpers: `MakeClampedScissorRect` 591-604,
|
||
`MapScissorRectToDefaultFramebuffer` 609-617, `MakeDefaultFramebufferScissorRect` 619-647,
|
||
`ComputeGLScissorRect` (…-5869, reads `ScissorTestEnabledMask` 5860 and `ScissorBoxes[index]` 5866).
|
||
|
||
The declared Vulkan dynamic states are exactly eight:
|
||
`MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp:390-397` — `VIEWPORT`, `SCISSOR`,
|
||
`BLEND_CONSTANTS`, `DEPTH_BIAS`, `LINE_WIDTH`, `STENCIL_COMPARE_MASK`, `STENCIL_WRITE_MASK`,
|
||
`STENCIL_REFERENCE`. Note `PipelineFactory.cpp:485` warns against adding
|
||
`VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE`.
|
||
|
||
### 4.4 Magma facts worth flagging
|
||
|
||
- **`FrontFaceModeSetting` is never read by Magma.** `.frontFace` is hardcoded
|
||
`VK_FRONT_FACE_CLOCKWISE` at 5301 (and 4427, 4538); a repo-wide grep for `GetFrontFaceMode` /
|
||
`FrontFaceModeSetting` finds only `DirectGLES.cpp:2539-2542`, `GL_Getter.cpp:1632`,
|
||
`Core.cpp:1143-1144`, `Core.h:296` and `RenderState.{h,cpp}`. The winding is folded into
|
||
`ConvertCullFaceModeToVkEnum(mode, invertClockwise)` at 5299 instead. The claim in
|
||
`PipeSpanTable.inc:28-29` that `FrontFaceModeSetting` is "handled elsewhere in the payload path"
|
||
is **not backed by a read site** — verify before relying on it.
|
||
- Magma implements **no** `alphaToCoverageEnable` / `alphaToOneEnable` / `depthClampEnable`
|
||
(grep across `PipelineFactory.{h,cpp}` and `VulkanRenderer.cpp` is empty). So
|
||
`SampleAlphaToCoverageEnabled` is Espryt-only and `SampleAlphaToOneEnabled` is dead everywhere.
|
||
|
||
### 4.5 Other Magma readers of the version / block
|
||
|
||
- `TrySetupDrawFastPath` (SetupDrawSnapshot gate): `GetPipelineStateVersion()` at **6122**, then a
|
||
bulk fetch at **6131** to check only `DepthTestEnabled || StencilTestEnabled` against
|
||
`snap.drawUsesDepthStencil` (6131-6135); the cached-hash refresh repeats at 6335-6343 and the
|
||
memo scan at 6347-6353. Snapshot write at **6973** (`snap.renderStateVersion = MGB_CTX->GetPipelineStateVersion()`).
|
||
- `VulkanRenderer.cpp:4061` — index-rewrite path, bulk fetch for
|
||
`PrimitiveRestartEnabled`/`FixedIndex`/`PrimitiveRestartIndex`.
|
||
- `VulkanRenderer.cpp:12019` — multi-draw merge granularity, same two restart bits.
|
||
- Clear path 7269-7271 (`GetClearColor/Depth/Stencil`), 7207/7210/8621 (`GetScissorBox`),
|
||
7301/7390/7411/7527/7584 (`GetColorMaskIndexed`), 7328/7370/7513/7602 (`GetDepthMask`),
|
||
7341/7374/7515/7610 (`GetStencilState`), 7258/7440 (`RasterizerDiscard`),
|
||
7278/7482/8620 (`ScissorTest`), 6831-6832 (`DepthTest`/`StencilTest`), 3449-3450 (restart caps),
|
||
2772 (`GetClampReadColor`), 12768 (`GetProvokingVertexMode`).
|
||
|
||
---
|
||
|
||
## 5. How both backends read render state through the P1 accessors
|
||
|
||
The switch: `MobileGL/MG_Pipe/PipeInputsSwitch.h:19-26`. Push arm `MGB_CTX` =
|
||
`&MobileGL::MG_Pipe::gPipeInputs` (`:19`); pull arm = `MG_State::pGLContext` (`:24`).
|
||
The push-arm struct is `MobileGL/MG_Backend/MGPipe/PipeInputs.h`, the global at `:682`.
|
||
|
||
Accessor → storage-field table in `PipeInputs.h:84-126` (`X(Accessor, member)` rows). The
|
||
render-state ones: `GetClearColor`→`m_clearColor` (84), `GetColorMaskIndexed`→`m_colorMask` (87),
|
||
`GetDepthRangeIndexed`→`m_depthRange` (92), `GetPipelineStateVersion`→`m_pipelineStateVersion`
|
||
(102), `GetPixelStoreParameters`→`m_pixelStore` (103), `GetRenderStateParameters`→`m_renderState`
|
||
(111), `GetRenderStateParametersVersion`→`m_renderStateParametersVersion` (112),
|
||
`GetScissorBox`→`m_scissorBox` (114), `GetStencilState`→`m_stencil` (115),
|
||
`GetViewport`→`m_viewport` (123), `GetViewportIndexed`→`m_viewportIndexed` (124),
|
||
`IsCapabilityEnabled`→`m_capability` (125), `IsCapabilityEnabledIndexed`→`m_capabilityIndexed` (126).
|
||
|
||
Accessor bodies (each `MGP_INPUT_CHECK` + `MGP_INPUT_VERIFY_READ` then a plain field read):
|
||
`GetClearColor` 249-253, `GetClearDepth` 254-258, `GetClearStencil` 259-…,
|
||
`GetColorMaskIndexed` 264-…, `GetDepthRangeIndexed` 294-…, `GetPipelineStateVersion` 338-342,
|
||
`GetRenderStateParametersVersion` 343-347, `GetPixelStoreParameters` 348-…,
|
||
`GetRenderStateParameters` 378-…, `GetScissorBox` 388-…, `GetStencilState` 393-…,
|
||
`GetViewport` 428-432, `GetViewportIndexed` 433-441, `IsCapabilityEnabled` 442-447,
|
||
`IsCapabilityEnabledIndexed` 452-…, `GetBlendColor` 204-208, `GetBlendEquationIndexed` 209-218,
|
||
`GetBlendFuncIndexed` 219-231, `GetClampReadColor` 244-248.
|
||
Constants: `kCapabilityCount` 173, `kMaxViewports` 174, `kStencilFaceCount` 176,
|
||
`struct IndexedCapabilities { Bool Blend[8]; Bool ScissorTest[16]; }` 180-183.
|
||
|
||
Fill side: `MobileGL/MG_Impl/Pipe/PipeFill.cpp` — `GetClearColor` 90-91, `GetClearDepth` 93-94,
|
||
`GetClearStencil` 96-97, `GetColorMaskIndexed` 99-101 (loop), `GetDepthRangeIndexed` 118-120
|
||
(loop over 16), `GetRenderStateParameters` 183-184 (whole-block copy),
|
||
`GetRenderStateParametersVersion` 186-187, `GetScissorBox` 192-193,
|
||
`GetStencilState` 195-197 (both faces), `GetViewport` 221-222, `GetViewportIndexed` 224-227,
|
||
`IsCapabilityEnabled` 229-234 (all caps incl. the constant-false FramebufferSrgb, note at 230),
|
||
`IsCapabilityEnabledIndexed` 236-243 (Blend ×8, ScissorTest ×16). Cost note at 448.
|
||
|
||
Verb fill points: `MobileGL/MG_Pipe/FillPoints.def` — `GetRenderStateParameters` +
|
||
`GetRenderStateParametersVersion` are filled for `kDraw` (128-129), `kClear` (184-185),
|
||
`kBlitOrCopy` (208-209), `kReadback` (257-258), `kProgramOp` (280-281).
|
||
|
||
### 5.1 Complete backend call-site inventory (`grep MGB_CTX-><accessor>` under `MG_Backend/`)
|
||
|
||
| Accessor | n | Sites |
|
||
|---|---|---|
|
||
| `GetRenderStateParameters` | 11 | GLES 2056, 3828, 4158; VK 4061, 4818, 4829, 4941, 5859, 5944, 6131, 12019 |
|
||
| `GetRenderStateParametersVersion` | 2 | GLES 2028; VK 5920 |
|
||
| `GetPipelineStateVersion` | 3 | VK 4985, 6122, 6973 |
|
||
| `IsCapabilityEnabled` | 29 | GLES 2168, 3824, 4398, 4399; `DirectGLES/MultiDraw.cpp` 52, 53; `VkClearManager.cpp` 58; `VkRenderPassManager.cpp` 614, 966, 1112; `VkTextureManager.cpp` 953; VK 3449, 3450, 4816, 4817, 5173, 5174, 5176, 5179, 5181, 5182, 5288, 6831, 6832, 7258, 7278, 7440, 7482, 8620 |
|
||
| `IsCapabilityEnabledIndexed` | 1 | VK 5438 |
|
||
| `GetViewport` | 1 | GLES 2085 |
|
||
| `GetViewportIndexed` | 1 | VK 464 |
|
||
| `GetScissorBox` | 3 | VK 7207, 7210, 8621 |
|
||
| `GetDepthRangeIndexed` | 1 | VK 469 |
|
||
| `GetStencilState` | 8 | VK 650, 651, 5200, 5201, 7341, 7374, 7515, 7610 |
|
||
| `GetColorMaskIndexed` | 6 | VK 5443, 7301, 7390, 7411, 7527, 7584 |
|
||
| `GetClearColor` / `GetClearDepth` / `GetClearStencil` | 1 each | VK 7269 / 7270 / 7271 |
|
||
| `GetBlendColor` | 1 | VK 524 |
|
||
| `GetBlendFuncIndexed` / `GetBlendEquationIndexed` | 1 each | VK 5436 / 5437 |
|
||
| `GetLogicOp` / `GetDepthFunc` | 1 each | VK 5319 / 5318 |
|
||
| `GetDepthMask` | 5 | VK 5313, 7328, 7370, 7513, 7602 |
|
||
| `GetCullFaceMode` | 1 | VK 5299 |
|
||
| `GetProvokingVertexMode` | 1 | VK 12768 |
|
||
| `GetLineWidth` | 1 | VK 571 |
|
||
| `GetPatchVertices` | 3 | GLES 2844; `DirectGLES/Managers.cpp` 7194; VK 5295 |
|
||
| `GetPatchDefaultOuterLevel` | 3 | GLES 2846; `Managers.cpp` 7202; VK 5355 |
|
||
| `GetPatchDefaultInnerLevel` | 3 | GLES 2848; `Managers.cpp` 7205; VK 5356 |
|
||
| `GetPolygonOffsetFactor` / `GetPolygonOffsetUnits` | 1 each | VK 558 / 557 |
|
||
| `GetPolygonModeFront` | 1 | VK 5203 |
|
||
| `GetMinSampleShadingValue` | 1 | VK 5289 |
|
||
| `GetPrimitiveRestartIndex` | 3 | GLES 4404, 4444; `MultiDraw.cpp` 46 |
|
||
| `GetClampReadColor` | 1 | VK 2772 |
|
||
| `GetPixelStoreParameters` | 6 | GLES 6187, 7556, 9043, 9422; `DirectGLES/Utils.cpp` 2302; VK 10690 |
|
||
|
||
Zero backend call sites: `GetClipOrigin`, `GetClipDepthMode`, `GetHint`, `GetPointSize`,
|
||
`GetPointFadeThresholdSize`, `GetPointSpriteCoordOrigin`, `GetPolygonOffsetClamp`,
|
||
`GetPolygonModeBack`, `GetFrontFaceMode`, `GetSampleCoverageValue`, `GetSampleCoverageInvert`,
|
||
`GetSampleMaskValue` — all of these are reached (when at all) only through the bulk
|
||
`GetRenderStateParameters()` field reads, or not at all.
|
||
|
||
---
|
||
|
||
## 6. What already exists for the CSO / spans
|
||
|
||
### 6.1 The member list (G7) — the only thing P0 pinned
|
||
|
||
`MobileGL/MG_Pipe/generated/PipeSpanTable.inc` (68 lines, generated).
|
||
- Header comment 9-32, including the D-B1 history at 15-20 and the "deliberately absent" list at
|
||
22-29 (FramebufferSrgb/DepthClamp storage hole; ProvokingVertexModeSetting;
|
||
FrontFaceModeSetting/ClipOrigin/ClipDepthMode).
|
||
- `kMGPipePipelineStateMembers[]` **34-59**, 24 names:
|
||
`CullFaceEnabled`, `DepthTestEnabled`, `PolygonOffsetFillEnabled`, `RasterizerDiscardEnabled`,
|
||
`ColorLogicOpEnabled`, `StencilTestEnabled`, `PrimitiveRestartEnabled`,
|
||
`PrimitiveRestartFixedIndexEnabled`, `DepthMask`, `SampleShadingEnabled`, `MultisampleEnabled`,
|
||
`SampleMaskEnabled`, `SampleMaskValue`, `MinSampleShadingValue`, `PatchVertices`,
|
||
`PatchDefaultOuterLevel`, `PatchDefaultInnerLevel`, `PolygonModeFront`, `CullFaceModeSetting`,
|
||
`DepthFunc`, `LogicOp`, `StencilStates`, `BlendStates`, `ColorMasks`.
|
||
- `kMGPipePipelineStateMemberCount = 24` at 60, self-check 61-62.
|
||
- **`extern const MGPStateChunk kMGPipePipelineChunks[];` 66 and
|
||
`extern const MGPStateChunk kMGPipeDynamicChunks[];` 67 — declared, NEVER DEFINED.** No
|
||
`MGPipeRenderStateSpans.cpp` exists in the tree (grep confirms: only the forward declaration
|
||
`struct MGPipeRenderStateSpans;` at `MG_Pipe/MGPipe.h:61`, and the comments at
|
||
`MGPipe.h:57-60`, `MGPipeTypes.h:83-85`).
|
||
- Generator: `scripts/gen_pipe.py` — `PIPELINE_STATE_MEMBERS` list at **75-99**, provenance comment
|
||
**65-74** (copied from `VulkanRenderer.cpp:4805-4906` at `dev@81b17c0b`, explicitly refusing to
|
||
compute offsets in python), emitter `gen_span_table()` **1040-1072**, written at **1166**.
|
||
- Only current consumer: `MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp:354-358`
|
||
(`TEST(PipeCatalogue, PipelineSubsetMembersArePinned)` — asserts count 24, first `CullFaceEnabled`,
|
||
last `ColorMasks`). **The G7 setter-consistency test does not exist yet.**
|
||
|
||
### 6.2 Wire types (all defined, none used yet)
|
||
|
||
`MobileGL/MG_Pipe/MGPipeTypes.h`:
|
||
- `struct MGPStateChunk { Uint16 Offset; Uint16 Length; }` **86-89**, `MGP_ASSERT_POD(…, 4)` 90;
|
||
comment 83-85 states the split "is defined exactly once, in `MGPipeRenderStateSpans`".
|
||
- `struct MGPRenderStateDesc { MGPipeHandle Cso; MGPipeHandle BaseCso; Uint32 ChunkMask; Uint32 Pad0; MGPBlobRef Blob; }`
|
||
**215-221**, size 48 (222). Comment 213-214: carries ONLY the pipeline subset's chunk bytes;
|
||
`ChunkMask` allows an incremental create against `BaseCso`.
|
||
- `struct MGPBindRenderState { MGPipeHandle Cso; Uint16 Version; Uint16 PipelineVersion; }`
|
||
**225-229**, size 12 (230). Comment 224: "Steady state: 12 bytes on the wire, no hashing, no blob."
|
||
- `struct MGPDynamicState { Uint32 ChunkMask; Uint16 Version; Uint16 Pad0; MGPBlobRef Blob; }`
|
||
**236-241**, size 32 (242). Comment **232-235** enumerates the dynamic half verbatim: "viewport,
|
||
scissor, depth range, blend colour, line width, polygon offset, stencil ref/write mask, clear
|
||
values, sample coverage, hints and the point-size family".
|
||
|
||
Note the discrepancy: that comment puts **sample coverage** and **hints** in the dynamic half,
|
||
while `SetSampleCoverage` bumps the pipeline version (§2.5 item 1). One of the two has to change.
|
||
|
||
### 6.3 Calls and handles
|
||
|
||
`MobileGL/MG_Pipe/PipeCalls.def`:
|
||
```
|
||
92 X(CreateRenderState, MGPRenderStateDesc, kCtxCso, kHasBlob)
|
||
93 X(BindRenderState, MGPBindRenderState, kCtxCso, kNone)
|
||
94 X(DeleteRenderState, MGPHandleOnly, kCtxCso, kNone)
|
||
106 X(SetDynamicState, MGPDynamicState, kCtxState, kHasBlob)
|
||
```
|
||
Class taxonomy comment 15-50 (`kCtxCso` = 13 create/bind/delete entries).
|
||
`MGPipeHandleKind::RenderStateCso` at `MobileGL/MG_Pipe/MGPipeHandles.h:31` (alongside
|
||
`VertexElementsCso` 32, `SamplerCso` 33, `SamplerViewCso` 34, `ShaderCso` 35).
|
||
Call-class enum `kCtxCso` at `MGPipe.h:31`.
|
||
|
||
Field lists for the comparators: `PipeFields.def:62-63` (`MGP_FIELDS_MGPRenderStateDesc`),
|
||
`:65-66` (`MGP_FIELDS_MGPBindRenderState`); both are in `MGP_VERIFY_PAYLOAD_LIST` at
|
||
`PipeFields.def:298-299`.
|
||
|
||
### 6.4 The residual carrier that the CSO retires
|
||
|
||
`MGPipeTypes.h:516-525` — `struct ResidualValueBlock` whose **first member is
|
||
`RenderStateParameters RenderState;` (517) with the comment "until create/bind_render_state +
|
||
set_dynamic_state land"**, plus `PixelStoreParameters Pack` (518), `CapabilityBits` (519),
|
||
`PatchVertices` (520), `PatchOuter[4]` (522), `PatchInner[2]` (523).
|
||
`#define MGL_RESIDUAL_BLOCK_SIZE 1248` at **536** with the ratchet comment 527-535 (only ever goes
|
||
DOWN; P13 asserts 0). Carrier call `X(SetResidualValueState, MGPResidualValueState, kCtxState,
|
||
kHasBlob)` at `PipeCalls.def:126`; field list `PipeFields.def:147-148`.
|
||
**Landing the render-state CSO must delete `RenderState` from this struct and lower 1248 by 1168 →
|
||
80** (and the `PixelStoreParameters Pack` removal is `set_pixel_pack_state`, separate).
|
||
Test that will break: `MG_Test/Pipe/PipeCatalogueTest.cpp:111-112`.
|
||
|
||
### 6.5 Coverage map (which call answers which read)
|
||
|
||
`MobileGL/MG_Pipe/Coverage.def` already routes every render-state accessor:
|
||
- → `CreateRenderState`: `GetBlendEquationIndexed` (32), `GetBlendFuncIndexed` (33),
|
||
`GetColorMaskIndexed` (50), `GetCullFaceMode` (51), `GetDepthFunc` (53), `GetDepthMask` (54),
|
||
`GetLogicOp` (59), `GetMinSampleShadingValue` (61), `GetPolygonModeFront` (67),
|
||
`GetProvokingVertexMode` (77, with the "must answer for it before it freezes" note at 74-76),
|
||
`GetRenderStateParameters` (78), `GetStencilState` (82), `IsCapabilityEnabled` (93),
|
||
`IsCapabilityEnabledIndexed` (94).
|
||
- → `BindRenderState`: `GetPipelineStateVersion` (65), `GetRenderStateParametersVersion` (79).
|
||
- → `SetDynamicState`: `GetBlendColor` (31), `GetClampReadColor` (46), `GetClearColor` (47),
|
||
`GetClearDepth` (48), `GetClearStencil` (49), `GetDepthRangeIndexed` (55), `GetLineWidth` (58),
|
||
`GetPolygonOffsetFactor` (68), `GetPolygonOffsetUnits` (69), `GetScissorBox` (80),
|
||
`GetViewport` (91), `GetViewportIndexed` (92).
|
||
- → `SetPatchState`: `GetPatchDefaultInnerLevel` (62), `GetPatchDefaultOuterLevel` (63),
|
||
`GetPatchVertices` (64) — **note: the patch trio is a separate call, not part of the render-state
|
||
CSO, even though all three ARE in `kMGPipePipelineStateMembers`.** That is a live inconsistency
|
||
the chunk table must resolve.
|
||
- → `SetPixelPackState`: `GetPixelStoreParameters` (66). → `DrawVbo`: `GetPrimitiveRestartIndex` (70).
|
||
|
||
### 6.6 Missing entirely
|
||
|
||
Grep across `MobileGL/`, `scripts/`, `docs/` finds **no** `CsoCache`, no
|
||
`ComputePipelineSubsetHash` / `MGPipeComputePipelineSubsetHash`, no
|
||
`MGPipeRenderStateSpans.{h,cpp}`, and no implementation of `kMGPipePipelineChunks` /
|
||
`kMGPipeDynamicChunks`. `CsoCache` (64 entries, keyed on the pipeline subset) and
|
||
`MGPipeComputePipelineSubsetHash()` are named only in the plan:
|
||
`docs/Disaggregated/ROADMAP.md:18` (P2 row) and `docs/Disaggregated/ARCHITECTURE.md:186`.
|
||
`MobileGL/Config.h:324` mentions "client-side content addressing of CSOs, which is the negative
|
||
control the CSO …" — the A/B switch the ROADMAP asks for.
|
||
|
||
### 6.7 The plan's own design constraints (for reference, `docs/Disaggregated/ARCHITECTURE.md:183-190`)
|
||
|
||
- Whole-block, not three CSOs: `RenderStateParameters` is a trivially-copyable POD whose **field
|
||
order is load-bearing** (`ScissorBoxWrittenMask`, `ClipDistanceEnabledMask` are deliberately in
|
||
the tail); splitting into blend/depth-stencil/rasterizer CSOs would need a hand-maintained
|
||
~150-field table with no trip wire (`:183`).
|
||
- Subset identity, not whole-block content addressing: hashing the whole block would make
|
||
`glViewport`/`glScissor`/`glBlendColor`/`glClearColor` mint a new CSO every call and flush the
|
||
server's pipeline memo — the regression `RenderState.h:165-170` records (`:184`).
|
||
- Dynamic subset enumerated at `:185`.
|
||
- The split lives in exactly one place: `MG_Pipe/MGPipeRenderStateSpans.{h,cpp}` chunk table +
|
||
`MGPipeComputePipelineSubsetHash()`, shared by client and both backends; G7 walks every public
|
||
`RenderState` setter (`:186`).
|
||
- Server side: one working `RenderStateParameters` (1168 B) per context; `bind` and
|
||
`set_dynamic_state` each scatter their chunks into it. Espryt gets an unchanged
|
||
`const RenderStateParameters&`; Magma's pipeline memo is rekeyed on `cso.slot` and the dynamic
|
||
tail still goes through `ApplyDynamicDrawStateTail` (`:188`).
|
||
- Client fetch order (`:187`): pipeline version unchanged → reuse the last CSO handle, zero hashing;
|
||
changed → xxHash the pipeline subset (~25-30 words, what Magma already hashes) → CSO map probe →
|
||
hit sends a 12-byte bind, miss sends a create of the changed chunks then a bind; `m_version`
|
||
changed but subset unchanged → `set_dynamic_state` only (~200 B).
|
||
|
||
---
|
||
|
||
## 7. Open questions the implementers must answer before the chunk table freezes
|
||
|
||
1. **The six-way G7 invariant violation** in §2.5. Either grow the pipeline subset beyond Magma's
|
||
24 hashed members, or demote the offending setters to `++m_version`. `SetSampleCoverage`,
|
||
`SetProvokingVertexMode`, `SetFrontFaceMode`, `SetCapability(ScissorTest)`, the 11 unlisted
|
||
capabilities, and `SetPolygonMode`'s back-face half.
|
||
2. **`StencilStates` straddles the split** (pipeline: `Func`/`FailOp`/`PassDepthFailOp`/
|
||
`PassDepthPassOp`; dynamic: `Ref`/`ValueMask`/`WriteMask`). A byte-range chunk table cannot put
|
||
one array in both halves without sub-member chunks. `StencilFaceState` is 28 bytes laid out
|
||
`Func(0) Ref(4) ValueMask(8) WriteMask(12) FailOp(16) PassDepthFailOp(20) PassDepthPassOp(24)`
|
||
— so per face the pipeline half is `[0,4)` ∪ `[16,28)` and the dynamic half is `[4,16)`. Four
|
||
chunks for the two faces, or reorder `StencilFaceState` (which changes nothing's size but does
|
||
move Espryt's shadow bytes, which is fine — Espryt compares against its own shadow).
|
||
3. **The patch trio** is `SetPatchState` in Coverage.def but is in the pipeline member list. Pick one.
|
||
4. **`FramebufferSrgb`/`DepthClamp` storage**, and the `sizeof == 1168` /
|
||
`MGL_RESIDUAL_BLOCK_SIZE` / `PipeFields.def` triple that must be updated with it.
|
||
5. **`ScissorTestEnabledMask` is both** — it bumps the pipeline version (for Espryt's `glEnable`)
|
||
and is a dynamic-tail input on Magma. Under the plan's model it is dynamic, and the Espryt
|
||
`glEnable` is served by the working block Espryt reads anyway, so demoting its version bump is
|
||
likely correct — but check `MGPipeValueTypes.h:362-367`, which asserts the opposite.
|
||
6. **~14 members no backend reads at all** (§1.2 "neither"). They still have to live somewhere in
|
||
the working block for `glGet`, but they need not be in either chunk set if the client answers
|
||
their getters locally (`kClientResolved`). That would shrink the wire payload meaningfully.
|