- README.md: what MGPipe is, the one-paragraph architecture, the P0 status line, the file and code map, and the commit range where the design competition and the three adversarial review rounds live
- ARCHITECTURE.md: the design as decided, one reason per decision - handles and generations, the 71-call catalogue by class with flags and cap bits, record and payload conventions, the tracker, texture subdata and dirty ownership, shader state and the P0.5 header extraction, the reverse channel, the backend strangler, the server side and the index host mirror, the transport as landed in MG_Remote, the persistent-map tiers as measured, roundtrips, present and threads, process and platform delivery, build shapes and purity gates, the five-part verification gate, and the knob tables marked landed versus planned
- ROADMAP.md: P0..P13 as one table of what lands, the gate and the dependency, the two tracks and milestones, the day-43 GO/NO-GO checklist with both exits, the re-baseline checkpoints, and the questions still open after P0
- MEASUREMENTS.md: spike A on both devices, the spike B tier matrix, the four-trace boundary-counter baselines on both backends, the desktop and corpus facts, and the harness traps with the exact commands
- every file:line kept is verified at 458ccde1 and the citation lint is clean; everything else cites a symbol plus a file
- dropped on purpose: the v1/v2 revision archaeology, rejected alternatives, per-subsystem day estimates, the Feat/CS-Delta-IPC reuse audit and the reviewer back-and-forth
12 KiB
P0 实测
每张表都写明设备、提交与命令,以便复现。设备:
35d0befa= Xiaomi 24129PN74C,Adreno 830,Android 16;3B159D009VZ00000= Oppo PLG110,Mali,Android 16(ColorOS)。设备运行日期 2026-09-05。设备锁协议照旧。
1. Spike A — 从应用自身进程 exec 第二个原生可执行文件
问题:Android 上能否把 server 以 lib*.so 打进 APK,并从应用自己的 untrusted_app 域 fork+execve 它(adb run-as 跑在别的域,证明不了)。
| 设备 | 结果 |
|---|---|
| Adreno 830 | OK。父进程 u:r:untrusted_app:s0:c173,c257,c512,c768 fork+execve <nativeLibraryDir>/libMobileGLServer.so → 子进程 pid 31348,exit 0;子进程 SELinux u:r:untrusted_app:s0:c173,c257,c512,c768(同域同 category);marker 文件、stdout 捕获、报告全在;execErrno=0;窗口内零 avc denial |
| Mali | OK,同形:子进程 pid 28433,exit 0,execErrno=0,u:r:untrusted_app:s0:c94,c257,c512,c768,零 avc denial |
主机侧已证的三条(随 8a239177):AGP 会把改名成 lib*.so 的 add_executable 打进 lib/arm64-v8a/,前提是把 RUNTIME_OUTPUT_DIRECTORY 重定向到 CMAKE_LIBRARY_OUTPUT_DIRECTORY;posix_spawn 在 minSdk 26 不可用(bionic API 28 起),出货臂是 fork+execve;应用进程 stdout/stderr 是 /dev/null,子进程用 marker 文件证明自己活过。
- 代码:
tools/spikes/server_stub/main.cpp(stub:打印并写 marker 自己的 pid/uid/SELinux 上下文)、android-plugin/app/src/trace/cpp/spawn_spike.cpp(RunSpawnSpike)、CMakeLists.txt:784-808(MOBILEGL_BUILD_SERVER_SPIKE)。 - APK:
p0-spike-a-android/trace-debug-spike-on.apk(在30d7595b构建,与7ef7c7e5源码相同)。 - ColorOS 陷阱:首次
adb install一个未安装的包会卡在com.oplus.appdetail InstallGuideActivity确认页,直到点"继续安装"(1272×2772 面板上input tap 353 2349);同签名重装静默通过。另一台设备上一个外来签名的 trace APK(versionCode 26080769)会让install -r报INSTALL_FAILED_UPDATE_INCOMPATIBLE,需先卸载。 - 42-device.sh 的 env 透传 A/B 腿在该 ROM 上跑不了(
run-as sh -c 'cat > files/…'被拒);透传由下面的 stats 基线端到端证明(--env MOBILEGL_PIPE_STATS=1必须在mobilegl.log里产生MGPipe stats行)。
2. Spike B — 跨进程外部内存分档
问题:AcquirePersistentMap 背后的内存能否共享给另一个进程并在那里映射,两个后端各走哪条路。探针 tools/spikes/extmem_probe/(39f982e6 源码,arm64,adb shell = u:r:shell:s0 域),4 MiB payload,64 KiB 同判决。每一行都取一次真 GPU 访问(vkCmdCopyBuffer + vkCmdFillBuffer + host-read barrier)并两侧字节校验才算 OK。
| 路线 | Adreno 830 | Mali |
|---|---|---|
T1-opaque-fd(server 导出 VkDeviceMemory fd,client 裸 mmap + 导入) |
OK 完整往返含 GPU 访问(/dmabuf:system,dedicatedOnly=1) |
UNSUPPORTED(vkCreateBuffer(external)=VK_ERROR_INVALID_EXTERNAL_HANDLE,advertisedExportable=0) |
| T1-dma-buf | UNSUPPORTED(VK_EXT_external_memory_dma_buf 缺) |
UNSUPPORTED |
T1-gles-memobj-fd(GL_EXT_memory_object_fd 导入导出的 fd) |
FAIL:导入 + glBufferStorageMemEXT 接受(GL_NO_ERROR)但每次 glMapBufferRange → GL_INVALID_OPERATION(persistent 与 plain 都是);GL_DEVICE_UUID 不可读 |
UNSUPPORTED(扩展字符串缺,入口点可解析) |
T0-ahb-blob-transfer(client 分配 AHardwareBuffer BLOB → socket 交接 → server Vulkan 导入 + GL 导入) |
OK 全链:cpu-lock、vk-import+map、GPU copy/fill、GL map persistent+coherent、写回 client 全部字节校验 | OK 全链,判决相同(glPersistentCoherent=1,gpuRan=1) |
T3-external-memory-host(VK_EXT_external_memory_host) |
UNSUPPORTED(扩展缺) | PARTIAL:导入 + map 往返,但 GPU 写对宿主映射不可见(只读档) |
| T3-memfd-cross-process / client-memfd-server-import | UNSUPPORTED | OK / PARTIAL(同样的 GPU 只读 caveat) |
P11 的分档决定:唯一在两台设备、两个后端上都是完整读写的档是 T0——client 分配 AHardwareBuffer BLOB,server 以 VK_ANDROID_external_memory_android_hardware_buffer(Magma)或 EGL_ANDROID_get_native_client_buffer + glBufferStorageExternalEXT(Espryt)导入,两侧 persistent+coherent 映射。Adreno 另有 T1(Vulkan 路径);Mali 无任何 server 导出路线,host-pointer 导入只读。Caveat:运行域是 shell 不是 untrusted_app;AHB 的 socket 交接是每个与 SurfaceFlinger 共享 buffer 的应用都在走的路径,域风险在 memfd/opaque-fd 腿上。
复现:
ANDROID_NDK=$HOME/android-sdk/ndk/27.3.13750724 tools/spikes/extmem_probe/build_android.sh /tmp/extmem-build
S=<serial>; adb -s $S push /tmp/extmem-build/extmem_probe /data/local/tmp/extmem_probe \
&& adb -s $S shell "chmod 755 /data/local/tmp/extmem_probe && /data/local/tmp/extmem_probe; echo EXIT=\$?" | tee out-$S.txt
判决语义(OK / PARTIAL / FAIL / UNSUPPORTED)与逐腿 trace 格式见 tools/spikes/extmem_probe/README.md。主机构建(lavapipe)用来证明探针本身报得对:T1/T3 在 lavapipe 上全 OK;T1-gles 在 llvmpipe 上 GL_OUT_OF_MEMORY 是 Mesa interop 缺口,不是探针缺陷。
3. 边界计数器基线(双设备、双后端、四条 trace)
MOBILEGL_PIPE_STATS=1 经 retrace 通道的 --env 透传;trace APK 从 7ef7c7e5 构建,spike OFF。取每次运行的最后一个完整 120 帧窗口。accessor/draw 与 memo 门数字是软件确定的(同一 trace 在两台设备上完全相同:它们数的是代码路径不是硬件),只有墙钟/CPU 时间随设备变。
| trace(窗口内帧数) | 后端 | draws/f | acc/draw | buf B/f | tex B/f(发射 box/rect) | ubo-global B/f | ubo-named B/f | memo 门(hit/miss) |
|---|---|---|---|---|---|---|---|---|
minecraft-1.21.4-in-world(360) |
Espryt | 91.6 | 9.28 | 13.5 K | 635 K(185 box / 0 rect) | 16.7 K | 0 | ers 9257/2577,etl 10538/1296,eub 10720/1114 |
minecraft-1.21.4-in-world(360) |
Magma | 91.6 | 8.56 | 13.5 K | 39.9 K(97 box / 89 rect) | 16.7 K | 0 | mfp 0/10994,mpm 9240/1754,mdt 9120/1874 |
minecraft-1.21.4-fabric-iris-bsl-in-world(120,memo 冷) |
Espryt | 23.2 | 21.04 | 32.6 K | 8.8 K | 1.8 K | 0 | ers 1958/1843,etl 722/3079 |
minecraft-1.21.4-fabric-iris-bsl-in-world(120,memo 冷) |
Magma | 23.2 | 11.26 | 313 K | 256 K | 1.8 K | 0(vtxc 1.7 K) | mpm 1890/895,mdt 1573/1212 |
improved-transparency-minecraft-26.3(1200) |
Espryt | 1320 | 8.44 | 333 K | 0 | 0 | 0 | ers 156925/2791,etl 148606/11110,eub 148246/11470 |
improved-transparency-minecraft-26.3(1200) |
Magma | 1320 | 6.53 | 173 K | 0 | 0 | 331 K | mfp 21360/137036,mpm 134421/2615,mdt 156611/1785 |
minecraft-1.21.1-neoforge-create-indirect-in-world |
两者 | — | — | — | — | — | — | 两台设备都失败(§5),且不足 120 帧 |
门缩写:ers = EsprytRenderState,etl = EsprytTextureSyncList,eub = EsprytUnitBindingsEpoch,mfp = MagmaDrawFastPath,mpm = MagmaPipelineMemo,mdt = MagmaDynamicTail(MobileGL/MG_Util/Metrics/PipeStats.h:46-122)。accessor-calls 是约 10 个热入口的静态计数,是每 draw accessor 数的下界(站点清单 MobileGL/MG_Util/Metrics/PipeStats.cpp:16-100)。
对设计的读法:
- 真机稳态动态 accessor 成本是每 draw 6.5–9.3 次(预测区间 10–25 的下沿;llvmpipe 的 15.5/20.7 是 memo 冷的)。推送要打败的是 ~8 次 accessor + memo 探测,不是 124/169 的静态调用点数。GO/NO-GO 的 tracker 绝对 ns 上限从这里定。
stage-ubo-named(D-B8):Magma 在 26.3 世界每帧重打包 331 KB 具名 UBO 字节,Espryt 直接绑定为 0——host payload 决定的第一个真数字。- union box vs region list:vanilla 世界同样 185 次发射,Espryt 的整 box 路径移动 635 K 纹素字节/帧,Magma 的 rect 路径 40 K,16×——"server 选上传形状"这一条的量化依据(Mali 侧的 +6 ms/frame 作业数悬崖在另一个方向)。
复现(一台设备一次;两台必须串行,见 §4):
ANDROID_SERIAL=<serial> MSYS_NO_PATHCONV=1 \
python3 tools/trace_replay/run_android_retrace_local.py \
--case minecraft-1.21.4-in-world --backend DirectGLES \
--env MOBILEGL_PIPE_STATS=1 --env MOBILEGL_PIPE_STATS_PERIOD=120
# 数字在结果目录的 mobilegl.log 里,grep 'MGPipe stats:',取最后一个完整窗口
4. 桌面数据点与语料事实
- llvmpipe / lavapipe 动态 accessor(
GuiBatchScenario,14 帧 / 26 draw,memo 冷):Espryt 20.65 / Magma 15.54 次/draw——落在预测区间内,且因场景太短偏高;真机稳态数字见 §3。 - dirty-surface 面(
python3 scripts/gen_pipe_dirty_surface.py --summary,本树):MG_Impl/GLImpl41 个文件,926 次 mutator 调用,73 个不同 mutator;92 次(36 个即时发布点、7 个 mutator,836 次里绝大多数是RecordError)位于同函数内也到达后端的入口,其余 834 次由紧随的 verb 发布。映射表是 73 条目的问题。 - 读点覆盖(
python3 scripts/gen_pipe.py):71 条调用(11 screen / 60 context)、63 个 verify payload、61 个PipeInputs字段;477 行后端读点清单 → 299 调用、5 client 自答、6 反向通道、167 结构性句柄、0 UNMAPPED。 - OOM 探测惯用法:41 个 trace fixture 中 0 例——全部语料只有 9 次
glRenderbufferStorage调用散在 5 个 fixture,无一在其后 3 个调用内跟glGetError;语料里真实的成功性检查是glCheckFramebufferStatus。→glRenderbufferStorage*不 ack。 FramebufferSrgb/DepthClamp:FramebufferSrgb的六个后端读点全部消费一个编译期常量false,DepthClamp零读点;两者的glEnable落到RenderState.cpp的default:分支既不存储也不报GL_INVALID_ENUM;41 个 fixture 无一开启任一项(补真存储不会改动任何既有 fixture 的输出)。GetIntegeri_v族:Espryt 实现里是GetIntegeri_v的 9 个分支 +GetInteger64i_v的 2 个(不是"15 个 case");GL_COMPUTE_WORK_GROUP_SIZE由GL_Program.cpp用ProgramObject::GetComputeLocalSize纯前端回答。- payload 尺寸(
MG_Pipe/MGPipeTypes.h的static_assert,arm64 与 x86-64 一致):MGPDrawInfo56、MGHostSpan32、MGPBindRenderState12、MGPResourceDesc88、MGPFramebufferState304、MGPProgramDesc192、MGPSubData72、MGPPixelPackState28、ResidualValueBlock1248(其中RenderStateParameters1168)。SEG_CMD按 56 B 固定头定尺:MC 帧 1000–4000 draw 时每帧 56–224 KiB 头字节。 - persistent map 采纳的既有基线(
dev,MC 26.3,Adreno):≥16 MiB 可变 store 定义时采纳为 coherent persistent map 后 p99 163→21 ms、稳态 40→115 fps、省 ~400 MB。P11 的回归上限对着它。 - Mali 上传作业数悬崖(Espryt 代码注释记录的既有实测):~100 个精灵 rect 对一个 union box 是 +6 ms/frame。
5. Harness 事实与陷阱
- trace app 从不到达
MobileGL::DestroyImpl,所以MOBILEGL_PIPE_STATS_FILE的 JSON 转储在设备上永远不会写——只有mobilegl.log里的周期汇总行;短于一个周期的 trace 什么都不产出。MOBILEGL_PIPE_STATS_PERIOD(458ccde1)为此而加:需要数字的运行把它设到足够小。 run_android_retrace_local.py每棵树共用一个.trace-work/android-retrace-result根并在每次调用时rmtree,所以两台设备必须从一棵树串行跑。--env值里嵌入的/data/...会被 runner 的 bash.exe 做 MSYS 路径转换(MSYS2_ARG_CONV_EXCL="/data/*"只覆盖开头匹配)——用MSYS_NO_PATHCONV=1跑。coherent_as_flush管线完好:--ez coherent_as_flush true→trace_replay_core.cpp的setenv,独立于--env透传。minecraft-1.21.1-neoforge-create-indirect-in-world在两台设备上都失败(Adreno 830:Espryt ~4.5 分钟后黑帧,Magma 纹理上传提交时VK_ERROR_DEVICE_LOST;Mali:SSIM 0.85 / 0.45)。Adreno 830 上用dev@81b17c0b基线 APK 复现,是基线就有的问题,不是本分支造成;它是 P3a/P8 验收清单里的用例,需先在dev修。