mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-15 22:13:24 +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.
457 lines
45 KiB
Markdown
457 lines
45 KiB
Markdown
## 4. 接口设计:MGPipe
|
||
|
||
### 4.1 文件布局与单一真相源
|
||
|
||
```
|
||
MobileGL/MG_Pipe/ # client 与 server 都 include;不链接 MG_State,不链接 MG_Impl
|
||
PipeCalls.def # X-macro:调用目录的唯一真相源,一行一个调用
|
||
MGPipe.h # 由 .def 生成的两张函数表 + 手写 payload 声明
|
||
MGPipeTypes.h # 全部 payload POD(trivially copyable,逐个 static_assert)
|
||
MGPipeHandles.h # MGPipeHandle、MGPipeKind、保留 handle、slot 分配契约
|
||
MGPipeHostSpan.h # 唯一一个"形状随传输而变"的访问器(§4.5.7)
|
||
MGPipeCallbacks.h # 反向通道(事件/回复)的函数表,见 §7
|
||
generated/PipeTables.inc # G1:两张函数表
|
||
generated/PipeThunks.inc # G2:monolith 直调 thunk
|
||
generated/PipeWire.inc # G3:wire 记录 + static_assert + 运行期边界检查 + applier switch
|
||
generated/PipeVerify.inc # G4:逐字段影子比对器
|
||
generated/PipeFilled.inc # G5:written-once 位图与 poison 断言
|
||
generated/PipeCoverage.inc # G6:477 读点 → MGPipe 调用的映射表(CI 重生成 + git diff --exit-code)
|
||
MobileGL/MG_Impl/Pipe/
|
||
Tracker.{h,cpp} # st_validate_state 类比物
|
||
SlotAllocator.{h,cpp} CsoCache.{h,cpp}
|
||
HostResolve.cpp # 客户端数组界限 / 索引扫描 / restart 重写 / indirect count 解析
|
||
CompositeResolver.cpp # program pipeline 合成体的 handle 生命周期
|
||
MobileGL/MG_Backend/MGPipe/
|
||
PipeInputs.h # backend 私有的"被推送状态"块(迁移载体,§6.2)
|
||
MGPipeImpl_DirectGLES.cpp # 用 Espryt 的函数填 MGPipeContext
|
||
MGPipeImpl_DirectVulkan.cpp # 用 Magma 的函数填 MGPipeContext
|
||
MobileGL/MG_Remote/ # 传输,继承 PLAN.md §13(删掉 Server/ReplicaContext.*)
|
||
Server/PipeApplier.cpp Server/PipeObjectTables.{h,cpp}
|
||
scripts/gen_pipe.py # 跑 G1..G6
|
||
```
|
||
|
||
`PipeCalls.def` 一行一个调用,**六个生成器**消费它:
|
||
|
||
```cpp
|
||
// MG_Pipe/PipeCalls.def — X(Name, PayloadStruct, Class, Flags)
|
||
// Class : kScreen | kCtxCso | kCtxState | kCtxObject | kCtxVerb | kCtxQuery
|
||
// Flags : kNone | kNeedsAck | kHasBlob | kVarTail | kHostSpan | kReplySlot | kOptional
|
||
#define MGP_CALL_LIST(X) \
|
||
/* ---- screen ---- */ \
|
||
X(GetCaps, MGPCaps, kScreen, kReplySlot) \
|
||
X(ResourceCreate, MGPResourceDesc, kScreen, kNone) \
|
||
X(ResourceRespecify, MGPResourceDesc, kScreen, kNone) \
|
||
X(ResourceDestroy, MGPHandleOnly, kScreen, kNone) \
|
||
X(MapPersistent, MGPHandleOnly, kScreen, kReplySlot|kOptional) \
|
||
/* ---- CSO ---- */ \
|
||
X(CreateRenderState, MGPRenderStateDesc, kCtxCso, kHasBlob) \
|
||
X(BindRenderState, MGPBindRenderState, kCtxCso, kNone) \
|
||
/* ---- state ---- */ \
|
||
X(SetFramebufferState, MGPFramebufferState, kCtxState, kNone) \
|
||
X(SetSamplerViews, MGPSamplerViews, kCtxState, kVarTail) \
|
||
/* ---- verb ---- */ \
|
||
X(DrawVbo, MGPDrawInfo, kCtxVerb, kHostSpan|kVarTail) \
|
||
X(ResourceSubData, MGPSubData, kCtxObject,kHasBlob) \
|
||
X(RenderbufferStorage, MGPRbStorage, kCtxObject,kNeedsAck) \
|
||
/* … 共约 72 项,完整目录见 §4.4 与 part4 的速查表 … */
|
||
```
|
||
|
||
| 生成器 | 产物 | 替代/新增 |
|
||
|---|---|---|
|
||
| **G1** | `struct MGPipeScreen { … };` / `struct MGPipeContext { void (*DrawVbo)(const MGPDrawInfo*, …); … };` | 替代今天手写的 `GLFunctionsTable` |
|
||
| **G2** | monolith thunk:`inline void MGP_DrawVbo(const MGPDrawInfo* p){ gPipeCtx.DrawVbo(p); }` — 一次**已经在付**的间接调用 | 替代 `gBackendFunctionsTable.GL.*`(89 个 MG_Impl 站点改名即可) |
|
||
| **G3** | wire 记录结构 + 每种一条 `static_assert(sizeof==N)` + applier 分发前的运行期边界检查 → `Fatal{ProtocolCorruption}` | 继承并扩展 `PLAN.md` §6.3 的 `Records.def` 机制到**全部**调用 |
|
||
| **G4** | `MOBILEGL_PIPE_VERIFY` 的逐字段比对器:对每个 `kCtxState`/`kCtxCso` 调用生成 `Compare(pushed, snapshot, &firstDivergentFieldName)` | **新增**:这是每份候选设计都被判缺失的语义绊线 |
|
||
| **G5** | `PipeInputs::m_filledMask` 的位定义 + 读未填字段时的 `Fatal{UnmigratedPipeInput, "<field>"}` | **新增**:完整性的**运行期/构建期**绊线,不只是测试绊线 |
|
||
| **G6** | 477 行读点清单 → MGPipe 调用的映射,CI 重生成并 `git diff --exit-code`,0 UNMAPPED | 改造自 `Feat/CS-Delta-IPC` 的 `extract_backend_read_inventory.py`(删掉制造"0 UNMAPPED"的前缀兜底规则) |
|
||
|
||
**G4 与 G5 从同一份 `.def` 生成,因此不可能与调用目录漂移。** 这是把"完整性只有测试绊线、没有构建绊线"这条历史评审结论一次性关掉的机制。
|
||
|
||
**接口表用函数指针 struct,不用虚基类。** 三条本仓库自己的理由:(1) 边界今天**就是**函数指针 struct,装在 `MG_Backend/Init.cpp:44` 的唯一 hook 点上;(2) `nullptr` 项**已经**表示"未实现,前端回退",写进 `BackendObject.h:212-215` 与 `:265-269`,而 DirectVulkan 确实留空 8 项——**一个 null `set_*` 恰好就是"这个子系统还没迁移,继续拉取"**,这是 strangler 的原生表达,纯虚类只能用说谎的 stub override 来模拟;(3) `MG_Test` 已经会替换这张表做 mock。稀有的 EGL/caps 面继续留在 `pActiveBackendObject` 的虚函数上(20 个方法,`BackendObject.h:543-568, 597-598`),它们本来就罕见。
|
||
|
||
### 4.2 对象模型
|
||
|
||
#### 4.2.1 Handle
|
||
|
||
```cpp
|
||
// MG_Pipe/MGPipeHandles.h
|
||
enum class MGPipeKind : Uint8 {
|
||
Buffer=1, Texture, Renderbuffer, Framebuffer, Xfb,
|
||
RenderStateCso, VertexElementsCso, SamplerCso, SamplerViewCso, ShaderCso,
|
||
Fence, Query, Context
|
||
};
|
||
struct MGPipeHandle { // 8 B,POD,可哈希,按值传寄存器对
|
||
Uint32 slot; // 按 kind 稠密分配(client 侧 SlotAllocator)
|
||
Uint32 gen; // slot 复用时 ++;client 侧维护
|
||
};
|
||
```
|
||
|
||
- **slot 稠密、按 kind 分配**。这一条是把 server 的对象表从哈希表变成**数组**的唯一途径,也是删掉 `TwinLookupMemo` 的斐波那契散列 + owner 相等性检查的原因。`SlotAllocator` 是一个 free-list + 高水位,与 `IndexGenerator` 无关(后者的 LIFO 复用正是问题本身)。
|
||
- **`gen` 只在 slot 复用时 ++,不是每次 respecify 都 ++**。`{slot, gen}` 在**同一个 slot 被复用 2³² 次**之前唯一。文档写明这条上界,debug 构建断言它。
|
||
- **GL name 只在 `resource_create` 的 payload 里出现一次,纯诊断用途**,永不做身份、永不进任何 memo 键、永不进任何 content hash。
|
||
- **`GetLifetimeId()` 留在 client 侧**作为 tracker 自己的身份(它已经是永不复用的),不过线。client 侧维护 `lifetimeId → slot` 映射。
|
||
- **保留 handle**:`{0,0}` = null;`{slot=0, gen=1, kind=Framebuffer}` = 默认帧缓冲(这一条退役 `DirectGLES.cpp:1917, 2838, 2867, 9675` 四处对 `pDefaultFramebufferInfo->defaultFBO` 的身份比较);`ShaderCso` 的高 1/16 slot 段保留给 **program pipeline 合成体**(§5.6),因为它们没有 GL name。
|
||
|
||
#### 4.2.2 两种 generation,严格分开
|
||
|
||
| | 拥有者 | 回答什么 | 是否过线 |
|
||
|---|---|---|---|
|
||
| **身份**(`MGPipeHandle::gen`) | client | "还是同一个 GL 对象吗?" | 是,随每个 handle |
|
||
| **`MGGen`**(server 纪元) | **server** | "**我自己**是不是重铸了驱动对象 / 冲了自己的缓存?" | **client→server 永不;server→client 只以纹理拉取请求的形式出现**(§7.5) |
|
||
|
||
`MGGen` 是 §0.3 推论 3 那 12 个计数器的 wire 名字。**接口规范条款:任何 MGPipe 调用都不得要求 client 提供或知晓 `MGGen`。** 这条是"server 拥有自己的状态机"的可执行定义。反过来也是规范:**client 侧的版本计数器永远不是新鲜度的唯一证明**——每一个回绕的 `Uint16`(§2.6)在过线时要么被加宽到 32 位、要么与 `{slot, gen}` 同行。
|
||
|
||
#### 4.2.3 CSO vs 可变对象
|
||
|
||
| 类别 | 形态 | 因为 backend 今天就是这么缓存的 |
|
||
|---|---|---|
|
||
| `VertexElementsCso` | `create/bind/delete` | `VertexInputStateFactory::m_cache`,键正是 `VertexAttribute` 那组字段的 content hash(`VertexInputStateFactory.cpp:19-50`) |
|
||
| `SamplerCso` | `create/bind/delete` | `VkSamplerManager::m_samplers`(`VkSamplerManager.h:104-117`);Espryt 的 `BackendSamplerObject`(`Managers.h:1808-1824`) |
|
||
| `SamplerViewCso` | `create/delete` + 由 `set_sampler_views` 绑定 | `TextureResource::{perMipViews, perMipSampledViews, attachmentViews, alternateSampledViews, storageImageViews}`(`VkTextureManager.h:173-370`);Espryt 的 `SyncTextureViewToBackend`(`Managers.cpp:3616-3707`) |
|
||
| `ShaderCso` | `create/bind/delete` + **server 侧惰性特化**(D-B2) | `ProgramFactory::m_cache`(`ProgramFactory.h:614-642`);`BackendProgramObjectImpl`(`Managers.h:1473-1725`) |
|
||
| `RenderStateCso` | `create/bind/delete`,**payload 是整块 blob**(D-B1) | Espryt 的值镜像 + 三段 memcmp;Magma 的 `ComputePipelineStateHash` 单次 bulk fetch |
|
||
| Buffer / Texture / Renderbuffer | `create` / `respecify` / `subdata` / `destroy`(可变,带存储) | `GLESBufferResource`、`BackendTextureObject`、`VkBufferResource`、`TextureResource` |
|
||
| Framebuffer / Xfb | per-context 身份 + `set_*` payload(不带存储) | `BackendFramebufferObject`、`m_xfbCounterSlotByObject` |
|
||
|
||
**CSO 在 client 侧内容寻址**(Mesa `cso_context`/`cso_cache` 的先例):`MG_Impl/Pipe/CsoCache` 每类一张 `ska::flat_hash_map<Uint64 xxHash, MGPipeHandle>`,容量上限(blend/DS/rasterizer 合并后的 render-state 64、vertex-elements 1024、sampler 256、sampler-view 4096、shader 不设上限跟随 `ProgramObject` 生命周期),LRU 淘汰时发 `delete_*_state`。**内容寻址的一个直接收益**:两个不同 program 设置了相同的 blend 状态时,server 侧**零状态转换**。
|
||
|
||
**任何 `create_*` 都不返回 server 铸造的 handle。** 这是对 gallium 的**有意偏离**(gallium 的 `create_*_state` 返回 driver 指针),也是这份目录能在**零创建 round trip** 下远程化的根本原因。今天的 `BackendSyncHandle`/`BackendQueryHandle = void*`(`BackendObject.h:110, 115`)随之变成 `MGPipeHandle`——fence 与 query 需要的唯一改动。
|
||
|
||
### 4.3 `MGPipeScreen` 与 `MGPipeContext`
|
||
|
||
GL 的 share-group 规则本身就是分界,而 MobileGL 的容器归属**已经**吻合:
|
||
|
||
| `MGPipeScreen`(share group) | `MGPipeContext` |
|
||
|---|---|
|
||
| caps、format 能力表、renderer 字符串;buffer / texture / renderbuffer / sampler / shader 的对象命名空间;fence | 全部 `set_*`、全部 CSO 绑定、VAO / FBO / XFB 对象 / query 的命名空间、命令流、present |
|
||
|
||
v1 只有一个 screen、一个 context、一条 flow(`shareCtx` 是死状态,§1.2)。**但两张表从第一天就分开**,因为事后拆分意味着给每个记录种类重新编号。两处必须重新归类的事实:`GetTextureBindGeneration()` 与 `GetSamplingResolutionGeneration()`(`Core.h:130, 136`)是**绑定**(context)事实却住在 share-group 作用域的 `TextureState` 里;`GetTextureContextId()`(`:143`)已经存在正是为了在 context 重建时区分它们——它直接**就是** context handle。
|
||
|
||
### 4.4 完整调用目录
|
||
|
||
> 完整速查表(含 payload 大小与 flags)在 part4 的附录。这里给出结构与每项的来源/取代关系。
|
||
|
||
#### 4.4.1 `MGPipeScreen`(14 项)
|
||
|
||
| 调用 | payload | 取代 |
|
||
|---|---|---|
|
||
| `get_caps(MGPCaps* out)` | `DynamicBackendParameters`(`BackendObject.h:302-522`,~90 标量,平坦 POD)+ `RendererInfo` + `FormatCapabilityCache`(`:88-99`,14 个能力位 × 全部 internalformat × 12 target 的稠密表 + 每格 `SampleCounts`)+ `callMask` | 40 个 `pActiveBackendObject->` 站点、89 个 caps 读点 |
|
||
| `resource_create(h, const MGPResourceDesc*)` | §4.5.1 | buffer/texture/renderbuffer 的创建 |
|
||
| `resource_respecify(h, const MGPResourceDesc*)` | 同上 | `BufferBackendOps::Respecify`(`BufferObject.h:80`)泛化;server 自己按 busy 跟踪决定 swap-vs-in-place |
|
||
| `resource_destroy(h)` | handle | `OnDestroy`(`:101`)+ **两个 `WeakPtr` GC 扫描**(`Managers.h:353-390`、`VkTextureManager.cpp:1694-1720`)。server 的 pooling / 延迟释放(`Managers.cpp:1271-1300`、`VkBufferManager::OnResourceDestroyed`)原样保留 |
|
||
| `map_persistent(h) → MGPMapResult` / `unmap_persistent(h)` | — | `AcquirePersistentMap`(`:112`)。**改造期不碰**(D-B4) |
|
||
| `fence_create/status/wait/destroy` | handle (+timeout) | `FenceSync`…`GetSyncStatus`(`:220-224`)。两值契约(`:243-249` "false = 还没好,留着 handle")**逐字保留** |
|
||
| `query_create/begin/end/available/result/destroy` | handle + kind | `BackendObject.h:230-256` |
|
||
| EGL 生命周期 8 项 | 见 `BackendObject.h:548-559` | 原样保留为虚函数(罕见) |
|
||
|
||
**`callMask` 取代"槽位是否为 null"这个隐式能力探测**(`GL_Query.cpp:471, 545, 768` 拿 `BeginOcclusionQuery != nullptr` 当能力位)。它同时是 emulation 归属的判据(§5.7):`kCapPrimitiveRestart`、`kCapPrimitiveRestartFixedIndex`、`kCapMultiDraw`、`kCapMultiDrawIndirect`、`kCapMultiDrawIndirectCount`、`kCapViewportArray`、`kCapFloat64VertexAttrib`、`kCapResidentSubData`、`kCapCpuXfbPrimitiveAccounting`、`kCapNeedsHostIndexBytes`、`kCapTimerQuery`、`kCapOcclusionQuery`、`kCapXfbPrimitivesQuery`。
|
||
|
||
#### 4.4.2 `MGPipeContext` — CSO(15 项)
|
||
|
||
`create/bind/delete` × { `render_state`, `vertex_elements`, `sampler`, `sampler_view`, `shader` }。payload 见 §4.5.2-4.5.5。
|
||
|
||
#### 4.4.3 `MGPipeContext` — `set_*`(14 项)
|
||
|
||
| 调用 | 取代的拉取点 |
|
||
|---|---|
|
||
| `set_framebuffer_state` | `GetFramebufferBindingSlot` ×19、`GetAllAttachmentObjects`、`GetDrawBuffers`、`GetReadBuffer`、4 处 `pDefaultFramebufferInfo` |
|
||
| `set_vertex_buffers(start, count, const MGPVertexBuffer*)` | VAO binding-point 走查 |
|
||
| `set_index_buffer(const MGPIndexBuffer*)` | `GetIndexBufferBindingSlot`;**独立调用**——VAO config version 不是它的超集(D5) |
|
||
| `set_indirect_buffers(drawIndirect, parameter)` | `GetBufferBindingSlot(DrawIndirect/Parameter)` |
|
||
| `set_sampler_views(stage, start, count, const MGPBoundView*)` | `GetTextureUnitObject` ×19、`GetActiveTextureUnit` ×8、`GetTextureBindGeneration` ×5。**client 侧已解析**(§5.5) |
|
||
| `bind_sampler_states(stage, start, count, const MGPipeHandle*)` | `TextureUnit.h:394` |
|
||
| `set_shader_images(start, count, const MGPImageView*)` | `GetImageTextureBinding` ×14;**这一条退役 `ImageUnitFormatsStillMatch`**(`Managers.cpp:6545-6573`) |
|
||
| `set_shader_buffers(cls, start, count, const MGPBufferRange*, writableMask)` | `GetBufferBindingPoint` ×19、`GetTouchedBufferBindingPointCount` ×2。`cls` ∈ {Uniform, ShaderStorage, AtomicCounter} |
|
||
| `set_stream_output_targets(count, const MGPBufferRange*, const Uint32* offsets, Uint64 generation)` | XFB 绑定走查 |
|
||
| `set_global_constants(shaderCso, MGPBlobRef, Uint32 version)` | `MapUBO`/`GetUBOData`/`GetUBOSize`/`GetUBOContentVersion`(§4.6 D6) |
|
||
| `set_vertex_attrib_defaults(Uint32 mask, const MGPAttribValue*)` | `GetCurrentVertexAttribute` ×2;float/int/uint 视图由 `ClassifyVertexAttribType`(`Core.h:51`)在 client 侧解析 |
|
||
| `set_pixel_pack_state(const PixelStoreParameters*)` | 6 个 PACK 读点。**没有 unpack 对应项**(§4.6 D5) |
|
||
| `set_patch_state(Uint32 vertices, const Float outer[4], const Float inner[2])` | `GetPatchVertices`/`…OuterLevel`/`…InnerLevel` ×6。**同时是 shader variant 输入**(`RenderState.h:243-249`) |
|
||
| `set_draw_program(shaderCso)` / `set_dispatch_program(shaderCso)` | `GetProgramForDraw` ×7、`GetProgramForDispatch` ×3。含 composite(§5.6) |
|
||
|
||
**迁移期额外一项(显式临时)**:`set_residual_value_state(MGPBlobRef)`,见 §6.3。它带一个**编译错误式退役绊线**。
|
||
|
||
#### 4.4.4 `MGPipeContext` — transfer(12 项)
|
||
|
||
`resource_subdata`(buffer + texture 同一形状,**同时携带 union box 与 rect 列表**,§4.5.6)、`resource_flush_range(h, Range1D, Flags<BufferMappingAccessBit>)`(携带应用**真实**的 access flags,`BufferObject.h:94-96`)、`resource_readback(h, off, size, MGPReplySlot)`、`resource_copy_region`、`blit`、`clear`(一条,判别式合并今天的 `Clear` + 4 个 `ClearBuffer*` + 4 个 `ClearNamedFramebuffer*`)、`generate_mipmap(h, target, const MGPMipPlan*)`、`read_pixels(const MGPReadbackInfo*, MGPReplySlot)`、`get_texture_image(...)`、`buffer_subdata_resident(h, off, MGPBlobRef)`(**可为 null**,见下)。
|
||
|
||
**`buffer_subdata_resident` 的 per-backend 可选性必须被接口允许,不能抹平。** Espryt 注册它、Magma 故意不注册(`VkBufferManager.cpp:104-111`),差别是 `glBufferSubData` 在活的 coherent map 上的排序语义(`BufferObject.h:84-92` 的 Minecraft 撕裂 postmortem)。表现为 `kCapResidentSubData` 位 + null 项。**统一强制的接口会静默改变 Magma 的行为并发布一个 Minecraft chunk 撕裂。**
|
||
|
||
#### 4.4.5 `MGPipeContext` — 命令(4 + 4 + 2 项)
|
||
|
||
```cpp
|
||
void draw_vbo (const MGPDrawInfo*, Uint32 drawIdOffset,
|
||
const MGPDrawIndirect*, const MGPDrawRange*, Uint numDraws);
|
||
void launch_grid(const MGPGridInfo*); // + indirect 变体字段
|
||
void memory_barrier(GLbitfield bits, Bool byRegion);
|
||
void begin_stream_output(GLenum primitiveMode);
|
||
void end_stream_output(const MGPXfbAccounting*);
|
||
void pause_stream_output(); void resume_stream_output();
|
||
void flush(Uint32 flags); // GL_SYNC_FLUSH_COMMANDS_BIT 语义
|
||
void present(Uint64 frameSerial); void set_swap_interval(Int interval); // 后者可 null(Magma)
|
||
```
|
||
|
||
**今天 20 个 draw 入口塌成 `draw_vbo` 一条**——这正是 gallium 的合并,而 `MGPDrawRange[]` **就是** `MultiDraw*` 族今天的形状(gallium 的 `pipe_draw_start_count_bias`)。
|
||
|
||
#### 4.4.6 显式删除、不移植的项
|
||
|
||
- `GetIntegeri_v` / `GetInteger64i_v` / `GetProgramiv`(`BackendObject.h:195-197`)。`DirectGLES.cpp:7264-7386` 的 15 个 case 完全不碰 GL(占 Espryt 124 个读点里的 14 个);Magma 同形(`DirectVulkan.cpp:685, 759`),只有 `GL_COMPUTE_WORK_GROUP_SIZE`(`:790-795`)是真后端答案,进 `MGPCaps`。
|
||
- `ShaderStorageBlockBinding`(`:207-208`)→ 折进 `MGPProgramDesc` 的反射归档(`LinkArtifacts::shaderStorageBlockBinding` 按**名字**索引,`ProgramObject.h:1325`)。它自己的注释已经说 program interface 由前端回答。
|
||
- **总规则:server 不回答任何 client 能自己回答的问题;剩下的每个 server 查询都是 async-with-handle,绝不阻塞。**
|
||
|
||
### 4.5 关键 payload
|
||
|
||
#### 4.5.1 `MGPResourceDesc`(判别式,三种 GL 存储类合一)
|
||
|
||
```cpp
|
||
struct MGPResourceDesc {
|
||
Uint8 target; // Buffer | Tex1D..TexCubeArray | Tex2DMS.. | Renderbuffer | TexBuffer
|
||
Uint8 storageKind; // Mipmap | Buffer (== TextureStorageType, TextureEnum.h:61-64)
|
||
Uint16 bindMask; // VERTEX|INDEX|CONSTANT|SHADER_BUFFER|INDIRECT|SAMPLER|SHADER_IMAGE|
|
||
// RENDER_TARGET|DEPTH_STENCIL|STREAM_OUTPUT|ATOMIC (== PIPE_BIND_*)
|
||
Uint32 internalFormat; // GL internalformat,已在前端解析为非压缩后备
|
||
Uint32 width, height, depth;
|
||
Uint16 arrayLayers, levels, samples;
|
||
Uint8 fixedSampleLocations, immutable;
|
||
Uint32 usage; // BufferUsage
|
||
Uint32 storageFlags; // glBufferStorage flags
|
||
Uint8 hasDefinedContent; // NULL-data respecify(orphaning 惯用法)之后为 false,BufferObject.h:216
|
||
Uint8 imageBindableHint; // client 侧 everImageBound,预防性分配(§7.5(a))
|
||
Uint8 glNameForDiag[2]; // 仅诊断
|
||
MGPipeHandle viewOf; // 纹理视图的存储属主(GetViewStorageOwner,TextureObject.h:100)
|
||
MGPipeHandle bufferForTexBuffer; Uint64 bufOffset, bufSize; // kWholeBuffer = ~0,实时解析
|
||
};
|
||
```
|
||
|
||
**Renderbuffer 保持独立类,不折进纹理。** 它有自己的 format-capability target 索引(`BackendObject.h:85`)、自己的 `ComponentSizes` 上报(`RenderbufferObject.h:37-43`)、自己的 twin(`Managers.h:1838`)。合并零收益且丢掉一张独立能力表。`bindMask` 让它在**绑定语义**上与纹理统一,这就够了。
|
||
|
||
#### 4.5.2 `MGPRenderStateDesc` / `MGPBindRenderState`(D-B1)
|
||
|
||
```cpp
|
||
struct MGPRenderStateDesc { // create:整块,每个唯一状态一次
|
||
MGPipeHandle cso;
|
||
Uint8 spanMask; // bit0=head, bit1=blend[], bit2=tail;未命中时只发变化段
|
||
MGPipeHandle baseCso; // 增量基(spanMask != 0b111 时有效)
|
||
MGPBlobRef blob; // RenderStateParameters 的对应段
|
||
};
|
||
struct MGPBindRenderState { // bind:稳态 12 B
|
||
MGPipeHandle cso; Uint16 version; Uint16 pipelineVersion;
|
||
};
|
||
```
|
||
段划分**与 `DirectGLES.cpp:2038-2047` 现有的 head/blend/tail 完全一致**,所以两侧对"段"的定义按构造相同。两个版本号都过线(`RenderState.h:522` / `:529`)。
|
||
|
||
#### 4.5.3 `MGPVertexElements`
|
||
|
||
携带**两个视图,缺一不可**:解析后的 `VertexAttribute[32]`(`VertexArrayObject.h:17-53`)**和** `VertexBufferBindingPoint`(`:58-64`,注意初始 stride 是 **16** 而不是 0,`:61-62`)。`VertexArrayObject.h:22-29` 记录了合并它们的代价:pointer 调用的 stride 0 被解析成 element size,而 binding-model 的 stride 0 意味着每个顶点读**同一个** element,塌成一个害了 `KHR-GL43.vertex_attrib_binding.basic-input-case7/8`。`IsLong` 与 `Type == Float64` **分开携带**(`:34-39`:`glVertexAttribFormat(GL_DOUBLE)` 读 double 转 float,`glVertexAttribLFormat` 保留 64 位,`GL_VERTEX_ATTRIB_ARRAY_LONG` 上报这个区别)。**仅供查询的 `LegacyStride`/`LegacyPointer`(`:51-52`)留在 client,永不过线。**
|
||
|
||
#### 4.5.4 `SamplerParameters` 与 `MGPSamplerView`
|
||
|
||
`SamplerParameters`(`SamplerObject.h:468-492`)**逐字节原样过线,包括 `borderColorForm`**(`:462-466`):`:455-461` 明说没有它 backend 无法在 `glSamplerParameterIiv` 与 `fv` 之间、或在 `VkBorderColor` 家族之间选择,因为三种表示(`borderColor`/`borderColorI`/`borderColorUI`)**永远都被数值填满**。
|
||
|
||
```cpp
|
||
struct MGPSamplerView { // = pipe_sampler_view
|
||
MGPipeHandle cso, texture;
|
||
Uint32 internalFormat; // 别名格式(glTextureView)
|
||
Uint8 target, depthStencilMode, pad[2];
|
||
Uint16 minLevel, numLevels, minLayer, numLayers;
|
||
Uint8 swizzle[4];
|
||
Uint16 samples; Uint8 fixedSampleLocations, pad2;
|
||
};
|
||
```
|
||
**`glTextureView` 因此从"ES name 重铸特殊路径"(`Managers.cpp:3616-3707`)变成一个普通 view CSO**,这对两个 backend 都是净简化。真正需要保留的区别是 `GetViewStorageOwner()`(`TextureObject.h:96-100`,一个 `SharedPtr`,且**它自己永远不是 view**)——它变成 `resource_create` 的 `viewOf` + server 侧 keep-alive。
|
||
|
||
#### 4.5.5 `MGPProgramDesc`(`create_shader_state` 的 payload)
|
||
|
||
```cpp
|
||
struct MGPProgramDesc {
|
||
MGPipeHandle cso;
|
||
Uint32 stageMask; // == GetLinkedShaderStages()
|
||
MGPBlobRef spirv[6]; // GetGeneratedSpirv(),逐 stage,顺序与 GetLinkedShaderSnapshot() 一致
|
||
MGPBlobRef reflection; // Visit() 归档的 LinkArtifacts + SpirvArtifacts(全结构体)
|
||
Uint32 globalUboSize; // GetUBOSize()
|
||
Uint32 reservedNumSamplesOffset;
|
||
Uint8 spirvStatus, nativeFloat64, pointSizeDemoted, enableSpirvValidation;
|
||
};
|
||
```
|
||
|
||
反射归档**序列化整个结构体,而不是 backend 今天读的那 ~40 个字段**——这样 backend 新增一次 read 永不需要改协议。机制沿用 `PLAN.md` §6.9 的 `Visit()` + `sizeof` 绊线,但**用途改变**:它不再是"分歧预言机"(没有可分歧的对象),而是**schema 完整性绊线**:
|
||
|
||
```cpp
|
||
// MG_State/GLState/ProgramState/ProgramArtifactsArchive.h
|
||
template <class Ar> void Visit(Ar& ar, LinkArtifacts& a) { ar(a.writtenUniformLocationBits, /*…全字段…*/); }
|
||
static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE,
|
||
"新字段请加进 Visit() 并 bump MGL_LINKARTIFACTS_SIZE");
|
||
```
|
||
|
||
归档必须覆盖:`uniformReflection`/`blockReflection`/`pipeInputReflection`/`pipeOutputReflection`(`ProgramObject.h:1210-1395`,各带 `TypeFacts`)、`uniformSamplerOrImageUnitIndex`(`:1298`)、`uniformBlockBinding`(`:1314`)、`shaderStorageBlockBinding`(按名字,`:1325`)、`explicitOpaqueUniformBindings`(`:1303`)、`xfbVaryings`/`xfbStrides`/`xfbPackedStride`/`xfbNeedsScatteredCapture`(`:1357-1394`)、`computeLocalSize`、GS/TCS/TES 事实(`:1373-1388`)、`usesReservedNumSamples`(`:1345`)、`uniformOffsets`(`:1416`)。
|
||
|
||
**`XfbVarying`(`:1146-1171`)必须带两套拼写**:GL 名字(供 Espryt 的 ESSL 驱动侧捕获列表)**和** `blockInstanceName`/`blockName`/`blockMemberIndex`/`blockMemberElement`(`:1163-1170`),因为 SPIR-V 装饰的是 block 的实例变量与成员索引,无法装饰单个数组元素。
|
||
|
||
#### 4.5.6 `MGPFramebufferState` 与 `MGPSubData`
|
||
|
||
```cpp
|
||
struct MGPSurface { // = pipe_surface
|
||
MGPipeHandle res;
|
||
Uint32 internalFormat; // 内联!让四个跨对象 mask 在推送时刻零查表推出
|
||
Uint8 kind; // Texture | Renderbuffer | None
|
||
Uint8 layered; Uint16 level;
|
||
Uint32 layer; Uint16 uploadTarget; Uint16 pad;
|
||
};
|
||
struct MGPFramebufferState {
|
||
MGPipeHandle fbo; // {0,1} = 默认帧缓冲
|
||
MGPSurface color[8], depth, stencil;
|
||
MGPSurface readSurface; // *** client 侧已解析的读表面,不是索引 ***
|
||
Int8 drawBuffers[8]; // attachment 索引,-1 = NONE
|
||
Uint16 width, height, layers, samples;
|
||
Uint8 fixedSampleLocations, isDefault, complete, pad;
|
||
Uint64 contentHash; // client 计算;server 的 render-pass memo 键
|
||
};
|
||
```
|
||
|
||
三点值得单独说:
|
||
|
||
1. **`readSurface` 是 client 解析后的表面,不是 `glReadBuffer` 的索引。** 这一条**按结构消灭** read-buffer-shared-FBO 缺陷类(同一个 FBO 既做 draw 又做 read 时跳过 read-buffer 同步,导致回读全取 attachment 0)。
|
||
2. **`internalFormat` 内联在每个 `MGPSurface` 里**,所以四个跨对象 mask(`g_snormFallbackClampOutputMask`、`g_unormFallbackClampOutputMask`、`g_alphaWidenedDrawBufferMask`、`g_integerColorDrawBufferMask`,`Managers.cpp:5616-5619`)在 `set_framebuffer_state` 内部零查表推出。配合 D-B3 的固定顺序,`DirectGLES.cpp:2712-2732` 的 fragColor 重推导 workaround 与 `g_broadcastMemo*` 一起删除。
|
||
3. **`contentHash` 是全表最有价值的一个字段**:它用一次 64 位比较取代 D7 的四元组同步戳与 D15 的 `(pointer, lifetimeId, Uint16 version)` 三元组,由一个本来就在跟踪全部输入的 client 计算。attachment enum 到 Color31(`FramebufferObject.h:65-66`)而 `MAX_DRAW_BUFFERS` 是 8(`:106`)——surface 数组是 8,其余由 `drawBuffers[]` 的解析结果承载。`ARB_framebuffer_no_attachments` 的默认值是独立字段(`:134-138`)。
|
||
|
||
```cpp
|
||
struct MGPSubData {
|
||
MGPipeHandle res;
|
||
Uint16 target, level;
|
||
MGPBox box; // union box
|
||
Uint32 rectCount; // rect 列表在变长尾
|
||
MGPBlobRef blob;
|
||
};
|
||
```
|
||
**同时携带 union box 与 rect 列表,由 server 选上传形状。** 这不是冗余:Mali 按**作业数**给纹理上传计价,本项目实测 ~100 个精灵 rect 对一个 union box 的差是 **+6 ms/frame**(`Managers.cpp:4311-4319`)。client 按 `MipmapStorage::GetDirtyRects` 的语义产生区域形状(96-rect 级联合并 + `summedArea*4 >= unionArea*3` 回退,`MipmapStorage.cpp:300-305`),**决策留在付 GPU 代价的那一侧**。
|
||
|
||
#### 4.5.7 `MGPDrawInfo` 与 `MGHostSpan`
|
||
|
||
```cpp
|
||
struct MGPDrawInfo { // = pipe_draw_info,GL 有对应概念的字段逐一对齐
|
||
Uint32 mode;
|
||
Uint8 indexSize; // 0 = arrays,否则 1/2/4
|
||
Uint8 flags; // kHasUserIndices | kPrimitiveRestart | kRestartRewritten |
|
||
// kXfbCountUnknown | kIndicesAreClient
|
||
Uint16 pad;
|
||
Uint32 instanceCount, startInstance;
|
||
Uint32 restartIndex;
|
||
Uint32 minIndex, maxIndex; // client 计算(gallium 有这两个字段);~0 = 未知
|
||
MGPipeHandle indexResource;
|
||
MGHostSpan userIndices; // = pipe_draw_info::index 的 union
|
||
Uint64 xfbCpuCapturedVertices; // GetTransformFeedbackCapturedVertices(),给 scatter 路径定容量
|
||
};
|
||
struct MGPDrawRange { Uint32 start, count; Int32 indexBias; }; // = pipe_draw_start_count_bias
|
||
```
|
||
|
||
**`MGHostSpan` 是整份接口里唯一一个"形状随传输而变"的东西**,它把 backend 今天在 draw 时刻解引用**客户端字节**的四个类统一成一个访问器:
|
||
|
||
```cpp
|
||
struct MGHostSpan { // 32 B
|
||
const void* ptr; // monolith:指向前端 shadow / 应用内存。split:nullptr
|
||
Uint64 size;
|
||
Uint32 seg; // split:SEG_STAGE id
|
||
Uint32 pad;
|
||
Uint64 offset; // split:段内偏移
|
||
};
|
||
inline const void* MGPipeHostBytes(const MGHostSpan&); // 一次分支,每次使用解析一次
|
||
```
|
||
|
||
| 消费者 | 今天的站点 | monolith 填法 | split 填法 |
|
||
|---|---|---|---|
|
||
| client 顶点数组 | `Managers.cpp:2500-2592`(每 draw 每属性上传 `(first+count-1)*stride+elementSize`)、`VulkanRenderer.cpp:3737` | `ptr = attrib.Offset` | tracker 暂存同样的范围进 `SEG_STAGE` |
|
||
| client 索引数组 | `DirectGLES.cpp:4425-4442`、`VulkanRenderer.cpp:3418-3433` | `ptr = indices` | 暂存 `count*indexSize` |
|
||
| indirect / parameter 命令块 | `DirectGLES.cpp:4666-4695`(`MultiDrawElementsIndirectCount` 从 `parameterBuffer->MappedData()` 读实际 draw 数)、`:4768-4793`、`VulkanRenderer.cpp:12045` | `ptr` 指向 shadow | tracker **解析出计数**并暂存解析后的命令块 |
|
||
| restart 重写 / multi-draw 展平的索引字节 | `DirectGLES.cpp:4412-4415`、`MultiDraw.cpp:498-540`、`VulkanRenderer.cpp:4159` | `ptr` 指向 shadow | 暂存,或 client 已重写(§5.7) |
|
||
|
||
**monolith 代价为零**(一次指针加载),而且它顺带消灭了"backend 在 draw 中途回头调前端来 reconcile"这一整类:20 处 `SyncPersistentMappedRange` + 6 处 `SyncGpuWrites` 全部是"我马上要读你的字节,先把 shadow 对齐",现在改由 **tracker 在填 span 之前**做同样的 reconcile。这在 monolith 里也是严格改进。
|
||
|
||
**`kCapNeedsHostIndexBytes`** 存在的理由正是这条 monolith/split 的不对称:monolith 里 span 是一个免费指针,split 里是一次拷贝。让一个从不需要宿主索引字节的 backend 不付这笔钱。
|
||
|
||
### 4.6 与 gallium 的对应与偏离(八条,逐条记名)
|
||
|
||
| # | gallium | MGPipe | 理由(证据) |
|
||
|---|---|---|---|
|
||
| **D1** | `create_*_state` 返回 driver 指针 | **调用方提供 handle**,create 返回 void | 零创建 round trip;handle 是稠密 slot,让 server 对象表变成数组;退役全部 D 类指针 memo |
|
||
| **D2** | `pipe_screen::get_param(cap)`、`is_format_supported(...)` 逐项查询 | **一个 `MGPCaps` POD + 一张稠密 format 表** | `DynamicBackendParameters`(`BackendObject.h:302-522`)与 `FormatCapabilityCache`(`:88-99`)本来就是平坦结构,逐项 RPC 等于每次 `glGetInternalformativ` 一次 round trip |
|
||
| **D3** | CSO 切分是 D3D10 时代的:polygon offset、stencil write mask 在 rasterizer/depth-stencil CSO **内部** | **CSO 边界跟 Vulkan 动态状态走**:depth bias、stencil ref/write mask、line width、min sample shading、blend color、viewport、scissor、depth range 是 `set_*` 而非 CSO 字段 | `RenderState.h:523-528` 记录了共用一个版本号导致 `glViewport` 冲掉 pipeline memo **和** draw 快路径;`m_pipelineStateVersion`(`:529`)恰好是 CSO 相关子集;Magma 的 `DynamicStateShadow`(`VulkanRenderer.cpp:277-340`)与 `ApplyDynamicDrawStateTail`(`:5871`)已经这么切 |
|
||
| **D3b** | 三个独立 CSO:blend / depth_stencil / rasterizer | **一个 `RenderStateCso`,payload 是整块 blob** | D-B1 的全部理由:`is_trivially_copyable_v` 断言(`DirectGLES.cpp:2035`)、三段 memcmp(`:2042-2047`)、**字段顺序承重**(`RenderState.h:359-368`)、两个 backend 都按 span/bulk 消费、拆分需手工维护 ~150 字段划分表且无完整性绊线 |
|
||
| **D4** | `transfer_map`/`transfer_unmap`(scoped) | **`resource_subdata` 推送 + `map_persistent`(永久地址空间捐赠)** | `AcquirePersistentMap`(`BufferObject.h:102-118`)把指针交给**应用**,成为唯一真相源,≥16MiB 自动走到(`:226-228`)。实测 p99 163→21ms、40→115fps。不是 scoped map |
|
||
| **D5** | driver 看得见压缩格式与 pixel-unpack 状态 | **两者在 MGPipe 里都不存在** | 前端在 `glTexImage` 时把压缩 internalformat 解析成非压缩后备(`GL_Texture.cpp:298-306`);`ScopedDefaultUnpackState`(`Managers.cpp:2888-2910`)强制 unpack 默认值,因为 `PixelStoreProcessor` 已经规整过。**只有 PACK 方向过线** |
|
||
| **D6** | 默认 uniform block = `constant_buffer 0` | **独立入口 `set_global_constants(shaderCso, bytes, version)`** | `SpirvArtifacts::globalUboScratch`(`ProgramObject.h:1418`)是 link **phase B** 产出的 CPU 数组,布局由**优化后**的 SPIR-V 决定(`:1400-1408` 说明它无法从 glslang 反射提出来,因为 spirv-opt 就地跑且能删掉整个 block)。它没有 GL name、没有 `BufferObject`、没有 `PipeResource`。`~0u` 跳过回绕的规则保留(`:792`,那是 backend 的"从未上传"哨兵) |
|
||
| **D7** | `pipe_shader_state` = tokens → 完成的 handle | **handle + server 侧惰性特化**,variant 键取自**已推送**状态 | D-B2 的 8 个输入。这其实**就是** gallium:Mesa 的 `st_variant` 也按已绑定状态键控(`st_get_vp_variant`) |
|
||
| **D8** | `pipe_context::flush` + fence 是唯一反向通道 | **`MGPipeCallbacks`**:9 个具名回复/事件(§7) | gallium 没有 shadow writeback、GPU-write 通知、纹理重发请求、default-FB 几何这些词汇。把它们具名化好过藏在 95 个写回点里 |
|
||
| **D9** | `set_viewport_states(start_slot, num)` | **float 数组 + 独立的 `writtenMask`** | viewport 是 **float** 不是 int(`RenderState.h:229-237`:`KHR-GL43.viewport_array.viewport_api` 用 `==` 无容差比较);scissor 必须单独带 `ScissorBoxWrittenMask`(`:363`),因为矩形本身回答不了"应用说过话没有"——`ScissorBoxes` 初值全零而 `glScissor(0,0,0,0)` 是合法 GL、意思是"拒绝每个片元",把空框反转成"接受一切"被 `KHR-GL43.viewport_array.scissor_zero_dimension` 抓到(`:352-362`) |
|
||
|
||
**没有 `pipe_transfer`、没有 `set_pixel_unpack_state`、没有压缩格式概念、renderbuffer 不折进纹理。**
|
||
|
||
### 4.7 覆盖论证
|
||
|
||
#### 4.7.1 对 477 读点分类的逐类映射
|
||
|
||
`../MobileGL-CS/docs/CS_Refactor/backend_read_inventory.md`(`Feat/CS-Delta-IPC` 生成)把 477 个 backend 读点分成 19 个 delta 类、0 UNMAPPED。它的**行**已经过时(`TwinLookupMemo` 族、buffer mutation epoch、`DrawTextureSyncKeys`、`SetupDrawSnapshot`/`VaoDrawMemo`、三条 persistent ring 都晚于它),但它的**分类学是现成的覆盖清单**。逐类映射:
|
||
|
||
| delta 类 | n | 满足它的 MGPipe 调用 | 残余 |
|
||
|---|---|---|---|
|
||
| handle 化(wire 句柄) | 167 | 每个命名对象的调用签名里的 `MGPipeHandle` | — |
|
||
| RenderStateBlob | 99 | `create/bind_render_state` | — |
|
||
| ObjectBind:Texture / Sampler | 33 | `set_sampler_views` + `bind_sampler_states` | — |
|
||
| ObjectBind:Buffer(array/element/indirect/parameter) | 29 | `set_vertex_buffers` / `set_index_buffer` / `set_indirect_buffers` | — |
|
||
| ObjectBind:BufferRange(UBO/SSBO/AC/XFB) | 24 | `set_shader_buffers` / `set_stream_output_targets` | — |
|
||
| FboAttach + DrawBuffers + ReadBuffer | 19 | `set_framebuffer_state` | — |
|
||
| Buffer ops delta | 17 | `resource_create/respecify/destroy`、`resource_subdata`、`buffer_subdata_resident`、`resource_flush_range`、`resource_readback`、`map_persistent` | — |
|
||
| XfbOp | 15 | `set_stream_output_targets` + `*_stream_output` | — |
|
||
| ObjectBind:Image | 14 | `set_shader_images` | — |
|
||
| ObjectBind:VAO | 12 | `bind_vertex_elements_state` + `set_vertex_buffers` + `set_index_buffer` | — |
|
||
| ObjectBind:Program | 10 | `set_draw_program` / `set_dispatch_program` | — |
|
||
| TexParam / SamplerParam | 9 | `create_sampler_view`(base/max level、swizzle、dsMode)+ `create_sampler_state` | — |
|
||
| Texture state(dirty level/rect) | 7 | `resource_subdata` | **归属反转**(§7.3) |
|
||
| PixelStoreBlob | 6 | `set_pixel_pack_state` | unpack **删除** |
|
||
| client-resolved(error queue) | 6 | **删除**:`RecordError` → `on_gl_error` 回调(§7) | — |
|
||
| ProgramPublish | 3 | `create_shader_state` | — |
|
||
| client-resolved(validation) | 3 | **删除**:client 自答 | — |
|
||
| CurrentAttrib | 2 | `set_vertex_attrib_defaults` | — |
|
||
| client-resolved(compile env) | 2 | **删除**:`on_caps_invalidated` | — |
|
||
| Patch 参数 | — | `set_patch_state` | 同时是 variant 输入 |
|
||
| 条件渲染 | — | **client 解析,永不过线** | `Core.h:387-391`:谓词只在 `glBeginConditionalRender` 解析一次 |
|
||
| XFB CPU 计数 | — | **纯 client**;`MGPDrawInfo::xfbCpuCapturedVertices` + `MGPCaps` 的 `kCapCpuXfbPrimitiveAccounting` | — |
|
||
| backend 重铸纪元 | — | **无 client 对应物**:`MGGen`,server 私有 | — |
|
||
|
||
那 1997 个前端 getter 站点不是第二个面:89 个纯版本读**根本不过线**(推送即信号),72 个数据字节读全部落在 §5.7 与 `MGHostSpan`,38 个 `GetLifetimeId()` 变成 handle,其余是 backend 现在自己持有的 POD 记录的字段读。
|
||
|
||
#### 4.7.2 覆盖论证不是这张表,是这条门
|
||
|
||
上表是**声明**,不是证明。证明是机械的:
|
||
|
||
> **接口纯度门。** 在 `MOBILEGL_PIPE_PUSH=all` 构建里,`MG_Backend` 被编译时 `MG_State/GLState/Core.h` **不可用**、`MG_State::pGLContext` **未声明**。任何接口没满足的读都是一次**指名文件与行号的编译错误**。strangler 结束时:`grep -c 'pGLContext' MG_Backend/` == 0(**注意 grep 的是 `pGLContext` 不是 `pGLContext->`**,因为还有 58 行非箭头用法,§2.4);`MG_Backend` 只允许 include 一张共享**值**头白名单(`RenderState.h` 的 `RenderStateParameters`、`SamplerObject.h` 的 `SamplerParameters`、`RenderState.h` 的 `PixelStoreParameters`、`VertexArrayObject.h` 的 `VertexAttribute`、纹理/格式枚举),今天是 **50 行 include / 18 个不同头文件**;`nm --undefined-only libMobileGLServer.so | grep -E 'MG_State::GLState::|glslang'` 为空。
|
||
|
||
**这比生成一张 477 行的清单严格得多:它禁止那次读,而不是给它编目,而且它不会过期。** 那份 inventory 仍然保留,作为 tracker 侧的覆盖检查表(G6 生成,CI `git diff --exit-code`,0 UNMAPPED)。
|
||
|
||
#### 4.7.3 21 条 D 类身份 memo 的重键表
|
||
|
||
| # | 今天的键 | 守什么 | MGPipe | 净效果 |
|
||
|---|---|---|---|---|
|
||
| D1 | `StateBackendObjectRegistry` 用裸 `StateObject*` + 同址 `weak_ptr`(`Managers.h:282-325`)×6 | 分配器地址复用;**也是唯一的删除信号** | 按 slot 索引的数组 + `gen` 比较;显式 `resource_destroy` | GC(1024/64 阈值)**删除** ×6 |
|
||
| D2 | `TwinLookupMemo` ×3 + `OwnerEquals`(`DirectGLES.cpp:62-131`) | 复用堆地址命中 memo 槽 | **删除**——数组下标**就是**查表 | ~75 行 + 140KiB |
|
||
| D3 | `UnitTextureSyncEntry` + `PairingsIntact`(`:1441-1481`) | 不移动任何计数器的 slot 交换(DSA by-name) | **结构性删除**:`set_sampler_views` 就是信号 | ~115 行 |
|
||
| D4 | `IsBufferDrawClean` 身份优先比较(`Managers.cpp:1436`) | respecify 交给前端一个**新**资源 | server 拥有资源表;`gen` 比较;`GetChangeSerial()`(`Uint64`,不回绕)继续过线 | 简化 |
|
||
| D5 | `ResolvedDrawBuffers::iboFrontend`(`Managers.h:711-716`) | 索引 slot 重绑而无 epoch/config 移动 | `set_index_buffer` 是独立调用 | 结构性 |
|
||
| D6 | `m_syncedIndexBufferObject` 陪一个回绕 `Uint16`(`:775-780`) | 版本回绕后换了个 buffer | `{slot, gen}` 比较,不回绕 | 结构性 |
|
||
| D7 | `StampSyncedFBO` 四元组(`DirectGLES.cpp:1856-1901`);`packed_pixels` postmortem `:2815-2827` | 版本回绕 + backend 侧纹理重铸 | `MGPFramebufferState::contentHash` + server 私有 `attachmentRemintEpoch`(`MGGen`) | 一次 64 位比较 |
|
||
| D8 | `g_fboTextureSyncList`(`:1580-1601`) | 同 D3,针对 attachment | **删除**:`set_framebuffer_state` 是信号 | — |
|
||
| D9 | `ResolvedTextureBindingMemo`:9 个键 + 驱动绑定影子的 `memcmp`(`:3218-3291`) | 任何未枚举的写者扰动某个 unit | `(shaderCso.slot, viewSetSerial)` 两字比较;`viewSetSerial` 由 server 在 `set_sampler_views` **内部** ++ | 更便宜;自校验性质保留(server 的绑定缓存**就是**它当年比对的那份影子) |
|
||
| D10 | `UnitSamplerLookupMemo` 的 `WeakPtr` owner 测试(`:3105-3125`) | 死 sampler 复活 | 数组下标 | 删除 |
|
||
| D11 | `VertexInputStateFactory::ComputeHash` 混入 `GetLifetimeId()`(`:38-49`) | 复用 buffer 地址重现整个 content hash(XFB 捕获拿回死 VAO 的顶点数据) | CSO handle **就是**身份;`gen` **混进** server 侧每个 content hash 而不只是比较 | 删除一整类 |
|
||
| D12 | `SetBackendStateMemo(&entry, evictionEpoch)`:**前端 VAO 里存后端堆裸指针**(`VertexInputStateFactory.cpp:78`) | table 淘汰 | **直接删除,不翻译**。无 wire 对应物,也不需要;Magma 自己的 `VaoDrawMemo` 已经演示了替代品 | — |
|
||
| D13 | `VaoDrawMemo` 槽(`VulkanRenderer.h:1230-1245`,注释:"两条声明的防线都失守,因为都归约到 content hash,而 content hash 的 buffer 身份分量本身就是复用的堆地址") | ABA | CSO handle | 2 字 |
|
||
| D14 | `SetupDrawSnapshot` 的三组 `(ptr, lifetimeId, version)` + **有损的** `sampledContentSum`/`sampledParamsSum`(`.cpp:6249-6250`) | 一切 | 三个 handle + 两个 server 纪元 + dirty mask | ~14 个探测字段 → 1 次比较;**顺带消灭一类哈希碰撞**(求和会碰撞) |
|
||
| D15 | `m_rpFast*`(`VkRenderPassManager.h:305-320`) | ABA | `contentHash` + `MGGen` | 1 次比较 |
|
||
| D16 | `VkTextureManager::TextureIdentity{ptr, lifetimeId}` + `pGLContext->GetTextureObject(name)` 存活探测(`VkTextureManager.cpp:806-819`) | 名字复用 / 删了但仍被 FBO 引用 / 默认纹理 | `{slot, gen}` + 显式 destroy | 三种被记录的失效模式**一起消失** |
|
||
| D17 | `VkClearManager::TextureIdentity`(`VkClearManager.h:76-83`) | ABA | `{slot, gen}` | — |
|
||
| D18 | 纹理/renderbuffer 资源用**节点式** `std::unordered_map`(postmortem `VkRenderPassManager.h:375-397`) | 扩表搬迁使缓存的 `Resource*` 失效(`BlitFramebuffer` 静默停在 "layout undefined") | **UNCHANGED。** 接口对此零约束;这是 server 内部分配纪律。slot 数组在插入下稳定,实际更好——但**这条 postmortem 注释必须逐字带进 review checklist** | 保留 |
|
||
| D19 | `ProgramFactory::m_cacheStructureEpoch` | 守 `SetupDrawSnapshot` 里的 server 内部裸指针 | **UNCHANGED**(`MGGen` 族) | 保留 |
|
||
| D20 | `ConvertedVertexStreamKey` + **纯为防地址复用**持有的 `SharedPtr sourcePin`(`VulkanRenderer.h:1124-1127`) | ABA | server 拥有资源;`changeSerial` 过线 | **pin 删除** |
|
||
| D21 | `m_xfbCounterSlotByObject[GetBoundTransformFeedbackName()]`(`VulkanRenderer.cpp:11136-11146`) | **什么都没守——这是一个活的潜伏 bug**:删了又重建的 XFB 对象会继承前任的计数槽,配上匹配的 `m_xfbLastSeenGeneration` 会**恢复**一次本该重启的捕获 | XFB 对象 handle | **顺带修一个 bug**。这一条值得先独立落到 `dev` |
|
||
|
||
**总计:12 条直接删除,8 条重键成比它替代的东西更便宜的比较,1 条(D18)原样不动。** 这就是"身份键 memo 不可移植"这条历史反对意见的具体回答——它们不是**以指针形式**可移植,接口的职责正是给它们一个可移植的键,而树里的证据已经把那个键是什么讲清楚了。
|