From 37da3c3a0780b9e8bd5afc1001c5ef9df6f44dd2 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 8 Sep 2026 10:23:59 -0400 Subject: [PATCH] [Docs] (Disaggregated): record the P3a landing - the five-part gate on the finished tree, the three seam defects the real-path round found, the Track H census, the two-device three-arm A/B on Release APKs (pull / P2-only 0x7f / P3a) with the P2 -O0 erratum, DriverBench T1/T2 against the 0x7f arm, the exit-order UAF closure, and the rulings (ID-13/15/17) --- docs/Disaggregated/ARCHITECTURE.md | 18 ++- docs/Disaggregated/MEASUREMENTS.md | 221 ++++++++++++++++++++++++++++- docs/Disaggregated/README.md | 6 +- docs/Disaggregated/ROADMAP.md | 15 +- 4 files changed, 245 insertions(+), 15 deletions(-) diff --git a/docs/Disaggregated/ARCHITECTURE.md b/docs/Disaggregated/ARCHITECTURE.md index 9c22585d..a6b4405f 100644 --- a/docs/Disaggregated/ARCHITECTURE.md +++ b/docs/Disaggregated/ARCHITECTURE.md @@ -62,6 +62,8 @@ CSO 在 client 侧内容寻址(Mesa `cso_cache` 先例):每类一张 `ska::flat_hash_map`,容量上限 render-state 64 / vertex-elements 1024 / sampler 256 / sampler-view 4096 / shader 跟随 `ProgramObject` 生命周期,LRU 淘汰时发 `delete_*`。两个不同 program 设置了相同状态时 server 零状态转换。 +**[deviation] D-G1(P3a 落地):vertex-elements CSO 在 P3a 是身份寻址,不是内容寻址。** Espryt 根本没有 vertex-elements CSO,它有的是**逐 VAO 的 twin**(`BackendVertexArrayObject`,`MobileGL/MG_Backend/DirectGLES/Managers.h:957-1163`),twin 持有一个驱动 VAO 名(`:1096`)、32 个 client-array scratch buffer id(`:1097`)与 32 个 fp64 scratch id(`:1101`);两个格式相同的前端 VAO 不能共享它,因为驱动 VAO 同时持有 element-array 绑定与逐属性缓冲绑定,共享 CSO 会把它们变成每次 `BindVertexElements` 都要重发——严格比今天更慢。所以 P3a 逐前端 `VertexArrayObject` 铸一个 `VertexElementsCso` 句柄(配置变化时**在同一句柄上重发** `CreateVertexElements`,`MGPipeHandle::Gen` 只在槽位复用时递增),**这是一个命中率恒为 1 的合法内容寻址缓存**。上面那张 1024 项的内容寻址表是 **P7** 的活——Magma 的 `VertexInputStateFactory` 接管 CSO 时,`VkPipelineVertexInputStateCreateInfo` 要的正是内容寻址;它加在 P3a 同一组 `CreateVertexElements`/`BindVertexElements`/`DeleteVertexElements` 与同一个 slot 分配器**之上**,P3a 的线上形状与 applier 记录都不妨碍它。 + ## 3. 调用目录(P0 已落地) ### 3.1 单一真相源 @@ -127,7 +129,7 @@ Flags:`kNeedsAck`(调用方等 server 确认;目录里目前无条目携 |---|---|---| | `MGPResourceDesc` | 88 | buffer / 全部纹理 target / renderbuffer 一个判别式 create/respecify 形状;`BindMask` 的 `ELEMENT_ARRAY` 位是索引镜像的开关;`ImageBindableHint` 预防性分配 image-bindable 存储;`ViewOf` 是纹理视图的存储属主(server 侧 keep-alive);`BufferForTexBuffer/BufOffset/BufSize` 实时解析(`kMGPipeWholeBuffer = ~0`)。Renderbuffer 保持独立类(自己的 format-capability target、`ComponentSizes`、twin) | | `MGPRenderStateDesc` / `MGPBindRenderState` / `MGPDynamicState` | 48 / **12** / 32 | §5.3 | -| `MGPVertexElements` | 40 | blob 同时带解析后的 `VertexAttribute[]` **和** `VertexBufferBindingPoint[]`,缺一不可(pointer 调用的 stride 0 = element size,binding 模型的 stride 0 = 每顶点读同一 element);`IsLong` 与 `Type == Float64` 分开携带;仅供查询的 `LegacyStride/LegacyPointer` 留在 client | +| `MGPVertexElements` | 40 | blob 同时带解析后的 `MGPVertexAttribWire[]` **和** `MGPVertexBindingPointWire[]`(P3a 落地的两个 POD 线上形,24 B / 16 B,`MobileGL/MG_Pipe/MGPipeValueTypes.h:568-600`)。**两个视图都过线的理由是记录自洽,不是 stride 消歧**:前端已经把 pointer 调用的 stride 0 解析成 element size,一个活到 `VertexAttribute::Stride` 的 0 只可能来自 binding 模型(`MobileGL/MG_Pipe/MGPipeValueTypes.h:496-503`),后端从来不读 binding point(`MG_Backend` 里 `VertexBufferBindingPoint` / `GetAttributeBindingIndex` / `GetAttributeRelativeOffset` 零命中);真正承重的是 `MGPVertexElements` **声明**了 `BindingPointCount`,一条不描述自己 blob 的记录会让 applier 的边界门永远无法收口。代价按配置变化付一次、不按 draw 付(blob 只搭 `CreateVertexElements`),裁掉第二个视图是 P13 的重调项。`IsLong` 与 `Type == Float64` 分开携带;`Divisor` 不在属性视图里(走 `MGPVertexBuffer::Divisor`);仅供查询的 `LegacyStride/LegacyPointer` 留在 client | | `MGPSamplerDesc` | 32 | `SamplerParameters` 逐字节过线**含 `borderColorForm`**(三种 border color 表示永远都被数值填满,没有它后端无法在 `Iiv`/`fv` 或 `VkBorderColor` 家族间选择) | | `MGPSamplerView` / `MGPTextureParams` | 36 / 32 | view 只带视图限制(min/num level、min/num layer、别名格式);纹理参数(base/max level、swizzle、depth-stencil mode、LOD 钳、`ForceResync`)挂在纹理对象上 | | `MGPProgramDesc` | 192 | 逐 stage SPIR-V blob ×6 + 反射归档 blob + `StageMask`/`GlobalUboSize`/`ReservedNumSamplesOffset` + 四个状态字节,§7 | @@ -281,7 +283,7 @@ GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 samp | `OnLog(level, text)` | ≤WARN 有损,≥ERROR 无损 + 速率限制 | | `OnXfbScatterReady(scratch, packedStride, vertices)` | §8.5 | -95 个写回点的其余归属:`MarkStorageDirty` 大多是 server 本地记账(零消息);后端凭空造的前端对象(Magma 占位纹理、swapchain default-FB 占位)→ server 原生;`SetBackendResource` 删除(server 拥有资源表);`SetBackendStateMemo`(前端 VAO 里存后端堆裸指针)直接删除;`SetBackendHashMemo/AuxMemo` → server 侧 per-slot 字段。20 处 `SyncPersistentMappedRange` + 6 处 `SyncGpuWrites` 按 §5.7 逐站点归属,其中至少一处消费者搬不走:Magma 的 `ResolveUniformBufferPayload` 把具名 UBO 打进自己的 UBO ring → `SetShaderBuffers` 的 host payload(D-B8)。 +95 个写回点的其余归属:`MarkStorageDirty` 大多是 server 本地记账(零消息);后端凭空造的前端对象(Magma 占位纹理、swapchain default-FB 占位)→ server 原生;`SetBackendResource` 删除(server 拥有资源表)——**[deviation] D-K(P3a):P3a 并没有删它**。`PipeResource::m_backend`、`SetBackendResource`、`ReleaseBackend`、`BackendBufferResource` 在 push 下只是**不再被写**(buffer twin 已经搬进第七张 slot 表,§9.5),真删掉会移动 pull 构建里 `sizeof(BufferObject)`,那是直接的 G1 破坏;这组删除随 pull 路径在 **P13** 退役。`SetBackendStateMemo`(前端 VAO 里存后端堆裸指针)直接删除;`SetBackendHashMemo/AuxMemo` → server 侧 per-slot 字段。20 处 `SyncPersistentMappedRange` + 6 处 `SyncGpuWrites` 按 §5.7 逐站点归属,其中至少一处消费者搬不走:Magma 的 `ResolveUniformBufferPayload` 把具名 UBO 打进自己的 UBO ring → `SetShaderBuffers` 的 host payload(D-B8)。 ### 8.2 有序性是正确性要求 @@ -290,7 +292,7 @@ GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 samp ### 8.3 错误、ack 与日志 - 纹理分配的 OOM 在 monolith 里就已推迟到 sync 时刻(`glTexImage*`/`glTexStorage*` 只 `MarkStorageDirty`,Espryt 惰性分配;连 `glRenderbufferStorage*` 也在 `SyncToBackend` 里惰性做),拆分不改变可观察行为,这批不同步 ack。 -- **唯一允许同步 ack 的入口是 `glBufferStorage`(真同步分配)**。`glRenderbufferStorage*` 不 ack:41 个 trace fixture 里 OOM 探测惯用法出现 0 次(9 次调用散在 5 个 fixture,无一在 3 个调用内跟 `glGetError`;语料里的成功性检查是 `glCheckFramebufferStatus`,client 本地作答)。目录里目前没有条目携带 `kNeedsAck`(`ResourceRespecify` 是 `kNone`),标记随 P3a 的 buffer 路径落地。 +- **唯一允许同步 ack 的入口是 `glBufferStorage`(真同步分配)**。`glRenderbufferStorage*` 不 ack:41 个 trace fixture 里 OOM 探测惯用法出现 0 次(9 次调用散在 5 个 fixture,无一在 3 个调用内跟 `glGetError`;语料里的成功性检查是 `glCheckFramebufferStatus`,client 本地作答)。**P3a 起 `ResourceRespecify` 携带 `kNeedsAck`,并且带一个逐记录谓词**:flag 是**逐调用**的静态属性,而 `ResourceRespecify` 同时服务 `glBufferData` 与 `glBufferStorage`,裸 flag 会把 Minecraft 的整块 chunk 上传变成每 store 一次往返。所以真正拍板的是 `MGPipeResourceRespecifyNeedsAck(desc) == (desc.Immutable != 0)`(`MobileGL/MG_Pipe/MGPipeTypes.h:680`),`PipeCalls.def:18-24` 的图例把这条规则写在目录里(调用行 `:82`),`MG_Test/Pipe/PipeCatalogueTest.cpp:552-585` 的 `ResourceRespecifyAcksOnlyImmutableStorage` 对两种惯用法各钉一次。monolith 下 ack 是 `((void)0)`(applier 只隔一次函数调用),P5 把门铃接到这个谓词上。 - 其余错误一律晚到,走有序的 `OnGlError`。 - `OnLog` 分级:≤WARN 有损(覆盖最旧 + `eventDropped` 计数);≥ERROR 无损,加入触发 `eventRingFull` + 停止 apply 的语义事件集;每秒 ERROR 速率限制器,超限发一条 "N errors suppressed";`MGLOG_E_ONCE` 的 latch 变 per-server。理由:后端 link 失败只以一行 ERROR 呈现,统一有损会让最有诊断价值的那一行在日志压力下消失。 @@ -366,6 +368,12 @@ Track V 的 55% 不需要逐字段接口条目就能跑起来,所以 P2 发一 `MOBILEGL_PIPE_PUSH` 子系统位图(含一位关闭 CSO 内容寻址,负面对照)在阶段 B 是真正的旧-vs-新 A/B;阶段 C 之后不是——位清零时 `SnapshotFromGLContext()` 仍要合成句柄,后端仍跑重键后的 memo 代码,一个重键 bug 两臂都在。对策:**编译期** `MOBILEGL_PIPE_LEGACY_MEMOS`(默认 ON)在 P3a/P4a 期间保留 registry / `TwinLookupMemo` 实现活在同一个 `PipeInputs` 接口之下,随 pull 路径在 P13 退役(各阶段 +1 天维护)。 +**P3a 的臂,逐项点名**(两个子系统的 arm 判定在 `MobileGL/MG_Backend/DirectGLES/Managers.cpp:2292`(resources,位 7)与 `:2312`(vertex input,位 8),逐进程各打一行 `MGLOG_D`): + +- **buffer 家族的 pre-handle 臂是 `Ops_*` 表加 `g_glesBufferBackendOps`,它们无条件编译**,不在 `MOBILEGL_PIPE_LEGACY_MEMOS` 之下——所以位 7 单独清零永远有一条真臂可跑,`NoArm` 对这个家族不可达(`Managers.cpp:2294-2298` 把这句写在代码里)。前端的十一处分发点(`MobileGL/MG_State/GLState/BufferState/BufferObject.cpp:45`、`:71`、`:87`、`:103`、`:237`、`:375`、`:438`、`:450`、`:493`、`:602`、`:656`)按 `MGPipeResourceSubsystemEnabled()` 二选一。 +- **VAO twin 的前端读取臂是有条件的**:pre-handle 的 `SyncToBackend` 本体与它读的那组 memo(`m_syncedIndexBufferVersion` / `m_syncedIndexBufferObject` / `m_hasSyncedConfigVersion` / `m_syncedConfigVersion` / `m_syncedAttributeVersions`)都在 `MOBILEGL_PIPE_LEGACY_MEMOS` 里(`MobileGL/MG_Backend/DirectGLES/Managers.h:1109-1130`)。位 8 清零 + `LEGACY_MEMOS=0` 是**没有任何 vertex-input 臂**的配置,`Managers.cpp:2343-2351` 明确报 `PipeLegacyMemosDisabled` 而不是静默。位 8 还要求位 7(属性的缓冲 id 经资源 slot 表解析),`0x17f` 会点名拒绝并回落到 legacy 臂。 +- **base-instance 的 ambient 作用域同在其中**:`SetPendingFetchBaseInstance` / `GetPendingFetchBaseInstance` / `ScopedFetchBaseInstance` 与 `DirectGLES.cpp:5293` 的三个 scope(`Managers.h:1189-1200`)。句柄臂改由 `MGPipeApplierState::VertexFetchBaseInstance` 供给,但这三个声明**不能删**:删掉会从 pull 构建移走两个符号(G1)。 + P13:删 `SnapshotFromGLContext()` 的非 verify 分支、`MGB_CTX`、`MOBILEGL_PIPE_PUSH`、`MOBILEGL_PIPE_LEGACY_MEMOS`;**保留 `MOBILEGL_PIPE_VERIFY` 连同它需要的 `SnapshotFromGLContext()` 与 `MG_State` include**(D-B5,verify 构建永不出货);三道纯度门在非 verify 构建上转绿。 ## 10. server 侧 @@ -474,6 +482,8 @@ server 没有第二份 `BufferObject`,所以不存在"staging → server 侧 s | T3 — host pointer 导入(`VK_EXT_external_memory_host`) | | Adreno 无扩展;Mali 只读(GPU 写对宿主映射不可见) | | T2 — 拒绝(永久正确回退) | `AcquirePersistentMap` 返回 `nullptr`,前端已在三处容忍 | 此档下 client 侧推送强制 | +**`map-persistent-roundtrips`(`mpr`)的定义,P3a 拍板并落地**:它数的是**每一次 `MapPersistent` 发射,铸成还是拒绝都算**(`MobileGL/MG_Impl/Pipe/PipeFill.cpp:736`,client 侧发射器,`CallClass::MapPersistentRoundtrips`,`MobileGL/MG_Util/Metrics/PipeStats.h:126`,摘要行印 `mpr=`)。理由是 `ROADMAP.md:7` 的"每个门必须能因它存在的理由变红":定义成"真正发生的往返次数"在 monolith 下按构造恒为 0,永远红不了。按"每次获取尝试"计数则两种模式下**数字相同**,恰好等于上表 T1 那句"每次存储定义一次 round trip",在 monolith 下就非零、就可断言,而一个改成逐 draw 获取的回归立刻现形。门是 `StorageBufferRegrowScenario.NStorageDefinitionsCostNMapPersistentRoundtripsNotOnePerDraw`(`MobileGL/MG_IntegrationTest/Scenarios/StorageBufferRegrowScenario.cpp:255`)与 `LargeArenaAdoptionScenario.AnAdoptionCostsExactlyOneMapPersistentRoundtrip`(`MobileGL/MG_IntegrationTest/Scenarios/LargeArenaAdoptionScenario.cpp:450`)。 + `MOBILEGL_IPC_ADOPT_TIER`(`auto`/0/1/2)做负面对照;与 `MOBILEGL_IPC_RESPAWN` 互斥(被采纳的 store 是 server 拥有的内存)。 **client 侧 persistent map 推送三件套**(T2 档强制,P5): @@ -585,7 +595,7 @@ CMake: | 变量 | 默认 | 说明 | |---|---|---| -| `MOBILEGL_PIPE_PUSH` | pull 构建 `0`;**push 构建 `0x7f`**(`kMGPipeSubsystemsMigratedAtP2`,`MobileGL/ConfigLoader.cpp:254`) | 子系统位图,十进制或 `0x`;位按 ROADMAP 顺序分配、永不复用(`MobileGL/MG_Pipe/MGPipe.h:72-85`):`0x01` 渲染状态、`0x02` pixel pack、`0x04` patch state、`0x08` vertex attrib defaults、`0x10` residual values、`0x20` Espryt slots(Track H)、`0x40` Magma vertex input(Track H);位 7..62 留给后续阶段。**位 63 不是子系统而是行为**:`kMGPipeBehaviourNoCsoContentAddressing` 关掉 client 侧 CSO 内容寻址(每次 pipeline 版本变化都铸新 CSO、永不探测 map),即 P2 的负面对照。`0` = 全 pull,但 P2 之后只有在 `MOBILEGL_PIPE_LEGACY_MEMOS` 编进了 pre-handle 臂时才是有效对照 | +| `MOBILEGL_PIPE_PUSH` | pull 构建 `0`;**push 构建 `0x1ff`**(`kMGPipeSubsystemsMigratedAtP3a`,`MobileGL/MG_Pipe/MGPipe.h:95`,读入点 `MobileGL/ConfigLoader.cpp:257`;P2 的默认是 `0x7f` = `kMGPipeSubsystemsMigratedAtP2`,`MGPipe.h:94`,保留作分阶段对照) | 子系统位图,十进制或 `0x`;位按 ROADMAP 顺序分配、永不复用(`MobileGL/MG_Pipe/MGPipe.h:72-95`):`0x01` 渲染状态、`0x02` pixel pack、`0x04` patch state、`0x08` vertex attrib defaults、`0x10` residual values、`0x20` Espryt slots(Track H)、`0x40` Magma vertex input(Track H)、**`0x80` 位 7 resources(P3a:`resource_*` 家族,`kMGPipeSubsystemResources`,`MGPipe.h:84`)**、**`0x100` 位 8 vertex input(P3a:vertex elements / vertex buffers / index buffer,`kMGPipeSubsystemVertexInput`,`MGPipe.h:85`)**;位 9..62 留给后续阶段。**位 8 依赖位 7**:属性的缓冲 id 经资源 slot 表解析,只有位 7 填那张表,所以 `0x17f` 会打一行 ERROR 点名拒绝位 8 并回落到 legacy vertex-input 臂(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2312`)。**位 63 不是子系统而是行为**:`kMGPipeBehaviourNoCsoContentAddressing`(`MGPipe.h:90`)关掉 client 侧 CSO 内容寻址(每次 pipeline 版本变化都铸新 CSO、永不探测 map),即 P2 的负面对照。`0` = 全 pull,但 P2 之后只有在 `MOBILEGL_PIPE_LEGACY_MEMOS` 编进了 pre-handle 臂时才是有效对照 | | `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` | 0 | 负面对照 C(push 构建才有,`MobileGL/Config.h:360-371`):故意打掉句柄身份,让 `HandleRecycle` 的 ABA 臂重现旧的 A-B-A 污染。它变绿即为控制失效 | | `MOBILEGL_PIPE_VERIFY` | 0 | 逐 draw 逐字段影子比对 | | `MOBILEGL_PIPE_STATS` | 0 | 边界计数器(§附 B) | diff --git a/docs/Disaggregated/MEASUREMENTS.md b/docs/Disaggregated/MEASUREMENTS.md index db4e41a6..c74ff3a7 100644 --- a/docs/Disaggregated/MEASUREMENTS.md +++ b/docs/Disaggregated/MEASUREMENTS.md @@ -1,4 +1,4 @@ -# 实测记录(P0、P1、P2) +# 实测记录(P0、P1、P2、P3a) > 每张表都写明设备、提交与命令,以便复现。设备:`35d0befa` = Xiaomi 24129PN74C,Adreno 830,Android 16;`3B159D009VZ00000` = Oppo PLG110,Mali,Android 16(ColorOS)。设备运行日期 2026-09-05。设备锁协议照旧。 @@ -166,6 +166,8 @@ python3 tools/trace_replay/run_android_retrace_local.py \ ## 10. 设备配对 A/B:逐线程 CPU(D.4.2) +> **勘误(2026-09-08,P3a 收尾时发现)**:本节两机 64 次运行所用的两臂 APK 是**未优化构建**——其 `libMobileGL.so` 43.2 MB、`.text` 21.8 MB,而同一 clang 18.0.4 的 Release 库是 15.5 MB / `.text` 9.4 MB(两者都无 `.debug_info`),同一 `improved-transparency-minecraft-26.3` 用例在 Oppo 上 Espryt pull 的 CPU p50 为 42.1 ms 对 Release 的 9.7 ms。因此本节的**绝对值与 +8–18% 的差值都是 -O0 读数**(tracker 这类内联密集的代码在 -O0 下付出最多),不能作为性能基准线。**基准线以 §20 的 P3a 两机 A/B(3e298c9a 的 Release APK)为准**:其 pull 臂与 P2 的 pull 路径符号一致(P3a 全程 G1 0/0/0/0,与 44c2b5cf 只差 d7655247 的 5 行),其 `MOBILEGL_PIPE_PUSH=0x7f` 臂就是 P2 边界在 Release 下的代价。本节其余内容(协议、计数器、harness 误杀)仍然有效。构建脚本 `~/w7/notes/tools/wsl_build_trace_apks.sh` 从此打印库尺寸(Release `.text` ≈ 9.4 MB)。 + 协议:reboot-clean、同热窗口、按项目协议定频(大核 1.96 / 小核 1.55 GHz、GPU 拉满、40 °C 门),两臂背靠背同一会话,一次一台设备(`run_android_retrace_local.py` 每棵树共用一个结果根,见 §5.2),`--benchmark-no-finish` 为主臂(P2 问的是 CPU)。数字取 `benchmark.json` 的 `frameCpuTimesMs[]` 尾 200 帧,主机侧算 p50/p99。`minecraft-1.21.1-neoforge-create-indirect-in-world` 不在设备 A/B 里(§5.5,基线就坏)。 每格取 runner 自己的 "best of 3"(三次重复里平均墙钟帧时间最低的那次;`run_android_retrace_local.py` 的约定),p50 按设备的中位数规则、p99 为 nearest-rank,都在同一尾窗口上算;`tools/device_bench/pin_device.sh check` 在每次运行前后各跑一次,判定记在最后一列(P = 三个节点都在钉住的频率上;D = 大核被热管理钳在 1689600 kHz,脚本钉的是 1958400——用例前后两臂同一状态才可比)。APK 两臂出自 `55d2af9b`(P2 集成后、ABA 对照补丁前;差异只在负面对照臂)。 @@ -307,3 +309,220 @@ $ ctest --test-dir build-push -R 'HandleRecycle' --no-tests=error -j 4 --output- 修好之后 32/32(多出来的四条是新增的 `AbaControlHandles` 臂,让对照能够到 P2 出货的 `{slot, gen}` 臂而不只是 pre-handle 臂),并且逐臂把判决打出来:`arm=Handles expected=FRESH observed=FRESH`、`arm=AbaControl expected=STALE observed=STALE`,两个臂都如此。**对照确实承重**:把 `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` 关掉,两个臂都变成 `observed=FRESH` 并**失败**——污染由被打掉的身份产生,别无他因,而退役的旧守卫与出货的 `{slot, gen}` 都能拦住它。 +--- + +# P3a 实测(`feat/disaggregated@fde5fda3`) + +> 基线:`5cb826b0`(P2 的 `44c2b5cf` 加上 `dev@9eae9858` 的合并);两个版本号提交之后 G1 的 pull 基线在 `b9eaa474` 重取。四个包 = contract/wire、client、espryt、gates,集成顺序 contract → wire → client → espryt → gates。 +> +> **口径不变**:性能对着 pull 臂**记录**、不作阻塞门;正确性门照旧是硬门。所以下面 §16 是门,§19–§21 是记录与遗留判定。 + +## 15. 用户的口径规则,逐字 + +2026-09-08 的用户规则,`docs/Disaggregated/` 里此前没有一处写下它,所以逐字抄在这里: + +> (a) advance the roadmap as fast as possible without a large performance regression; performance is RECORDED against the pull baseline, never a blocking gate in P3a; correctness gates stay. + +落到 P3a 的后果:五部分门的**第 4 部分**(`ARCHITECTURE.md:514` 的 monolith 性能)与 `ROADMAP.md:19` 里那句"MC 26.3 在 Adreno 上 p99 不变"都变成**测了就发布**的义务,不是通过/不通过的判据;第 1、2、3、5 部分仍是硬门。一次回归要写下数字与怀疑的成因并带进 P4a 的排期——**唯一不可谈判的是数字必须真的采到**:没测到的回归不算记录在案。 + +## 16. P3a 五部分门(本地 `~/w7/pipe`,HEAD `3e298c9a`,基线 `44c2b5cf`) + +**第 1 部分 —— 接口纯度** + +| 门 | 结果 | +|---|---| +| 三个构建(pull / push / verify) | rc 0 / 0 / 0。fail-fast 规则原样保留:任一非零就打印 `BUILD FAILED - gate aborted (no stale-binary verdicts)` 并退出,绝不拿陈旧二进制下判决 | +| A 门 include 闭包 | 4 个探针,0 skip,**0 problem** | +| C 门 `MG_Backend` 里的 `pGLContext` | 空 | +| G13 `PipeApply.h` 的 ops 块里的 `MG_State` | 空 | +| **G1** pull 构建符号(P3a 的认定 resize 集**为空**) | `.text` 10806323 → **10806323(+0,+0.000%)**;27811 → 27811 个已定义符号,**0 增 / 0 删 / 0 resize / 0 重命名** | +| **G5** 十个函数逐字节相同 | rc 0,对 `44c2b5cf` **与** `5cb826b0` 都成立(九个 pool / 延迟释放 / ring,加 ID-11 追加的 `FlushPendingRangesNow`) | +| `RenderStateImpl` 段 sha(P2 的不变量) | `d8fd1c48716056c53675`,逐字相同 | +| **G8** `HandleRecycle`(verify 构建) | **60/60**,含新增的 buffer 用例三臂 | + +**第 2 部分 —— 语义影子比对(决定性的一条)** + +| 门 | 结果 | +|---|---| +| `integration-verify` | **842/842**,`Fatal{` 行 **0** | +| 79 例 retrace,`MOBILEGL_PIPE_VERIFY=1` | **79/79 通过**,**79/79 全部 armed**(每份日志都带 `MGPipe verify:` 行),带 `Fatal{` 的日志 **0**——即零分歧、零未迁移读 | + +**第 3 部分 —— 行为 A/B** + +| 门 | 结果 | +|---|---| +| **G2** pull 与 push 的 ctest 名集合 | 差 **0** 行 | +| **G14** 测试名 | **0 删除 / +58** | +| 单元 | **1619** 全绿 × {pull, push, verify} | +| `integration-gpu` pull / push | **958/958** / **958/958** | +| `MOBILEGL_PIPE_PUSH=0`(全 pull 臂) | **958/958** | +| `MOBILEGL_PIPE_PUSH=0x7f`(P3a 两个子系统关闭,**G12**) | **958/958** | +| `MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1`(具名的 kill-switch 臂,见 §21) | **958/958** | +| buffer/VAO 族(`LargeArenaAdoption`、`StorageBufferRegrow`、`VertexAttribBinding`、`MultiDraw`、`PrimitiveRestart`、`CrossFrameBuffer`、`ResidentIndex`、`BufferTexture`、`AtomicCounter`、`XfbCaptureBufferReuse`、`PackedWordReadback`、`DoublePrecision`、`VertexArrayEnableDisable`、`DrawParameters`) | **202/202** | +| P2 的 G7 setter 一致性阴性对照 | rc 0,按设计变红后树又恢复绿 | +| **G7** vertex-input 阴性对照 | rc 0 —— **变红并点名 `IsBgra`**,随后恢复、重建、再变绿 | +| `CsoContentAddressing` + `ResourceSubsystemControl`(**G12**:开关真的改变行为,不是死代码) | **10/10** | +| verify 构建的对照组(`PoisonOmitted`、`VerifyCorrupted`、`HandleRecycle`、`AbaControl*`、`ResourceSubsystem*`、`MapPersistentRoundtrips*`) | **64/64** | +| 79 例 retrace(push) | **79/79 通过**(`failed: []`),每条都在自己的 SSIM 阈值之上 | +| **G3b** 具名五条(`create-indirect`、`create-instancing`、`rd12-odinlite`、`improved-transparency-minecraft-26.3`、`fabric-sodium`) | **桌面侧全绿,但要看清是怎么绿的。** 五条**都在上面那次 79 例 push 扫描里通过**——它们全都在 `tools/trace_replay/trace_cases.json` 的语料内,而那次扫描是 79/79。**单独跑的那次具名扫描自己选中了 0 例**(`passed 0 / 0`):`retrace_gate.py` 的 `--only` 收的是**正则**,门脚本传的却是逗号分隔的清单,于是过滤器一条都没匹配上。这是门脚本的缺陷、不是本阶段的失败,重跑要写成 `--only 'create-indirect\|create-instancing\|rd12-odinlite\|improved-transparency-minecraft-26\.3\|fabric-sodium'`。**设备侧**按 `ROADMAP.md` 开放问题 17 把 `create-indirect` 排除在 A/B 之外,所以 **G3b 整体记为"桌面全绿、设备侧部分(受阻于 `dev` 的开放问题 17)"**,不当作完整通过 | + +**第 4 部分 —— 性能:记录,不设门**(规则 (a))。见 §20。 + +**第 5 部分 —— 覆盖、poison、句柄纪律** + +| 门 | 结果 | +|---|---| +| `gen_pipe.py --check` / `--self-test` | 生成物是最新的;self-test **7 个阴性对照全部触发**,正对照 OK | +| `gen_pipe_dirty_surface.py --check`(**G9**,扫描根已扩到 `MG_State/GLState`) | **75 个 mutator 全映射、无陈旧行**;45 条 render-state 答案由 `RenderState.cpp` 推导并吻合,另 10 条 (mutator, bit) 答案由各自在 `Tracker.h` 里的快门推导,**0 COARSE / 0 UNDECIDED** | +| `gen_pipe_dirty_surface.py --self-test` | **21 个阴性对照全部触发**,正对照 OK | +| `RenderStateSpans` / `Residual` / **`VertexInputEmit`(G6)** / **`ResourceEmit`** | **48/48** | +| `p3a_untouched_regions.sh --self-test`(**G5** 自己的阴性对照) | rc 0 —— 3 个正对照 + **2 个阴性对照**(扰动 `ClearBufferPool` 与 `FlushPendingRangesNow` 各被点名) | + +**G15(完整 `gl44to46` caselist,约 56,271 例,两台设备)**:P3a 是五个架构边界之一,按 `ROADMAP.md:34` 排在 `dev` 合并时、**关键路径之外**;`MOBILEGL_PIPE_POISON_OMIT` 的清扫(§14 那 8 条静态过近似填充行)搭同一次 caselist 运行。 + +## 17. D.2 的"重键前红"证据:buffer 类 + +`ROADMAP.md:7` 要求每个门必须能因它存在的理由变红。P2 已经为 VAO 类留下了证据(§14 末尾);P3a 为 **buffer 类**留下这一份,取自 `p3a/contract` 树上、package C 还没注册 `MGPipeResourceOps` 的时刻,`ctest --test-dir build-verify -R HandleRecycle`: + +``` +16/60 Test #2488: DirectGLES.HandleRecycle.Handles.HandleRecycleScenario + .ABufferAtARecycledAddressDoesNotInheritItsPredecessorsContents ......***Skipped +22/60 Test #2494: DirectVulkan.HandleRecycle.Handles.HandleRecycleScenario + .ABufferAtARecycledAddressDoesNotInheritItsPredecessorsContents ......***Skipped +28/60 Test #2500: DirectGLES.HandleRecycle.Legacy.HandleRecycleScenario + .ABufferAtARecycledAddressDoesNotInheritItsPredecessorsContents ...... Passed +40/60 Test #2512: DirectVulkan.HandleRecycle.AbaControl.HandleRecycleScenario + .ABufferAtARecycledAddressDoesNotInheritItsPredecessorsContents ...... Passed +100% tests passed, 0 tests failed out of 60 +``` + +读法与预期逐条吻合:**`.AbaControl` 通过**——那个臂**断言的就是被污染的内容**(把句柄身份打掉之后,复用地址上的新 buffer 读到前一个的字节),这正是"重键前红"的正面证据;**`.Legacy` 通过**(pre-handle 守卫拦得住);**`.Handles` 可见地 skip** 并写明缺什么,而不是一条消失的测试。package C 注册 `MGPipeResourceOps` 之后这两条 skip 转为断言,最终树上 `HandleRecycle` 是 **60/60**(§16 第 1 部分)。完整日志见 `~/w7/notes/p3a/p3a-results/handlerecycle-before.log`(另有 `-verbose.log`,含 22 行逐臂判决)。 + +## 18. 验证轮在真实流量下找到的三个缝隙缺陷 + +句柄路径第一次对着**真的发射器与真的 applier 本体**跑起来,是 espryt 的验证轮。默认位图那条 integration 通道当时留下 13 条红,其中 11 条是真的,掩码二分把它们干净地劈开(`0x7f`:0 条;`0xff`(只开位 7):5 条;`0x1ff`:11 条)。三个缺陷都在本包的文件清单内,都有实测的前后对比。**这一轮是承重的**——三个都是别的门看不见的形状: + +| # | 缺陷 | 位置 | 表现 | +|---|---|---|---| +| **F1** | vertex buffer 条目按属性的 **GL 绑定点**解析,而不是按**属性下标** | `MobileGL/MG_Backend/DirectGLES/Managers.cpp` 的查找助手(`VertexBufferForBindingIndex` → `VertexBufferForAttributeIndex`)与它的调用点 | `MGPVertexBuffer::BindingIndex` 与 `MGPVertexAttribWire::BindingIndex` 是两个不同的数:Espryt 消费的是**已解析**的属性,所以 client 把 `set_vertex_buffers` 发成"逐属性槽一条",`BindingIndex == 属性下标`;另一个是 `glVertexAttribBinding` 绑上去的 GL 绑定点,而那个视图这条臂从不读。两者在所有 `glVertexAttribPointer` 配出来的属性上恰好相等——这就是别的场景全绿的原因;它们只在 `KHR-GL43.vertex_attrib_binding` 的题材上分叉,于是 `VertexAttribBindingScenario` 的 6 条读到禁用/零默认值。单这一个修完 6 → 3 | +| **F2** | ensure 路径去问一个**没有任何内容调用会刷新**的描述符,来判断影子里有没有字节 | `Managers.cpp` 的 `EnsureBufferResourceForHandle` | `MGPResourceDesc::HasDefinedContent` 陈述的是**上一次 `resource_respecify` 当时**的事实,此后 `NotifySubData` / `NotifyFlushMappedRange` / `NotifyContentWrite` / `MarkGpuWritten` / `LandBytesIntoResidentStore` 都只改前端的 `m_hasDefinedContent`、不重发描述符(逐 `glBufferSubData` 重发是新线上流量,设计上禁止)。于是在语料里最常见的惯用法 `glBufferData(size, NULL)` + `glBufferSubData(data)` 之后,句柄臂用**应用的字节**配上**applier 的描述符**,重定义出一个"已声明为当前"的空 store,应用的字节被丢掉且没有任何诊断——**静默的错像素**,不是崩溃。改为在持有前端对象时读 `HasDefinedContent()`(无对象的纯句柄排水仍读描述符)。关掉了全部 4 条 `XfbCaptureBufferReuse` 与 `LargeArenaAdoption.RespecifiedIndexArenaKeepsVaoBinding`,即所有 `0xff` 红 | +| **F3** | 惰性生成的 twin 从不发布影子基址,于是 fp64 收窄拒绝每一个 draw | `Managers.cpp` 的 `EnsureBufferResourceForHandle` | `GLESBufferResource::hostBytes` 是那些**不持有**前端对象的读者读的(回读队列排空、flush-range 的 kill-switch map 臂、`SyncFloat64AttributeAsFloat32ByHandle`)。`ResourceCreate` 是 no-op,twin 因此是**惰性**建的,第一次 `resource_respecify` 找不到 twin,把基址丢在地上;对"`glGenBuffers` + `glBufferData(size, data)` 之后再无内容调用"的普通静态顶点数组,`hostBytes` 就此终身为 null。整份重传没事(它走另一个取基址的路径),fp64 收窄却因 `sourceBase == nullptr` 直接失败 → Adreno workaround 禁用该属性 → 着色器读到 `(0, 0, 0, 1)`。修法是在已经算出基址、且每个用到该 store 的 draw 之前都会跑到的那一处补发布,且**只对非采纳资源**(采纳臂更早返回,所以 adopted store 的 `hostBytes` 仍为 null、字节仍走 `persistentPtr`)。关掉最后 3 条 `VertexAttribBinding` | + +三个修完之后那条通道在 espryt 自己的树上是 920/920,最终集成树上是 §16 的 958/958。诊断用的五行临时 `MGLOG_E("TRACE …")` 在任何提交之前就已删除。 + +同一轮里的两条方法学记录。**包的 worktree 里 `tools/trace_replay/fixtures` 是 LFS 指针**(131/133 字节的 stub 对 `~/w7/pipe` 的 803 MB),对着指针文件跑 retrace 会报 `passed 2 / 79`、每条 Minecraft 用例 `ssim=None`,读起来跟"整体回归"一模一样——那是**假红**,建树脚本不物化 fixture。**base-instance 对照在 llvmpipe 上要先关掉原生 base-instance 才可证伪**:llvmpipe 暴露 `GL_EXT_base_instance`,原生门因此为真、`fetchBaseInstance` 被钳成 0,而属性偏移仿真才是 `MGPipeApplierState::VertexFetchBaseInstance` 在整个后端里**唯一**的消费者。把两个原生门都强制为假之后,对照两个方向都成立:三个调用点在时 10/10 绿,抠掉调用点时 8/10。**这条门是 `VertexAttribBindingScenario` 的 `BaseInstanceMovesTheInstancedArraysStartElement` 与 `BaseInstanceLeavesPerVertexArraysWhereTheyWere` 两条,不是 `DrawParametersScenario` 的八条**——后者观察的是 `gl_BaseInstance` / `gl_DrawID`,来自 `SetCurrentBaseInstance`,与取数偏移正交,两个方向都绿是对的。 + +## 19. Track H 单位成本普查(`ROADMAP.md:49` 的判据) + +**日历口径:P3a 全部四个包 = 1 天**(2026-09-08)。contract/wire、client、espryt、gates 四个包各一轮实现加一轮对抗性评审加至多两轮返工,连同集成与本地五部分门,全部落在同一个日历日内。对着 `ROADMAP.md:66` 的 **27 天绊线**是 **1 / 27**,估时是 18–23 天,**没有超出估计的 50%**,不触发重定基线,也不需要 `ROADMAP.md:70` 的 `inproc` 证伪数字。§14 那条"Track H 单位成本的日历口径:未记录"到此补上一半——P3a 自己的口径有了,P2 两个包的仍然没有。 + +**diff 规模**(`git diff --stat 5cb826b0 3e298c9a`,区间内 44 个提交): + +``` +37 files changed, 9273 insertions(+), 230 deletions(-) +``` + +**memo 账**(`ARCHITECTURE.md:363` 那份 21 条普查;P2 付了 11 条直接删除里的全部 11 条与 7 条重键里的 2 条): + +| 类别 | P3a 付了什么 | +|---|---| +| 直接删除(普查 11 条里的最后一条) | `ConvertedVertexStreamKey` 的 `sourcePin`(`ConvertedFloat64Stream::sourceLifetimeId`) | +| 句柄臂上另外退役的 twin 成员 | `m_hasSyncedConfigVersion`、`m_syncedConfigVersion`、`m_syncedAttributeVersions`(`Array`,本阶段最大的一处)、`m_syncedIndexBufferVersion`、`m_syncedIndexBufferObject`(裸前端指针,堵回绕洞的那个身份补丁) | +| 重键 | VAO twin 的索引槽 memo(回绕 `Uint16` + 裸 `BufferObject*` → 一个 server `Serial`);`ResolvedDrawBuffers`(`configVersion` → `{elementsHandle, elementsSerial, buffersSerial}`,`Entry` 与 `iboFrontend` 各加一个 `MGPipeHandle`);twin 的同步门(config version + 32 组逐属性版本 → `{elementsHandle, elementsSerial}` + `VertexBuffersSerial`);`ConvertedFloat64Stream`(前端 lifetime id + change serial → buffer `{slot, gen}` + applier `Serial`);`GLESBufferResource::syncedChangeSerial` 从镜像 `BufferObject::GetChangeSerial()` 改为镜像 applier 的 `Serial` | +| **保留**(`MOBILEGL_PIPE_LEGACY_MEMOS`) | 上面每一条"退役"都只是**句柄臂上**的:成员仍在 `MOBILEGL_PIPE_LEGACY_MEMOS` 下编译(`MobileGL/MG_Backend/DirectGLES/Managers.h:1109-1130`、`:1067-1075`),pull 构建强制该开关 ON,所以 `sizeof` 一处不动——**G1 的 0/0/0/0 就是这条的度量** | +| **保留,且计划里的"grep 为空"不可达** | `g_pendingFetchBaseInstance` / `SetPendingFetchBaseInstance` / `GetPendingFetchBaseInstance` / `ScopedFetchBaseInstance` 与它的三个 scope(`MobileGL/MG_Backend/DirectGLES/Managers.h:1189-1200`、`MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:5293`)。句柄臂改由 `MGPipeApplierState::VertexFetchBaseInstance` 供给,但**真删会从 pull 构建移走两个符号**,是直接的 G1 破坏;所以计划里那条"`grep` 结果为空"在 P3a **不可达**,每一处残留命中都在一个 `MOBILEGL_PIPE_LEGACY_MEMOS` 臂里。这是记录在案的偏差,随 pull 路径在 P13 退役 | +| **保留(D-K)** | `PipeResource::m_backend`、`SetBackendResource`、`ReleaseBackend`、`BackendBufferResource`:push 下不再被写,删除会移动 pull 构建里的 `sizeof(BufferObject)`,同样随 P13 退役(`ARCHITECTURE.md:286`) | + +## 20. 设备配对 A/B、MC 26.3 的 p99、DriverBench(记录项) + +协议与 §10 完全相同:reboot-clean、同热窗口、按 `tools/device_bench/pin_device.sh` 定频(大核 1.96 / 小核 1.55 GHz、GPU 拉满、40 °C 门,每次运行前后各 `check` 一次,DRIFT 的那次作废重跑)、两臂背靠背、一次一台设备、`benchmark.json` 的 `frameCpuTimesMs[]` 尾 200 帧在主机侧算 p50/p99(p99 用 nearest rank,p50 按设备自己的中位数规则)。臂 = {pull APK, push APK} × {`--benchmark-no-finish`(主臂,P3a 问的是 CPU), finish(GPU 时间没动的 sanity)} × 4 用例 × 2 后端。用例:`improved-transparency-minecraft-26.3`、`minecraft-1.21.4-rd12-odinlite-in-world`、`minecraft-1.21.4-fabric-sodium-in-world`、`minecraft-1.21.1-neoforge-create-instancing-in-world`(一个 `coherent_as_flush` fixture)。**`minecraft-1.21.1-neoforge-create-indirect-in-world` 排除在外**,理由与 P2 相同:它在 `dev@81b17c0b` 的基线 APK 上就在两台设备上失败(§5.5、`ROADMAP.md` 开放问题 17),不是本分支的回归;它留在桌面 SSIM 语料里。同一批运行里读出六个 memo 门的 hit/miss、`stage-*` 字节类与 **`mpr`**。 + +**读数纪律,必须写在数字上面而不是下面**:`CallClass::AccessorCalls` 是约 10 个热入口上的**静态计数**(`MobileGL/MG_Util/Metrics/PipeStats.cpp:16-100`,那份站点清单本身就是契约),所以一个只把读**挪了位置**的改动会拿到更低的 `acc/draw` 而并没有减少工作量。带头看的应当是**六个 memo 门的 hit/miss** 与 **CPU 时间序列**;引用 `acc/draw` 必须同时复核那些 tally 常量。 + +**两机配对 A/B(D.4.2),三臂**:APK 都是 `3e298c9a` 的 Release trace 构建(`wsl_build_trace_apks.sh`,`.text` 9.4 MB,debug 签名);pull 臂 = pull 库;`P2` 臂 = 同一个 push APK 跑 `--env MOBILEGL_PIPE_PUSH=0x7f`(只开 P2 的七个子系统,P3a 关);`P3a` 臂 = push APK 默认掩码 `0x1ff`。协议同 P2(reboot-clean、`pin_device.sh` 三次校验、每次运行前后 check、`--benchmark-repeats 3 --benchmark-tail-frames 200`、runner 的 best-of-3、`MOBILEGL_PIPE_STATS_PERIOD=120`);表取 `--benchmark-no-finish` 臂,单位 ms/帧,p50 括号内为相对 pull 的增量。 + +| 设备 | trace | 后端 | pull p50 | P2 臂 p50 | P3a 臂 p50 | pull p99 | P3a p99 | 备注 | +|---|---|---|---|---|---|---|---|---| +| 小米 Adreno 830 | `improved-transparency-minecraft-26.3` | Espryt | 10.810 | 11.485(+6.2%) | 11.697(**+8.2%**) | 25.457 | 26.322 | finish 开:10.789 → 11.705(+8.5%) | +| 小米 | 同上 | Magma | 10.675 | 11.473(+7.5%) | 11.459(**+7.3%**) | 25.014 | 26.035 | finish 开:10.710 → 11.486 | +| 小米 | `minecraft-1.21.4-rd12-odinlite-in-world` | Espryt | 8.220 | 9.117(+10.9%) | 10.650(**+29.6%**) | 21.895 | 24.487 | finish 开:8.241 → 10.656 | +| 小米 | 同上 | Magma | — | — | — | — | — | 三臂都在启动数秒后 SIGABRT(scudo map error,见下),既有问题 | +| 小米 | `minecraft-1.21.4-fabric-sodium-in-world` | Espryt | 1.292 | 1.349(+4.4%) | 1.382(**+7.0%**) | 2.418 | 2.443 | | +| 小米 | 同上 | Magma | 0.485 | 0.504 | 1.010 | 1.469 | 2.187 | 亚毫秒帧,pull 自己两模式相差 2×(0.485 / 0.984),噪声,不读 | +| 小米 | `minecraft-1.21.1-neoforge-create-instancing-in-world` | Espryt | 944.4 | 945.2 | 949.9(+0.6%) | — | — | fixture 只有两帧(p50 = mean),是正确性 fixture 不是基准 | +| 小米 | 同上 | Magma | 1003.9 | 1019.5 | 1015.6(+1.2%) | — | — | 同上 | +| Oppo Mali | `improved-transparency-minecraft-26.3` | Espryt | 9.741 | 10.570(+8.5%) | 10.542(**+8.2%**) | 26.312 | 27.022 | finish 开:9.625 → 10.698(+11.1%) | +| Oppo | 同上 | Magma | 8.162 | 8.920(+9.3%) | 8.973(**+9.9%**) | 22.851 | 23.638 | finish 开:8.120 → 9.064 | +| Oppo | `minecraft-1.21.4-rd12-odinlite-in-world` | Espryt | 9.938 | 11.009(+10.8%) | 12.963(**+30.4%**) | 27.137 | 29.765 | finish 开:9.944 → 12.852 | +| Oppo | 同上 | Magma | 8.001 | 8.933(+11.6%) | 10.161(**+27.0%**) | 24.723 | 27.355 | finish 开:7.718 → 10.226 | +| Oppo | `minecraft-1.21.4-fabric-sodium-in-world` | Espryt | 1.896 | 1.770 | 1.841(−2.9%) | 2.993 | 3.079 | 亚 2 ms 帧,噪声内 | +| Oppo | 同上 | Magma | 0.399 | 0.431 | 0.437(+9.5%) | 1.206 | 1.409 | 亚毫秒帧 | +| Oppo | `minecraft-1.21.1-neoforge-create-instancing-in-world` | Espryt | 1075.1 | 1045.1 | 1060.1(−1.4%) | — | — | 两帧 fixture | +| Oppo | 同上 | Magma | 758.3 | 731.0 | 760.0(+0.2%) | — | — | 两帧 fixture | + +所有入表运行前后 `pin_device.sh check` 都是 PINNED(小米 rd12/Magma 崩溃后 GPU pwrlevel 被重置,其后的 sodium/create-instancing 行两臂同状态)。 + +**读法。** (1) **P2 的边界在 Release 下的真实代价是 +6–12%**(`0x7f` 臂),两机两后端一致,比 -O0 表的 +8–18% 小但同量级;(2) **P3a 在 26.3 与 sodium 上几乎不再加价**(P3a 臂与 P2 臂在 26.3 上相差 −0.1 ~ +2 个百分点),**但在 rd12 上把差距从 +11% 推到 +27–30%**——rd12(Odin Lite 世界)每帧的 VAO/buffer 绑定切换远多于 26.3(26.3 的 1350 draw/帧大多复用同一 VAO),每次切换都走一遍 `set_vertex_buffers` 构造 + `ContentHash` + applier 记录 + Espryt 侧逐属性走查;Magma 上没有句柄消费者也多 15 个百分点,说明 client 侧发射本身就是大头;(3) **MC 26.3 在 Adreno 上的 p99**(`ROADMAP.md:19` 点名的那个数):pull 25.46 ms → P3a 26.32 ms(+3.4%),finish 开 25.48 → 26.29;对着 `MEASUREMENTS.md:87` 的采纳基线(p99 163 → 21 ms)仍在 21–26 ms 档,没有回到采纳前的形态——按口径记录,不判门;(4) `mpr`(map-persistent-roundtrips,按窗口累加):26.3 两机都是 8(首窗 4,之后两次 2——都是 ≥16 MiB store 定义时的采纳),sodium 1,rd12 与 create-instancing 0;P2 臂上恒 0(子系统关)——G10 在设备上成立;(5) `CreateVertexElements` 每帧字节数:统计行没有这一类(`vtxc` 是 client 数组),**未测**,留给 P4a 给汇总行加类;(6) 计数器(`acc/draw`、六个 memo 门、`resid=`、`csom/csob`)在 pull/P2/P3a 三臂间逐字相同——它们数的是代码路径,P3a 没有改它们的定义。 + +**小米 rd12 + Magma 的崩溃**:三臂(含 pull)都在启动后数秒 `SIGABRT`:`scudo::reportMapError` ← `remapImpl` ← `scudo_calloc` ← `libMobileGL.so`(0x818b14 / 0x7e2c44,已剥符号),当时 MemAvailable 6.1 GB——一次巨大或负尺寸的 calloc,在 Adreno 830 + Magma + 这条 fixture 上;pull 库与 P3a 前的 Magma 路径符号一致,所以是**既有 bug**,不入 P3a 账,已开独立任务(先符号化再修)。Oppo/Magma 与小米/Espryt 上同一 fixture 正常。 + +**harness 与设备陷阱(本轮新增)**:Oppo ColorOS 的安装确认页是 `topResumedActivity=…InstallGuideActivity`(`mCurrentFocus` 显示 systemui 窗口,按它 grep 永远匹配不到),首次安装不点会等到 harness 超时;在 streamed install 进行中点它会让设备从 adb 掉线几秒(那一轮的三次重复全失败,需要重跑);小米 32 次运行后立即 reboot+pin 必因热钳失败(大核钳 1689600 或 GPU 读 1050 MHz),要等 cpuss-0-0 <42 °C;一次崩溃会把 GPU pwrlevel 范围重置成 2..5。 + +对照的既有基线是本文件 `:87`——`dev` 上 MC 26.3 在 Adreno 上把 ≥16 MiB 可变 store 采纳为 coherent persistent map 之后的 p99 163→21 ms、稳态 40→115 fps、省 ~400 MB。P3a 的 `AcquirePersistentMap` 一个语句都没动(`ARCHITECTURE.md:474` 的 D-B4),`FlushPendingRangesNow` 的三档排水在 G5 的逐字节集合里,所以这条 p99 是"没动过的东西是否真的没动"最直接的读数。 + +**DriverBench(桌面 llvmpipe / lavapipe,`~/w7/notes/tools/wsl_p3a_bench.sh`:Release,240 帧,每臂 5 次重复取中位数;pull 库 = `build-linux`,push 库 = `build-push`,都是 `c20e2f2b`)**,`ns_per_op`: + +| 臂 | `mc_vanilla_draw`(ns/draw) | `mc_state_toggle`(ns/开关对) | `mc_pass_switch`(ns/pass) | +|---|---|---|---| +| native | 4759 | 22715 | 438175 | +| Espryt pull | 5115 | 23287 | 435815 | +| Espryt push,默认 `0x1ff` | 6162 | 24496 | 435975 | +| Espryt push,`MOBILEGL_PIPE_PUSH=0x7f`(只开 P2 子系统) | 5463 | 24576 | 436031 | +| Espryt push,`MOBILEGL_PIPE_PUSH=0` | 5693 | 24465 | 436598 | +| Espryt push,位 63(无 CSO 内容寻址) | 6156 | 24856 | 435276 | +| Magma pull | 17099 | 32612 | 436748 | +| Magma push,默认 `0x1ff` | 17709 | 33537 | 437314 | +| Magma push,`0x7f` | 17287 | 33550 | 436458 | +| Magma push,`0` | 17739 | 33800 | 440446 | +| Magma push,位 63 | 17729 | 34222 | 436465 | + +分解(`mc_vanilla_draw`,ns/draw;T2 在本阶段定义为 `0x7f` 臂 = P2 的边界): + +| | Espryt | Magma | +|---|---|---| +| **T1** = push(`0x1ff`) − pull(整个边界) | **+1048**(pull 的 +20.5%) | **+611**(+3.6%) | +| **T2** = push(`0x7f`) − pull(P2 的边界) | +349 | +189 | +| **T1 − T2 = P3a 自己加的** | **+699** | **+422** | +| push(`0`) − pull(P1 残余填充,全部拉取) | +578 | +640 | +| 位 63 − push(CSO 内容寻址净值) | −6(≈ 0) | +20(≈ 0) | +| blend-toggle(ns/开关对) | +1209(+5.2%) | +925(+2.8%) | +| pass switch | +160(≈ 0) | +566(≈ 0) | + +读法(记录项,不设门):P3a 的 buffer/VAO 句柄路径在**每个 draw** 上多花 Espryt 699 / Magma 422 ns——这是 P3a 到目前为止最大的一笔边界成本,来源是每 draw 的 vertex-input 发射(`set_vertex_buffers` 的 `MGPVertexBuffer[]` 构造与 `ContentHash`、`bind_vertex_elements`)、applier 的记录写入,以及 Espryt 侧 `SyncToBackendFromApplier` 对记录的逐属性走查;Magma 没有句柄化的 VAO 消费者,它多出的 422 ns 全是 client 侧发射 + applier 记录(P7 之前的纯开销)。与 P2 相比,T2 本身从 P2 实测的 +322/+345 变为 +349/+189(同量级;Magma 的差别是 P2 数字来自同批次内的相对比较)。这个数字进优化阶段的清单:候选是 vertex-input 发射的 per-draw 抑制(同一 VAO/同一 buffer 集合的连续 draw 不重发;现在 `ContentHash` 命中时仍走一遍构造)与 applier 记录的就地更新。 + +`DriverBench` 的 T1/T2 口径与 §11–§12 相同,只是分档换了:**T1** = `ns_per_op(push, 默认位图 0x1ff) − ns_per_op(pull)`(整个边界的每 draw 代价),**T2** = `ns_per_op(push, MOBILEGL_PIPE_PUSH=0x7f) − ns_per_op(pull)`(P2 的边界本身),于是 **T1 − T2 恰好隔离出 P3a 加了什么、删了什么**;`mc_state_toggle` 作为非 P3a 的对照并列发布(一次 blend 开关既不碰 buffer 也不碰 VAO,它不该动)。`DriverBench` 走独立的 `build-bench`(门的构建配了 `-DMOBILEGL_BUILD_BENCHMARK=OFF`),并且必须经 `DRIVERBENCH_EGL_LIB` 显式 `dlopen` 一个 provider、**不得靠 `LD_LIBRARY_PATH` 遮挡**——glvnd 系统上一个裸 `libEGL.so.1` 会解析到 Mesa/llvmpipe,即在基准底下把 GPU 悄悄换成软件光栅器。**不钉上限、也不强制上限**(规则 (a));数字发布出来,让 P4a 自己决定要不要钉。 + +## 21. 决定与遗留判定 + +**整体 diff 终审(`5cb826b0..3e298c9a`)与其返工(`3e298c9a..c20e2f2b`,12 个提交)**:终审在四个包各自过审之后又抓出两个跨包接缝——(1) client 为每个 VAO 铸的 `VertexElementsCso` 槽只有 Espryt 的死亡通知会释放,Magma 不装 `StateObjectDeathOps`,默认掩码下每个 VAO 泄漏一个槽和约 1.3 KB 的 applier 记录,过 65536 个槽后 `create_vertex_elements` 永久 `Fatal{ProtocolCorruption}`;修法是 `~VertexArrayObject` 走后端无关的死亡路径(`MGPipeEmitVertexElementsDestroyAndFree`:先删记录、再发通知、最后释放槽,Espryt 的通知退化为可重入的第二条路径),泄漏测试在 DirectVulkan 上修前 48 轮 live 2→50、修后不增长;(2) `FlushPendingRangesNow` 位于只在 pull 构建里编译的 `#else`,G5 的文本哈希覆盖不到 push 构建真正跑的 `FlushPendingRangesFrom`——裁定如下。 + +> **ID-15(取代 ID-13 里"定义在任何 `#if` 之外"那句)。** `Managers.cpp` 把三层 flush 排水梯各带一份、每个预处理臂一份,任一构建只编译其中之一:`#if MOBILEGL_PIPE_PUSH` 下的 `FlushPendingRangesFrom` 是 **push** 构建跑的(legacy 调用点与 `Ops_H_Readback` 都到它),`#else` 里与 `5cb826b0` 逐字节相同的 `FlushPendingRangesNow` 是 **pull** 构建跑的;push 构建根本不编译 `FlushPendingRangesNow`。转发函数不可行:G5 的提取器不认预处理,一个转发的 `FlushPendingRangesNow` 会让同名出现两个定义、门直接退出 2(门跑不了)而不是比对。因此**两个名字都是 G5 行**:十一个函数,pull 梯对着 P3a 基线比、push 梯对着钉在 `3e298c9a` 的 sha 比;两条梯都不许漂,也不许彼此漂而门不响。 + +> **M-3 裁定:fp64 顶点数组收窄在 handle 臂上少做 D-N 十一处 `SyncGpuWrites` 里的一处,这是 P3a 的裁定而非疏漏。** D-N 的措辞是"不把这些站点*搬*离前端";这一臂不是搬走了它,而是**根本做不了**——`SyncFloat64AttributeAsFloat32ByHandle` 手里只有句柄,那次调用需要前端 `BufferObject`,而 server 没有回到前端对象的反向映射正是设计本身(`ARCHITECTURE.md` §4.2),不是设计的缺口。保住这个站点的两条路各会破坏 D-N 或 D-J 保护的东西:句柄→对象的映射正是拆分要去掉的东西,而 client 在每个 draw 上急切拉回字节是新行为和新成本。**影响面,写明以便日后核对**:默认掩码下,一个 64 位顶点数组、其*源* buffer 被 shader 写过且尚未回读,handle 臂收窄到的是旧字节、legacy 臂是新字节。就这一种:fp64 顶点数组、由 shader 写的 buffer 喂、中间没有显式回读。其它属性类型不走这条路,持久映射的源另行排除(memo 从不信任它)。**P8 把拉取搬到对象所在的 client 侧即关闭**;在那之前这是默认掩码下两臂唯一的行为差异。(同一段文字在 `MobileGL/MG_Backend/DirectGLES/Managers.cpp` 的站点上;带 adopted 源的 `DoublePrecisionScenario` 用例未加——它需要一条新工作负载而非既有用例的参数,留给 D 包。) + +新的"看穿 GL API"接缝:`MG_IntegrationTest/Harness/PipeSlotPeek.{h,cpp}` 直接读 client 的槽分配器(泄漏测试的依据),与 `BackendCapsPeek` 并列。 + +**CI 独有的 teardown 崩溃(`DirectVulkan.Verify.CrossFrameBufferScenario.{Vertex,Index}CopyBufferSubData`,`double free or corruption`,本地 30 次不复现)**:ASan 定位为 `exit()` 时的静态析构顺序 UAF——命名空间作用域的 `gPipeInputs` 持有 `SharedPtr`,其析构链 `~VertexArrayObject → ~BufferObject → MGPipeEmitResourceDestroyAndFree → MGPipeSlotAllocator::FindByLifetimeId` 读的是已被 `~MGPipeSlotAllocator` 释放的哈希表(`MGPipeSlots()` 是首个 buffer 时才构造的 Meyers 单例,先于 `gPipeInputs` 析构)。全部 13 个用例 × 两个后端都走这条链,只有释放后的表恰好还能解析出句柄时 `Free()` 才写坏堆——CI 的分配器布局中招、本地没有;原生复现钥匙是 `GLIBC_TUNABLES=glibc.malloc.tcache_count=0`(c20e2f2b 上恰好那两例失败,修后 0)。修法:`MGPipeSlots()`、`MGPipeResourceTrackerInstance()`、`g_applier` 改为永不析构的单例(堆上构造、退出时有意泄漏),一并覆盖 C-1 返工新增的第二条到达路径(`~VertexArrayObject` 的 `MGPipeEmitVertexElementsDestroyAndFree`)。 落地为三个提交:`d54ec57a`(三个单例永不析构)、`6515c8e6`(**从源头收口**:`gPipeInputs`、`g_snapshot`、`g_readScratch`、`probe` 这四个持有前端 `SharedPtr` 的静态 `PipeInputs` 改为退出时泄漏的存储——树里只有它们持有前端对象,于是没有任何前端析构函数会从 exit handler 里跑;这是 `Init.cpp`/`GlobalObjects.cpp` 已有的规则)、`fde5fda3`(其余四个 MGPipe 单例永不析构,作为纵深防御——C-1 返工把 `MGPipeVertexInputEmitterInstance()` 放上了 `~VertexArrayObject` 的死亡路径,ASan 在 6/6 代表用例上抓到 `VertexInputEmit.h` 的 heap-use-after-free)。证据:开着钥匙 `integration-verify` 844/844、push 臂 `integration-gpu` 966/966,ASan 10/10 干净;整条 verify 通道在 ASan 下只剩 `IntegerBorderColorScenario` 一个与本阶段无关的既有测试 bug(2×2 上传的 4 字节源配 `UNPACK_ALIGNMENT=4` 的栈越界读),另开任务。 + +**`MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1` 那 186 条过滤:P3a 跑,跑一次,在 push 下跑,不进比对器。** §14 里 P2 把这条推给 P3b,理由是"P2 在 buffer/回读方向什么也没改";**P3a 改的恰恰是这个过滤的题材**——它是 `FlushPendingRangesNow` 第一档的 kill switch(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:1071`、`:1331`),而 P3a 重写了那个函数的调用方。所以门里加了具名的一步 `MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1 ctest --test-dir build-push -L integration-gpu`,结果 **958/958**(§16 第 3 部分)。**放进 5–10× 的比对器里跑仍然推迟到 P3b**;这次是把拆开的两半都写下来,而不是让 CI 里那句注释再吊一轮。 + +**`g_uploadRing` 不被重置的不对称:原样保留,记为 `dev` 侧跟进。** `OnBackendContextDestroyed`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2481`)对 `g_uboRing` 与 `g_unpackRing` 调 `ResetRingForNewContext`(`:2492-2493`),**不对 `g_uploadRing` 调**;`RingAvailable`(`:3186`)在首次使用时按 `contextGeneration` 自愈,所以它是良性的。P3a **故意不在飞地顺手修**(`ROADMAP.md:98` 那条纪律:拆分不得借机修不相关的 `dev` 问题),把它作为 `dev` 侧跟进项留在这里。 + +**`FlushPendingRangesNow` 定义一次,句柄臂另有一条自己的档梯。** G5 的第十项与 G1 的空 resize 集之间有一处真冲突:就地重构那几个 helper 会 resize 五个 pull 符号(`FlushPendingRangesNow +14` 在内),G1 不允许。落地形状是:**`FlushPendingRangesNow` 只定义一次、对 `5cb826b0` 逐字节相同**(在 `#if MOBILEGL_PIPE_PUSH` 的 `#else` 臂里,`Managers.cpp:1316`,调用点 `:1723`、`:2906`),句柄臂另有一个 `FlushPendingRangesFrom(twin, hostBase, size)`(`:1051`,调用点 `:1721`、`:2044`、`:2766`、`:2904`)。**在 P3a 接受档梯在 push 构建里被复制一份**(与 respecify 核心已经用过的形状相同),代价是两条梯子会漂移;对冲是 `CrossFrameBufferScenario` 的十三条加 `StreamedArenaScenario` 的两条 recycle 用例,以及 §20 的 MC 26.3 p99。**它随 pull 臂在 P13 退役**(`ARCHITECTURE.md:367`)。共享模板加访问器接口的方案被否决:它同样 resize pull 符号(G1)。 + +**`MG_Test/Buffer/BufferTest.cpp` 的 fixture 只 scope 了一半的表。** push 构建下后端在 bring-up 同时装 `BufferBackendOps` 与 `MGPipeResourceOps`,而 `ScopedBackendOps` 只 scope 前者,于是 86 条 `BufferBackendOps` 分发用例里有 **26 条**被路由进了 pipe(症状是 `EnsureGpuResidentStorage()` 返回 `false`、mock 从没被调用过)。这是**合并缝**的典型形态——两个分支各自绿、合起来红:espryt 那边没有东西经 pipe 发射,client 那边没有东西注册表。集成者落了单 scope 的修法(fixture 现在像 `MG_Test/Pipe/ResourceEmitTest.cpp` 的 `ApplierGuard` scope applier 那样,保存 / 置空 / 恢复 pipe 表):**修完 86/86,整套单元 1619/1619**。它不削弱任何东西——那 86 条是 `BufferBackendOps` 的分发测试,pipe 侧的分发有 `ResourceEmitTest` 自己的覆盖。**跟进(不属于本阶段)**:给这个 fixture 一个 pipe 形的 mock,让同样的 86 条断言在句柄路径上再跑一遍。 + +**逐 dirty 位的触发率与每 draw payload 直方图:仍然未测。** §13 记的那两条在 P3a 也没有补上——没有任何一份 P3a 的结果文件报过位 5 / 9 / 10 收窄前后的 `FireCount` / `WalkCount`,也没有报过 24 桶的直方图(它仍然只在 teardown 的 JSON 里输出,而 trace app 从不到达那次 teardown,§5.1)。计划把这两项列为"虽然没人要也要发布"的项,**P3a 没有做到,如实记在这里**;补法与 §13 写的一样(给 `FormatWindowLine` 加一行,或从一个场景里经访问器读)。 + +**峰值 RSS(push vs pull,79 例 retrace):工具不报,所以没有数。** `~/w7/retrace_gate.py` 只有五个参数(`--tree --lib --out -j --only`),代码里没有任何 `rss` / `maxrss` / `getrusage` 引用。这个数原本是用来盯第七张 slot 表泄漏的——一个没人销毁的 buffer 会永远漏掉它的 twin,而这对每一个正确性门都不可见;**这一轮拿不到它**,要拿必须先给那个工具加测量。对冲仍在:`ResourceDestroy` 是从 `~BufferObject` **无条件**发射的、不是靠清扫,顺序(先发射、后 `MGPipeSlots().Free`)由 `HandleRecycleScenario` 的三个臂把关(§16、§17)。 + diff --git a/docs/Disaggregated/README.md b/docs/Disaggregated/README.md index c017ca09..0660dd02 100644 --- a/docs/Disaggregated/README.md +++ b/docs/Disaggregated/README.md @@ -1,8 +1,8 @@ # MGPipe:MobileGL 前后端拆分 -> 状态:**P0、P0.5、P1、P2 已落地**(`feat/disaggregated@738b289d`,基线 `dev@50fb1343`)。第 43 天 GO/NO-GO 判定为**继续**,下一步 P3a。见 `ROADMAP.md`。 +> 状态:**P0、P0.5、P1、P2、P3a 已落地**(`feat/disaggregated@fde5fda3`,基线 `dev@9eae9858`)。第 43 天 GO/NO-GO 判定为**继续**。P3a(handle wave 1:Espryt 的 buffer 与 VAO)已交付,**下一步 P4a**(handle wave 2:FBO / 纹理 / sampler / program 的身份与描述符)。见 `ROADMAP.md`。 > -> 性能纪律(2026-09-08 起):逐线程 CPU 与 tracker 绝对 ns **对着 pull 臂基线记录**,不再作阻塞门(push 比 pull 多约 10% 逐线程 CPU 已被接受),专门的优化阶段排在路线图推完之后。 +> 性能纪律(2026-09-08 起):逐线程 CPU 与 tracker 绝对 ns **对着 pull 臂基线记录**,不再作阻塞门(push 比 pull 多约 10% 逐线程 CPU 已被接受;该读数出自 -O0 APK,Release 基准线见 `MEASUREMENTS.md` §20),专门的优化阶段排在路线图推完之后。 ## 是什么 @@ -31,7 +31,7 @@ MGPipe 是 MobileGL 前端(`MG_State` + `MG_Impl`)与后端(`MG_Backend` |---|---| | `ARCHITECTURE.md` | 已定稿的设计与架构:句柄与世代、调用目录、记录约定、tracker、纹理路径、shader 制品、反向通道、后端改造、传输、persistent map 分档、进程/EGL/平台、构建与纯度门、验证策略 | | `ROADMAP.md` | P0…P13 阶段表、两条跑道、GO/NO-GO 清单、再基线检查点、仍然开放的问题 | -| `MEASUREMENTS.md` | P0 实测:spike A/B 结论、双设备四条 trace 的边界计数器基线、桌面数据点、语料事实、复现命令 | +| `MEASUREMENTS.md` | 逐阶段实测:P0(spike A/B、双设备边界计数器基线、桌面数据点、语料事实)、P1(verify harness 门)、P2(五部分门、两机配对 A/B、DriverBench T1/T2、计数器)、P3a(门、接缝缺陷、Track H 普查、两机 A/B)与复现命令 | 代码地图(P0 已落地的部分): diff --git a/docs/Disaggregated/ROADMAP.md b/docs/Disaggregated/ROADMAP.md index 19d633e6..c46853aa 100644 --- a/docs/Disaggregated/ROADMAP.md +++ b/docs/Disaggregated/ROADMAP.md @@ -1,6 +1,6 @@ # MGPipe 路线图 -> 状态:P0、P0.5、P1、P2 已落地(`feat/disaggregated@738b289d`)。第 43 天 GO/NO-GO 判定为**继续**,下一步 P3a。设计见 `ARCHITECTURE.md`,实测见 `MEASUREMENTS.md`。天数是各阶段所含子系统行的求和(低端 / 高端),总计 **267–337 人天**(不含 CTS 周转);两个工程师、P7 与 P5/P6/P8 并行约 7–9 个月,真正的约束是两台设备的争用。 +> 状态:P0、P0.5、P1、P2、P3a 已落地(`feat/disaggregated@fde5fda3`)。第 43 天 GO/NO-GO 判定为**继续**,下一步 P4a。设计见 `ARCHITECTURE.md`,实测见 `MEASUREMENTS.md`。天数是各阶段所含子系统行的求和(低端 / 高端),总计 **267–337 人天**(不含 CTS 周转);两个工程师、P7 与 P5/P6/P8 并行约 7–9 个月,真正的约束是两台设备的争用。 ## 通用纪律(每个 commit) @@ -15,8 +15,8 @@ | **P0** 卫生、度量、门、骨架 | 9–11 | ✅ 边界计数器(字节 / 动态 accessor / 六个 memo 门 / 上传形状);`PipeCalls.def` 完整目录 + payload POD + 七个生成器 + CI `pipe-gates`;`gen_pipe_dirty_surface.py`;`check_doc_citations.py`;八个 `MOBILEGL_PIPE_*` 开关;`MG_Remote/{Protocol,Transport}` 骨架(`SCM_RIGHTS` 第一优先、双 tail 双三元组的 `RingControl`、双向 doorbell、校验型 `Framing`、`ShmSegment`、`InProcessTransport`)+ `protocol.fbs` + `flatc-check` + `MG_Test/Wire` 五个套件;三个严格 no-op 收益(`GetInteger64i_v`/`GetProgramiv` 退役、`RenderbufferObject::GetLifetimeId()`、D21 XFB 计数槽重键);compute 限制进 `DynamicBackendParameters`;spike A、spike B;retrace 通道 `--env` 透传 | ✅ 单元/集成/40 trace 逐名不变;wire 层测试(fd 传递、doorbell、ring、封帧、inproc)绿;两台设备的字节/调用基线在案;spike A/B 出结论;citation lint 绿 | — | | **P0.5** 值头与制品头抽取 | 6–9 | ✅(`5d99ee43`)`MG_Pipe/MGPipeValueTypes.h`(`RenderStateParameters`、`SamplerParameters`、`PixelStoreParameters`、`VertexAttribute`… 不 include `MG_State/GLState`);`MG_State/GLState/ProgramState/ProgramArtifacts.h`(五个反射类型,不 include `ShaderObject.h`/`SpvcSession.h`,8 个 includer 零改动——类内 `using` 别名保住每一种既有拼写);`Visit()` 归档 + `sizeof` 绊线;CI `-H` include 闭包断言(`scripts/check_include_closure.py`,text + clang 两模式、自带阴性对照、`--require-all` 棘轮;`scripts/symbol_report.py` 做逐符号归因)。实测落地:测试名零删除、`MG_Backend` 零 diff、`.text` 字节不变、符号 0 增 / 0 删 / 42 重命名;`DynamicBackendParameters` 未搬(含 `SizeT` 与 `TextureTarget` 成员,搬动不是纯移动),`MGPipeTypes.h` 仍 include `BackendObject.h`,闭包门 A 因此断言 `MGPipeValueTypes.h` | 全套测试逐名不变(纯搬移);两条闭包断言绿且人为加回一个 `MG_State` include 能变红;`nm`/`.text` 变化可逐符号归因 | P0;**P1 与 P7 的硬前置** | | **P1** `PipeInputs` 替换与 verify harness | 10–13 | ✅ `MG_Backend/MGPipe/PipeInputs.h`(63 字段;Espryt 32 / Magma 56 访问器);**实测 277 处箭头 + 58 行非箭头**(Espryt 113+9、Magma 164+49)逐条转换;逐 verb 类填充点(G5 表,~93 个边界站点);逐 verb 世代 poison;G4 影子比对器 + 第三种 CI 模式;20 处 `SyncPersistentMappedRange` + 6 处 `SyncGpuWrites` 的逐站点归属表 | pull 构建 `nm --defined-only` 不变、`.text` 差异逐行归因(空守卫/三元重写推迟到 P2);40 trace + 全部集成测试在 `MOBILEGL_PIPE_VERIFY=1` 下零分歧;故意损坏一个快照字段能让 verify 变红;故意在 `glGenerateMipmap` 的填充表漏一个字段能在**那条 verb** 上触发 poison Fatal | P0.5 | -| **P2** 渲染状态 CSO + 第一片 Track H + 残余值块 | 18–26 | ✅(`738b289d`)`MG_Impl/Pipe/Tracker`(dirty 位、5 个聚合世代、抑制器骨架);`gen_pipe_dirty_surface.py` 首轮映射成门(73 个 mutator 全映射,45 条 render-state 答案 + 8 条 (mutator, bit) 答案逐字段导出,0 COARSE / 0 UNDECIDED,`.github/workflows/test.yml:1601-1604`);`MGPipeRenderStateSpans` chunk 表 **7 个 pipeline chunk / 396 B + 8 个 dynamic chunk / 772 B = 1168**(`MobileGL/MG_Pipe/MGPipeRenderStateSpans.h:190-191`)+ G7 setter 一致性测试;`CsoCache`(**64 项**,键 = pipeline 子集,`MobileGL/MG_Impl/Pipe/CsoCache.h:53`);`create/bind_render_state` + `set_dynamic_state`(Espryt `SyncRenderState` 一行不动;Magma `ComputePipelineStateHash`/`GetOrCreatePipeline`/`ApplyDynamicDrawStateTail` 改从 CSO 与动态 payload 取);`set_pixel_pack_state`、`set_patch_state`、`set_vertex_attrib_defaults`;`set_residual_value_state` + `ResidualValueBlock` 绊线(**棘轮 1248 → 8**,`MobileGL/MG_Pipe/MGPipeTypes.h:546`);**第一片 Track H**:Espryt 0b(`SlotAllocator` + 6 个 registry → slot 数组 + 删 `TwinLookupMemo`×3/`OwnerEquals`/`g_fbSlotCache`/GC)与 Magma 子系统 4(`VertexInputStateFactory`/`VaoDrawMemo` 重键,删前端 VAO 里的后端裸指针)——共 **11 条身份 memo 直接删除 + 2 条重键**(`ARCHITECTURE.md` §9.5 的普查),pre-handle 臂在 `MOBILEGL_PIPE_LEGACY_MEMOS` 下并存到 P13;`MOBILEGL_PIPE_LEGACY_MEMOS`(`CMakeLists.txt:36`);补**三个** capability 存储 `FramebufferSrgb`/`DepthClamp`/`TextureCubeMapSeamless`(`MobileGL/MG_Pipe/MGPipeValueTypes.h:303-305`) | ✅ 集成 × 2 后端 × {pull, push} 逐名相同(名差 0);pull 构建符号 0 增 / 0 删 / 0 重命名、四个已认定 resize、`.text` +160 B;`RenderStateImpl` sha 不变;单元 1566 × {pull, push, verify};`integration-gpu` 916/916(pull、push、`MOBILEGL_PIPE_PUSH=0` 三臂);79 例 retrace push 下 79/79、verify 下 79/79 零分歧;`integration-verify` 828 条零 `Fatal{`;verify 构建的三组对照(`PoisonOmitted`、`VerifyCorrupted`、`HandleRecycle`)44/44,且 ABA 对照关掉句柄身份即红;G7 负面对照按设计变红并点名 `SetColorMask`;`CsoContentAddressing` 6/6;测试名 0 删除 / +119。设备(小米 Adreno 830,配对、定频、尾 200 帧 p50):push 比 pull 多约 10% 逐线程 CPU——**用户 2026-09-08 决定接受**,性能自此对 pull 基线只记录不设门;两机各 16 行在案(小米 p50 +8–14%,Oppo Espryt +8–18% / Magma +10–11%);桌面 `DriverBench` T1 = +322 / +345 ns/draw(Espryt / Magma),T1 − T2 = −228 / −354(P2 比它替掉的 P1 残余填充便宜),blend-toggle +4.7% / +3.5%,见 `MEASUREMENTS.md` §9–§14 | P1 | -| **P3a** handle wave 1(Espryt):buffer、VAO | 18–23 | 7 个 `BufferBackendOps` → `resource_*`、`buffer_subdata_resident`(可 null)、`resource_flush_range`、`resource_readback`、`map_persistent`(不碰实现);pool 与延迟释放原样搬;vertex elements 三件(两个视图都带);`set_vertex_buffers`(`baseInstance` 显式字段);`set_index_buffer`;Adreno SIGSEGV workaround 保留 | 全套门;buffer/VAO 族场景(`LargeArenaAdoption`、`StorageBufferRegrow` 发布 `map-persistent-roundtrips`、`VertexAttribBinding`、`MultiDraw`、`PrimitiveRestart`…);Create/rd12/26.3/sodium trace;MC 26.3 在 Adreno 上 p99 不变。**再基线检查点 1:超过 27 天必须重定基线** | P2 | +| **P2** 渲染状态 CSO + 第一片 Track H + 残余值块 | 18–26 | ✅(`738b289d`)`MG_Impl/Pipe/Tracker`(dirty 位、5 个聚合世代、抑制器骨架);`gen_pipe_dirty_surface.py` 首轮映射成门(73 个 mutator 全映射,45 条 render-state 答案 + 8 条 (mutator, bit) 答案逐字段导出,0 COARSE / 0 UNDECIDED,`.github/workflows/test.yml:1601-1604`);`MGPipeRenderStateSpans` chunk 表 **7 个 pipeline chunk / 396 B + 8 个 dynamic chunk / 772 B = 1168**(`MobileGL/MG_Pipe/MGPipeRenderStateSpans.h:190-191`)+ G7 setter 一致性测试;`CsoCache`(**64 项**,键 = pipeline 子集,`MobileGL/MG_Impl/Pipe/CsoCache.h:53`);`create/bind_render_state` + `set_dynamic_state`(Espryt `SyncRenderState` 一行不动;Magma `ComputePipelineStateHash`/`GetOrCreatePipeline`/`ApplyDynamicDrawStateTail` 改从 CSO 与动态 payload 取);`set_pixel_pack_state`、`set_patch_state`、`set_vertex_attrib_defaults`;`set_residual_value_state` + `ResidualValueBlock` 绊线(**棘轮 1248 → 8**,`MobileGL/MG_Pipe/MGPipeTypes.h:546`);**第一片 Track H**:Espryt 0b(`SlotAllocator` + 6 个 registry → slot 数组 + 删 `TwinLookupMemo`×3/`OwnerEquals`/`g_fbSlotCache`/GC)与 Magma 子系统 4(`VertexInputStateFactory`/`VaoDrawMemo` 重键,删前端 VAO 里的后端裸指针)——共 **11 条身份 memo 直接删除 + 2 条重键**(`ARCHITECTURE.md` §9.5 的普查),pre-handle 臂在 `MOBILEGL_PIPE_LEGACY_MEMOS` 下并存到 P13;`MOBILEGL_PIPE_LEGACY_MEMOS`(`CMakeLists.txt:36`);补**三个** capability 存储 `FramebufferSrgb`/`DepthClamp`/`TextureCubeMapSeamless`(`MobileGL/MG_Pipe/MGPipeValueTypes.h:303-305`) | ✅ 集成 × 2 后端 × {pull, push} 逐名相同(名差 0);pull 构建符号 0 增 / 0 删 / 0 重命名、四个已认定 resize、`.text` +160 B;`RenderStateImpl` sha 不变;单元 1566 × {pull, push, verify};`integration-gpu` 916/916(pull、push、`MOBILEGL_PIPE_PUSH=0` 三臂);79 例 retrace push 下 79/79、verify 下 79/79 零分歧;`integration-verify` 828 条零 `Fatal{`;verify 构建的三组对照(`PoisonOmitted`、`VerifyCorrupted`、`HandleRecycle`)44/44,且 ABA 对照关掉句柄身份即红;G7 负面对照按设计变红并点名 `SetColorMask`;`CsoContentAddressing` 6/6;测试名 0 删除 / +119。设备(小米 Adreno 830,配对、定频、尾 200 帧 p50):push 比 pull 多约 10% 逐线程 CPU——**用户 2026-09-08 决定接受**(**勘误**:这批 APK 是 -O0 构建,见 `MEASUREMENTS.md` §10 勘误;Release 基准线以 §20 的 P3a 表为准),性能自此对 pull 基线只记录不设门;两机各 16 行在案(小米 p50 +8–14%,Oppo Espryt +8–18% / Magma +10–11%);桌面 `DriverBench` T1 = +322 / +345 ns/draw(Espryt / Magma),T1 − T2 = −228 / −354(P2 比它替掉的 P1 残余填充便宜),blend-toggle +4.7% / +3.5%,见 `MEASUREMENTS.md` §9–§14 | P1 | +| **P3a** handle wave 1(Espryt):buffer、VAO | 18–23 | ✅(`fde5fda3`,44 个提交,`git diff --stat 5cb826b0 3e298c9a` = 37 文件 / +9273 / −230)**九条 `resource_*` 调用**接线:`ResourceCreate`、`ResourceRespecify`、`ResourceSubData`、`BufferSubDataResident`(`kOptional`,Magma 仍不注册)、`ResourceFlushRange`、`ResourceReadback`、`ResourceDestroy`、`MapPersistent`、`UnmapPersistent`——全部在 GL 调用时刻从 `BufferObject` 的十一处分发点发射(`ARCHITECTURE.md:157` 的唯一例外),落到新的句柄形后端表 `MGPipeResourceOps`(`MobileGL/MG_Pipe/PipeApply.h`,签名里没有任何 `MG_State` 类型);Espryt 的 `Ops_*` 行为逐条不变,只换了读输入的地方。**五条 vertex-input 调用**:`CreateVertexElements`/`BindVertexElements`/`DeleteVertexElements`(blob 两个视图都带,`MGPVertexAttribWire[]` + `MGPVertexBindingPointWire[]`,`ARCHITECTURE.md:132`)、`SetVertexBuffers`(`BaseInstance` 显式字段,取代 `g_pendingFetchBaseInstance` 那个 ambient 全局)、`SetIndexBuffer`。**第七张 Espryt slot 表**:`GLESBufferResource` 搬出 `PipeResource::m_backend`,进 `BackendSlotTable`(`MobileGL/MG_Backend/DirectGLES/Managers.h:691-692`)——第一张按 client 铸造、随调用过线的句柄索引的表。`kNeedsAck` 首次落到目录里(`ResourceRespecify`,逐记录谓词 `MGPipeResourceRespecifyNeedsAck(desc) == (desc.Immutable != 0)`,`MobileGL/MG_Pipe/MGPipeTypes.h:680`)。pool、延迟释放与三条 ring 原样搬(G5 十个函数逐字节相同);Adreno 禁用属性 SIGSEGV workaround 保留。**memo 普查(`ARCHITECTURE.md` §9.5)**:句柄臂上**退役 6 条身份 memo 成员**——VAO twin 的 `m_hasSyncedConfigVersion` / `m_syncedConfigVersion` / `m_syncedAttributeVersions`(`Array`,本阶段最大的一处)/ `m_syncedIndexBufferVersion` / `m_syncedIndexBufferObject`(裸前端指针),加 `ConvertedFloat64Stream::sourceLifetimeId`(即普查里 11 条直接删除的最后一条,`ConvertedVertexStreamKey` 的 `sourcePin`)。六条都**仍在 `MOBILEGL_PIPE_LEGACY_MEMOS` 下编译**(`Managers.h:1109-1130`、`:1067-1075`),pull 构建强制该开关 ON,所以 `sizeof` 一处不动——G1 的 0/0/0/0 就是这条的度量,真正的删除随 pull 路径在 P13 发生;**4 条重键** —— twin 的同步门(config version + 32 组逐属性版本 → `{elementsHandle, elementsSerial}` + `VertexBuffersSerial`)、twin 的索引槽 memo(回绕 `Uint16` + 裸 `BufferObject*` → 一个 `Uint64 m_syncedIndexSerial`)、`ResolvedDrawBuffers`(`configVersion` → `{elementsHandle, elementsSerial, buffersSerial}`,`Entry` 与 `iboFrontend` 各加 `MGPipeHandle`)、`ConvertedFloat64Stream`(前端 lifetime id + change serial → buffer `{slot, gen}` + applier `Serial`),另 `GLESBufferResource::syncedChangeSerial` 从镜像 `BufferObject::GetChangeSerial()` 改为镜像 applier 的 `Serial`;**保留不删** —— `g_pendingFetchBaseInstance` / `SetPendingFetchBaseInstance` / `GetPendingFetchBaseInstance` / `ScopedFetchBaseInstance` 与它的三个 scope 仍在 `MOBILEGL_PIPE_LEGACY_MEMOS` 下编译(`Managers.h:1189-1200`、`DirectGLES.cpp:5293`),因为真删会从 pull 构建移走两个符号 = 直接的 G1 破坏,计划里"grep 为空"那条在 P3a **不可达**;`PipeResource::m_backend` / `SetBackendResource` / `ReleaseBackend` / `BackendBufferResource` 同理(D-K,`ARCHITECTURE.md:286`),push 下只是不再被写,随 pull 路径在 P13 退役。**`map-persistent-roundtrips`(`mpr`)**:定义为**每一次 `MapPersistent` 发射,铸成或拒绝都算**(`ARCHITECTURE.md:485`),所以 monolith 下非零、可断言。子系统位 7(resources)与位 8(vertex input),push 默认 `0x7f` → **`0x1ff`**(`kMGPipeSubsystemsMigratedAtP3a`) | ✅ 五部分门(本地 `~/w7/pipe`):**G1** pull 符号 0 增 / 0 删 / 0 重命名 / **0 resize**、`.text` 10806323 字节不变(P3a 的认定 resize 集为空);**G5** 十个 pool / 延迟释放 / ring / flush-drain 函数对 `44c2b5cf` **与** `5cb826b0` 都逐字节相同,self-test 两个阴性对照(`ClearBufferPool`、`FlushPendingRangesNow`)都按名变红;`RenderStateImpl` sha 仍不变;**G2** pull 与 push 逐名相同(名差 0);**G14** 测试名 0 删除 / **+58**;单元 **1619** × {pull, push, verify};`integration-gpu` **958/958** × {pull、push、`MOBILEGL_PIPE_PUSH=0`、`0x7f`(G12 的子系统关闭臂)、`MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1`};buffer/VAO 族 **202/202**;`RenderStateSpans`/`Residual`/`VertexInputEmit`/`ResourceEmit` **48/48**;`HandleRecycle`(verify)**60/60**,含新的 buffer 用例三臂;`CsoContentAddressing` + `ResourceSubsystemControl` **10/10**,controls **64/64**;**G7** 两个阴性对照都按设计变红,vertex-input 那个**点名 `IsBgra`**;`integration-verify` **842/842**,零 `Fatal{`;`gen_pipe` 与 `gen_pipe_dirty_surface`(scan root 已扩到 `MG_State/GLState`,75 个 mutator 全映射、0 COARSE / 0 UNDECIDED)`--check` + `--self-test` 全绿。79 例 retrace:push 下 **79/79**,`MOBILEGL_PIPE_VERIFY=1` 下 **79/79 全部 armed、零分歧、零 `Fatal{`**;G3b 的具名五条都在这两次扫描里通过,**但单独那次具名扫描因门脚本把正则写成了逗号清单而选中 0 例**,且设备侧仍按开放问题 17 排除 `create-indirect`,所以 G3b 记为"桌面全绿、设备侧部分"——详见 `MEASUREMENTS.md` §16。**性能只记录不设门(用户 2026-09-08 规则 (a))**:MC 26.3 在 Adreno 上的 p99 对与两机 p50/p99 表见 `MEASUREMENTS.md` §20。**再基线检查点 1:实际日历 1 天(2026-09-08,四个包),远低于 27 天,不触发重定基线** | P2 | | **P4a** handle wave 2(Espryt):FBO / 纹理 / sampler / program 身份与描述符 | 26–34 | `set_framebuffer_state`(解析后的 `ReadSurface`、内联格式、`ContentHash`、`{0,1}`);sampler CSO(含 `borderColorForm`);sampler view + `set_texture_params`;`set_sampler_views`/`bind_sampler_states`/`set_shader_images`;shader CSO(SPIR-V + 归档);`set_draw/dispatch_program`;`set_global_constants`;`CompositeResolver`;纹理/renderbuffer 的 `resource_*`。emulation 在 split 下显式 Fatal 直到 P8 | 全套门;framebuffer/纹理/program 族场景;**新增"只作 attachment / image 单元 / CopyImage 端点的纹理其 `glTexParameter` 生效"场景(落地前必须红)**;两台设备 `KHR-GL46.direct_state_access.framebuffers*` 与整个 `packed_pixels` 块(~3300 例,句柄复用压力测试)。**再基线检查点 1b:超过 39 天** | P3a | | **P5** 传输 + inproc applier + 发射表 | 12 | `MG_Remote/Client` 发射表;`Server/PipeApplier`、`ServerLoop`(`mgl-srv-io` + `mgl-srv-apply`);`Init.cpp` 单一 hook 装 `BackendObject_Remote`;`MGPCaps` 快照;阻塞 `read_pixels`;client 侧保守 `MarkGpuWritten`;**client 侧块粒度 persistent-map 推送**;`InProcessTransport` 走与 spawn 相同的 G3 编解码;trace-replay `SPLIT` 后缀 + `-DTRACE_TRANSPORT=`;`MOBILEGL_TRANSPORT` 解析 | `DirectGLES.Split.*(ClearThenReadPixels|Triangle)` 在 `inproc` 下绿;OpenRA trace split SSIM ≥ 0.99;`PersistentCoherentMapScenario` 绿;两个角色峰值 RSS 在案;`persistent-map-push` 出数;未迁移字段读 = `Fatal{UnmigratedPipeInput}`。**第 99 天:首个 IPC 帧(缩减路径)** | P4a | | **P6** spawn transport | 5 | `SocketTransport`(socketpair + fork/execve,envp 剔除 + 强制 monolith 双保险);`ServerMain`;`MOBILEGL_IPC_SERVER_PATH` + `dladdr` 兜底;有界重试握手;EOF 即时退出;device-lost latch | P5 全部测试在 `spawn` 下绿;进程树只多一个子进程;`HeadlessGL` fork 预检无孤儿;OpenRA 在 Adreno 830 上 split SSIM ≥ 0.99。**第 104 天:首个跨进程帧** | P5 | @@ -37,6 +37,7 @@ - **第 25 天(P1 出口)**:verify harness 逐 draw 逐字段证明"推送等价于拉取"。零产品风险,**不是** GO/NO-GO。 - **第 43 天(P2 出口):GO/NO-GO —— 判定继续**。P2 的五部分门全绿(逐名相同、零分歧、负面对照能红),逐线程 CPU 代价约 +10% 被接受,下一步立即开 P3a。 +- **P3a 出口(2026-09-08)**:五部分门全绿,pull 构建仍 0/0/0/0,`resource_*` 与 vertex-input 两个家族在 Espryt 上换到句柄寻址。**实际日历口径:1 天**——contract / wire / client / espryt / gates 四个包(五轮)全部在 2026-09-08 内完成、集成并跑完门,对着"P3a > 27 天"的绊线是 **1 / 27**,不触发重定基线,也不需要 `inproc` 的证伪数字(`:70`)。Track H 单位成本的完整普查见 `MEASUREMENTS.md` §19。 - 第 99 天:首个 `inproc` IPC 帧(缩减路径);第 104 天:首个跨进程帧;第 145 天:全功能 split;第 187 / 267 天:三道纯度门转绿。 ## 第 43 天 GO/NO-GO 清单 @@ -53,7 +54,7 @@ 判据与出口: -- **口径变更(用户 2026-09-08)**:push 比 pull 多约 10% 逐线程 CPU 可接受;性能自此**对着 pull 臂基线记录**、不作阻塞门,第 43 天的绝对 ns 上限降为记录项;路线图先推完,专门的优化阶段排在其后(或首个 IPC 帧之后)。 +- **口径变更(用户 2026-09-08)**:push 比 pull 多约 10% 逐线程 CPU 可接受(该读数来自 -O0 APK,`MEASUREMENTS.md` §10 勘误;Release 数字在 §20);性能自此**对着 pull 臂基线记录**、不作阻塞门,第 43 天的绝对 ns 上限降为记录项;路线图先推完,专门的优化阶段排在其后(或首个 IPC 帧之后)。 - **继续**(本次结算):五部分门全绿,两片 Track H 按计划的产出全部落地且零回归,按两条跑道推进,下一步 P3a。原判据(两台设备 p50 与 p99 逐线程 CPU 增量都不为负;tracker 绝对 ns 在上限内)保留为后续阶段的记录口径。 - **收缩为 headless 工装用途或重新评估**:任一判据落空。**不回滚**:P0/P0.5/P1/P2 的产物(句柄基建与重键、两个头文件抽取、计数器、verify harness、渲染状态 CSO)全是自洽的 monolith 交付物,留在 `dev`;MGPipe 收缩为 `MG_Test` mock 后端 → MGPipe recorder(给 trace_replay 一种记录已解析状态的录制格式)+ `inproc` 渲染线程实验;IPC 跑道搁置到出现新判据。 - 沉没成本:P0 与 P0.5 无论走哪条路都要花(后者本身是 monolith 净收益);真正只为 MGPipe 押上的是 P1 + P2 ≈ 28–39 天,NO-GO 分支下仍留下上述产物。 @@ -62,7 +63,7 @@ | 触发 | 动作 | |---|---| -| P3a > 27 天 | "窄句柄化"的前提错了,P4a 开始前重定基线 | +| P3a > 27 天 | "窄句柄化"的前提错了,P4a 开始前重定基线。**未触发**:P3a 实际 **1 天**(2026-09-08,四个包全部落地),`MEASUREMENTS.md` §19 记了逐包口径 | | P4a > 39 天 | 同上 | | P7 中点(第 40–52 工作日)完成子系统 < 40% | 立即重定基线(P3a 的检查点发现不了 Magma 特有的超期) | @@ -89,11 +90,11 @@ P0 已回答的不再列出(spike A 的域、spike B 的分档、`posix_spawn` 7. **`MG_Util` 的切割缝。** server 需要 SPIRV-Cross pass 流水线、ESSL 转译缓存、格式处理器、POST 探针;client 需要 glslang phase A/B 与反射层。P0.5 解决了 `ProgramObject.h` 一处,`MG_Util` 内部是否有干净的 Transpile-vs-Reflect 缝未审计。 8. **一份反射归档能否服务三个消费者**(Espryt 读前端表、Magma 跑 SPIRV-Reflect、`DirectVulkan.cpp` 为 `glGetProgramResource*` 又反射一遍)。 9. **viewport-array 回放能否塞进一次 `draw_vbo`**:`EndViewportRoutingPasses` 会 `InvalidateSyncedRenderState`,各遍之间观察到的状态是否与今天一致未验证。 -10. **`ResidentSubData` 的不对称怎么收口。** null 项保住今天的行为;给 Magma 补真实现是行为变更,独立 `dev` PR。 +10. **`ResidentSubData` 的不对称怎么收口。** null 项保住今天的行为;给 Magma 补真实现是行为变更,独立 `dev` PR。**P3a 的处置(仍开放)**:不对称原样保留。`MGPipeResourceOps::SubDataResident` 是 `kOptional`、允许为 null,前端检查它就像今天检查 `g_bufferBackendOps->ResidentSubData` 一样;**P3a 没有给 Magma 补实现**(Magma 的 buffer 路径是 P7),`kCapResidentSubData` 也没有接线。所以这条问题原样留给 P7 / 独立 `dev` PR。 11. **`SEG_STAGE` 的上限。** 六类新字节需要 P8 之后用 MC in-world 与 Create 两类 fixture 的 `stage-*` 计数器给 p99 占用;G3 分块路径需要设计与测试。 12. **P13 之后 split-only 渲染 bug 的 server 侧第二意见。** verify 构建 + recorder 只覆盖推送内容,不覆盖后端对它的解释。 13. **烘焙后的内部 shader 能否在没有活 `ProgramObject` 的情况下表达 uniform location 与 UBO 布局。** 未做原型。 14. **推送模型改变哪些按拉取模式调过的缓存命中率。** 幸存者容量在 P13 重调。 15. **monolith 的 `*IndirectCount` 不调 `SyncGpuWrites()` 是不是潜在缺口**(compute 写的 indirect buffer)。独立 `dev` 问题,拆分不得借机顺手修。 16. **索引宿主镜像的实际内存占用。** MC/Sodium/Iris 语料里 element-array buffer 总量未测;若显著超 64 MiB,退化路径的频率与代价必须实测。 -17. **create-indirect fixture 在 Adreno 830 上的失败**是 `dev@81b17c0b` 就有的(基线 APK 复现),不是本分支造成;它是 P3a/P8 验收清单里的用例,需要先在 `dev` 上修。 +17. **create-indirect fixture 在 Adreno 830 上的失败**是 `dev@81b17c0b` 就有的(基线 APK 复现),不是本分支造成;它是 P3a/P8 验收清单里的用例,需要先在 `dev` 上修。**P3a 出口的状态:仍然开放,仍是 `dev` 侧的活。** P3a 没有碰它,也不该碰(`ROADMAP.md:7` 的纪律:拆分不借机顺手修 `dev` 的 bug)。处置沿用 P2:`minecraft-1.21.1-neoforge-create-indirect-in-world` **留在桌面 79 例 SSIM 语料里**(G3/G3b 的具名扫描包含它,桌面栈上它是通过的),**排除在两台设备的 A/B 之外**(D.4.2);因此 G3b 记为"桌面全绿,设备侧因开放问题 17 而部分",不当作设备侧的通过。P8 要在这条 fixture 上断言 `roundtrips-per-frame == 0`,所以 `dev` 的修复在别人的关键路径上,不在 P3a 的。