Files
MobileGL/docs/Disaggregated/notes/recovered/wf1/final/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

33 KiB
Raw Blame History


15. 分阶段实施计划

通用纪律(每个 commit 都适用):默认 ALL target 必须能完整构建;禁止提交热路径插桩;每个门必须能因它存在的理由变红;Windows 机器不是正确性门(其 Vulkan 缺 vkCreateHeadlessSurfaceEXT,占该机 567 个基线集成失败中的 423 个);设备对比走 reboot-clean + 同窗口配对 A/B;每个阶段的出口都跑一次 §12 第 4 层的 nm/.text monolith 门(不只是 P0)。

P0 — 卫生、骨架与两个 spike(5 天)

交付物

  • 清除工作树 [IBOTX]/[BUFTX] fprintf(DirectGLES.cpp:290-303、Managers.cpp:875-877,后者在 pendingMutex 临界区内)。
  • RenderbufferObject::GetLifetimeId() 与 GetVersion()(§5.4)。
  • 两个 CMake option:MOBILEGL_BUILD_DISAGGREGATED(OFF) 与 MOBILEGL_BUILD_DISAGGREGATED_INPROC(OFF);MOBILEGL_TRANSPORT 解析;§13 的 flatbuffers include-dir guard。
  • MG_Remote/{Protocol,Transport} 骨架:ITransport、InProcessTransport、校验型 Framing、Ring + RingControl(双 tail、双游标三元组、双向 doorbell)、Doorbell、ShmSegment(memfd/ASharedMemory/shm_open/CreateFileMappingW)、SCM_RIGHTS fd 传递(第一优先)。
  • protocol.fbs + 提交的 protocol_generated.h + gen_protocol.py + CI flatc-check;Records.def 的 static_assert 与运行期边界检查生成。
  • gen_backend_state_surface.py + Coverage.def 和 gen_impl_mutation_surface.py + MutationCoverage.def + CoverageAssert.cpp + CI coverage-check。
  • MG_Test/Wire/ 目录(复制 MG_Test/Buffer/CMakeLists.txt)。
  • TracyPlot 字节计数器,装在 wire 两侧,按类别分:cmd-records、stage-buffer、stage-texture、stage-ubo、persistent-map-push、server-ring、server-staging(树里今天完全没有 per-frame 字节度量:MG_Util/Metrics 只是格式算术,Tracy 只有 zone 无 plot,MC 26.3 战役的 PANDIAG 已不在树里)。
  • mobilegl_server_main 的 extern "C" __attribute__((visibility("default"))) 声明(§11.2)。
  • spike A(Android 交付链,半天):从根 CMakeLists 造一个平凡的 libMobileGLServer.so(add_executable + PREFIX "lib"/SUFFIX ".so"),确认 AGP 把它打进 lib/arm64-v8a/;让 TraceReplayActivity 从 getApplicationInfo().nativeLibraryDir posix_spawn 它并打一行日志——在**应用自身进程(untrusted_app 域)**验证 exec,而不是靠 run-as。同时把一个通用 env 透传(--es mobilegl_env "K=V;K=V")接进 trace 路径的五个文件(trace-replay-ci.sh、TraceReplayActivity.java、JNI Request marshalling、trace_replay_core.cpp、run_android_retrace_local.py),取代逐 knob 加 --es/--ez。
  • spike B(external memory 可行性,半天):最小程序,导出一个 HOST_VISIBLE|HOST_COHERENT VkBuffer 的 fd,mmap 后回读校验,在 35d0befa(Adreno 830)与 3B159D009VZ00000(Mali)各跑一次。与 SCM_RIGHTS 测试同批。目的是让 P7 的结论在第一周就有方向:若两台都不行,P7 缩为"记录并回退",省 6 天。

验收

  • Linux 与 Android/NDK 上 cmake --build . 默认 target 成功。
  • ctest -L unit、-L integration-gpu 与 81b17c0b 同一通过集。
  • MG_Test/Wire 的 fd 传递测试把一个 memfd 从 fork 出的子进程传回父进程并读到相同字节。
  • nm --defined-only 与去符号 .text size 与改动前的 libMobileGL.so 一致(OFF 构建);nm -D | grep mobilegl_server_main 在 RelWithDebInfo 下命中。
  • spike A:设备上打出那行日志。
  • spike B:结论写进 §17 的开放问题并驱动 P7 的排期。
  • §12.2 的取舍拍板:inproc 走"四全局角色隔离"还是"降级为纯测试模式",写进文档。

P1a — 垂直切片(client + inproc applier),Linux 门(6 天)

范围刻意收窄到 OpenRA 需要的东西:仅 DirectGLES;buffer(仅 shadow,采纳强制关,含 §5.10 的 persistent-map 推送);2D 纹理的整 level 与 union-box 上传(含 §5.6a 的 clear-on-emit);VAO;FBO;render state;binds;索引与非索引 draw;clear;present;server 从源码 relink(带全字段 reflectionDigest);一条阻塞 ReadPixels;client 侧 MarkGpuWritten 保守置位(§5.6b)。不含 sync/query/XFB/compute/dirty-rects/MultiDraw。

交付物:WireMirror(含 PublishImplicitState、PersistentMapTracker、GpuWritePending)、EmitTable、EmitBufferOps、BackendObject_Remote、CapsMirror、ClientArrayBounds、CompositeResolver(P1-4 走"server 自建 composite + digest 校验",见 §5.7);ReplicaContext、Applier(含 MG_Remote::Shared:: 的 XFB/mipmap helper 接线,即使这一阶段还用不到 XFB)、ServerLoop(io+apply);InProcessTransport 上跑通。

验收

  1. ctest -R "DirectGLES\.Split\..*(ClearThenReadPixels|Triangle)" 在 Linux + MOBILEGL_TRANSPORT=inproc 绿。
  2. 新增 PersistentCoherentMapScenario(map PERSISTENT|WRITE|COHERENT、写、不做任何其它 GL 调用、draw、readback 校验)在 split 下绿。这是本计划里唯一一个专为一个 fatal 缺陷设的门,必须在 P1a 就绿。
  3. 记录两个进程/两个角色的峰值 RSS(不只是 server 的)作为 P5 与 §16-R14 的基线。
  4. Tracy 计数器给出 persistent-map-push 的字节量(§5.10 保守版的代价)。

明确非目标:性能。P1-4 双份 glslang,MC 级负载不在此测。

P1b — spawn transport,Linux 门(4 天)

交付物:SocketTransport(socketpair + fork/execve + 显式 envp 剔除 MOBILEGL_TRANSPORT/MOBILEGL_IPC_*)、ServerMain、MOBILEGL_IPC_SERVER_PATH 发现链、就绪握手与有界重试、EOF 即时退出。

验收

  1. P1a 的全部测试在 MOBILEGL_TRANSPORT=spawn 下绿(两个真进程、真 socket、真 SCM_RIGHTS 段)。
  2. fork 链测试:spawn 一个 server 并断言进程树只多出恰好一个子进程(§11.1)。
  3. HeadlessGL 预检交互测试:在开着 fork 预检的 Linux 上跑整套 split 集成用例,断言没有孤儿 server(用 pgrep 计数 + 预检结束后 100ms 内归零)。

P2 — 广度:集成套件、trace 语料对齐、设备首跑(9 天)

交付物

  • 其余记录种类(MultiDraw/indirect 族含 client 数组范围计算与索引扫描、纹理 dirty rects、texture view、buffer texture、image unit、sampler、renderbuffer storage、program pipeline、CopyImageSubData、BlitNamedFramebuffer、PixelStorePack、CurrentAttrib)。
  • 完整 caps mirror 与 tableSlotMask。
  • DirectVulkan applier 支持(SwapchainObject 的 default-FBO 占位写变成 EvDefaultFramebufferInfo)。
  • add_trace_replay_test 的 SPLIT 参数:测试名加后缀、-DTRACE_TRANSPORT= 与 run_trace_case.cmake 的消费、MOBILEGL_IPC_SERVER_PATH 注入(§13)。
  • 一个无 present 的 split 集成用例(§9.3)。
  • ClientArrayAfterComputeWriteScenario(§6.10)。

验收

  1. ctest -L integration-gpu -R '^DirectGLES\.Split\.' 与 '^DirectGLES\.' 逐名同一通过/失败集;DirectVulkan 同。
  2. CI 全部 trace case(OpenRA、minecraft-1.21.4-startup、-main-menu、1.21.11、1.17、两个 Create)在 Linux split 模式 SSIM ≥ 0.99。两个带 coherent_as_flush: true 的 Create 用例在 split 与 monolith 下都开着该开关跑(§5.10 已让两侧走同一路径),若 Tracy 显示保守推送在这两个 fixture 上代价不可接受,则把 §5.10 的精确版(P4.5 的块脏位)提前到本阶段——这是全计划唯一允许因测量改变阶段顺序的地方。
  3. python tools/trace_replay/run_android_retrace_local.py --case OpenRA --backend DirectGLES 在 35d0befa 上 SSIM ≥ 0.99(split 模式) —— 本阶段的出口判据(从 P1 移来),每轮约 1 分钟。

P2.5 — inproc 渲染线程:单机收益证伪门(3 天)

交付物:add_trace_replay_test 的 INPROC 变体;应用线程与 apply 线程的逐线程 CPU 时间插桩(不只是墙钟);MOBILEGL_IPC_SERVER_AFFINITY 的大核绑定(复用 ShaderCompilePool.cpp:73-96);用现有 --benchmark --benchmark-tail-frames --benchmark-result 在全部 fixture 上跑。

验收:inproc 与 monolith 的应用线程帧时差 + 两侧 CPU 时间在 Create/Flywheel 与 MC fixture 上被测量并记录,且带亲和性开/关两组。若不利,整个计划的价值主张在第 6 周(而不是第 15 周)被重新审视。这是本计划最早的证伪点,也是 §16-R15 排期风险的退火器。

P3 — sync / query / present 节奏(5 天)

交付物

  • client 铸造的 sync/query handle;轮询入口的 publish + 饥饿升级(§7.2)。
  • fence 完成度来自真实逐 fence 退休(§8 末尾):server 侧真 FenceSync + 非 present 轮询 + EvFenceSignaled。
  • DirectGLES 的非 present fence tick(§9.3)。
  • EvQueryResult;present credit 默认 1 + 三个 seq 水位;swap interval 搭 RecPresent。
  • §8 的三个 dev 独立修复。
  • per-frame round-trip 计数器;输入延迟直方图(记录发射 → present 完成,§9.1)。

验收

  1. XfbPrimitiveQueryScenario、PrimitivesGeneratedNoXfbScenario、AsyncCompileScenario 在 split 下绿。
  2. round-trip 计数器:在全部 trace case 的稳态帧上,draw/state/upload 路径的 round trip 读 0;conditional render 与阻塞式 query 的次数按用例列表公布(不是笼统宣称"零 round trip")。
  3. 零 timeout 轮询循环测试:一个只有 glFenceSync + while(glClientWaitSync(...,0)==GL_TIMEOUT_EXPIRED){} 的用例必须在有界时间内退出(若无 §7.2 的 publish 规则它会永久挂起)。
  4. bench.sh 在 35d0befa 配对 A/B(两侧均关采纳)显示 split 帧时在 monolith 的 10% 内,且输入延迟直方图的 p50/p99 被记录。

P4 — 回读与 GPU-written(5 天)

交付物:SEG_REPLY;阻塞 ReadPixels → 客户内存;PBO readback 变 fire-and-forget + client 侧 MarkGpuWritten;EvGpuWritten 作为收窄提示;EvBufferWriteback;glGetTexImage/GetTextureImage 路由 + per-level serverAuthoritative 位(只覆盖生成 mip 与 CopyImage 镜像两处,§6.6);EvGlError + 分配类入口的 kNeedsAck(§5.6c);SEG_EVENT 溢出策略与等待中排空(§7.4)。 glCopyTexSubImage* / glClearTexImage 保持前端实现不变(推翻上一版的"移到 server + EvTexWriteback")。

验收

  1. split 下 DepthStencilReadbackScenario、DepthStencilReadbackMatrixScenario、DepthStencilReadbackAttachmentShapeScenario、PackedWordReadbackScenario、LayeredTextureReadbackScenario、ClearThenReadPixelsScenario、PixelStoreSweepScenario、CopyImage*(4)、SsboArrayLengthScenario、AtomicCounterScenario、StorageBufferRegrowScenario 双 backend 全绿。
  2. OOM 探测用例:请求一个必然失败的巨大 renderbuffer,断言紧接着的 glGetError() 返回 GL_OUT_OF_MEMORY。
  3. 事件 ring 溢出故障注入:client 被 present credit 阻塞时灌满 SEG_EVENT,双方都不死锁,eventDropped 只统计到 EvLogLine。

P4.5 — 零拷贝 shadow-in-shm 前移(4 天)

(原计划推到 P6;MC pan 每帧 ~9MB 的额外拷贝不该背六个阶段)

交付物:ShadowArena;MapAlignedAllocator 与 MipmapStorage level vector 的 shm arena(≥256KiB 才走,整段 #if MOBILEGL_BUILD_DISAGGREGATED 包裹,§12 第 3 层);per-shadow 64KiB 块发送水位 WAR 规则;shadow 块退休规则(§6.1);§5.10 精确版 persistent-map 推送复用同一套块脏位;MOBILEGL_IPC_SHADOW_SHM 开关。

验收

  1. P2/P4 门在开关两态下均不回归。
  2. 两侧 TracyPlot 显示 buffer 上传路径的总拷贝次数从 4 降到 3(或选方案 B 则到 2,§6.4);staged-copy 回退率被记录成数字。
  3. nm/.text monolith 门仍绿(这一条是本阶段最容易破的)。
  4. 对象删除/重定义与未 apply 记录并发的压力测试不读到别的对象的字节。

P5 — ProgramPublish,退役 server relink(6 天)

交付物:ProgramArtifactsArchive.h(Visit() + sizeof 绊线);ProgramObject::InstallPublishedLink;GLContext::SetReplicaResolvedDrawProgram(#if MOBILEGL_BUILD_DISAGGREGATED 包裹)+ client 侧 composite 解析;MOBILEGL_IPC_PROGRAM=publish|relink;publish 下移除 server compile pool;顺带把 DirectVulkan 的 blit / depth-mipmap 四段固定 shader 在构建期烘成 SPIR-V(VulkanRenderer.cpp:4211-4356,同时也从 monolith 启动里去掉一次 glslang 编译链接;逃生口 MOBILEGL_BAKED_INTERNAL_SHADERS=0)。

验收

  1. P2 全门在 publish 下重跑不变。
  2. relink 下 reflectionDigest 在每个 trace case 绿(即它是活门不是死门)。
  3. 35d0befa 上用 minecraft-1.21.4-startup trace 做首帧 link 延迟 A/B,publish ≤ relink。
  4. nm 复核 libMobileGLServer.so 在 publish 下不再引用 glslang 库符号(注意 ProgramObject.h 传递包含 ShaderObject.h → ShaderCompileTask.h,所以这条必须用 nm 验证而不是断言)。
  5. server 峰值 RSS 相对 P1a 基线下降;两个进程的 RSS 合计与 §16-R14 的预算对表。

P6 — 数据面性能(6 天)

交付物:PendingResidentWrite 借用 ring slot(用 *RetiredTail 门控);全局 UBO ring 进 shm;解码移到 mgl-srv-io;可选 mgl-client-tx;bind 合并(凭数据决定);mirror-map 两次映射消除 ring wrap;MOBILEGL_IPC_INLINE_PAYLOADS 负面对照;§6.4 方案 B(replica adopt client shadow)的可行性评估与实现(若 Tracy 数据支持);Windows AF_UNIX 评估(§11.5);MOBILEGL_IPC_SPIN_US 与 MOBILEGL_IPC_PRESENT_CREDIT 的设备调优。

验收:两台设备上 minecraft-1.21.4-fabric-sodium-in-world 的配对 A/B,每项优化用自己的开关单独可 A/B;P2/P4 门在任意开关组合下不回归;输入延迟直方图不因任何优化恶化。

P7 — persistent map 与 ≥16MiB 采纳(8 天,若 P0 spike B 全否则缩为 2 天)

交付物:SEG_ADOPT(server 分配)+ 三档探针(T2/T1/T0)+ 自动回退到 P4.5 路径;阻塞 AcquirePersistentMap;client 侧注册 ResidentSubData;MOBILEGL_IPC_RESPAWN 与 MOBILEGL_IPC_ADOPT_TIER 的互斥检查(§5.8)。

验收:LargeArenaAdoptionScenario、ResidentIndexScenario 在采纳开启下绿;bench.sh 在 Mali 设备 3B159D009VZ00000 上用 minecraft-1.21.4-in-world 报出 {monolith, split+采纳, split+回退} 的 p99 帧时,以 MC 26.3 的 163→21ms 为标尺。 "设备 X 上拒绝,已记录,回退成本 N ms" 是本阶段的可接受结论——因为回退路径在 P1a/P4.5 已交付并测量。

P8 — XFB / compute / 健壮性 / 多线程(6 天)

交付物:XFB capture writeback 与 scatter(全部在 server 对 replica 执行,只有合并后的 range 过线);RecXfbAccounting 与共享 helper 的完整接线(§2(g)-2;注意它必须跟着 RecBindTransformFeedback 的对象切换走,Core.cpp:1273,1296);GS strip 顺序修正移到 server;compute dispatch/indirect/barrier/image load-store;EvGlError 与 glGetError 的顺序 + MOBILEGL_IPC_STRICT_ERRORS 诊断开关;server 死亡的 device-lost 闩锁与 client 死亡的 server 拆机;外来线程 sync/query 的 AuxRequest;修 EGLOperationMutex 既有漏洞(ReleaseThread、SwapInterval)。

验收

  1. split 下 Xfb*(5)、Tessellation*(2)、SsboArrayDynamicIndexScenario、ImageLoadStoreSsoScenario 双 backend 绿。
  2. tools/cts/scripts/run_cts_local.py --backend {DirectGLES,DirectVulkan} --env MOBILEGL_TRANSPORT=spawn 在 GL33 caselist 上 conformance rate 与 monolith 相差 ≤ 0.5 个百分点(按项目既定的逐 backend 表格式报告:行=GL 版本/扩展,列=状态计数,conformance rate = Pass/(Pass+Fail),分母不含 NS)。
  3. 故障注入测试在帧中 SIGKILL server,client 干净地以 EGL_CONTEXT_LOST 退出而不崩溃。

注:XFB 场景的 RecXfbAccounting 骨架其实在 P1a 就要落地(helper + 记录 + applier 分支),只是这里才被真正测到。§5.9b 的生成器会在 P0 就把它标成未映射并让编译失败,从而强制这个顺序。

P9 — Android 生产窗口路径(10 天)

交付物:MobileGLServerService(android:process=":mgl");Messenger/AIDL 的 Surface 交接;surface 生命周期(surfaceDestroyed、1×1 pbuffer 交换舞、resize)作为协议消息;ResyncSnapshot;APK 打包与 validate-plugin-apks.sh 更新;MOBILEGL_TRANSPORT 进 plugin V2 metadata 与 FCL 用户 env 偏好。

验收:FCL 在 35d0befa 上以 split 模式把 Minecraft 1.21.4 拉到主菜单并进入世界;bench.sh 在同一个热窗口内报出 split vs monolith 的游戏内 FPS 与输入延迟;plugin APK 通过 .github/scripts/validate-plugin-apks.sh;旋屏/后台切换的 surface 销毁重建无泄漏无挂起;两个进程的合计 RSS 落在预算内。

合计 ≈ 77 人日 ≈ 16 周(5+6+4+9+3+5+5+4+6+6+8+6+10)。里程碑:第 3 周末 Linux 上跨进程渲染出第一帧(P1b),第 5 周末真机 OpenRA 绿(P2),第 6 周有 monolith 侧的独立收益数字(P2.5)。


16. 风险与对策

# 风险 对策
R1 replica applier 在某个长尾副作用上与 client 的 MG_State 语义分歧——具体形态是 MG_Impl 在 table 调用旁做的 mutation(§2(g) 已确认两族:EnsureGeneratedMipmapStorageAllocated、AccountTransformFeedbackPrimitives)。症状是错误像素或错误查询结果,不是崩溃 §5.9b 的第二个生成器把这一面变成编译期门:MG_Impl 里任何与 table 调用同函数的 mutator 未映射即 #error。两族已知实例在 P1a 就用共享 helper 接线。P2 的门是全部集成场景 + trace 语料的逐名通过集对齐,远比 Feat/CS-Delta-IPC 的两 GLContext 逐字段比较(且只查了 5 域中的 2 域)严苛。外加 MOBILEGL_IPC_VALIDATE_SERVER(server 侧保留 MG_Impl 校验器,分歧变成 server 侧 GL error 而非错误像素;CI 常开,出货构建用 kPrevalidated 短路)
R2 应用通过 coherent persistent map 写下的字节丢失(SyncPersistentMappedRange 无 client 侧调用者) §5.10 三件套:map/unmap 上线、client 侧块粒度推送、PersistentCoherentMapScenario 作为 P1a 门。这是本轮新增的最高优先级修复
R3 read-after-GPU-write 静默读到陈旧 shadow(MarkGpuWritten 无 client 侧建立者) §5.6b:client 在每个 draw/dispatch 发射点保守置位并记 emitSeq;读入口强制 publish+等待+排空;EvGpuWritten 降级为收窄提示。§7.4 的排空点补上四个 buffer 读入口
R4 零 timeout 轮询循环挂死(轮询入口不是 publish 触发器) §7.2:glClientWaitSync/glGetSynciv/glGetQueryObject*(AVAILABLE|NO_WAIT) 全部成为门铃点,GL_SYNC_FLUSH_COMMANDS_BIT 无条件 publish;连续 N 次无进展升级为阻塞 round trip。P3 有专门的门
R5 fence 完成度退化成帧计数推断(DirectGLES 的 completedFrameSerial 只在 Present 前进),重蹈 MC 1.21.5 的 native-heap OOM §8 末尾:server 侧真 fence + 非 present 轮询 + EvFenceSignaled;§9.3 的非 present fence tick 同时解决无 present 循环下的 ring 饥饿
R6 纹理每次更新都传整 level(永不清 dirty flag ⇒ union box 单调增长) §5.6a:client 在发射后立刻 MarkStorageDirty(...,false);ack 问题由"resync 从完好 shadow 传整 level"+"硬 drain 后重发未 apply 记录"两条收口。已确认 MG_Impl 从不读自己的 dirty 状态,所以清是安全的
R7 每 draw 编解码成本超过它替换掉的东西,MC 级帧(1000-4000 draw)反而更慢;且总 CPU 工作量本来就变大(遍历跑两次) 记录是 FlatBuffers struct(8B header + 定长),无 verifier walk;publish 是每记录一次 release store 而不是 64KiB 攒批(§7.2)。TracyPlot 两侧计数器在 P0 就落地;P2.5 在第 6 周给出 inproc 的证伪数字并带逐线程 CPU 时间;mgl-srv-apply 绑大核(§10),mask 打日志;P3 门要求 split 帧时在 monolith 10% 内才授权后续优化
R8 client 侧等待全是跨进程自旋(无 producer 侧门铃),手机上一颗大核满频空转 §6.2a 的双向 doorbell:producerParked + 反向 1 字节;自旋窗口 MOBILEGL_IPC_SPIN_US 可调可测。inproc 用 condvar
R9 SEG_EVENT 满 + client 被 credit 阻塞 = 双向死锁 §7.4:等待循环内必须排空;EvLogLine 有损(覆盖最旧 + eventDropped 计数);语义事件无损,满时 server 置 eventRingFull 并停在记录边界上停止 apply。P4 有故障注入门
R10 端到端延迟叠加(client credit + server FIF + 驱动深度 = 4-5 帧) §9.1:credit 默认 1;文档写出叠加公式;P3/P9 增加输入延迟直方图门,只有实测吞吐收益抵得过实测延迟才调高
R11 inproc 因为四个进程全局而不可行,从而 P2.5 这个最早的证伪门消失 §12.1/§12.2:拆成两个 CMake option(出货只开 spawn,热路径无 TLS);四个全局都要角色隔离,shim 需求列全,非箭头用法实测 133 处;P0 结束前必须拍板是做隔离还是把 inproc 降级为纯测试模式,并写清后者对 P2.5 的含义
R12 分配类 GL 错误晚到,OOM 探测惯用法失效 §5.6c:只把分配类入口标 kNeedsAck(罕见且本来就贵),其余保持晚到;glGetError 永远本地。P4 有 OOM 探测门
R13 server 分配的 host-visible coherent 内存无法导出重映射,丢掉 ≥16MiB 采纳(值 p99 163→21ms、~400MB RSS) 排在最后(P7),且 P0 的 spike B 在第一周就给出方向。此时 P1a/P4.5 的 shadow 路径已交付并测量。阶段明确允许"拒绝,已记录"的结论。前端已容忍 nullptr(三处),kill switch 已存在,无需回滚任何代码
R14 内存翻倍无预算:client 段(SEG_CMD 8MiB + SEG_STAGE 32MiB↑)+ 完整 replica context(每 buffer 一份 PipeResource、每 texture level 一份 MipmapStorage)+ server 自己的三个 ring(UBO/unpack/upload 各 4→64MiB,Managers.cpp:82-96)+ 64MiB buffer pool(Managers.cpp:566)。合计可达 ~450MiB 新增,而本项目把"省 400MB"当作采纳修复的头条成果,且有 blanket-immutable 导致 LMK 屠杀的记忆 计划里与 round-trip 预算并列写出稳态内存预算;P1a 验收记录两个进程的 RSS(不只是 server);SEG_STAGE 上限由实测定而不是默认 256MiB;优先推进 §6.4 方案 B(replica 采纳 client shadow),因为它同时消掉重复 shadow 而不只是一次拷贝
R15 排期乐观(P0 5 天含两个 spike + 四平台 shm + SCM_RIGHTS + 两个代码生成器;P1a+P1b 10 天做完整 client 与 server)。校准点:Feat/CS-Delta-IPC 10 个 commit / 6668 行、从未渲出一帧,并自承四天耗在一个不可复现的回归上 P1 已拆成 P1a/P1b,设备 retrace 移到 P2 出口;P2.5 是排期风险的退火器——第 6 周就能拿到"这条路值不值得走"的数字,且它本身不依赖任何跨进程工作。若 P0/P1 超期 50%,先跑 P2.5 的 inproc 部分再决定是否继续
R16 socket transport 是新实现,而上一版有每次 send 的 UAF、无上限分配、无 fd 传递 从设计草图重写而非修补:读时按 64MiB 上限校验 magic/长度;接收缓冲不足时返回所需大小且保留消息;async_write 用 shared_ptr payload 自持缓冲;socketpair + 继承 fd 完全去掉 accept/connect(Windows 用 overlapped named pipe 对,§11.5)。SCM_RIGHTS 是 P0 交付物并带独立测试
R17 Android 交付链(server .so 打包、untrusted_app 域 exec、trace app env 透传)比想象的重,或被 AGP/SELinux 挡住 P0 的 spike A 在第一周就验证;P1-P8 全部离屏且不依赖它(Linux 门优先);两条回退:裸 exec PIE server 配 AHardwareBuffer_sendHandleToUnixSocket blit-back;或把 split 作为 headless/工装专用配置发布
R18 spawn 出来的 server 继承 MOBILEGL_TRANSPORT 而无限 fork §11.1 双保险:显式 envp 剔除 + mobilegl_server_main 强制 Monolith;P1b 有进程树计数门
R19 HeadlessGL 的 fork 预检留下持有 GPU 的孤儿 server §11.3:EOF 即时退出(亚秒);就绪握手有界重试;P1b 有 pgrep 计数门
R20 MobileGLServer 在两个桌面门里都找不到(dladdr 对静态链接的 itest 与显式 -DMOBILEGL_LIBRARY 的 retrace 都失效) §11.1:MOBILEGL_IPC_SERVER_PATH 为主、dladdr 兜底;RUNTIME_OUTPUT_DIRECTORY 对齐;每条新 ctest ENVIRONMENT 都注入;并复核 CI artifact 搬运后绝对路径是否还成立
R21 mobilegl_server_main 在出货构建里 dlsym 不到(非 Debug 的 hidden visibility preset) §11.2:显式 visibility("default");P0 加 nm -D 断言
R22 Magma 的 present 节奏被 IPC credit 改变(它从不注册 SetSwapInterval 且偏好 MAILBOX/IMMEDIATE) MOBILEGL_IPC_PRESENT_CREDIT 可配;P6/P9 在设备上测量输入延迟与帧节奏;若 Magma 需要,把"注册 SetSwapInterval 并映射到 FIFO"作为独立的 dev 变更,不让两套机制同时管节奏
R23 两件 Android 产物版本漂移 一份共享库两个角色:server 是 ~30 行 stub,dlopen(libMobileGL.so) + dlsym(mobilegl_server_main);Hello/Welcome 里的 build fingerprint(git hash + Records.def hash)不匹配 → 明确报错而非静默协议故障
R24 SEG_CMD 的记录被并发写坏导致 applier 游标走飞 §6.3 的运行期边界检查(size >= sizeof(T) && size <= remainingRingBytes && (size%8)==0,kVarTail 另查尾长自洽),违反即 Fatal{ProtocolCorruption},绝不进入 UB

17. 开放问题

  1. T1/T0 采纳在 Adreno 830 与 Mali-G925 上到底能不能用? 由 P0 的 spike B 在第一周回答(导出 HOST_VISIBLE|HOST_COHERENT VkBuffer 的 fd,client mmap 后回读),与 SCM_RIGHTS 测试同批。若两台设备都不行,P7 缩为"记录并回退",节省 6 天;若可行,还要回答 GLES 侧能否用 GL_EXT_memory_object_fd + glBufferStorageMemEXT 走同一条路(DirectGLES 的采纳今天走的是 glBufferStorageEXT + glMapBufferRange(PERSISTENT|COHERENT),不是外部内存)。
  2. glGetError 的严格性 CTS 到底要求到什么程度? §5.6c 已把分配类改成同步 ack,剩下的晚到错误里,哪些 CTS case 可能观察到?P8 需要列出清单。若清单为空,MOBILEGL_IPC_STRICT_ERRORS 可以永久保持默认关。
  3. P2.5 的 inproc 数字若为负怎么办? 需要事先约定:若 inproc 相对 monolith 无收益甚至更慢(含亲和性绑定之后),是继续(因为拆分本身还有内存隔离、崩溃隔离、工装价值)还是收缩到 headless 工装用途?建议在 P2.5 前由协调者拍板判据,并同时约定"绑大核后仍无收益"与"未绑核无收益"是两个不同的结论。
  4. §12.2 的隔离取舍:inproc 做四全局角色隔离(含 133 处非箭头用法的 shim)值不值?若判定不值而把 inproc 降级为纯测试模式,P2.5 测的就不再是 monolith 渲染线程交付物——那时 monolith 侧的收益要靠什么证明?P0 结束前必须有答案。
  5. §6.4 的拷贝目标选方案 A 还是 B? 方案 B(replica 的 PipeResource 采纳 client 的 SEG_SHADOW 只读映射)能把 buffer 上传路径从 3 次降到 2 次并消掉重复 shadow(对 R14 的内存预算意义更大),但要处理 server 侧写(WritebackFromBackend、生成 mip、CopyImage 镜像)的 copy-on-write 升级。P4.5 先做 A 并测量,P6 由数据决定是否做 B。
  6. SEG_SHADOW 在 Android 上应该用 ASharedMemory 还是 memfd? 前者是平台正道且有 setProt 只读降权(正好匹配"client 拥有、server 只读"),后者有 sealing。大 buffer 频繁重映射的场景需要一次实测。
  7. client 侧是否需要 mgl-client-tx 发送线程? 只有 P6 的 TracyPlot 数据能回答;在此之前不要预先加线程(会引入拷贝或锁)。
  8. ResyncSnapshot 与采纳的互斥能否放松? §5.8 目前规定 MOBILEGL_IPC_RESPAWN=1 与 MOBILEGL_IPC_ADOPT_TIER != 2 互斥,因为 adopted store 的字节在 server。是否值得为 adopted buffer 单独做一条"server 死亡时其内容视为丢失、按 hasDefinedContent=false 重建"的降级路径?取决于 MC 的 chunk arena 在 respawn 后能否被应用自己重填。
  9. Windows AF_UNIX-everywhere 是否值得? asio 的 IOCP async_accept 走 AcceptEx(AF_UNIX 从不支持);我们用继承 overlapped 句柄绕开 accept,理论上可行但需真编真跑。P6 评估,named pipe 是已知可用的默认。
  10. P9 的 ART 启动成本具体是多少? 若不可接受,是否接受"游戏内走 monolith,工装/CTS 走 split"的长期二元形态?
  11. tools/trace_replay 的 Android 应用内路径是否从非主线程驱动 GL、是否每重放帧调 Present? 桌面重放器传 --singlethread(trace_replay_core.cpp:430),Android 应用内路径本次未完整追踪,它决定该工装能否验证节奏模型(尤其是 §9.1 的输入延迟直方图)。
  12. MOBILEGL_IPC_PERSISTENT_BLOCK_KB 的默认值与脏块判定方式:P1-4 的保守版(整 mapped span 按块重传)在 Create/Flywheel fixture 上的实测代价是多少?精确版用 memcmp 还是 mprotect 写屏障?前者对 1MB 块是 ~50µs 量级且只在真正 mapped 的 buffer 上跑,看起来够用,但需要 P2 的数据确认。
  13. SEG_STAGE 的上限该定多少? R14 要求由实测定而不是默认 256MiB。需要 P2 之后用 MC in-world 与 Create 两类 fixture 的 stage-* Tracy 计数器给出 p99 占用。

附:环境变量与 CMake 选项汇总

CMake

选项 默认 说明
MOBILEGL_BUILD_DISAGGREGATED OFF 出货形态。开启后 MG_Remote/** 进 SOURCE_FILES,支持 spawn/unix:/pipe:。四个进程全局保持普通全局,GL 热路径无 TLS
MOBILEGL_BUILD_DISAGGREGATED_INPROC OFF CI/调试形态,隐含开启上者,额外加四全局角色隔离 shim
MOBILEGL_FLATC_EXECUTABLE 空 只服务 CI 的 flatc-check;默认构建图里没有 flatc
MOBILEGL_BAKED_INTERNAL_SHADERS ON (P5+) DirectVulkan 的 blit/depth-mipmap shader 构建期烘 SPIR-V;monolith 也受益

运行时

变量 默认 说明
MOBILEGL_TRANSPORT monolith monolith / inproc / spawn / unix:<path> / pipe:<name>
MOBILEGL_IPC_SERVER_PATH 空 server 可执行文件路径(主要发现机制,dladdr 兜底)
MOBILEGL_IPC_RING_MB 8 SEG_CMD 大小
MOBILEGL_IPC_STAGE_MB 32 SEG_STAGE 初始大小;上限由实测定(§17-13)
MOBILEGL_IPC_PRESENT_CREDIT 1 client 允许领先的 present 数(1-4);延迟叠加见 §9.1
MOBILEGL_IPC_SPIN_US 50 挂起前的自旋窗口(两侧 doorbell 共用)
MOBILEGL_IPC_POLL_ESCALATE 64 同一 handle 连续无进展轮询多少次后升级为阻塞 round trip
MOBILEGL_IPC_PERSISTENT_BLOCK_KB 64 persistent-map 推送的块粒度
MOBILEGL_IPC_PROGRAM relink (P1-4) → publish (P5+) program artifact 传输方式;relink 保留为常驻 oracle
MOBILEGL_IPC_ADOPT_TIER auto auto/0(T0)/1(T1)/2(T2 拒绝);与 MOBILEGL_IPC_RESPAWN 互斥(§5.8)
MOBILEGL_IPC_SHADOW_SHM 1 (P4.5+) shadow-in-shm 零拷贝
MOBILEGL_IPC_INLINE_PAYLOADS 0 负面对照:一律内联,不用 SEG_STAGE
MOBILEGL_IPC_SERVER_AFFINITY auto mgl-srv-apply 的核绑定;auto 用 ShaderCompilePool 的大核探测
MOBILEGL_IPC_VALIDATE_SERVER CI=1,出货=0 server 侧保留 MG_Impl 校验器,分歧变成 server GL error
MOBILEGL_IPC_STRICT_ERRORS 0 诊断开关:让所有 backend 错误同步 ack(分配类默认已是同步)
MOBILEGL_IPC_AUDIT 0 记录级审计日志
MOBILEGL_IPC_TRACE 0 逐记录 trace(仅调试构建)
MOBILEGL_IPC_ATTACH 空 附着到已运行的 server(调试)
MOBILEGL_IPC_RESPAWN 0 server 死亡后重启 + ResyncSnapshot
MOBILEGL_IPC_IDLE_EXIT_S 30 server 的最后保险看门狗(EOF 应当即时退出)

保留的既有负面对照开关:MOBILEGL_ESPRYT_DISABLE_UBO_RING、_UNPACK_RING、_UPLOAD_RING、_INVALIDATE_FLUSH、MOBILEGL_DISABLE_LARGE_BUFFER_ADOPTION、MOBILEGL_COHERENT_AS_FLUSH(在拆分模式下照常生效,§5.10/§6.8)。