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.
33 KiB
15. 分阶段实施计划
通用纪律(每个 commit 都适用):默认 ALL target 必须能完整构建;禁止提交热路径插桩;每个门必须能因它存在的理由变红;Windows 机器不是正确性门(其 Vulkan 缺
vkCreateHeadlessSurfaceEXT,占该机 567 个基线集成失败中的 423 个);设备对比走 reboot-clean + 同窗口配对 A/B;每个阶段的出口都跑一次 §12 第 4 层的nm/.textmonolith 门(不只是 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_RIGHTSfd 传递(第一优先)。protocol.fbs+ 提交的protocol_generated.h+gen_protocol.py+ CIflatc-check;Records.def的static_assert与运行期边界检查生成。gen_backend_state_surface.py+Coverage.def和gen_impl_mutation_surface.py+MutationCoverage.def+CoverageAssert.cpp+ CIcoverage-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().nativeLibraryDirposix_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_COHERENTVkBuffer 的 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与去符号.textsize 与改动前的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 上跑通。
验收
ctest -R "DirectGLES\.Split\..*(ClearThenReadPixels|Triangle)"在 Linux +MOBILEGL_TRANSPORT=inproc绿。- 新增
PersistentCoherentMapScenario(mapPERSISTENT|WRITE|COHERENT、写、不做任何其它 GL 调用、draw、readback 校验)在 split 下绿。这是本计划里唯一一个专为一个 fatal 缺陷设的门,必须在 P1a 就绿。 - 记录两个进程/两个角色的峰值 RSS(不只是 server 的)作为 P5 与 §16-R14 的基线。
- 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 即时退出。
验收
- P1a 的全部测试在
MOBILEGL_TRANSPORT=spawn下绿(两个真进程、真 socket、真SCM_RIGHTS段)。 - fork 链测试:spawn 一个 server 并断言进程树只多出恰好一个子进程(§11.1)。
- 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)。
验收
ctest -L integration-gpu -R '^DirectGLES\.Split\.'与'^DirectGLES\.'逐名同一通过/失败集;DirectVulkan 同。- 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 的块脏位)提前到本阶段——这是全计划唯一允许因测量改变阶段顺序的地方。 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)。
验收
XfbPrimitiveQueryScenario、PrimitivesGeneratedNoXfbScenario、AsyncCompileScenario在 split 下绿。- round-trip 计数器:在全部 trace case 的稳态帧上,draw/state/upload 路径的 round trip 读 0;conditional render 与阻塞式 query 的次数按用例列表公布(不是笼统宣称"零 round trip")。
- 零 timeout 轮询循环测试:一个只有
glFenceSync+while(glClientWaitSync(...,0)==GL_TIMEOUT_EXPIRED){}的用例必须在有界时间内退出(若无 §7.2 的 publish 规则它会永久挂起)。 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")。
验收
- split 下
DepthStencilReadbackScenario、DepthStencilReadbackMatrixScenario、DepthStencilReadbackAttachmentShapeScenario、PackedWordReadbackScenario、LayeredTextureReadbackScenario、ClearThenReadPixelsScenario、PixelStoreSweepScenario、CopyImage*(4)、SsboArrayLengthScenario、AtomicCounterScenario、StorageBufferRegrowScenario双 backend 全绿。 - OOM 探测用例:请求一个必然失败的巨大 renderbuffer,断言紧接着的
glGetError()返回GL_OUT_OF_MEMORY。 - 事件 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 开关。
验收
- P2/P4 门在开关两态下均不回归。
- 两侧
TracyPlot显示 buffer 上传路径的总拷贝次数从 4 降到 3(或选方案 B 则到 2,§6.4);staged-copy 回退率被记录成数字。 nm/.textmonolith 门仍绿(这一条是本阶段最容易破的)。- 对象删除/重定义与未 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)。
验收
- P2 全门在
publish下重跑不变。 relink下reflectionDigest在每个 trace case 绿(即它是活门不是死门)。35d0befa上用minecraft-1.21.4-startuptrace 做首帧 link 延迟 A/B,publish ≤ relink。nm复核libMobileGLServer.so在publish下不再引用 glslang 库符号(注意ProgramObject.h传递包含ShaderObject.h→ShaderCompileTask.h,所以这条必须用nm验证而不是断言)。- 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)。
验收
- split 下
Xfb*(5)、Tessellation*(2)、SsboArrayDynamicIndexScenario、ImageLoadStoreSsoScenario双 backend 绿。 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)。- 故障注入测试在帧中 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. 开放问题
- T1/T0 采纳在 Adreno 830 与 Mali-G925 上到底能不能用? 由 P0 的 spike B 在第一周回答(导出
HOST_VISIBLE|HOST_COHERENTVkBuffer 的 fd,clientmmap后回读),与SCM_RIGHTS测试同批。若两台设备都不行,P7 缩为"记录并回退",节省 6 天;若可行,还要回答 GLES 侧能否用GL_EXT_memory_object_fd+glBufferStorageMemEXT走同一条路(DirectGLES 的采纳今天走的是glBufferStorageEXT+glMapBufferRange(PERSISTENT|COHERENT),不是外部内存)。 glGetError的严格性 CTS 到底要求到什么程度? §5.6c 已把分配类改成同步 ack,剩下的晚到错误里,哪些 CTS case 可能观察到?P8 需要列出清单。若清单为空,MOBILEGL_IPC_STRICT_ERRORS可以永久保持默认关。- P2.5 的 inproc 数字若为负怎么办? 需要事先约定:若 inproc 相对 monolith 无收益甚至更慢(含亲和性绑定之后),是继续(因为拆分本身还有内存隔离、崩溃隔离、工装价值)还是收缩到 headless 工装用途?建议在 P2.5 前由协调者拍板判据,并同时约定"绑大核后仍无收益"与"未绑核无收益"是两个不同的结论。
- §12.2 的隔离取舍:
inproc做四全局角色隔离(含 133 处非箭头用法的 shim)值不值?若判定不值而把inproc降级为纯测试模式,P2.5 测的就不再是 monolith 渲染线程交付物——那时 monolith 侧的收益要靠什么证明?P0 结束前必须有答案。 - §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。 SEG_SHADOW在 Android 上应该用ASharedMemory还是 memfd? 前者是平台正道且有setProt只读降权(正好匹配"client 拥有、server 只读"),后者有 sealing。大 buffer 频繁重映射的场景需要一次实测。- client 侧是否需要
mgl-client-tx发送线程? 只有 P6 的TracyPlot数据能回答;在此之前不要预先加线程(会引入拷贝或锁)。 ResyncSnapshot与采纳的互斥能否放松? §5.8 目前规定MOBILEGL_IPC_RESPAWN=1与MOBILEGL_IPC_ADOPT_TIER != 2互斥,因为 adopted store 的字节在 server。是否值得为 adopted buffer 单独做一条"server 死亡时其内容视为丢失、按hasDefinedContent=false重建"的降级路径?取决于 MC 的 chunk arena 在 respawn 后能否被应用自己重填。- Windows AF_UNIX-everywhere 是否值得? asio 的 IOCP
async_accept走AcceptEx(AF_UNIX 从不支持);我们用继承 overlapped 句柄绕开 accept,理论上可行但需真编真跑。P6 评估,named pipe 是已知可用的默认。 - P9 的 ART 启动成本具体是多少? 若不可接受,是否接受"游戏内走 monolith,工装/CTS 走 split"的长期二元形态?
tools/trace_replay的 Android 应用内路径是否从非主线程驱动 GL、是否每重放帧调Present? 桌面重放器传--singlethread(trace_replay_core.cpp:430),Android 应用内路径本次未完整追踪,它决定该工装能否验证节奏模型(尤其是 §9.1 的输入延迟直方图)。MOBILEGL_IPC_PERSISTENT_BLOCK_KB的默认值与脏块判定方式:P1-4 的保守版(整 mapped span 按块重传)在 Create/Flywheel fixture 上的实测代价是多少?精确版用memcmp还是 mprotect 写屏障?前者对 1MB 块是 ~50µs 量级且只在真正 mapped 的 buffer 上跑,看起来够用,但需要 P2 的数据确认。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)。