Files
MobileGL/docs/Disaggregated/ARCHITECTURE.md
T
swung0x48 43b5cfa94e [Docs] (Disaggregated): the four index documents keep only summaries, and every phase's detail lives in its own notes/<phase>/README.md
- ROADMAP, MEASUREMENTS and ARCHITECTURE are rewritten as summaries (333 KB of index text -> 84 KB).
  ROADMAP's phase table is one line per phase plus a link; MEASUREMENTS keeps headline numbers under
  its old §1-§13 numbering (plus §14-§16 for P6.5, P7 and P12); ARCHITECTURE keeps every decided rule
  under the same §1-§17 / appendix numbering, so the `ARCHITECTURE.md §N` references in code still
  land, and §17 becomes an index of the phase landing shapes.
- CURRENT_STAGE_PROGRESS now describes the current stage (the P12 on-screen-server subset) and absorbs
  CURRENT_STAGE_STATUS, which is removed. The P7 status tables, the P6 closing snapshot and the P6.5
  first-wave snapshot move to notes/p7, notes/p6 and notes/p65.
- Each phase gets notes/<phase>/README.md: a short summary, then the verbatim material that left
  the index documents (its ROADMAP row, its MEASUREMENTS section with subsection numbers kept, its
  ARCHITECTURE §17 part, milestones) and a file index. P8-P11 and P13 get their planned scope;
  the full open-question list moves to notes/OPEN-QUESTIONS.md.
- The phase reports that sat at the top level move into their phase folders with unchanged names:
  P5D-INPROC-PERFORMANCE, P5E-RUNAHEAD, P5F-WIRE-COMPLETENESS, P6-SPAWN-PLAN, P6-CONTRACT-DRAFT,
  P6-ENDSTATE-REVIEW. Their relative links, CONTRACT-P6.md's links to them and one tool docstring
  are updated; notes/README.md maps every old path and section number to its new place.
- 71 files under notes/recovered/ that were byte-identical copies of phase-folder files are removed.
- Corrections made while condensing: the server process reads Transport as Spawn (CONTRACT-P6 §3.1
  amended the old "child forces monolith" rule); both backends now publish kCapRunAheadApply;
  MOBILEGL_IPC_RESPAWN is a named refusal and MOBILEGL_IPC_IDLE_EXIT_S is not parsed; the Redmi
  pin row is in tools/device_bench/pin_device.sh.

check_doc_citations over docs/Disaggregated/*.md: 0 problems. No relative link is newly broken.
2026-09-24 22:21:09 +00:00

459 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MGPipe 设计与架构
> 只写**已决定**的设计,每条附一句理由。章节编号稳定(代码注释按 `ARCHITECTURE.md §N` 引用它们)。阶段号与状态见 [`ROADMAP.md`](ROADMAP.md),实测见 [`MEASUREMENTS.md`](MEASUREMENTS.md);各阶段的落地细节、偏差与当时的论证在 `notes/<阶段>/README.md`(§17 是索引)。wire 契约原文在 `MobileGL/MG_Remote/CONTRACT-*.md`,与本文冲突时以更新的契约为准。
## 1. 边界
### 1.1 一句话
`MG_Backend` 本来就是一台贴着目标 API 的状态机(Espryt = DirectGLES,Magma = DirectVulkan),缺的是一份"我被告知了什么"的显式声明。MGPipe 就是这份声明:前端在每条 verb 之前把变化**推**过去,后端不再拉 `MG_State::pGLContext`。接口从两个后端自己维护的关键结构反推(`SetupDrawSnapshot`、`DrawTextureSyncKeys`、`ResolvedVertexBindings`、`g_syncedRenderStateParameters`、`BufferBackendOps` …);gallium 是词汇的目的地,不是推导前提。
### 1.2 两张函数指针表
`MGPipeScreen`(share-group 作用域:caps、resource、persistent map、fence)与 `MGPipeContext`(其余全部),由 `PipeCalls.def` 生成。
- 函数指针 struct 而非虚基类:null 项已表示"未实现,前端回退",`MG_Test` 整表替换即可 mock。
- 两张表从第一天分开,事后拆会给记录重新编号;v1 只有一个 screen、一个 context、一条 flow。
- EGL 生命周期与 caps 面留在 `pActiveBackendObject` 的虚函数上(罕见路径,走控制面)。
### 1.3 三种形态,一份后端
| 形态 | 表里装的是什么 | 用途 |
|---|---|---|
| `monolith`(默认) | 后端自己的函数,回调直调 `MG_State` | 出货 |
| `inproc` | 发射器 → 同进程第二个线程上的 applier | CI 主验收;也就是 monolith 的**渲染线程** |
| `spawn` | 发射器 → 数据面 → 另一个进程的 applier → 同一批后端函数 | 两进程形态;控制面可以是 `fork` / `unix:` / `tcp://`(§11.9),server 可在另一台机器上 |
唯一 hook 点是 `MG_Backend::Init()` 里一个 `#if MOBILEGL_BUILD_DISAGGREGATED` 分支:`MG_Config::Transport != Monolith` 时装 `MG_Remote::BackendObject_Remote`。`MG_Impl` 的边界调用点零 `#ifdef`。
## 2. 对象模型
### 2.1 句柄 = `{slot, gen}`(`MG_Pipe/MGPipeHandles.h`)
- 8 字节 POD;**client 铸造,server 永不返回句柄** → 整份目录零创建 round trip(对 gallium 的偏离 D1)。
- slot 按 kind 稠密分配(free list + 高水位),server 对象表是数组;`gen` 只在 slot 复用时 ++。
- kind:`Buffer, Texture, Renderbuffer, Framebuffer, Xfb, RenderStateCso, VertexElementsCso, SamplerCso, SamplerViewCso, ShaderCso, Fence, Query, Context`。保留:`{0,0}` = null;`Framebuffer {0,1}` = 默认帧缓冲;`ShaderCso` 高段给 program pipeline 合成体。
- GL name 只作诊断(`GlNameForDiag`),永不做身份、memo 键或 content hash。
### 2.2 两种世代,严格分开
| | 拥有者 | 回答 | 过线 |
|---|---|---|---|
| `MGPipeHandle::Gen` | client | "还是同一个 GL 对象吗?" | 是 |
| `MGGen`(后端纪元,如 `g_bufferMutationEpoch`) | server | "我自己是否重铸了驱动对象?" | **永不**(只以纹理拉取请求的形式出现,§8.4) |
client 的回绕 `Uint16` 版本计数器永远不是新鲜度的唯一证明:过线时要么加宽,要么与 `{slot, gen}` 同行。
### 2.3 CSO 与可变对象
- CSO(render state、vertex elements、sampler、sampler view、shader)在 **client 侧内容寻址**(Mesa `cso_cache` 先例),LRU 淘汰时发 `delete_*`;render-state 容量 64(P13 重调)、sampler state 256。
- Buffer / Texture / Renderbuffer 走 create / respecify / subdata / destroy;Framebuffer / Xfb 是 per-context 身份 + `set_*` payload。
- 两条偏离:**D-G1** Espryt 的 vertex-elements 按 VAO 身份寻址(twin 持驱动 VAO 名,不能共享);**D-F2** Espryt 的 sampler view 按纹理身份寻址,sampler state 内容寻址且带引用计数(被 `BuiltinSampler` 指着的项不许被 LRU 挤掉)。
## 3. 调用目录
### 3.1 单一真相源
`MobileGL/MG_Pipe/PipeCalls.def`:一行一个 `X(Name, Payload, Class, Flags, WaitClass)`。**线上 opcode 就是行序**,只能追加、退役保留槽位;行数由 `MGP_CALL_LIST_DOCUMENTED_COUNT`(今天 81)与 `PipeCatalogueTest` 钉住。生成器产物提交进树,CI `pipe-gates` 重生成并 `git diff --exit-code`:
| | 产物 | 内容 |
|---|---|---|
| G1 | `PipeTables.inc` | 两张函数指针表 |
| G2 | `PipeThunks.inc` | monolith 直调 thunk |
| G3 | `PipeWire.inc` | wire 记录、尺寸 `static_assert`、applier 边界检查 |
| G4 | `PipeVerify.inc` | `MOBILEGL_PIPE_VERIFY` 逐字段比对器(`PipeFields.def`) |
| G5 | `PipeFilled.inc` | `PipeInputs` 字段 id 与逐 verb 世代 poison |
| G6 | `PipeCoverage.inc` | 后端读点 → 调用映射(`Coverage.def`),0 UNMAPPED 是门 |
| G7 | `PipeSpanTable.inc` | render-state pipeline 子集成员表 |
| G8 | `PipeFieldOwnership.inc` | 每个 `PipeInputs` 字段恰属 `RECORD-SUPPLIED / APPLIER-DERIVED / BARRIER-PULLED / FATAL` 之一(P5f 起 0 BARRIER-PULLED) |
另有 `FillPoints.def`(填充点)与 `DirtySurface.def`(§5.2)。G1 codegen 规则:capability gate 替换指针表达式时保留**表达式形状**,否则 pull `.text` 漂移。
### 3.2 分组与 flag
- Class:`kScreen`(caps、resource、persistent map、fence、`ApplierReset`)、`kCtxQuery`、`kCtxCso`(create/bind/delete × 五种 CSO)、`kCtxState`(`Set*` 状态、`SetContextValues`、`SetProgramBindings`、迁移期的 `SetResidualValueState`)、`kCtxObject`(subdata、readback、mip、`ObjectDeath` …)、`kCtxVerb`(blit、clear、`DrawVbo`、dispatch、XFB、`Present` …)。
- Flags:`kNeedsAck`、`kHasBlob`、`kVarTail`、`kHostSpan`、`kReplySlot`、`kOptional`。第五列 `WaitClass`(P5e)决定 run-ahead 下 client 等不等(§11.6)。
- 20 个 draw 入口塌成 `DrawVbo` 一条;`Clear` 一条判别式;sampler 集合**没有 stage 维度**(192 单元合并空间);`SetTextureParams` 按资源寻址、与 sampler view 分开(D10);`SetIndexBuffer` 独立于 VAO 配置(D5)。
- 显式不移植:`GetIntegeri_v` / `GetInteger64i_v` / `GetProgramiv`、`set_pixel_unpack_state`(前端已解析)、压缩格式概念、`pipe_transfer`。
### 3.3 能力位(`MGPCapBit`)
`CallMask` 取代"槽位是否为 null"这一隐式能力探测;`MGPCaps` = `DynamicBackendParameters` + `CallMask` + 格式能力表与 renderer 字符串两个 blob,握手后快照、`MakeCurrent` / `InitCapabilities` 后按 generation 重发布。位包括 viewport array、fp64 顶点、`kCapResidentSubData`、XFB / query 家族、`kCapNeedsHostIndexBytes`、`kCapNeedsHostUboBytes`、`kCapBackendOwnsXfbCapture`、`kCapRunAheadApply`(`MGPipeRunAheadCapBitsFor`:Espryt 自 P5e、Magma 自 P5f 之后的 Magma run-ahead 起发布,后者契约 `MG_Remote/CONTRACT-MAGMA-RUNAHEAD.md`)。**不存在"归属开关"(D-B7)**:multi-draw 分档与 restart 重写永远由 server 拥有,client 只在 caps 要求时提供索引字节。
## 4. 记录与 payload 约定(`MG_Pipe/MGPipeTypes.h`)
- 每个 payload 是平坦 POD、显式 padding、`static_assert` 精确尺寸;**永不含指针**。
- `MGPBlobRef{Offset, Size, Seg}` 指向 blob 区;`MGHostSpan` 是唯一随传输而变的形状(monolith 下是指针,split 下字节在 `SEG_STAGE`),只进变长尾。
- wire 记录头 `MGPWireRecHeader{Op, Flags, Size}`(8 B);**没有逐记录序号**,seq 就是记录序数。
- 单条记录上界 = ring 一半,**记录本身永不分块**(R-10);**内容侧分块**:会超出 `SEG_STAGE` 的 blob 按 `MGPipeStageChunkBytes()`(默认 segment/4 = 8 MiB)切成多条记录(buffer 范围走查、纹理整宽 slab,server 拼回整级)。
### 4.1 关键 payload
| payload | 要点 |
|---|---|
| `MGPResourceDesc` | buffer / 纹理 / renderbuffer 一个判别式 create/respecify 形状;`ImageBindableHint`、`ViewOf`(视图的存储属主)、`HasDefinedContent` |
| `MGPRenderStateDesc` / `MGPBindRenderState` / `MGPDynamicState` | §5.3 |
| `MGPVertexElements` | blob 同时带解析后的属性与绑定点视图(记录自洽) |
| `MGPSamplerView` / `MGPTextureParams` | view 只带视图限制;纹理参数挂纹理对象,`BuiltinSampler` 句柄不许为空(D-E1) |
| `MGPProgramDesc` | 逐 stage SPIR-V + 反射归档(§7) |
| `MGPFramebufferState` | 8 color + depth + stencil + client 解析后的 read surface;`ContentHash` 同时是 render-pass memo 键与发射抑制器;`Target` 含 `Named = 3`,applier 按 framebuffer 句柄存 |
| `MGPSubData` / `MGPSubRegion` | §6 |
| `MGPDrawInfo` / `MGPDrawRange` | `DrawVbo`;multi-draw 范围与用户索引在变长尾,indirect 的 `DrawCount` 由 client 解析 |
每条 `kVarTail` 的 `set_*` 带 `ContentHash`,hash 未变就不发(§5.4)。
## 5. 前端 state tracker(`MG_Impl/Pipe/Tracker`)
### 5.1 推送发生在 verb 之前的 validate 时刻,不在 GL setter 里
per-setter 推送会把 Blaze3D 每个 batch 的冗余 `glEnable/glDisable` 变成接口调用,严格慢于今天;正确形态是 gallium `st_validate_state`。落地为唯一的 `MGPipeValidateForVerb(MGPipeVerb)`(九个 validate 类,`FillPoints.def`),恰在每次经表调用之前、每个提前返回之后。只有今天就在 GL 调用时刻分发的资源 op(`BufferBackendOps` 的七个 hook)在调用时刻推送。
### 5.2 dirty 位:值类零新增记账,对象类 5 个聚合世代
- 值类组(render state、pixel pack、patch、attrib defaults、shader bindings …)直接复用既有版本计数器作快门。
- 对象类组(vertex buffers、framebuffer、sampler views / samplers / images、const / shader buffers、SO targets)靠 5 个**聚合世代**把快门降成一次 `Uint64` 比较——没有聚合就回答不了"有没有哪张已绑定纹理动了"。
- 完整性由 `scripts/gen_pipe_dirty_surface.py` 保证(每个 mutator → 必须 bump 的世代,`--check` + `--self-test` 进 CI)。**快门看不见自己的主体**是踩过三次的坑:新增字段必须带"记录字段 → setter → 快门"一行,优先混入已有世代(新计数器会撑大 pull 对象,G1 不允许)。
### 5.3 渲染状态:整块 blob 过线,身份只取 pipeline 子集(D-B1)
```
create_render_state(cso, pipelineSubsetChunks) // 只带 pipeline 子集
bind_render_state(cso, version, pipelineVersion) // 稳态 12 B
set_dynamic_state(dynamicChunks, version) // 只带变化的动态 chunk
```
- `RenderStateParameters`(1168 B)整块过线:Espryt 做三段 memcmp,字段顺序承重;拆成多个 CSO 要手工维护 ~150 字段划分且无绊线。
- 身份只取 pipeline 子集,否则 `glViewport` / `glScissor` 每次都铸新 CSO、冲掉 server 的 pipeline memo。划分只写在一处:`MGPipeRenderStateSpans` 的 chunk 表(7 pipeline chunk 396 B + 8 dynamic chunk 772 B,`static_assert` 完整性),G7 断言"子集 hash 变 ⟺ `m_pipelineStateVersion` 变"。
- Espryt 的 `SyncRenderState` 一行不动;Magma 的 pipeline memo 键是 `cso.slot`。
### 5.4 验证不变式、合并与抑制器
规范(D-B3):**一条 verb 的全部 `set_*` / `bind_*` 在该 verb 之前完成;server 在 verb 处从已推送状态惰性特化 shader 与 pipeline。**除"资源 create 先于对它的 bind"外,`set_*` 之间无顺序要求。合并:整块优于逐字段;高水位直接是 `count` 实参;只发 program 解析过的集合;**集合 hash 抑制器**在 client 侧去抖(原先在后端的 ~175 行)。
### 5.5 sampler view 在 client 侧解析
shader 看见哪个纹理取决于 sampler uniform 类型、mipmap 完备性与未定义默认纹理;解析留在 client(带 memo),gallium "每槽一个 view" 就是解析后的形态。两处后端特定后处理留在 server:Espryt 的 raw-depth-fetch sampler 替换、Magma 的 feedback-loop 检测。
### 5.6 生命周期、共享组、composite program
- `resource_create` 在前端对象构造时发,存储由 `resource_respecify` 惰性定义,`resource_destroy` 在析构时发;死亡转发给每个 emitter(只释放 slot 曾导致 UAF)。view 先于存储属主销毁、FBO attachment 钉住纹理、buffer texture 钉住 buffer。
- program pipeline 合成体(`CompositeResolver`)完全在前端合成、推一个句柄;释放恰好一次(resolver 换掉或 `~ProgramObject`,D-H7);记忆按 `(ContextId, 管线 GL 名)` 键控。
### 5.7 emulation 的归属
规则:**驱动表达不了的变换在 tracker 里 lowering,硬件 / 驱动强加的变换在 driver 里 lowering。**
| 归 client(读前端字节的纯 CPU 变换) | 归 server |
|---|---|
| client 顶点 / 索引数组(字节过线,永不是指针)、最大索引扫描、`*IndirectCount` 计数解析、CopyImage shadow 镜像、XFB CPU 图元计数、压缩纹理 / unpack 规整 | primitive-restart 重写、multi-draw 分档与展平(从索引宿主镜像读,§10.3)、viewport-array 回放、fp64 顶点窄化、image-bindable 存储加宽、生成 mipmap(client 分配 level 存储) |
**陈旧索引纪律是逐站点表**:client 侧扫描前的 reconcile 必须逐字复现 monolith 的集合(最大索引扫描 = `SyncPersistentMappedRange()` + `SyncGpuWrites()`;`*IndirectCount` 只前者)。
## 6. 纹理 subdata 与 dirty 归属
- 纹理上传由 Espryt 在 sync 时刻按**累积**区域做(Mali 按作业数计价,~100 个 rect 对一个 union box 实测 +6 ms/帧),逐 `glTexSubImage` 发记录会复现这个悬崖。
- 因此 client 在自己的 rect 模型里累积,在下一个 validate / flush 点发**一条** `ResourceSubData`,同时携带 union box 与 region 列表,**由 server 选上传形状**(决策留在付 GPU 代价的一侧)。步长显式携带,不再靠指针比较。
- **dirty 归属反转**:client 维护发射游标、发射后清自己的标志;清 dirty 要等 applier 的 acceptance(被拒的 level 留在脏表里);server 自己的重新变脏是 server 的事。
- `TextureUploadShapeScenario` 把上传形状录成金标(P3b/P4b D2 起是门;Mali 帧时增量无设备,按 Adreno 记录)。
## 7. Shader state = SPIR-V + 反射归档
- `CreateShaderState` 过线的是逐 stage SPIR-V + 反射归档,**不是源码**:glslang 全在 client、SPIRV-Cross 全在 server,文件级切割;server 侧无编译池。归档序列化器 `ProgramArtifactsCodec`,monolith 只在 verify 构建里调它(D-H3)。
- server 在 verb 时刻从已推送状态**惰性特化**(D-B2:draw FBO clamp mask、fragColor 广播数、storage-block 绑定签名、image 格式、patch 参数等)——正是两个后端原来的做法。
- link / compile 失败不需要同步返回:`GL_LINK_STATUS` 由 client 从 `ProgramObject` 回答,后端失败以 `OnLog` ≥ERROR 无损呈现。
- 内部 shader(颜色 blit、深度 mip、multisample resolve)烘焙成签进树的 SPIR-V(`Wire*Spirv.h`),新鲜度门 `MOBILEGL_BAKED_INTERNAL_SHADERS`(`MG_Test/SelfTest/BakedInternalShadersTest.cpp`)重跑树内 glslang 逐字节比对(P7)。
## 8. 反向通道
### 8.1 `MGPipeCallbacks`(`MG_Pipe/MGPipeCallbacks.h`)
九个具名回调 + 一个正向终止符(`ResourceSubDataComplete`),取代后端直接 poke 前端对象的 95 个调用点(具名化是有意偏离 D8)。monolith 下直调,split 下是 `SEG_EVENT` 上的记录。
| 回调 | 作用 |
|---|---|
| `OnGlError` | 延迟的 GL 错误;**必须对命令流有序** |
| `OnGpuWritten` | GPU 写过的 buffer 范围(收窄 client 保守自建的 pending 集) |
| `OnBufferWriteback` | PBO 回读、XFB 捕获结果;必须与 epoch bump 有序 |
| `OnTextureWriteback` | CPU 回退生成 mip 的纹素 |
| `OnTexturePullRequest` | §8.4 |
| `OnMipLevelsGenerated` | 只带形状 |
| `OnSurfaceChanged` | 默认帧缓冲的格式与尺寸(server 拥有显示时 client 靠它得知窗口尺寸) |
| `OnCapsInvalidated` | 取代 `InvalidateCompileEnv` |
| `OnLog` | ≤WARN 有损,≥ERROR 无损 + 速率限制 |
后端凭空造的前端对象(Magma 占位纹理、swapchain 默认 FB 占位)改为 server 原生;pull 构建里的 `m_backend` 类成员真删会动 `sizeof`(G1),随 P13 退役(D-K)。
### 8.2 有序性是正确性要求
每次 writeback 之后的 epoch bump 必须在任何后续读该 handle 的命令之前被 server 应用。**反向通道需要与正向通道相同的有序保证。**
### 8.3 错误、ack 与日志
- **唯一允许同步 ack 的入口是 `glBufferStorage`**:`ResourceRespecify` 的 `kNeedsAck` 由逐记录谓词收窄到"不可变 **buffer**"(D-A2),纹理 OOM 在 monolith 里本来就推迟到 sync 时刻。respecify 带逐 level 作用域与"只换元数据"的形式,不误丢已接受的待上传。
- 其余错误一律晚到,走有序的 `OnGlError`。
### 8.4 唯一的新停顿类:server 发起的纹理重铸拉取(D-B6)
server 不保留纹素;image-bindable 重铸、整格式再生、view 源重铸会要求重发 level。缓解:`ImageBindableHint` 预防主因;拉取异步(阻塞 apply 线程,不阻塞应用线程);保留 LRU 默认关(`MOBILEGL_PIPE_TEXEL_RETAIN_MB=0`);显式终止符可带零个 region。真实语料上发生率可忽略(780 个统计窗口 2 次);split 下这条路径今天仍是具名 `Fatal{UnmigratedEmulation, "texture-remint-pull"}`,归 P9。
### 8.5 XFB scatter 留在 server
补丁循环需要"捕获前的字节",而拆分后权威影子归 server(`StagedShadow`),所以 scatter 在 server 原地跑完;回程只有一条 `OnBufferWriteback`,载补好的整段,按 `SEG_EVENT` 容量的四分之一切片(`MGPipeBufferWritebackSliceBytes`),最后一片落地即蕴含之前每一片。孤儿目标(`HasDefinedContent == 0`)零填充后照常散射。`OnXfbScatterReady` 已删除(回调 10 → 9)。落地论证见 [`notes/p34b/README.md`](notes/p34b/README.md)(espryt D1)。
## 9. 后端状态机改造
### 9.1 原样不动的东西
Espryt 的 persistent ring、buffer pool、fallback-repack、scratch FBO、驱动绑定影子、Adreno / Mali workaround、SPIRV-Cross 会话、restart 重写与 multi-draw 分档;Magma 的 `VulkanRenderer` memo、`PipelineFactory`、`ProgramFactory`、`UniformManager`、五个 `Vk*Manager`、`SwapchainObject`、D18 的节点式容器纪律。"输入变了、算法不许动"的那一类由 **G5 字节一致门**把关(`scripts/p3a_untouched_regions.sh` 十一函数、`scripts/p4a_untouched_regions.sh` 17 区),re-pin 必须两份同改。
### 9.2 strangler 脚手架:`PipeInputs` + 逐 verb 填充 + poison 世代(P1)
```cpp
// MG_Backend/MGPipe/PipeInputs.h —— 按 memo 键组织,访问器类型与后端原来读到的完全一致
#if MOBILEGL_PIPE_PUSH
# define MGB_CTX (&::MobileGL::MG_Pipe::gPipeInputs)
#else
# define MGB_CTX (::MG_State::pGLContext)
#endif
```
三步:A 别名(P1,机械替换 + 逐 verb 类填充点,`nm` 不变)→ B 推送(P2,tracker 填 `gPipeInputs`,verify 构建逐 draw 比对快照版)→ C 句柄化(P3a–P4a、P7,`SharedPtr<前端对象>` → 句柄 + 描述符,写回变回调)。poison 是**逐 verb 世代**:读一个当前 verb 未填的字段是 `Fatal{UnmigratedPipeInput, "<Getter>@<Verb>"}`。
### 9.3 Track V / Track H
Track V(值类型:render state、pixel store、capability 位、标量)是机械迁移;Track H(167 个 `SharedPtr<MG_State…>` 读点)是真活。74% 的读点是翻译输入,所以"bump 一个版本让 server 自己拉"行不通,值本身必须过去。
### 9.4 残余值块
迁移期临时调用 `SetResidualValueState`(`ResidualValueBlock`):尺寸只降不升(1248 → 8,只剩 `CapabilityBits`),P13 变成 `static_assert(sizeof == 0)`;布局逐成员 `offsetof` 断言;stats 单独计字节。
### 9.5 身份 memo 的重键
`{slot, gen}` + 显式 destroy 让 21 条身份 memo 中 **11 条直接删除**、2 条的去抖搬到 client(§5.4)、7 条重键成更便宜的比较、1 条(D18)不动。统一事实:进 memo 键的版本计数器要么会回绕,要么根本不被它害怕的 mutation bump,身份比较是堵洞的补丁。
### 9.6 A/B 与口径收窄
`MOBILEGL_PIPE_PUSH` 子系统位图在阶段 B 是真 A/B;阶段 C 之后位清零时后端仍跑重键后的代码,所以**编译期** `MOBILEGL_PIPE_LEGACY_MEMOS`(默认 ON)保留 pre-handle 臂,随 pull 路径在 P13 退役。位依赖两侧都拒(客户端族门里**根本不发射**,否则会"客户端已清 dirty、服务端却走旧臂"而丢上传);没有消费者的后端一条不发。退役的 twin 成员仍在 pull 构建里编译——真删会动 G1。
## 10. server 侧
### 10.1 对象表与 applier
`MG_Remote/Server/PipeApplier`:解码 → 更新对象表与 `PipeInputs` → 调后端。server 不持有 buffer 的完整副本(只有 staged shadow)、不持有前端对象图;任何传输下都不得有 `SharedPtr` 或裸前端指针跨过 applier 边界(P5c 角色守卫、P5e 规则 F)。`InProcessTransport` 与 spawn 走完全相同的编解码路径。
### 10.2 monolith 侧的净收益
即使 IPC 永不上线:复用地址 ABA 一整类不可表达;FBO → program 排序 hazard 消失;`SwapchainObject` 写 `MG_Impl` 的分层倒置消失;`inproc` = 渲染线程。monolith 净代码量是增加的,论据只看逐线程 CPU。
### 10.3 索引宿主镜像(`Server/IndexHostMirror`,P8,待重裁)
设计:`kCapNeedsHostIndexBytes` 下,server 从本来就要收的 `ResourceCreate/Respecify/SubData` 流增量维护 element-array buffer 的宿主镜像,零额外流量;预算 `MOBILEGL_PIPE_INDEX_MIRROR_MB`(默认 64),超预算退化为逐 draw 经 `MGHostSpan` 传送。**P6 的 a6 审计实测宿主索引 span 无 producer、client 索引已是 owned buffer**,所以 P8 要按真实消费者重新裁定,不照抄本节([`notes/p8/README.md`](notes/p8/README.md))。
## 11. 传输与数据面(`MobileGL/MG_Remote/`)
### 11.1 段
| 段 | 拥有者 | 默认 | 内容 |
|---|---|---|---|
| `SEG_CMD` | client(server 只读) | 8 MiB | `RingControl` 页 + POD 记录 + 小内联负载 |
| `SEG_STAGE` | client | 32 MiB | bulk 字节:buffer / 纹理 subdata、UBO、client 数组、persistent-map 脏块 |
| `SEG_REPLY` | server | 16 MiB,8 × 2 MiB slot | readback 像素、acceptance 答案(ID-47) |
| `SEG_EVENT` | server | 256 KiB SPSC ring | 回调事件 |
| `SEG_SHADOW` / `SEG_ADOPT` | client / server | — | 零拷贝 shadow(Phase 2)/ ≥16 MiB 采纳(P11);stream 数据面上具名拒绝 |
段在同机上由 `ShmSegment` 创建(Android `ASharedMemory`、Linux `memfd`、Windows `CreateFileMappingW`),描述符经 `SCM_RIGHTS` 传递;stream 数据面上两端各持一份同尺寸的私有段(§11.9)。
### 11.2 `RingControl`(`Ring.h`)
一页 4 KiB,每个争用组一条 cache line,按写者分组(P6 lk):producer 线 `cmdHead` / `submittedSeq`;consumer 线两个 tail;四个 consumer 写的水位收成 `Progress`(`appliedSeq` / `retiredSeq` / `completedFrameSerial` / `presentAckSerial`);最后是 epoch、generation、park 标志与 `eventRingFull` / `eventDropped`。游标是单调字节计数、2 的幂掩码、永不重置;pad 记录处理 wrap;不可能的头 → `Fatal{ProtocolCorruption}`。
### 11.3 双向 doorbell(`Doorbell.h`)
两个方向都是"先自旋、再置 parked、再阻塞",发布方仅当对端 parked 时敲;丢失唤醒由两个 `seq_cst` fence 关闭。`CondVarDoorbell`(同进程,带 `Kill()`)与 `SocketDoorbell`(跨进程,对端关闭即死亡检测)。stream 数据面上门铃是本地条件变量,由读线程敲。
### 11.4 控制面(`protocol.fbs`、`Framing.h`、`ITransport.h`)
- 一份 FlatBuffers schema:热路径是 `struct` 直接进 ring(与 POD 逐条 `static_assert` 对齐);罕见 / 变长 / 需演进的走控制连接的 `CtrlMsg`(`Hello`、`Welcome`、`Refuse`、`CapsSnapshot`、`SurfaceOp/SurfaceReply`、`SessionFault`、`LogLine` …),union tag 只追加,控制修订号钉在 `sha256(protocol.fbs)` 上。
- 生成物提交进树,CI `flatc-check` 重生成并 diff;codegen 不进默认构建图。
- 封帧 `[u32 'MGLF'][u32 len][payload]`,64 MiB 上限,读时校验,坏帧立即闩住失败。
### 11.5 WAR 危害、拷贝账与背压
Phase 1(今天):GL 调用时刻把字节拷进 stage slot,slot 到 apply 越过它为止不可变,危害按构造消除,代价一次 memcpy;Phase 2(`SEG_SHADOW` 零拷贝)未做。server 没有第二份 `BufferObject`,不存在中间拷贝。分配失败升级:扩容 → 对最老未退休批次有界等待 → 硬 drain + generation bump(之后 tracker 全部重推)。
### 11.6 publish、序号与 credit
- 每条记录 release-store `cmdHead`,仅当 consumer parked 时敲门铃;显式门铃点:`present`、`kNeedsAck`、`eglMakeCurrent`、`glFlush`、stage 余量不足、轮询类入口。
- seq = 记录序数;字节 credit 与 present credit 两个独立窗口。
- **等待规则由 `WaitClass` 列决定(P5e)**:`kWaitReply` / `kWaitPresent` / `kWaitApplied` / `kWaitNone`;run-ahead 武装时 `kWaitNone` 发布即返回。barriered 谓词(`MGPipeBarriered`)client 与 server 用同一个函数、同一份数据算。
- 强制等待点:`MapBuffer(READ)` / `GetBufferSubData` 源走 `SyncGpuWrites`;`glFinish` = 等 `appliedSeq` 追上再排空反向通道;每个 `Server*` EGL forwarder 之前等一次。`glGetError` 最多晚一个 present credit。
### 11.7 事件回传与溢出
client 在 `glGetError`、query / sync 查询、`eglSwapBuffers`、buffer 回读与每轮等待循环中排空 `SEG_EVENT`。日志有损;语义事件无损:环满时 server 置 `eventRingFull`、在记录边界停 apply、敲 client,client 排空后清标志并敲回(两半都在,才不死锁)。run-ahead 下 producer 阻塞等排空,整笔预算 `MOBILEGL_IPC_EVENT_WAIT_MS`;client 不排空 / 已走 / 会话停止 → `ReverseChannelForfeit{…}` 弃投并闩住(Ph PH-6),而不是 `Fatal` 打掉 server。不发布 `kCapRunAheadApply` 的 server(lockstep 臂)保留 P5c 的 Fatal。
### 11.8 fence 与无 present 负载(P10)
fence 完成度必须来自真的逐 fence 退休,不是 present 水位(DirectGLES 的 `g_completedFrameSerial` 今天只在 `Present` 前进);无 present 循环需要 server 插非 present fence tick。Magma 那半已随 run-ahead 落地。
### 11.9 传输栈的两根轴(P6.5)
终局(2026-09-22):client 与 server 可在不同机器、不同 OS / 架构上经 TCP 连接,同机 `spawn` 保留。传输栈拆成**两根独立可选、握手协商、可混搭**的轴,上层只见两个接口:
| 轴 | 接口 | 实现 | 选项(`MOBILEGL_IPC_CONTROL` / `MOBILEGL_IPC_DATA`) |
|---|---|---|---|
| 控制面 | `ITransport`(只承载控制帧,不承载描述符) | `SocketTransport` | `fork`(继承 fd)、`unix:<path>`、`tcp://host:port` |
| 数据面 | `ILink`(记录预留 / 提交、水位、reply、event、span 解析) | `ShmLink`(共享段 + `RingControl` + 门铃)、`StreamLink`(分块封帧,水位与 reply 变消息,发送窗口) | `shm`、`stream`、`auto` |
- 配对在会话建立时选定一次;`LinkTerms`(数据面、窗口尺寸、`maxReplyBytes` …)由 **server 陈述**,client 请求可被钳制。
- 共享段交付与控制面解耦:`ShmLink` 自带 AF_UNIX aux 汇合名,在 `Welcome` 里公布,所以同机"TCP 控制 + 共享段数据"是合法配对。`auto` 的判据是**段实际交付成功**,回落到 `stream` 必须具名进日志。
- **上层零链路种类分支**:`ShmLink` / `StreamLink` / `RingControl` 等只出现在 `Transport/` 与其测试(纯度 grep 门);角色谓词回答"谁在哪个进程",不回答"用什么链路"。
- 两端是不同二进制:`wireFingerprint`(`PipeFields.def` 派生的逐成员布局摘要 + 目录摘要 + 字节序 / 指针宽度)永远比较;`buildFingerprint` 只在 `Dial == Fork`(或 `MOBILEGL_IPC_REQUIRE_SAME_BUILD=1`)下比较;不一致是 `Refuse`,不是 abort。
- TCP 上每条 `kWaitReply` 就是一个 RTT、`SEG_STAGE` 字节对的是 30–100 MB/s 的链路:这两列从"记录项"变成可行性数(开放问题 19、20)。
第一波(全 TCP 控制 + stream 数据)已落地;同机混搭(nd `auto` / sd)是第二波。细节 [`notes/p65/README.md`](notes/p65/README.md),契约 `MobileGL/MG_Remote/CONTRACT-P65.md`。
## 12. persistent map 与 ≥16 MiB 采纳
`AcquirePersistentMap` 是永久的地址空间捐赠(≥16 MiB 可变 store 自动采纳,MC 26.3 p99 163 → 21 ms),整个 monolith 改造期不动(D-B4)。拆分下三档由 POST 探针选择(spike B 实测):
| 档 | 形态 | 结论 |
|---|---|---|
| T0 | client 分配 `AHardwareBuffer` BLOB,server 导入(Vulkan / GLES) | 唯一在两台设备、两个后端上都完整读写的档(P11 主攻) |
| T1 | server 导出 opaque fd | 仅 Adreno 的 Vulkan 路径 |
| T2 | 拒绝,client 侧推送 | 永久正确回退;**今天 split 用的档**(`MOBILEGL_IPC_ADOPT_TIER=2`),stream 数据面上强制 |
T2 下 client 侧推送三件套:不做 map/unmap 命令对(payload 带 `hasLiveHostWrites`);在每个 validate 点按块(`MOBILEGL_IPC_PERSISTENT_BLOCK_KB`,默认 64)推送可达的已映射 buffer,脏页追踪 + 哈希抑制只发变化的块(P5d);门 `PersistentCoherentMapScenario`。`mpr` 数的是每一次 `MapPersistent` 发射(铸成或拒绝都算),所以在 monolith 下也可断言。
## 13. 回读、roundtrip 清单与验证
### 13.1 稳态零 roundtrip 与不可避免的阻塞点
- 零 roundtrip:全部 draw / clear / blit / copy / dispatch / XFB / bind / CSO / `set_*` / 上传 / `present`;全部 caps 站点;`glGetError` / `glFinish` / `glFlush`;fence 与 query 的创建和非阻塞轮询;`eglSwapBuffers`;restart / multi-draw。
- 不可避免(罕见):握手;surface 生命周期;`glReadPixels` 到客户内存;GPU-write pending 的 buffer 首次 CPU 读;带超时的 sync / query 结果;`glBufferStorage` 的 ack;纹理拉取;ring / stage 耗尽与 present credit。
- 跨机上每条 `kWaitReply`(`ResourceCreate`、`SetTextureParams`、纹理 `ResourceSubData`)都是一个 RTT:逐帧次数是 P6.5 必测数,减少它是 P9 / P10 的活。
### 13.2 五部分验证门(取代 monolith 的字节一致门)
1. **接口纯度**:A 门 include 图(`scripts/check_include_closure.py`)、B 门 server 镜像不引用 `MG_State::GLState` 与 glslang(P6 的 a6 实测 server 仍链 `MG_Impl`,由 `scripts/link_ratchet.py` 的下行棘轮追踪,P13 转绿)、C 门 `MG_Backend` 里零 `pGLContext`;外加"每个后端 memo 键都是 `{slot, gen}`"(`HandleRecycleScenario`)。
2. **语义影子比对** `MOBILEGL_PIPE_VERIFY=1`:tracker 再用 `SnapshotFromGLContext()` 填一份 `PipeInputs`,G4 逐字段每 draw 比对,打印第一个分歧;verify 构建永不出货,活过 P13。
3. **行为 A/B**:trace 语料在 monolith-pull / monolith-push / split 下 SSIM ≥ 0.99;`DirectGLES.` 与 `DirectGLES.Split.` 等名集合逐名相同(G2)、测试名只增不删(G14);CTS 逐后端 0.5 pp 内;上传形状金标。
4. **性能**:Redmi `2f7cbe2e` reboot-clean、同热窗口配对 A/B,指标是**逐线程 CPU 时间**;对着 pull 臂**只记录、不设门**(用户 2026-09-08)。
5. **覆盖 + poison + 句柄纪律**:G6 0 UNMAPPED、dirty-surface 0 未映射、G8 归属完备、逐 verb poison、G7、残余块棘轮。
两条幸存的字节级等式:`MOBILEGL_BUILD_DISAGGREGATED=OFF` 时库里零 `MG_Remote` 符号;**G1** pull 构建的符号集与 `.text` 对基线恒等(每阶段 0 增 / 0 删 / 0 resize / 0 重命名)。**每个门必须能因它存在的理由变红**(R-16):带阴性对照并真跑过一次红;公共 GL 看不见的改白盒断言;拒绝普查逐用例读私有日志(`ctest -V` 是假零)。
### 13.3 长期语义门:MGPipe recorder(P13)
`MG_Test` 的 mock 后端变成 MGPipe recorder,在一组 fixture 上录每 draw 的已推送状态做金标;它只覆盖推送内容,不覆盖后端对它的解释(开放问题 12)。
## 14. Present、线程与帧节奏
- `eglSwapBuffers` → `present{frameSerial}` → publish + 敲门铃 → 返回,除非超出 credit;**`present` 与 `eglSwapBuffers` 严格 1:1**(后端的帧边界排空只在 `Present` 内发生)。
- `MOBILEGL_IPC_PRESENT_CREDIT` 默认 1(range 1..8):credit 是 run-ahead 唯一的稳态背压,`FrameSerial` 由 client 铸造、1 起算,server 在 `Present()` 返回后归还 credit;等待发生在**编码之前**。credit > 1 从未量过(P10)。
- 预算(~850 draw/帧):`SEG_CMD` ~70 KB/帧,`SEG_STAGE` ~1 MB/帧;跨机时这 ~1 MB/帧对着的是 Wi-Fi。
- 线程:client v1 不加线程,编码在 GL 线程直接写 ring;server 有 io 线程与**终身持有原生 context** 的 `mgl-srv-apply`。全库无有效亲和性控制(Redmi 内核忽略 app 线程的 `sched_setaffinity`)。
- 拆机顺序:publish + server 排空 → 停 apply 线程 → 关传输 → client 排空编译池 → `MobileGL::Destroy()`(落地形状见 §17)。
## 15. 进程、EGL 与平台(P6 / P6.5 / P12)
### 15.1 启动与握手
- 端点:`fork`(`socketpair` + `fork`/`execve`,fd 3 = socket;server 由 `MOBILEGL_IPC_SERVER_PATH` 或 `dladdr` 同目录的 `libMobileGLServer.so` 定位)、`unix:<path>`、`tcp://host:port`。设备侧 supervisor `mobilegl_server_main <endpoint> --serve`:listen → 认证 → 每会话 fork 一个子进程(或进程内线程,P12),同时只服务一个会话,第二个连接 `Refuse{Busy}`。
- server 进程里 `Transport` 读作 `Spawn`,**不强制 monolith**(两百多处 `Transport != Monolith` 判断靠它选 server 臂;`CONTRACT-P6.md` §3.1 修订了原"子进程强制 monolith"的规则)。防无界 fork 链靠不继承的 `Dial == No`(`MOBILEGL_IPC_DIAL=no`),spawn 时构造的 envp 剔除 `MOBILEGL_TRANSPORT` 与 `MOBILEGL_IPC_*` 作第二道独立保险;server 主程序自设 `MOBILEGL_IPC_ROLE=server`。
- `Hello`(abi、后端类型、指纹、配置、令牌、`LinkTerms` 请求)→ `Welcome`(server 陈述的尺寸、段 / aux 名、`dataNonce`、真实 pid)或 `Refuse{code, detail, 对端值}`;握手路径零 abort。非 loopback 监听必须有令牌(≥16 字节、常量时间比较、数据面按 `dataNonce` 绑定到已认证的控制连接、fork 前认证;Ph,OQ-21 裁定不做 TLS)。
- TCP keepalive / 心跳失败**由传输层报告为挂断**,进 device-lost 闩;"闩绝不取自 apply 超时"不因此松动。冷启动期 server 发 `SurfaceProgress`,client 预算这段沉默(控制修订 2)。
### 15.2 Android
- 交付链(spike A 已证):APK 唯一可 exec 的位置是 `lib/<abi>/`,server 以 `add_executable` + `lib*.so` 命名构建;从 `untrusted_app` 进程 `fork`+`execve` 同域、零 avc denial;`posix_spawn` 在 minSdk 26 不可用。一份共享库两个角色(stub `dlopen` + `dlsym("mobilegl_server_main")`)。
- 离屏 server:前台 `MobileGLServerService`(`:mglsrv`),从 `nativeLibraryDir` exec,fork-per-session,EGL 是 pbuffer / surfaceless。
- **上屏 server(P12 子集)**:窗口归 **server 自己**(client 的窗口不跨进程)。`MobileGLDisplayActivity`(`:mglwin`)持全屏 SurfaceView,在自己的进程里用一个线程跑 TCP server(会话串行、会话间重置闩、后端按进程钉住);`ServerDisplay` 以 acquire / release / 几何三个钩子持有窗口,apply 线程以**租约**使用它。client 设 `MOBILEGL_IPC_SURFACE=server` 后**无头**:`eglCreateWindowSurface(NULL)` 发一帧 `WindowKind::ServerOwned`(控制修订 3),不发 `SetWindowHandle`,尺寸由 reply 带回,resize 变成对 server 窗口的几何请求。一台设备同一时刻一个 server;一个会话一种表面模式(`SurfaceModeMismatch`);无显示的 server 对 ServerOwned 具名拒绝(`NoServerDisplay`)。
- 失窗:`surfaceDestroyed` 有界(3 s)阻塞,直到 apply 线程(批内每条记录前也检查)放掉后端表面并以 `Fatal{ServerWindowLost}` 闩住会话;server 继续监听。进程内会话结束时手动做进程退出本来白给的清理(Espryt twin 与单元影子、swap interval、Magma renderer)。
- `HeadlessGL` 的 fork 预检会产生孤儿 server:server 的 EOF 检测即时且无条件退出。
### 15.3 Linux / Windows / 崩溃
- Linux / WSL / CI 永不开窗(`EGL_PLATFORM=surfaceless`);Wayland 不支持。Windows / 桌面 client 在 TCP 终局下是真实形态(Windows 是第二批,开放问题 22);macOS 不拆分;Windows 机器不是正确性门。
- server 死:client 读到挂断 → **device-lost 闩**(GL 调用 no-op、`eglSwapBuffers` 返回 `EGL_CONTEXT_LOST`、reset status `GL_UNKNOWN_CONTEXT_RESET`);`Session::Fail` 漏斗向对端发 `SessionFault` 帧命名家族(`FatalFamilies.def`)。**不重启**:`MOBILEGL_IPC_RESPAWN` 今天是具名拒绝。client 死:server 读到 EOF 立即销毁 context 并退出(supervisor 继续服务下一连接)。
## 16. 构建布局
```
MobileGL/MG_Pipe/ 永远进构建:*.def 目录与表、MGPipe*.h payload / 句柄 / 回调、PipeApply、PipeRoute、generated/*.inc
MobileGL/MG_Impl/Pipe/ Tracker、PipeFill、SlotAllocator、CsoCache、CompositeResolver、ResourceTracker、SetHashSuppressor、*Emit.h
MobileGL/MG_Backend/MGPipe/ PipeInputs.{h,cpp}
MobileGL/MG_Remote/ 仅 MOBILEGL_BUILD_DISAGGREGATED:CONTRACT-*.md、CapsCodec、FatalFamilies.def / FatalFunnel、Handshake.h
Protocol/ protocol.fbs、generated/、SurfaceOpCodec、mg_protocol_base.h
Transport/ ITransport、SocketTransport、InProcessTransport、ILink、ShmLink、StreamLink、Ring、SessionRings、ReplySlot、EventRing、
Doorbell、ShmSegment(+Posix/Win32)、FdPassing、Framing、AuthToken、ControlInbox、LinkMetrics、WireLog
Wire/ PipeWireCodec(记录编解码、WireVerbSink)
Client/ BackendObject_Remote、ClientSession、EmitTables、WireTables、CapsMirror、PersistentMapTracker、GpuWritePending
Server/ PipeApplier、ServerLoop、ServerSession、ServerMain / ServerEntry / ServerSpawn、PreAuthGate、StagedShadow、StagedTextureStore、
SurfaceControlFrame、ServerDisplay、InProcessServer、DisplayServerJni
```
- `MOBILEGL_BUILD_DISAGGREGATED`(默认 OFF)追加 `MG_Remote/**`;OFF 时 `MG_Config::Transport` 是 `constexpr Monolith`。`MOBILEGL_BUILD_DISAGGREGATED_INPROC` 隐含前者并加角色隔离:两个角色靠 apply 线程与控制邮箱分开,而不是给 1494 个 `pGLContext->` 读点加 TLS。
- `MOBILEGL_TRANSPORT = monolith | inproc | spawn` 是**拓扑**(G1 相关);控制面与数据面由 §11.9 的两个变量选。**split build 不设 `MOBILEGL_TRANSPORT` 时是 monolith 对照臂**。
- 四个构建 flavour:pull(默认)、push、verify、split;Android 三份 APK flavour(pull / push / split)。
- 测试接线:ctest `ENVIRONMENT` 用 `mgl_itest_join_environment(...)` 构造;每条 split / spawn / tcp 条目带独立日志路径,日志按角色分文件(`<base>.client.log` / `<base>.server.log`);spawn、tcp 车道与 split 名集合一致(`scripts/ci/spawn_lane_parity.py`),每条目记 arm 证明。
- CI(`.github/workflows/test.yml`、`apk.yml`):`pipe-gates`(G1–G8、生成器 self-test、符号报告、dirty-surface、字段归属、G5、文档引用 lint)、`flatc-check`、include 闭包、各 flavour 的 build / integration / retrace 车道、APK + AVD。两份 workflow 里的 `feat/disaggregated` 触发器是临时的,合入 `dev` 前移除。
## 17. 阶段落地形状(索引)
各阶段落地时的形状、偏差与论证原文在 notes 里;下表只列仍然生效的规则。
| 原节 | 主题 | 仍生效的规则 | 全文 |
|---|---|---|---|
| 17.1 | verb barrier 与诚实的同地址空间传输(P5) | 同地址空间不得成为旁路(R-2):encoder 把 `MGHostSpan::Ptr` 恒写 `nullptr`;reply 以记录序号为 slot id(R-3);等待看门狗 120 s 只判死锁,不是性能门 | [`notes/p5/README.md`](notes/p5/README.md) |
| 17.2 | 后端槽三类、caps、tight readback(P5) | A 本地答 / B 发射 / C 具名拒绝,永不回落 monolith(R-4);能力只读 `CallMask`(R-8);`ReadPixels` 线上恒 tight,client 按自己的 pack state 散射 | 同上 |
| 17.3 | server 角色、shadow 与退出(P5) | apply 线程终身持有 native context,EGL 罕见操作经控制邮箱进 apply 线程;staged shadow 只交 server-owned 拷贝;有界退出顺序 | 同上 |
| 17.4 | lane 隔离(P5) | 每条 split 条目私有日志;E1 / E3 控制把"选中条目被跳过"判为失败 | 同上 |
| 17.5 | class-C verb 迁移(P5b) | 迁移顺序由动态普查决定;一次迁移 = 一个发射器 + 一个 sink 体;记录逐字带 GL 调用参数,未测量的形式按名拒绝(规则 D) | [`notes/p5b/README.md`](notes/p5b/README.md) |
| 17.6 | 共享地址空间访问归零(P5c) | server 端纹理 staged shadow;`SEG_EVENT` 是唯一反向通道;sink / twin 按记录句柄解析;`applier_reset` / `object_death` 控制记录;角色守卫 `Fatal{RoleViolation}` | [`notes/p5c/README.md`](notes/p5c/README.md) |
| 17.7 | run-ahead(P5e) | 规则 F:unbarriered 记录的 apply 不读任何 client 内存(违反即具名 Fatal,与 strict 开关无关);`gPipeInputs` 是 server 角色内存;`RunAheadArmed()` 在首次 caps 采纳时锁存,只能关不能开;`RUN_AHEAD=0` 是 A/B 对照,`VERB_BARRIER=0` 会连带关掉 run-ahead | [`notes/p5e/README.md`](notes/p5e/README.md) |
## 附 A:开关
CMake:`MOBILEGL_BUILD_DISAGGREGATED`(OFF)、`MOBILEGL_BUILD_DISAGGREGATED_INPROC`(OFF,隐含前者)、`MOBILEGL_PIPE_PUSH`(OFF;push / verify / split flavour 打开)、`MOBILEGL_PIPE_VERIFY`(OFF;隐含 push,永不出货)、`MOBILEGL_PIPE_LEGACY_MEMOS`(ON)、`MOBILEGL_BUILD_SERVER_SPIKE`(仅 Android spike)、`MOBILEGL_FLATC_EXECUTABLE`(只服务 `flatc-check`)。
运行时,MGPipe(`MobileGL/Config.h`、`ConfigLoader.cpp`):
| 变量 | 默认 | 说明 |
|---|---|---|
| `MOBILEGL_PIPE_PUSH` | pull `0`;push `0x1fff` | 子系统位图(`MG_Pipe/MGPipe.h`,位永不复用;依赖两侧都拒);位 63 是"关 CSO 内容寻址"的行为对照 |
| `MOBILEGL_PIPE_VERIFY` / `_VERIFY_FATAL` / `_VERIFY_CORRUPT` / `_POISON_OMIT` | 0 / 1 / 空 / 空 | 影子比对与两个阴性对照(verify 构建才有) |
| `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` | 0 | 故意打掉句柄身份的阴性对照 |
| `MOBILEGL_PIPE_STATS` / `_STATS_PERIOD` / `_STATS_FILE` | 0 / 120 / 空 | 边界计数器(附 B);dump 按角色写 `<base>.client.json` / `<base>.server.json` |
| `MOBILEGL_PIPE_LEGACY_MEMOS` / `_TEXEL_RETAIN_MB` / `_INDEX_MIRROR_MB` | ON / 0 / 64 | pre-handle 臂、纹理拉取保留、索引镜像预算(P8) |
运行时,传输与 IPC:
| 变量 | 默认 | 说明 |
|---|---|---|
| `MOBILEGL_TRANSPORT` | `monolith` | 拓扑:`monolith` / `inproc` / `spawn` |
| `MOBILEGL_IPC_CONTROL` / `MOBILEGL_IPC_DATA` | `fork` / `auto` | 控制面端点与数据面(§11.9);`MOBILEGL_IPC_SERVER_PATH` 定位 fork 出的 server |
| `MOBILEGL_IPC_TOKEN` / `MOBILEGL_IPC_REQUIRE_SAME_BUILD` | 空 / 0 | 令牌(≥16 字节;无令牌只允许 loopback);要求两端同一构建(车道纪律) |
| `MOBILEGL_IPC_PREAUTH_*` / `MOBILEGL_IPC_AUTH_BACKOFF_*` | — | server 预认证的上限与失败退避(Ph) |
| `MOBILEGL_IPC_CONTROL_TIMEOUT_MS` / `MOBILEGL_IPC_COLD_START_MS` | 5000 / 20000 | 控制回复的稳态上限与冷启动预算 |
| `MOBILEGL_IPC_LOG_FORWARD` | 远端时开 | server 日志经控制连接前送,client 写进自己的 `<base>.server.log` |
| `MOBILEGL_IPC_SURFACE` | `offscreen` | `server` = 使用 server 自有窗口,client 无头(P12) |
| `MOBILEGL_IPC_RING_MB` / `MOBILEGL_IPC_STAGE_MB` | 8 / 32 | `SEG_CMD` / `SEG_STAGE`;stage 的 1/4 是内容分块预算 |
| `MOBILEGL_IPC_RUN_AHEAD` | 1 | P5e run-ahead;只是合取式的一半(server 须发布 `kCapRunAheadApply`);`0` 是 A/B 对照 |
| `MOBILEGL_IPC_VERB_BARRIER` | 1 | `0` 只作阴性对照,且同时关掉 run-ahead |
| `MOBILEGL_IPC_PRESENT_CREDIT` | 1 | 1..8,§14 |
| `MOBILEGL_IPC_BATCH_WAITS` | 1 | lockstep 下值类记录发布即返回;verify 强制 0 |
| `MOBILEGL_IPC_EVENT_WAIT_MS` | 2000 | 反向事件遇满环时 server 等 client 排空的整笔预算(§11.7) |
| `MOBILEGL_IPC_WIRE_DEFERRED_MB` | 64 | Magma wire 臂延迟回收的水位线;`0` 是 M2 阴性对照 |
| `MOBILEGL_IPC_PERSISTENT_BLOCK_KB` / `_PERSISTENT_HASH_SUPPRESS` | 64 / 1 | persistent-map 推送粒度 / 只推变化的块;`0` 是对照 |
| `MOBILEGL_IPC_ADOPT_TIER` | 2 | `auto/0/1/2`;T0 / T1 今天是 Fatal-at-use |
| `MOBILEGL_IPC_STRICT_ERRORS` / `MOBILEGL_IPC_AUDIT` / `MOBILEGL_IPC_ROLE_SPLIT_STATE` | 0 / 0 / 0 | 残余输入读升级为 Fatal / 退休 staging 填 `0xDD` / 双块演练(P5f) |
| `MOBILEGL_IPC_SPIN_US` / `MOBILEGL_IPC_SERVER_AFFINITY` | 50 / `auto` | park 前自旋预算 / apply 线程亲和(Redmi 内核忽略) |
| `MOBILEGL_IPC_RESPAWN` | — | 具名拒绝:没有阶段实现 server 重启后的全量重推 |
显式不设立:`MOBILEGL_IPC_PROGRAM`(没有 relink 档)、`MOBILEGL_IPC_VALIDATE_SERVER`。计划中未接线:`MOBILEGL_IPC_POLL_ESCALATE`(P10)、`MOBILEGL_IPC_IDLE_EXIT_S`(未解析)。
## 附 B:边界计数器(`MobileGL/MG_Util/Metrics/PipeStats.h`)
关闭时每站点一次全局 load + 一条永不命中的分支。字节类(stage buffer / texture / UBO / client 数组、`pmap`、`resid`、`csob-blob`、`seg` = 写进 `SEG_STAGE` 的字节)、调用类(`draws`、`accessor-calls`、纹理上传 emit / box / rect / jobs、`csom` / `csob`、`mpr`、`trp`、`rsp`、`wrec`、`maxrec` / `ringwraps` / `ringpads` / `ringwaits`、门铃 `srv` / `srvpark` / `cli` / `clipark`)与六个 memo 门的 hit/miss。每 `MOBILEGL_PIPE_STATS_PERIOD` 帧一条 `MGPipe stats:` 汇总行,按角色写日志。读法陷阱:`accessor-calls` 是静态下界,判性能只看 CPU 时间;spawn 下 client 不推进帧窗口(`frames=0`),窗口化字段在 spawn client 上无效,run total 仍有效([`notes/p6/README.md`](notes/p6/README.md) §12.1)。站点清单(哪些路径**没有**接线)写在 `PipeStats.cpp` 头部,那份清单是契约。