Files
MobileGL/docs/Disaggregated/notes/recovered/wf2/final/synth-part4.md
T
swung0x48 aba329cc53 [Docs] (Disaggregated): P6's plan and contract draft; the working notes move from a local directory into the repository
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.
2026-09-18 23:31:39 -04:00

275 lines
48 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.
## 11. 分阶段实施计划
> **通用纪律(每个 commit 都适用)**:默认 ALL target 必须能完整构建;禁止提交热路径插桩;**每个门必须能因它存在的理由变红**;Windows 机器不是正确性门(其 Vulkan 缺 `vkCreateHeadlessSurfaceEXT`,占该机 567 个基线集成失败中的 423 个);设备对比走 reboot-clean + 同热窗口配对 A/B,CPU 定频按项目协议(大核 1.96 / 小核 1.55GHz,GPU 拉满,40°C 门槛);**每个阶段的出口都跑一次 §10.3 的五部分门**(不只是 P0);**每个阶段的性能判据都是逐线程 CPU 时间**,不是墙钟帧时。
> **两条跑道**:P0-P4a、P3b/P4b、P7、P8、P13 是 **monolith 跑道**,每一段都可独立交付、可随时中止且 monolith 严格好于起点;P5、P6、P9-P12 是 **IPC 跑道**,整段继承 `PLAN.md` §6-§13。
### P0 — 卫生、度量、门与骨架(8 天)
**交付物**
- **清工作树 per-draw `fprintf`**:`DirectGLES.cpp:290-303`、`:640-663`、`Managers.cpp:875-877`(后者在 `pendingMutex` 临界区内)。CI 加 grep 门禁止 `MG_Backend/` 与 `MG_State/` 下出现 `fprintf(stderr` / `printf(`。
- **`TracyPlot` 逐帧字节/调用计数器,装在边界两侧**,按类分:`cmd-records`、`stage-buffer`、`stage-texture`、`stage-ubo`、`persistent-map-push`、`server-ring`、`server-staging`、`residual-value-block`、`texture-pull`。**树里今天完全没有 per-frame 字节度量**(`MG_Util/Metrics` 只是格式算术,Tracy 只有 zone 无 plot),所以之后每一个 ring 尺寸、批阈值、wire 粒度决策否则都是猜测。两台设备取基线。
- `MG_Pipe/PipeCalls.def` + `MGPipeTypes.h` + `MGPipeHandles.h` + `MGPipeCallbacks.h`:**完整调用目录,即使暂未实现的条目也占位**(记录编号绝不能churn)。
- `scripts/gen_pipe.py` 与六个生成器 G1-G6 的骨架 + CI `pipe-gen-check`(重生成 + `git diff --exit-code`)。
- `MOBILEGL_PIPE_PUSH` / `_VERIFY` / `_STATS` 在 `ConfigLoader.cpp` 与既有开关并列解析。
- **三个严格 no-op 的免费收益**:`GetIntegeri_v`/`GetInteger64i_v`/`GetProgramiv` 的纯前端 case 移回 `MG_Impl`(Espryt 14 / Magma ~10 个读点);`RenderbufferObject::GetLifetimeId()`(**不加 `GetVersion()`**);D21 重键(`m_xfbCounterSlotByObject` 改用 XFB 对象 lifetimeId,`VulkanRenderer.cpp:11136-11146`)——**这一条是潜伏 bug 修复,先独立落 `dev`**。
- 回答 `FramebufferSrgb`/`DepthClamp` 无存储是潜伏 bug 还是有意为之(§10.4-6)。**必须在渲染状态 blob 冻结之前。**
- `MG_Remote/{Protocol,Transport}` 骨架与 `PLAN.md` P0 完全一致(`ITransport`、`InProcessTransport`、校验型 `Framing`、`Ring`+`RingControl` 双游标三元组双向 doorbell、`Doorbell`、`ShmSegment`、**`SCM_RIGHTS` 第一优先**);`protocol.fbs` + 提交的 `protocol_generated.h` + `flatc-check`;`MG_Test/Wire/`。
- **`PLAN.md` P0 的两个 spike 原样跑**:spike A(Android 交付链:改名成 `lib*.so` 的 `add_executable` 能否被 AGP 打包、能否在 `untrusted_app` 域内 exec);**spike B(external memory 导出,两台设备)**。
**验收**:`AdvertisedLimitsScenario`(6 个测试)绿;367 集成 × 2 backend + 428 单元逐名不变;40 个 trace 全绿;`nm --defined-only` 与剥调试信息 `.text` 除三处刻意移动的符号外一致;两台设备的基线字节/调用/逐线程 CPU 数字记录在案;spike A/B 出结论并写进开放问题(spike B 的结论直接决定 P11 的规模)。
### P1 — `PipeInputs` 替换与 verify harness(8 天)
**交付物**
- `MG_Backend/MGPipe/PipeInputs.h`:每个 backend 真正用到的 `GLContext` 方法一个访问器(Espryt 32 / Magma 55),**字段类型与今天读到的完全一致**,按 memo 键组织。
- 机械 `sed`:`MG_State::pGLContext->` → `MGB_CTX->`(**293 处**);**外加逐条手工转换 58 行非箭头用法**(§2.4:~40 处 `MOBILEGL_ASSERT` 真值判定删除、~10 处空守卫改直读、3 处 patch 三元、`DirectGLES.cpp:146` 的 `.get()` 裸指针捕获、2 处 `!= nullptr` 条件、1 处注释)。**这份 58 行清单是本阶段的显式交付物**,不是"顺手处理"。
- `SnapshotFromGLContext()` 在 `PrepareForDraw`(`DirectGLES.cpp:2916`)与 `SetupDraw`(`VulkanRenderer.cpp:6371`)顶端。
- **G5 的 `m_filledMask` poison**:debug 与 disaggregated 构建里读未填字段 = `Fatal{UnmigratedPipeInput, "<field>"}`。
- **G4 的 `MOBILEGL_PIPE_VERIFY=1` 逐字段影子比对器** + 第三种 CI 模式接线。
**验收**:**pull 构建里 `nm --defined-only` + 剥调试信息 `.text` size 与替换前完全一致**——本阶段可证明是一次替换(这是最后一次这条等式成立);全部 40 个 trace 与 367 个集成测试在 `MOBILEGL_PIPE_VERIFY=1` 下零分歧;**故意损坏一个快照字段能让 verify 门变红**;故意留一个字段不填能在第一个 draw 上触发 poison Fatal。
**★ 第 16 天 — 最早可见里程碑:**零产品风险地证明"推送等价于拉取",逐 draw 逐字段。
### P2 — 值推送:渲染状态 CSO(双后端)+ 残余值块(8 天)
**交付物**
- `MG_Impl/Pipe/Tracker.{h,cpp}`:dirty 位来自既有计数器(§5.2,三个回绕 `Uint16` 在 tracker 边界加宽)+ 固定 validate 顺序(§5.3)。
- `MG_Impl/Pipe/CsoCache`:64 项 LRU,键是三段(head/blend/tail,与 `DirectGLES.cpp:2038-2047` 完全一致的划分)的 xxHash。
- `create_render_state` / `bind_render_state`(D-B1):Espryt 侧 `RenderStateImpl` 的 693 行函数体、三段 memcmp、`g_syncedColorMaskAlphaWidenMask`、dual-source decline **一行不动**(消除 4 个读点:`:2007, 2021, 2050, 2133`);Magma 侧 `ComputePipelineStateHash` / `GetOrCreatePipeline` / `ApplyDynamicDrawStateTail` 改从 CSO 取(消除 ~55 个读点)。两个版本号都过线。
- `set_pixel_pack_state`(PACK only)、`set_patch_state`、`set_vertex_attrib_defaults`。
- **`set_residual_value_state` + `ResidualValueBlock`**(§6.3):`static_assert(sizeof == MGL_RESIDUAL_BLOCK_SIZE)`(逐阶段**下调**)+ **逐成员 `offsetof` 断言** + split 下逐字段序列化。
**验收**:367 集成 × 2 backend × 2 模式(pull / push)逐名相同;40 个 trace 在 monolith-push 下 SSIM ≥ 0.99,双后端;`ClipDistance`、`SampleMaskScope`、`SampleVariables`、`DualSourceBlend`、`ViewportArray`、`PrimitiveRestart` 场景绿;verify 模式零分歧;**两台设备 reboot-clean 配对:monolith-push 在 p50 与 p99 逐线程 CPU 上落在 monolith-pull 噪声内或更好**。
**★ 第 24 天 — GO/NO-GO 决策点。** 此刻手上有:verify harness、双后端已推送的渲染状态、真实 CPU 增量、Track H 在两个最便宜子系统上的单位成本。**若 CPU 数字为负、或 Track H 单位成本超估计 50%,退回方案 A 只损失这 24 天中的 16 天**(P0 是 `PLAN.md` 共有的)。
### P3a — handle wave 1(Espryt):slot 基建、buffer、VAO(12 天)
**交付物**:`SlotAllocator`(按 kind 稠密 + `gen` + 保留段);`PipeObjectTables`(POD slot **数组**);**删** `TwinLookupMemo`×3、`OwnerEquals`、`g_fbSlotCache`、6 个 registry GC 扫描、`VkTextureManager::PruneDeadTextures`、`m_convertedVertexStreams` 的 `sourcePin`;7 个 `BufferBackendOps` → `resource_create/respecify/destroy`、`resource_subdata`、`buffer_subdata_resident`(**可 null,保住 Magma 的差异**)、`resource_flush_range`(带应用真实 access flags)、`resource_readback`、`map_persistent`(**不碰实现**);pool 与延迟释放机制原样搬;`create/bind/delete_vertex_elements_state`(**两个视图都带**:解析后的 `VertexAttribute[32]` 与 `VertexBufferBindingPoint`;`IsLong` 与 `Type` 分开);`set_vertex_buffers`(**`baseInstance` 是显式字段**,不再是调用方武装的 `ScopedFetchBaseInstance` 作用域);`set_index_buffer`(带 restart index 与模式);Adreno 禁用属性 SIGSEGV workaround 原样保留。
**验收**:全套门(monolith-push,DirectGLES);`LargeArenaAdoption`、`ResidentIndex`、`StorageBufferRegrow`、`AtomicCounter`、`BufferTexture`、`CrossFrameBuffer`、`SsboArrayLength`、`SsboArrayDynamicIndex`、`VertexArrayEnableDisable`、`VertexAttribBinding`、`DoublePrecision`、`DrawParameters`、`MultiDraw`、`PrimitiveRestart` 场景;`create-indirect`、`create-instancing`、`rd12-odinlite`、`improved-transparency-26.3`、`fabric-sodium` trace SSIM ≥ 0.99;**新增 `HandleRecycleScenario` 绿,且它在 slot 重键之前必须是红的**;MC 26.3 在 Adreno 上 p99 不变(16MiB 采纳结果不得回归)。
**⚠ 再基线检查点 1:若 P3a 超期 >50%(>18 天),"窄 handle 化"的前提就是错的,必须在 P4a 开始之前重定基线。**
### P4a — handle wave 2(Espryt):FBO / 纹理 / sampler / program 的身份与描述符(16 天)
**刻意推迟到首帧之后的部分**:memo 重键、dirty 归属反转、program 陈旧性重构(→ P3b/P4b)。本阶段只做 wire 需要的身份与描述符。
**交付物**:`set_framebuffer_state`(8 个 `MGPSurface` + **client 解析后的 `readSurface`** + 内联 `internalFormat` + `contentHash` + `isDefault` 保留 handle,退役 4 处 `pDefaultFramebufferInfo` 读);四个跨对象 mask 在推送时刻推出;`create/bind/delete_sampler_state`(`SamplerParameters` 逐字节含 `borderColorForm`);`create/delete_sampler_view`(`glTextureView` 变普通 view CSO);`set_sampler_views`(client 侧解析)+ `bind_sampler_states`;`set_shader_images`;`create/bind/delete_shader_state`(逐 stage SPIR-V + `Visit()` 全结构体反射归档 + `sizeof` 绊线);`set_draw_program` / `set_dispatch_program`;`set_global_constants`;`CompositeResolver.cpp`(保留 slot 段 + 生命周期);纹理与 renderbuffer 的 `resource_create/respecify/subdata`。emulation 路径在 split 模式下**显式 Fatal** 直到 P8。
**验收**:全套门;`CrossFrameBuffer`、`LayeredAttachmentShape/Barrier`、`SnormAttachment`、`RenderbufferBlendFormat`、`FragmentOutputArrayIndex`、`Orientation`、`ClearThenReadPixels`、`FragCoordOrigin`、`TextureView`、`ProgramPipeline`、`PostLinkAttach`、`RelinkStageSet`、`SpirvShaderBinary`、`AsyncCompile`(6 个)场景;`KHR-GL46.direct_state_access.framebuffers*` 与整个 `packed_pixels` 块在两台设备上绿(**~3300 个 framebuffer/用例,是 handle 复用的压力测试**)。
### P5 — 传输 + inproc applier + 发射表(12 天)
**交付物**:`MG_Remote/Client` 的发射表实现 `MGPipeScreen`/`MGPipeContext`;`Server/PipeApplier.cpp` 从记录填 `PipeInputs` 与 `PipeObjectTables`;`ServerLoop`(`mgl-srv-io` + `mgl-srv-apply`,后者终身持有原生 context);单一 hook 点 `MG_Backend/Init.cpp:48-70` 装 `BackendObject_Remote`(下游零 `#ifdef`);`MGPCaps` 快照;一条阻塞 `read_pixels`;client 侧保守 `MarkGpuWritten` 与 `emitSeq`;**client 侧块粒度 persistent-map 推送**(T2 档下强制,`PLAN.md` §5.10 第 2 条);`InProcessTransport`。
**验收**:`ctest -R 'DirectGLES\.Split\..*(ClearThenReadPixels|Triangle)'` 在 `MOBILEGL_TRANSPORT=inproc` 下绿;**OpenRA trace 在 split 模式下 SSIM ≥ 0.99**;**`PersistentCoherentMapScenario` 绿**(map PERSISTENT|WRITE|COHERENT、写、不做任何其它 GL 调用、draw、回读校验——这是唯一一个为一个致命缺陷而存在的门);**两个角色的峰值 RSS 记录在案**,作为对 `PLAN.md` R14 的基线;`persistent-map-push` 字节量出数;任何未迁移的 `PipeInputs` 字段读产生 `Fatal{UnmigratedPipeInput}` 而不是静默垃圾。
**★ 第 64 天 — 首个 IPC 帧(`inproc`)。诚实标注:这是缩减路径**——client 数组、restart 重写、indirect-count 解析在 split 下仍是 Fatal,全功能要等 P8。
### P6 — spawn transport(5 天)
**交付物**:`SocketTransport`(socketpair + fork/execve,**显式 envp 剔除 `MOBILEGL_TRANSPORT`/`MOBILEGL_IPC_*` 加 `mobilegl_server_main` 内强制 Monolith 的双保险**);`ServerMain`;`MOBILEGL_IPC_SERVER_PATH` 为主 + `dladdr` 兜底(两个桌面门都会挫败 `dladdr`);就绪握手有界重试;client EOF 即时退出;server 死亡的 device-lost latch;trace-replay 的 `SPLIT` 后缀与 `-DTRACE_TRANSPORT=` 接线。
**验收**:P5 全部测试在 `MOBILEGL_TRANSPORT=spawn` 下绿;fork 链测试断言进程树只多一个子进程;`HeadlessGL` 的 fork 预检交互测试无孤儿 server(`pgrep` 计数在 100ms 内归零);`run_android_retrace_local.py --case OpenRA --backend DirectGLES` 在 `35d0befa` 上 SSIM ≥ 0.99。
**★ 第 69 天 — 首个跨进程帧(缩减路径)。**
### P3b / P4b — 深化(Espryt):memo 重键、dirty 反转、program 陈旧性(28 天)
**交付物**:重键 `ResolvedDrawBuffers`、`PendingAttribValueMask`、`ConvertedFloat64Stream`、`SyncCurrentFBO` 四元组戳、`ResolvedTextureBindingMemo`、`SamplerPassMemo`、image sweep、program registry 到 `{slot, gen}`;**删** `g_unitTextureSyncList`、`g_fboTextureSyncList`、`g_unitSamplerLookupMemos`、`g_imageSweep*`、`DirectGLES.cpp:1372-1489` 的 ~115 行 unit-bindings epoch 推导;**dirty 归属反转**(§7.3,client 保 rect 模型与发射游标、发射后自清;`resource_subdata` 带 box **与** rect 列表、server 选形状);**删** fragColor 重推导 workaround(`:2712-2732`)与 `g_broadcastMemo*`;用推送的 FBO 状态、image 格式、`set_patch_state` 退役 9 条陈旧性判定里的第 4-6、8-9 条;Espryt 的 raw-depth-fetch `SamplerObject` 原生化。
**验收**:~25 个纹理场景(`TextureView`、`LayeredTextureReadback`、`ImageSizeAfterRespec`、`FormatlessImageBake`、`NonCoreImageFormat`、`ImageFormatQualifier`、`ImageTargetKind`、`ImageLoadStoreSso`、`UnboundImageDescriptor`、`SwizzleAccessRoutine`、`IntegerBorderColor`、`PixelStoreSweep`、`SampledSetStaleness`、`ThreeChannelAttachment`、`BufferTexture`、`CopyImage*`×3、`ClearTexImageUndefinedLevelZero`、`DepthStencilReadback`×3、`PackedWordReadback`);21 个 program 场景 + 整个 `MG_Test/ShaderTranspiler` 目录;两台设备上完整 `KHR-GL46.texture_*` / `internalformat.texture2d.*` / `shader_image_*` / `packed_pixels` 块,conformance 在 pull 基线 0.5pp 内;**每一个 Iris trace**;**Mali 上 box-vs-rects 上传形状不得回归**(+6ms/frame 悬崖);verify 模式全程零分歧。
### P7 — DirectVulkan(Magma)全量迁移(48-85 天,可与 P5/P6/P8 并行)
**交付物**:§6.5 的 11 个子系统,重点三项:`SetupDrawSnapshot` 的 ~14 个探测字段(含两个**有损**的版本求和)塌成 dirty mask 比较;**`UniformManager` 的 8 类占位 `TextureObject` 换成原生 `VkImage`+view+descriptor**(~120 行删除,43 个 `MOBILEGL_ASSERT(pGLContext)` 里的 9 个消失);**blit / depth-mipmap 内部 shader 烘焙成签进树的 SPIR-V + uniform location + UBO 布局,由一个 `MG_Test` 重跑树内 glslang 逐字节比对的用例守新鲜度**(不用 host 工具构建步骤);`VertexInputStateFactory` 的后端堆裸指针写回**直接删除**;`VkRenderPassManager` / `VkTextureManager` 的**节点式容器纪律原样保留**(D18,postmortem 注释逐字带进 review checklist)。
**验收**:367 集成 + 40 trace 在 DirectVulkan 的 monolith-push 与 split 下全绿;verify 零分歧;**`nm -D libMobileGLServer.so | grep glslang` 为空**——这是整个论点的强制执行点;`UnboundImageDescriptor`、`SampleMaskScope`、`ImageLoadStoreSso`、`AtomicCounter`、`SsboArrayDynamicIndex`、`NonCoreImageFormat`、`Orientation`、`DepthStencilReadback*` 场景;两台设备 CTS 在 0.5pp 内。
**⚠ 再基线检查点 2:P7 中点(第 24 个工作日)若已完成子系统 < 40%,立即重定基线**——P3a 的检查点只能发现 Espryt 侧的偏差,发现不了 Magma 特有的超期,而 P7 在单跑道下位于关键路径。
### P8 — emulation 下放 + 协议广度(14 天)
**交付物**:`MG_Impl/Pipe/HostResolve.cpp`——client 数组范围计算、**最大索引扫描**(`TryComputeMaxIndexFromHostBytes` 移到 client,唯一的无界应用指针读)、**primitive-restart 重写**、**indirect-count 解析**,每一条前面都有与 monolith 的 `SyncPersistentMappedRange()+SyncGpuWrites()` **完全相同位置**的 publish/wait/drain(§5.8.1);`MGHostSpan` 的 split 填法;**CopyImage shadow 镜像搬到 client**(删掉一整条 server→client 字节通道);`draw_vbo(info, indirect, ranges[], numDraws)` 收编 multi-draw 族;viewport-array 回放验证在一次 pipe 调用驱动下各遍之间观察到的状态与今天一致(`EndViewportRoutingPasses` 会调 `InvalidateSyncedRenderState`,`DirectGLES.cpp:3841`);`generate_mipmap` 返回 level 计划与 CPU 回退的纹素。
**验收**:`ctest -L integration-gpu -R '^DirectGLES\.Split\.'` 与 `'^DirectGLES\.'` **逐名相同**,DirectVulkan 同;40 个 trace 在 split 下双后端 SSIM ≥ 0.99,含两个 `coherent_as_flush: true` 的 Create fixture(**两种模式都开着该开关跑**);**新增 `ClientArrayAfterComputeWriteScenario` 绿,且去掉那次等待必须能看到几何缺失**;`MultiDraw`、`PrimitiveRestart`、`ViewportArray`、`DrawParameters`、`CopyImage*`×3、`GuiBatch` 场景。
### P9 — 反向通道(10 天)
**交付物**:`SEG_REPLY` 4KiB slot 池;阻塞 `read_pixels`;PBO 回读 fire-and-forget;`on_gpu_written{res, ranges}` 收窄(配 `set_shader_buffers` 的 `writableMask`);`on_buffer_writeback` **按操作级批处理**(今天两处逐行循环:`Utils.cpp:2342`、`DirectGLES.cpp:7633`)配 epoch bump 的排序规则(§7.4);`on_texture_writeback`(一个生产者);`on_mip_levels_generated`;**`on_texture_pull_request` 三条缓解全上**(§7.5);`on_gl_error` 有序 + 分配类 `kNeedsAck`;`on_caps_invalidated`;`on_surface_changed`;**`on_log` 按严重级分级**(≤WARN 有损 / ≥ERROR 无损 + 每秒速率限制器 + "N errors suppressed");`SEG_EVENT` 溢出策略 + 等待循环内排空。
**验收**:`DepthStencilReadback`×3、`PackedWordReadback`、`LayeredTextureReadback`、`ClearThenReadPixels`、`XfbAfterClipDistance`、`XfbCaptureBufferReuse`、`XfbRepeatedCapture`、`TessellationXfbCapture` 在 split 下绿;**新增 `TextureRemintPullScenario`**(同时强制 `RequireImageBindableStorage` 与帧中格式再生)绿,**且逐 trace 用例发布拉取计数**;故障注入:client 被 credit 阻塞时灌满 `SEG_EVENT`,两侧都必须恢复;**日志洪泛下注入一次 backend link 失败,那行 ERROR 必须出现**。
### P10 — sync / query / present 节奏(6 天)
**交付物**:client 铸造 sync 与 query handle;轮询入口成为门铃点 + `MOBILEGL_IPC_POLL_ESCALATE` 饥饿升级;**fence 完成度来自真的逐 fence 退休**(不是 present 水位——那正是 MC 1.21.5 native-heap OOM 的成因);DirectGLES 的非 present fence tick;`Present` 严格 1:1;`MOBILEGL_IPC_PRESENT_CREDIT` 默认 1 + 叠加公式写进文档;逐帧 roundtrip 计数器与**输入延迟直方图**(记录发射时刻 → present 完成时刻);`PLAN.md` §8 末尾的三个独立 `dev` monolith 修复。
**验收**:`XfbPrimitiveQuery`、`PrimitivesGeneratedNoXfb`、`AsyncCompile` 在 split 下绿;**40 个用例上 draw/state/upload 路径的 roundtrip 计数器读零**,条件渲染与阻塞 query 次数逐用例发布而非声称为零;零 timeout 轮询循环测试在有界时间退出(不加 publish 规则它会永远挂住);`bench.sh` 在 `35d0befa` 上配对 A/B:两侧都关采纳时 split 帧时在 monolith 10% 内,输入延迟直方图 p50/p99 记录在案。
### P11 — persistent map 与 ≥16MiB 采纳(8 天;spike B 全否则缩为 2 天)
**交付物**:由 P0 spike B 驱动的 POST 探针档位选择(T2 拒绝 / T1 server 导出 / T0 server 导入);`SEG_ADOPT` 生命周期绑 `completedFrameSerial`;`MOBILEGL_IPC_ADOPT_TIER` 覆盖开关做负面对照。
**验收**:`LargeArenaAdoptionScenario` 在所选档位下绿;`improved-transparency-minecraft-26.3` 与两个 Create fixture SSIM ≥ 0.99;`35d0befa` 上配对 reboot-clean 的 p99 帧时与峰值 RSS 对 monolith 采纳基线(p99 163→21ms、40→115fps、~400MB)——**split 在所选档位下 p99 不得回归超过 10%;若 T2 成为永久答案,其实测代价必须写进文档**。
### P12 — Android 生产窗口路径(10 天)
**交付物**:`android:process=":mgl"` 的 Service 收 Java `Surface`(Binder)后 `ANativeWindow_fromSurface`(minSdk 26 无公开 `ANativeWindow` 扁平化,这条路径是强制的;树内先例是 `android:process=":bench"` 的 `BenchService`);server 生命周期绑 Activity;FCL 用户 env 与 plugin APK V2 开关表接线(**零新增管线**,两条通道都已存在)。
**验收**:Minecraft 通过 FCL 在 spawn 模式下在 `35d0befa` 上双后端入世界;配对 reboot-clean bench + 输入延迟直方图;杀 server 产生干净的 device-lost latch 而不是挂起或崩溃;SIGKILL 故障注入。
### P13 — 退役 pull 路径(6 天)
**交付物**:删 `SnapshotFromGLContext()`、`MGB_CTX` 宏、`MOBILEGL_PIPE_PUSH`(推送成为唯一路径),**保留 `MOBILEGL_PIPE_VERIFY` 工装**;删 `set_residual_value_state` 与 `ResidualValueBlock`;`MG_Backend` 的 `MG_State` include 收缩到共享值头白名单;最终符号/尺寸/CPU 报告。
**验收**:**`static_assert(sizeof(ResidualValueBlock) == 0)` 编译通过**(临时物可证明地消失);`grep -c 'pGLContext' MG_Backend/` == 0;`nm --undefined-only libMobileGLServer.so` 无 `MG_State::GLState::`、无 glslang 符号;全套门(367 × 2 backend × {monolith, split}、428 单元、40 trace SSIM ≥ 0.99、两台设备 CTS 在 `81b17c0b` 基线 0.5pp 内);**monolith 逐线程 CPU 在两台设备的 p50 与 p99 上不差于 P0 基线**——本设计的性能主张在这里成立或倒下。
### 11.5 总估时、里程碑与 CTS 周转
**累计(单跑道)**:P0 8 → **8**;P1 8 → **16**;P2 8 → **24**;P3a 12 → **36**;P4a 16 → **52**;P5 12 → **64**;P6 5 → **69**;P3b/P4b 28 → **97**;P8 14 → **111**;P9 10 → **121**;P10 6 → **127**;P11 8 → **135**;P12 10 → **145**;P13 6 → **151**;**P7(Magma)48-85** → **199-236**。
**报作 200-260 人天**(上界含缓冲)。两个工程师、P7 与 P5/P6/P8 并行 → **约 6-7 个月**,真正的约束是两台设备的争用而不是人头。
**里程碑**:第 **16** 天 verify harness 全绿(零产品风险);第 **24** 天 GO/NO-GO;第 **64** 天首个 `inproc` IPC 帧(**缩减路径**);第 **69** 天首个跨进程帧(**缩减路径**);第 **111** 天全功能 split(P8 出口);第 **151/199-236** 天接口纯度门转绿。
**CTS 周转必须单独计价,不折进阶段估时。** `gl44to46` caselist 约 56,271 例。分层门控:逐阶段只跑该阶段改动可能影响的具名 CTS 块(P4a 的 `packed_pixels`、P3b/P4b 的 `texture_*`/`shader_image_*`、P9 的 `transform_feedback*`),**完整 caselist 只在四个架构边界跑**(P3a handle、P4a framebuffer、P3b/P4b 纹理、P13 纯度)**以及每次合并 `dev` 之前**,且放在 CI 而不是关键路径上。设备锁协议照旧。若实测周转仍主导排期,**诚实做法是加宽估时而不是削弱门**。
---
## 12. 风险与对策
| # | 风险 | 对策 |
|---|---|---|
| **B-R1** | **效率是方案 A 的 3 倍、首帧晚 4-5 倍**(200-260 天 vs 77;第 69 天 vs 第 15 天)。排期驱动的评审可以只凭这一条否掉本方案 | 把价值排在承诺之前:P0-P2(~24 天,其中 16 天是方案 B 独有)交付 handle 化 twin 与内容寻址的渲染状态 CSO——**零 IPC 风险的可测量 monolith 性能工作**——并产出字节计数器与第一个逐线程 CPU 数字。**第 24 天显式 GO/NO-GO。** P13 是一个完全自洽、不含任何 IPC 的 monolith 交付物;P5 的 `inproc` 在 P13 之后是本项目手上最大的单一 CPU 杠杆,只要 12 天 |
| **B-R2** | **中心性能主张未经测量。** 可达性遍历是**搬走**而不是消失;若 client 侧 dirty 走查比它替换掉的拉取更贵,整个论证反转。而树里今天没有任何 per-frame 字节或调用度量 | 字节/调用计数器是 **P0 交付物**,不是后续性能阶段的事(这是历次评审对四个候选设计的共同发现)。每阶段的验收都用**逐线程 CPU 时间**,两台设备、reboot-clean、配对。P2 刻意选最便宜的面,好让机制在花掉 200 天之前就被证明。**先清工作树 per-draw `fprintf`**——它污染每一个基线 |
| **B-R3** | **monolith 字节一致门按构造死亡**,P2 的逐名集成基线也随之移动(参考构建变了) | 五部分替代门,全部在 P0/P1 落地(§10.3),其中第 ② 部分(逐 draw 逐字段影子比对)在语义上严格强于任何符号 diff。两条字节等式仍作断言保留。**在设计文档里明写这条损失,不藏。** P2 的参考构建明确定义为"重构后的 monolith",并要求 P1 出口先证明重构本身是等价的 |
| **B-R4** | **server 发起的纹理拉取是新停顿类**,且触发路径之一(整格式再生 `Managers.cpp:3950-4195`)在普通 `glTexImage` 格式变更上就会触发、无法被 hint 预防 | 三条缓解同时上:`imageBindableHint` 预防主因;**异步**park-and-re-emit 让停顿落在 `mgl-srv-apply` 而不是应用线程;有上限的 ≤32MiB 保留 LRU 让一张纹理不会停顿两次。`TextureRemintPullScenario` 强制两个成因,**拉取计数逐 trace 用例发布**而不是声称为零。若实测率非平凡,保留缓存从可选升为强制并拿真预算 |
| **B-R5** | **P3b/P4b(纹理,28 天)与 P7 中的 `VkTextureManager` 是最大最险的段**,且正压在项目已实测 +6ms/frame 悬崖(rect 列表 vs union box)与 7 条 fallback-repack 路径上(其可行性判定要求 `uploadData == mipData`) | `resource_subdata` **同时**带 box 与 rect 列表、**server 选形状**——决策留在做过那次测量的那一侧。repack 族原地不动,只把输入从拉取的 shadow 指针换成 `MGPBlobRef`(monolith 里是同一个指针)。**在 Mali 设备上专门设门并公布帧时增量**。P3b/P4b 拆成两个可独立落地的半(先 sampler view + sampler,再 image unit + dirty 反转),让回归能二分到其中一半 |
| **B-R6** | **tracker 完整性**:推送之后 server 不能再重读活状态来校验快路径命中,它必须信任上一次推送。任何 tracker 忘记发的 mutator 会静默漂移,直到某个场景恰好触及。历史上最危险的正是这个形状——DSA by-name 模拟曾在不移动任何计数器的情况下交换一个纹理单元绑定(`DirectGLES.cpp:1441-1465`) | 三层:**(1) 构建期** G5 的 written-once 位图断言每个 `kCtxState` 组在第一个 verb 消费它之前被写过;**(2) 运行期** poison mask 在读未填字段时 `Fatal{UnmigratedPipeInput}`;**(3) 语义** `MOBILEGL_PIPE_VERIFY` 逐 draw 逐字段比对,专抓"dirty 位触发得太少"这个危险方向。删掉 `SnapshotFromGLContext()`(P13)之后完整性成为构建期事实。**迁移粒度是一个 accessor**,所以一次遗漏被限制在一个阶段 B 步骤内。477 行 inventory 保留为 tracker 侧检查表 |
| **B-R7** | **`AcquirePersistentMap` 跨进程无解**会葬送 MC 26.3 的结果(p99 163→21ms、40→115fps、省 400MB),而没有任何目标平台的支持被验证过 | **显式隔离**:改造期完全不碰(它已经是一个返回指针的显式调用,原样穿过 P0-P13),只有 IPC 那一步会打破它。决策交给 `PLAN.md` §6.8 的三档 POST 探针与 **P0 第一周的 spike B**。T2(拒绝)前端已在三处容忍并让 client 侧块推送成为强制(P5 就交付)。若两台设备都否,P11 从 8 天缩为 2 天的文档与负面对照。**不让一个平台未知数挡住 200 天的接口工作** |
| **B-R8** | **D18 的节点式容器纪律在重构中丢失**:`m_renderbufferResources` / `m_textureResources` 是**故意**用 `std::unordered_map` 而非本项目开放寻址的 `UnorderedMap`,因为调用方跨后续查表缓存 `Resource*`;一次扩表搬迁曾让 `BlitFramebuffer` 静默停在 "source image layout is undefined"(postmortem `VkRenderPassManager.h:375-397`)。把它"优化"回去正是一次大重构最容易犯的错 | D18 是全表**唯一**标为 UNCHANGED 的身份行,理由写进设计正文:接口对此零约束,`ska` 的 erase-shift 让 hazard 更糟而不是成为历史,**postmortem 注释必须逐字带进 P7 的 review checklist**。slot 数组在插入下稳定,实际改善了处境——但仍然点名,防止有人顺手改回去 |
| **B-R9** | **逐 backend 的行为不对称被统一接口抹平。** Magma 故意不注册 `ResidentSubData`(`VkBufferManager.cpp:104-111`),差别是 `glBufferSubData` 在活 coherent map 上的排序语义;`PrefersCpuXfbPrimitiveAccounting`、DirectVulkan 留空的 8 个槽、不同的 multi-draw 分档同形。强制统一会静默改变 Magma 行为并发布一次 Minecraft chunk 撕裂 | 可选性是**接口的一等属性**:null 项在本代码库里**已经**表示"未实现,前端回退"(`BackendObject.h:212-215, 265-269`),`MGPCaps` 携带显式的 `callMask` 能力位集,取代今天 `GL_Query.cpp:471, 545, 768` 的隐式槽位空否探测。**cap 位集同时决定每个 emulation 在哪一侧 lowering**,所以不对称成了机制而不是疣 |
| **B-R10** | **接口在未测量的形状上过早冻结**:渲染状态 CSO 粒度、`resource_subdata` 的 box+rect 双载荷、sampler-view 解析边界、screen/context 划分全在 P0-P2 定死;而若干 server 侧缓存的容量是按拉取模式调的(Magma 的 2048 槽 `VaoDrawMemo`、4 个 `SetupDrawSnapshot`、8 个 pipeline memo、8 个 `syncedTextureMemo`;Espryt 的 4096/256/64 槽 `TwinLookupMemo`) | payload 结构从第一天走 structSize-first 版本纪律(复用 `Feat/CS-Delta-IPC` 的 `mg_protocol_base.h` 词汇),可增长。字节计数器在 P0 落地,让 P2 的 CSO 粒度决策有数据。**P13 在计数器活着的情况下重调所有幸存缓存的容量**,并把它们当作带 env 覆盖的调优参数而不是常量。screen/context 划分在 P0 定进头文件但按 context 计数 == 1 实现——现在免费,事后是一次全面重编号 |
| **B-R11** | **58 行非箭头 `pGLContext` 用法的迁移缺口**:三份候选设计都提了"机械 sed 293 处",都没交代这 58 行;其中 `DirectGLES.cpp:146` 的 `.get()` 裸指针捕获 `sed` 完全抓不到 | §2.4 已把它们逐形态分类。P1 的交付物**包含这份 58 行清单的逐条转换**,不是"顺手处理"。**纯度门 grep 的是 `pGLContext` 而不是 `pGLContext->`**,所以漏一条就红 |
| **B-R12** | **残余值块是迁移期边界上的一个洞**:poison mask 抓得到"未填字段",抓不到"两侧布局不同"——而这正是一个异质 POD 并集跨编译器/ABI 边界最容易出的问题,且 monolith 的 verify harness **看不见它**(monolith 里两侧是同一个 TU) | 逐成员 `offsetof` 断言(不只是 `sizeof`)**加上** split 模式下逐字段序列化(走 G3 生成的编解码器)而不是整块 memcpy。块的字节量单独计一类,让"它还有多大"可见。`static_assert(sizeof == 0)` 让退役是编译错误 |
| **B-R13** | **`SEG_EVENT` 的 ERROR 无损化重新引入 §7.4 要防的死锁**:一个 shader 风暴产生的 `MGLOG_E` 洪泛可能停住 apply | 每秒 ERROR 速率限制器,超限发一条显式的 "N errors suppressed";`MGLOG_E_ONCE` 的 latch 变 per-server(这正是想要的);P9 的故障注入门要求"日志洪泛下注入一次 link 失败,那行 ERROR 必须出现"**且**"两侧都恢复" |
| **B-R14** | **排期估计偏乐观**:独立的改造成本分析给出 backend 工作**单独** 202-266 天;本文的 200-260(含 IPC)落在其乐观端,而 P7 的 48 天下界明显低于同口径的 85-111 | **两个再基线检查点**(P3a 超期 >50%、P7 中点完成度 <40%),且 P7 的上界 85 已经写进总估时区间。CTS 周转**单独计价**不折进阶段。若两个检查点任一触发,先跑 `inproc` 的证伪数字再决定是否继续——这与 `PLAN.md` R15 的退火思路一致 |
| **B-R15** | **在 GL setter 时刻推送**(而不是 draw validate 时刻)会让整件事变慢,且这是最容易被后续实现者做错的一处 | 写成规范条款并给出理由与证据(`DirectGLES.cpp:2044-2046` 的 Blaze3D per-batch blend toggle);P2 的设备门直接暴露它(monolith-push 必须在 p50 **与** p99 逐线程 CPU 上落在噪声内);两个高水位标记(`TouchBindPoint`、`NoteUnitTouched`)明确要求留在 tracker 走查里 |
---
## 13. 开放问题
1. **client 侧 dirty 走查的真实每 draw CPU 代价是多少?** 中心性能主张是"遍历搬走而不是翻倍",树里没有任何度量。这是 P2 的头号数字,按逐线程 CPU、两台设备报。
2. **真实语料上纹理重铸拉取的实际发生率?** `imageBindableHint` 能预防主因,但整格式再生(`Managers.cpp:3950-4195`)在普通 `glTexImage` 格式变更上就触发。若 MC 或 Iris fixture 上的实测率非平凡,保留 LRU 从可选升为强制并需要真预算。
3. **`AcquirePersistentMap` 跨进程能不能成?** P0 spike B 第一周回答。未验证:`VK_KHR_external_memory_fd` 的 host-visible-coherent 支持在 Adreno 830 / Oppo Mali / lavapipe / Windows ANGLE lane 上的可用性;GLES 侧能否用 `GL_EXT_memory_object_fd` + `glBufferStorageMemEXT` 走同一条路(Espryt 今天的采纳走的是 `glBufferStorageEXT` + `glMapBufferRange(PERSISTENT|COHERENT)`,不是外部内存)。
4. **渲染状态的 wire 粒度定多少?** ~1.2KB 整块 vs 三个现有段 vs 逐 dirty 位子集。P0 落计数器、P2 用数据定。CSO LRU 的容量(暂定 64)同理。
5. **`MG_Util` 的切割缝在哪里?** server 需要 SPIRV-Cross pass 流水线、ESSL 转译缓存、像素/纹理格式处理器、POST 探针、loader;client 需要 glslang phase A/B 与反射层。`ProgramObject.h` 传递性 include `ShaderObject.h → ShaderCompileTask.h`,所以任何链接真 `ProgramObject` 的 client 都会拉进编译机制。**没有审计过是否存在一条干净的 Transpile-vs-Reflect 缝。**
6. **一份反射归档能服务三个消费者吗?** Espryt 读前端表,Magma 跑 SPIRV-Reflect,而 `DirectVulkan.cpp:161` 为 `glGetProgramResource*` 又反射了第二遍。没有确认一份 payload 能同时满足三者。
7. **viewport-array 回放能塞进一次 `draw_vbo` 吗?** 今天它从 14 个 draw 入口经 `ForEachViewportRoutingPass` 重发应用的 draw N 次,而 `EndViewportRoutingPasses` 会调 `InvalidateSyncedRenderState`(`DirectGLES.cpp:3841`)。没有验证单次 pipe 调用驱动下各遍之间观察到的状态是否与今天一致。
8. **`ResidentSubData` 的不对称该怎么收口?** null 项保住今天的行为,但拆分工作可能正是给 Magma 补一个真实现的时机——那是**行为变更而不是重构**,应当作为独立的 `dev` PR。
9. **`SEG_STAGE` 的上限定多少?** 现在多了四类字节(client 顶点数组、client 索引数组、multi-draw 参数块、client 解析的 indirect 命令块)。体量不变但都走 ring slot。需要 P8 之后用 MC in-world 与 Create 两类 fixture 的 `stage-*` 计数器给 p99 占用。
10. **`FramebufferSrgb` / `DepthClamp` 无存储是潜伏 bug 还是有意为之?** 六个 backend 消费者今天读到恒定 false(`RenderState.cpp:380, 428-429`)。**必须在渲染状态 blob 冻结之前回答**,因为它决定接口里要不要这个字段。
11. **P13 之后 `MOBILEGL_IPC_VALIDATE_SERVER` 还有对应物吗?** 方案 A 靠"server 侧保留 `MG_Impl` 校验器"把分歧变成 server GL error。方案 B 的 server 没有 `MG_Impl`,所以 split-only 的渲染 bug 没有第二意见。什么替代那个诊断能力?
12. **client 侧 restart 重写与 indirect-count 解析会不会改变可观察行为?** 两者今天都以 `m_valid=false` 加诊断的方式退出(`DirectGLES.cpp:4405-4409`、`:4685-4688`);把 decline 移到 client 改变了哪个线程打这条日志,也可能改变它相对 GL error 的顺序。
13. **Magma 的两个内部 shader 烘焙后,uniform location 与 UBO 布局能否在没有活 `ProgramObject` 的情况下表达?**(`VulkanRenderer.cpp:4238-4241, 4319-4324, 8450-8452`)未做原型。
14. **推送模型会改变哪些按拉取模式调过的缓存命中率?** Magma 的 2048 槽 `VaoDrawMemo`、4 个 `SetupDrawSnapshot`、8 个 pipeline memo、8 个 `syncedTextureMemo`;Espryt 的 4096/256/64 槽 `TwinLookupMemo`(后者会消失)。幸存者的容量要在 P13 重调。
---
## 14. 对方案 A 文档与 `Feat/CS-Delta-IPC` 的复用清单
### 14.1 对 `PLAN.md` 的复用(本文的 §8.1 是权威列表,这里给的是"取/改/弃"总账)
| 判定 | `PLAN.md` 章节 |
|---|---|
| **原样取(不复述,以 `PLAN.md` 为准)** | §6.1(段布局、shm 矩阵、`SCM_RIGHTS` 第一优先、`SEG_SHADOW` 退休规则);§6.2/§6.2a(`RingControl`、双向 doorbell);§6.3(记录格式与运行期边界检查);§6.4(WAR 纪律与 P4.5 零拷贝);§6.5(ring 分配与背压);§6.6 前三条;§6.7 第 2、5 行;§6.8(三档采纳与 POST 探针);§7.1-§7.3;§8 末尾(真 fence 退休 + 三个独立 `dev` 修复);§9-§9.3;§10(线程模型、核心放置、拆机顺序);§11.1-§11.6;§12 第 1-3 层与 §12.4;§13(目录、一库两角色、submodule guard、三个 ctest 陷阱、`SPLIT` 接线、CI 门);§15 P0 的卫生与两个 spike |
| **取并改** | §7.4(事件通道:**`on_log` 按严重级分级**,§7.4);§12.2(隔离从四个进程全局降到**两个**);§5.10(第 2、3 条逐字取,第 1 条缩成一个 `hasLiveHostWrites` 位);§6.10(四类应用指针改由 cap 门控 lowering;**陈旧索引纪律逐字保留并加门**);§5.9a(READ 面**编目**生成器改为**禁止**门);§6.4 的拷贝账(删掉第 (3) 行,P1-4=3 / P4.5=2,`PLAN.md` 的"方案 B"目标结构性达成) |
| **弃** | §5.0、§5.1、§5.2、§5.4 的 replica 对象表规则与 `Fatal{IdentityDivergence}`、§5.6a、§5.7 的 Phase 1-4 分支与 `SetReplicaResolvedDrawProgram` 钩子、§5.9b 与全部 mutation 覆盖机制(`gen_impl_mutation_surface.py`、`MutationCoverage.def`、`ImplMutationSurface.inc`、`MG_Remote::Shared::`)、§6.9 的 relink 档与 `MOBILEGL_IPC_PROGRAM`、§12 第 4 层的字节一致断言、`Server/ReplicaContext.*`、阶段 **P5**、风险 **R1** 与 **R6**、开放问题 **§17-5** |
| **新增(`PLAN.md` 没有的)** | `MG_Pipe/` 全套与六个生成器;`PipeInputs` + poison mask;`MOBILEGL_PIPE_VERIFY` 影子比对;残余值块与它的编译错误退役绊线;`on_texture_pull_request` / `on_texture_writeback` / `on_mip_levels_generated` 三个事件;纹理拉取的三条缓解与计数器;`HandleRecycleScenario` / `TextureRemintPullScenario` 两个场景;`RenderbufferObject::GetLifetimeId()`(**不含 `GetVersion()`**);D21 的潜伏 bug 修复 |
### 14.2 对 `Feat/CS-Delta-IPC`(worktree `../MobileGL-CS`)的复用
`PLAN.md` §14 的 REUSE / CHANGE / DROP 判定**整体继承**。方案 B 的四处差异:
| 条目 | `PLAN.md` 判定 | 方案 B 的差异 |
|---|---|---|
| `docs/CS_Refactor/HandleSessionGeneration.md`(`546895aa`) | REUSE,三处修改,其中"handle 清单补 `RenderbufferObject::GetLifetimeId()` **与 `GetVersion()`**" | **只补 `GetLifetimeId()`**。`GetVersion()` 只是 replica 的 delta 触发器;推送模型里 `glRenderbufferStorage*` **本身**就是一次 pipe 调用 |
| `docs/CS_Refactor/backend_read_inventory.md` + `extract_backend_read_inventory.py`(`546895aa`) | CHANGE 成 `gen_backend_state_surface.py`,未知 accessor 一律 UNMAPPED 并编译失败 | **同意其修正**(删掉制造"0 UNMAPPED"的 `GetBuffer*`/`GetTexture*`/`GetProgram*`/`GetVertex*` 前缀兜底规则 `:234-241`,把真 pull point 与 signature handle 化分开统计),但**用途改变**:它变成 tracker 侧的**覆盖检查表**(G6),而真正的门是 §4.7.2 的**纯度门**——禁止那次读,比编目它强得多,且不会过期。**另外:`gen_impl_mutation_surface.py` 在方案 B 里不存在**(没有 replica 就没有 mutation 要 replay) |
| `MobileGL/RemoteClient/StateEmitter.h:39-307`(仅 emit 半边,`b50f3348`+`d96be9f3`) | CHANGE,各域字段遍历抬进 `WireMirror` | **更直接可用**:在 MGPipe 下那些字段集**就是** pipe 的状态对象 payload(`MGPFramebufferState`、`MGPVertexElements`、`MGPSamplerView` …),不再是一份 delta 目录。同样必须修的缺陷不变:GL name 换 lifetimeId(`:48-49, 85, 166-168, 203, 230`)、O(n²) 线性扫描换 handle map(`:175-181, 244-249, 253-258, 293-298`)、固定 6 attachment(`:232-236`)换 `MaxColorAttachments`、补上被跳过的 texture view(`:70-74`)。**applier 半边(`:312-501`)仍然不取** |
| `MobileGL/Protocol/mg_protocol_base.h`(`546895aa`) | REUSE(干净无依赖的词汇) | **同意**,且 **structSize-first 版本纪律是 B-R10 的对策**:payload 结构体从第一天就可增长 |
**DROP 名单完全一致**:`bfa.h`(480 行,"strict C ABI" 名不副实,且手抄的 60 字段 `MobileGLDynamicParameters` 是本项目已造成 481 例 CTS 失败簇的那类静默漂移炸弹)、`mgruntime_api.h` + `UtilRuntime/*`、`LocalSocketTransport` 的实现(每次 send 的 UAF、无上限分配、**`fd=-1` 硬编码**)、`ServerHost/main.cpp`(编译不过)、`StateEquivalenceTest.cpp`、`c7c9e346`+`29d721ef` 的 share-group sessioning、`b50f3348` 的 `RenderState::InstallParameters` + 裸 `public:`、`d96be9f3` 的 per-draw `fprintf` TRIAGE 指令。
---
## 附 A:接口调用目录速查表
> 完整 payload 定义见 `MG_Pipe/MGPipeTypes.h`;本表按 `PipeCalls.def` 的顺序。Flags:`A`=`kNeedsAck`、`B`=`kHasBlob`、`V`=`kVarTail`、`H`=`kHostSpan`、`R`=`kReplySlot`、`O`=`kOptional`(可为 null)。
### `MGPipeScreen`(14)
| 调用 | payload | flags | 取代 |
|---|---|---|---|
| `get_caps` | `MGPCaps` | R | 40 `pActiveBackendObject->` + 89 caps 读点 |
| `resource_create` | `MGPResourceDesc` | — | buffer/texture/renderbuffer 创建 |
| `resource_respecify` | `MGPResourceDesc` | — | `BufferBackendOps::Respecify` 泛化 |
| `resource_destroy` | handle | — | `OnDestroy` + 两个 `WeakPtr` GC 扫描 |
| `map_persistent` / `unmap_persistent` | handle | R, O | `AcquirePersistentMap`(改造期不碰) |
| `fence_create` / `fence_status` / `fence_wait` / `fence_destroy` | handle (+timeout) | — / — / R / — | `FenceSync`…`GetSyncStatus`(两值契约保留) |
| `query_create` / `_begin` / `_end` / `_available` / `_result` / `_destroy` | handle + kind | — | `BackendObject.h:230-256` |
### `MGPipeContext` — CSO(15)
`create/bind/delete` × `render_state` / `vertex_elements` / `sampler` / `sampler_view` / `shader`。
`create_render_state` 带 `B`(整块或变化段);`create_shader_state` 带 `B`(SPIR-V + 反射归档)。
### `MGPipeContext` — `set_*`(14 + 1 临时)
`set_framebuffer_state` · `set_vertex_buffers` · `set_index_buffer` · `set_indirect_buffers` · `set_sampler_views`(V) · `bind_sampler_states`(V) · `set_shader_images`(V) · `set_shader_buffers`(V) · `set_stream_output_targets`(V) · `set_global_constants`(B) · `set_vertex_attrib_defaults` · `set_pixel_pack_state` · `set_patch_state` · `set_draw_program` / `set_dispatch_program`
**临时(P2..P13)**:`set_residual_value_state`(B),带 `static_assert(sizeof(ResidualValueBlock)==0)` 退役绊线。
### `MGPipeContext` — transfer(12)
`resource_subdata`(B,V) · `buffer_subdata_resident`(B,O) · `resource_flush_range` · `resource_readback`(R) · `resource_copy_region` · `blit` · `clear` · `generate_mipmap` · `read_pixels`(R) · `get_texture_image`(R)
### `MGPipeContext` — 命令(10)
`draw_vbo`(H,V) · `launch_grid` · `memory_barrier` · `begin/end/pause/resume_stream_output` · `flush` · `present` · `set_swap_interval`(O)
### 反向:`MGPipeCallbacks`(9)
`on_gl_error` · `on_gpu_written` · `on_buffer_writeback` · `on_texture_writeback` · `on_texture_pull_request` · `on_mip_levels_generated` · `on_surface_changed` · `on_caps_invalidated` · `on_log`(**≤WARN 有损 / ≥ERROR 无损 + 速率限制**)
### 显式删除
`GetIntegeri_v` · `GetInteger64i_v` · `GetProgramiv` · `ShaderStorageBlockBinding`(折进 `MGPProgramDesc`)· `set_pixel_unpack_state`(不存在)· 压缩格式概念(不存在)· `pipe_transfer`(不存在)
---
## 附 B:环境变量与 CMake 选项
### CMake
| 选项 | 默认 | 说明 |
|---|---|---|
| `MOBILEGL_BUILD_DISAGGREGATED` | OFF | 出货形态。开启后 `MG_Remote/**` 进 `SOURCE_FILES`,支持 `spawn`/`unix:`/`pipe:`。**两个**进程全局保持普通全局,GL 热路径无 TLS |
| `MOBILEGL_BUILD_DISAGGREGATED_INPROC` | OFF | CI/调试形态,隐含开启上者,额外加角色隔离 shim(方案 B 下只需隔离 `gPipeCtx` 与 `pActiveBackendObject`) |
| `MOBILEGL_FLATC_EXECUTABLE` | 空 | 只服务 CI 的 `flatc-check`;默认构建图里没有 `flatc` |
| `MOBILEGL_BAKED_INTERNAL_SHADERS` | ON(P7+) | DirectVulkan 的 blit/depth-mipmap shader 烘焙成签进树的 SPIR-V,由 `MG_Test` 重跑树内 glslang 逐字节比对守新鲜度。**monolith 也受益**(少一次启动期 glslang 编译) |
> 注:`MG_Pipe/**` **不在任何 option 之后**——它是 monolith 的架构,永远进构建。
### 运行时(方案 B 新增)
| 变量 | 默认 | 说明 |
|---|---|---|
| `MOBILEGL_PIPE_PUSH` | 迁移期按阶段推进;P13 后删除 | 子系统位图(0 = 全 pull)。**任何提交都能在同一份二进制上按子系统 A/B** |
| `MOBILEGL_PIPE_VERIFY` | 0 | 逐 draw 逐字段影子比对(~5-10× 慢,永不出货,P13 后保留为工装) |
| `MOBILEGL_PIPE_STATS` | 0 | 字节/调用/roundtrip/纹理拉取/残余块计数器转储 |
| `MOBILEGL_PIPE_TEXEL_RETAIN_MB` | 32 | 纹理重铸拉取的保留 LRU 预算上限(§7.5c) |
### 运行时(继承 `PLAN.md` 附录)
`MOBILEGL_TRANSPORT`(`monolith` 默认 / `inproc` / `spawn` / `unix:<path>` / `pipe:<name>`)· `MOBILEGL_IPC_SERVER_PATH` · `MOBILEGL_IPC_RING_MB`(8) · `MOBILEGL_IPC_STAGE_MB`(32,上限由实测定) · `MOBILEGL_IPC_PRESENT_CREDIT`(**1**) · `MOBILEGL_IPC_SPIN_US`(50) · `MOBILEGL_IPC_POLL_ESCALATE`(64) · `MOBILEGL_IPC_PERSISTENT_BLOCK_KB`(64) · `MOBILEGL_IPC_ADOPT_TIER`(auto) · `MOBILEGL_IPC_SHADOW_SHM`(1,P4.5+) · `MOBILEGL_IPC_INLINE_PAYLOADS`(0,负面对照) · `MOBILEGL_IPC_SERVER_AFFINITY`(auto) · `MOBILEGL_IPC_STRICT_ERRORS`(0,诊断) · `MOBILEGL_IPC_AUDIT`(0) · `MOBILEGL_IPC_TRACE`(0) · `MOBILEGL_IPC_ATTACH`(空) · `MOBILEGL_IPC_RESPAWN`(0) · `MOBILEGL_IPC_IDLE_EXIT_S`(30)
**删除**:`MOBILEGL_IPC_PROGRAM`(没有 relink 档)· `MOBILEGL_IPC_VALIDATE_SERVER`(server 没有 `MG_Impl` 校验器——替代诊断手段见开放问题 11)
**保留的既有负面对照开关**:`MOBILEGL_ESPRYT_DISABLE_UBO_RING` · `_UNPACK_RING` · `_UPLOAD_RING` · `_INVALIDATE_FLUSH` · `MOBILEGL_DISABLE_LARGE_BUFFER_ADOPTION` · `MOBILEGL_COHERENT_AS_FLUSH`(**在拆分模式下照常生效**,这样两个 `coherent_as_flush: true` 的 Create fixture 在 split 与 monolith 下走同一条 buffer 路径,逐名对比才有意义)