mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Docs] (P9): the implementation hand-off - the roadmap row checked against the tree (half of it is done, one design premise changed), five work packages and two fault injections
This commit is contained in:
@@ -34,7 +34,7 @@ client(跑应用的一方)与 server(跑驱动的一方)可以在**不
|
||||
| [P7](notes/p7/README.md) | Vulkan 后端完整迁移;真机画面检查 36/36 通过 | ✅ 09-23 |
|
||||
| [**P12**](notes/p12/README.md) | **server 自己开窗口上屏,client 不需要窗口** | ✅ 09-29:出口门 B(跨机 TCP)✅ 09-28、A(FCL + 杀 server)✅ 09-29;契约 `MG_Remote/CONTRACT-P12.md`;G1 成立;余项进 [`DEBTS.md`](notes/DEBTS.md) |
|
||||
| [P8](notes/p8/README.md) | 把剩余的仿真路径挪到正确的一侧,补齐协议 | 待排 |
|
||||
| [P9](notes/p9/README.md) | 反向通道异步化(回读、写回不再同步等待) | 待排 |
|
||||
| [P9](notes/p9/README.md) | 反向通道异步化(回读、写回不再同步等待) | 待排;范围已按现状重划,交接见 [`HANDOFF-P9.md`](notes/p9/HANDOFF-P9.md) |
|
||||
| [P10](notes/p10/README.md) | 同步对象、查询与帧节奏 | 待排 |
|
||||
| [P11](notes/p11/README.md) | 同机大缓冲零拷贝共享 | 待排 |
|
||||
| [P13](notes/p13/README.md) | 删掉旧的"后端直接读前端"路径 | 待排 |
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# P9 交接:反向通道(重划范围)
|
||||
|
||||
> 2026-09-29,写给实现方。基于 `feat/disaggregated@acfd36785`。**[V]** = 我读了代码 / 文档 / 日志确认;**[I]** = 我的推断,实现前要自己核。
|
||||
> 路线图 P9 那一行(09-22 写的,原文在 [`README.md`](README.md))与树里的现状已经对不上:一半已经做完,一半的设计前提被后来的阶段改掉了。**先读 §2,再动手。**
|
||||
|
||||
## 1. 目标
|
||||
|
||||
server → client 的结果回传(回读、回调、回复)在 split 下不阻塞、不丢、不乱序,剩下的 `Fatal{Unmigrated*}` 与"没生产者的回调"要么落地、要么明确删除。P9 属于 IPC 跑道(`P12 → P9 → P10 → P11`),与 P8(monolith 跑道)互不依赖。
|
||||
|
||||
## 2. 路线图每一项的现状(先看这张表)
|
||||
|
||||
| 路线图写的 | 现状 | 处置 |
|
||||
|---|---|---|
|
||||
| 异步 reply 语义,建在 P6.5 消息式 reply 上 | **[V] 大半已做**,但不在 P9 名下:P12 落了 `ILink::DeclareReplyRead` + `RetainsReplies`(StreamLink 的"声明过的答案"存储,容量 24,溢出是 Fatal 不是淘汰;ShmLink 为 false)、`kRecNoReply`(`Transport/Ring.h`,"这条答案没人读,server 别回")、create 窗口(`MOBILEGL_IPC_CREATE_WINDOW` 默认 10,只在 `RetainsReplies` 的链路上生效)。rd12 加载帧的回包等待从 43,232 降到几百。见 `notes/p12/CREATE-WINDOW-MEASURED.md`、提交 `4e48702f2` / `3a2555805` | 不重做。剩下的只有 §3 的 W1 |
|
||||
| 2 MiB reply cap 与 chunked readback(ID-47) | **[V] 已做**:`ReadPixels` 超过一个回复槽时由 client 切成行带(P7 ID-P7-58,`EmitTables.cpp` 的 `PlanReadbackBands` / `ForEachReadbackBand`,`BandedReadbackScenario`);`GetTextureImage` 按 `DstOffset` 分窗(`TextureReadbackEmit.inc`) | 关闭;只在 W1 里保持行为不变 |
|
||||
| PACK-PBO 回读 fire-and-forget + client `MarkGpuWritten`(ID-57) | **[V] 没做**:目标是 PBO 时,client 仍然等回复、再把行 `UploadSubData` 进 PBO(`EmitReadPixels` 里的 `pbo` / `pboSynced`,`EmitGetTextureImage` 末尾) | **W1** |
|
||||
| 十个回调里未武装的四个:`OnTextureWriteback`、`OnTexturePullRequest`、`OnMipLevelsGenerated`、`OnLog` 分级 | **[V] 回调现有 9 个,`OnCapsInvalidated` 也没有生产者**(server 只装 4 个:`OnGlError`、`OnBufferWriteback`、`OnGpuWritten`、`OnSurfaceChanged`,`Server/ServerSession.cpp:823-827`;client 的消费者只有对应 4 种 `EventKind`,`Transport/EventRing.h`,`ClientSession.cpp` 的 `DrainEventRing`)。caps 失效走控制面的 CapsSnapshot 再到达(R-12),日志走控制面 `LogLine` | 逐个裁定,见 W2 / W3 |
|
||||
| `OnGpuWritten` 收窄;`OnBufferWriteback` 批处理 + epoch 排序 | **[I] 没有实测需求**;writeback 已按 SEG_EVENT 容量的四分之一切片(`MGPipeBufferWritebackSliceBytes`,`MG_Pipe/MGPipeCallbacks.h`),"最后一片落地蕴含此前每一片" | 不做,除非 §5 的计数器显示事件量是问题(W4) |
|
||||
| `OnGlError` 有序 | **[V] P5e 已裁定放宽**:run-ahead 动词的错误在下一个 drain 点观察,最多晚一个 present credit(`CONTRACT-P5E.md` §"glGetError relaxation"、`CONTRACT-P5C.md` §4.2) | 默认保持放宽,只记录(W5) |
|
||||
| 纹理拉取四条缓解 + 终止符(`TextureRemintPullScenario`) | **[V] 拉取协议至今不存在**(`ResourceSubDataComplete` 终止符 op 有,但没有请求事件);split 下重铸路径是 `Fatal{UnmigratedEmulation, "texture-remint-pull"}`(`MG_Backend/DirectGLES/Managers.cpp` 约 6519 / 6536)。**[I] 但设计前提变了**:`design/04-reverse-channel.md` §8.4 写"server 不保留纹素",而 P5c 的 tx 之后 server 有 `StagedTextureStore`(`MG_Remote/Server/StagedTextureStore.h`,`CONTRACT-P5C.md` §2)——已定义的层级字节就在 server 手里 | **W2:先查、再定**,很可能不需要拉取协议 |
|
||||
| XFB 命名空间(`DeleteTransformFeedback` 的 server 泄漏) | **[V] 已做**(`70df57bb2`):`DeleteStreamOutput` op + `ServerVerbSink::OnDeleteStreamOutput`(`Server/PipeApplier.cpp` 约 1105)清 span 与 lifetime id | 关闭 |
|
||||
| 两条故障注入 | 原文没说是哪两条 | §4 里我定了两条 |
|
||||
|
||||
**边界(别越界)**:`generate-mipmap-*`、`copy-image-shadow-mirror` 这些 Fatal 是 **P8**(把仿真挪到 client);`FenceWait` / `Query*` 的每帧回包是 **P10**;`ResourceReadback` / `ReadPixels` 本身是必须等的同步语义,不是 P9 要消掉的。
|
||||
|
||||
## 3. 工作包
|
||||
|
||||
### W1 — PACK-PBO 回读不再等回复(唯一确定要做的)
|
||||
|
||||
- **现在**:client 发 `ReadPixels` / `GetTextureImage` 记录并阻塞等回复;回复里是紧排的像素,client 按 PACK 状态(行长、对齐、skip)散射进 PBO。
|
||||
- **目标**:目标是 PBO 时,client **不等**:记录以 PBO 的 wire 句柄为目的(P5 已裁定"PBO 目的是资源句柄,不需要 `Seg` 字段",`CONTRACT-P5.md` 行 23),带 `kRecNoReply`;client 在发出时把该范围标成"GPU 写过"(`MG_Remote/Client/GpuWritePending.*` 的保守 pending 集),此后任何 CPU 访问(`glMapBufferRange` / `glGetBufferSubData`)走既有的 `ResourceReadback` → `OnBufferWriteback` 路径拿到数据。
|
||||
- **要先弄清的**:(1) monolith 的语义:原生读进驱动的 PBO 之后,**立刻**把整个 PBO 映射出来、`WritebackFromBackend` 进 client 的 buffer 影子,再 `BumpBufferMutationEpoch`(`DirectGLES.cpp` 约 15348–15361,日志 "PBO used, mapping buffer to client memory")——即读完影子就是新的。异步化必须保住"此后任何 CPU 访问都看到数据",有两条路:(a) client 只标"GPU 写过",数据留在 server 的 twin,CPU 访问时惰性拉(既有的 `ResourceReadback`);(b) server 读完后发一条 `OnBufferWriteback`,与 epoch bump 有序(`CONTRACT-P5C.md` §4.2 / §8.2)。选哪条要先量一下(PBO 通常多大、多久被 CPU 读)再定;(2) PACK 状态本来就推给 server(`PipeFill.cpp` 的 `EmitPixelPackState`),server 是否可以直接按 PACK 布局落字节,还是仍然回紧排数据、由 client 散射(后者就得等,等于没做);(3) PBO 太小 / 范围越界仍然是 client 侧的 `RecordReadbackRangeError`,别改。
|
||||
- **门**:新增场景——PBO 读 + 之后 `MapBufferRange` 读回的字节与 monolith 逐字节相同(含非紧 PACK 状态:`PixelStoreSweepScenario` 的形状);该 op 的 `by_op` 回包等待为 0(`MOBILEGL_PIPE_STATS=1` 的 `P65LinkMetrics kind=frame-op`);**red-once**:改回"等回复"后计数门变红;非 PBO 路径(`BandedReadbackScenario`)行为不变。
|
||||
|
||||
### W2 — `texture-remint-pull`:先做普查再定要不要协议
|
||||
|
||||
1. 普查:在四条 A/B trace 与你能拿到的 trace 语料上跑 `MOBILEGL_PIPE_STATS=1`,读 `TextureRemintPulls` 计数(`MG_Util/Metrics/PipeStats`;文档记录过"780 个统计窗口 2 次")。
|
||||
2. 判定:重铸重放的字节是否已在 `StagedTextureStore`(整段 staged run,按 (uploadTarget, level) 覆盖)。**[I]** 若是,client 拉取没有意义,重放应改读 server 自己的 store,`Fatal{UnmigratedEmulation,"texture-remint-pull"}` 变成普通路径;"已定义但没字节"的层(纯 `glTexStorage` 未上传)按 monolith 的行为(跳过 / 零)处理,先在 monolith 里测出来。
|
||||
3. 结论若是"不需要拉取":删掉 `OnTexturePullRequest`(回调 9 → 8,`kMGPipeCallbackCount` 与 `static_assert` 同步),写进裁定日志;`TextureRemintPullScenario` 改成"image 绑定后纹素与 monolith 相同、不 Fatal"。若确有 store 没有字节的层,再按 `design/04` §8.4 设计协议(server 发请求事件、client 回 `ResourceSubData` + `ResourceSubDataComplete{pullSerial}`、apply 线程有界等待、终止符可带零个 region),**并先回来问用户**。
|
||||
- 注意 Magma 与 Espryt 是两条路径(Espryt 的重铸在 `Managers.cpp`,Magma 的 T5 见 `CONTRACT-P5C.md` §2.3)。
|
||||
|
||||
### W3 — 日志分级(`OnLog`)
|
||||
|
||||
- **[V]** server 转发日志时级别写死 Info(`Server/ServerMain.cpp` 的 `ForwardLog`:`CreateLogLine(builder, LogLevel::Info, …)`),因为 `LogForwarder` 的签名没有级别(`MG_Util/Debug/Log.h:139`,`void (*)(void*, const char*)`)。`LogLine` 里本来就有 `level` 字段(`protocol.fbs`),**不需要改 schema**。
|
||||
- **做**:转发签名带上级别;client 写 `<base>.server.log` 时保留级别;`≤WARN` 有损(控制 socket 写不动就丢并计数)、`≥ERROR` 无损 + 限速。决定后把 `OnLog` 回调删掉(没有任何生产者,日志不走 `gMGPipeCallbacks`)。
|
||||
- **门**:单元测试覆盖级别映射与丢弃计数;故障注入 F2(§4)。
|
||||
|
||||
### W4 —(可选)事件量
|
||||
|
||||
只有当 `OnBufferWriteback` / `OnGpuWritten` 的事件数在某个负载里成为问题(看每帧事件数、`eventDropped`、writeback 切片数)才做批处理 / 收窄;否则写进裁定"没有实测需求,不做"。
|
||||
|
||||
### W5 — `OnGlError` 有序(默认不做)
|
||||
|
||||
保持 P5e 的放宽;把"下一 drain 点观察"写进 `CONTRACT-P9.md`。要收紧的话(`glGetError` 触发强制追平)先问用户,会引入新的同步点。
|
||||
|
||||
### 收尾(必做)
|
||||
|
||||
`MG_Remote/CONTRACT-P9.md`(只记实现所需的协议与行为)、`notes/p9/INTEGRATOR-DECISIONS-P9.md`(一条裁定一个 ID,形式同 `notes/p12/INTEGRATOR-DECISIONS-P12.md`)、`notes/p9/README.md` 的阶段状态、`ROADMAP.md` 的 P9 行与 `notes/DEBTS.md`(关掉的债、转出的债)。
|
||||
|
||||
## 4. 故障注入(两条,我定的)
|
||||
|
||||
| 编号 | 场景 | 期望 |
|
||||
|---|---|---|
|
||||
| F1 | server 在 W1 的异步 PBO 读还在飞的时候被杀,随后 client `glMapBufferRange` 该 PBO | 以 device-lost 收场(DECLINED / 具名 Fatal),**不能读到"看起来成功的旧字节"** |
|
||||
| F2 | client 停止读控制 socket(或 SEG_EVENT 不排空),server 持续产生 `≤WARN` 日志与事件 | apply 线程不被日志卡住超过一个有界时间;`≤WARN` 丢弃计数上涨;`≥ERROR` 不丢;事件环走既有的 `ReverseChannelForfeit`(Ph)而不是挂死 |
|
||||
|
||||
沿用 R-16:每条门都要能因它存在的理由变红,并真跑过一次红。
|
||||
|
||||
## 5. 怎么量、怎么验
|
||||
|
||||
- **回包等待的账**:`MOBILEGL_PIPE_STATS=1` 后 client 日志里每帧一行 `P65LinkMetrics kind=frame-op frame=N wait_replies=K by_op=<op>=<n>`,整段的汇总在 `kind=summary`。`op` 是 `MG_Pipe/PipeCalls.def` 里的 **1 起始序号**:回复行是 1 GetCaps、2 ResourceCreate、3 ResourceRespecify、5 MapPersistent、8 FenceStatus、9 FenceWait、14 QueryAvailable、15 QueryResult、47 SetTextureParams、48 ResourceSubData、52 ResourceReadback、55 GetTextureImage、58 ReadPixels、69 QueryTimestamp(共 14 行带 `kReplySlot`)。
|
||||
- **基线(Redmi,WSL client 经 Wi-Fi TCP)**:rd12 整段 324–350 次回包等待,全在加载帧;稳态每帧约 1 次(FCL 里的 Minecraft 是 `FenceWait`,op 9,属于 P10);openra 整段 1 次。数据与做法见 `notes/p12/CROSSHOST-ACCEPTANCE.md`、`tools/device_bench/disagg/crosshost_accept.sh`。**性能只记录、不设门**,对比用逐线程 CPU、Redmi 定频配对 A/B(`guide/pin-verification-2026-09-07.md`)。
|
||||
- **门**(`notes/p12/gate.sh` 是全套的写法):`unit`、`integration-split`、`integration-spawn`、`integration-tcp`、`integration-magma-{split,spawn,tcp}`、`integration-gpu`(`MOBILEGL_TRANSPORT=monolith` 与 `inproc` 各一遍)、卫生检查、`fatal_census.py`(79,只降不升)、`link_ratchet.py`(171)、`protocol_revision_pin.py`、`wire_declines_audit.py`、`check_doc_citations.py --strict`。
|
||||
- **改 wire 就要**:`protocol.fbs` 改动 → `MOBILEGL_PROTOCOL_CONTROL_REVISION` 加 1、`python3 scripts/ci/protocol_revision_pin.py --write`、重新生成头文件;`EventKind` / opcode 只追加;新 `Fatal{Word}` 要在 `MG_Remote/FatalFamilies.def` 有行。
|
||||
- **G1**:pull 构建的符号集与 `.text` 对基线恒等。所有新代码放在 `MOBILEGL_BUILD_DISAGGREGATED` / `MOBILEGL_PIPE_PUSH` 下。**G1 只能在同机同编译器上比**(`gate.sh` 已改成不同源就说"未比较")。本机的做法:WSL Arch 的 `~/w7/pipe`(`build-linux` 是 pull 配置,基线 `.text` = `0xa52203`,符号 0 / 0)。
|
||||
|
||||
## 6. 仓库规矩
|
||||
|
||||
- 提交一行:`[Type] (Scope): description`,**不加 `Co-Authored-By` 或任何署名行**;第一次提交前 `git var GIT_COMMITTER_IDENT` 必须是 `Swung0x48`。推送用 `git push origin refs/heads/feat/disaggregated`(origin = GitHub;Windows 上分支名大小写会撞 `Feat/`)。
|
||||
- 设备只用红米 `2f7cbe2e`(Adreno 830,root),别的 adb 设备一律不碰;只读动作(截图等)不用问。多设备在线时 adb 必须 `-s`。
|
||||
- 不要拉 GitHub LFS(限额):fixture 先从本地已 hydrate 的副本复制;`git lfs checkout` 在 WSL 上会把真文件覆盖成指针。
|
||||
- 临时文件放 scratchpad,阶段结束清掉(WSL 也要);耐久的放 `~/w7/notes/<阶段>/`。
|
||||
- 不顺手修 `dev` 上的 bug;发现的记成独立条目。README 只放介绍与操作流程,排查叙事进 `notes/p9/`。
|
||||
- 收官审查:P12 时用户裁定"不派 agent / Codex,自己核对并跑 G1";P9 收官前先问用户是否沿用。
|
||||
- FCL 是另一个仓库(用户的 fork),P9 不需要动它。
|
||||
|
||||
## 7. 回来汇报时给我
|
||||
|
||||
每个工作包一段:改了什么(提交号)、跑了哪些门(结果)、red-once 是哪条断言变红、没做的与为什么、需要用户裁的点。W2 的结论(要不要拉取协议)和 W3 的 `OnLog` 去留,请在动手实现前先报一次。
|
||||
@@ -1,6 +1,6 @@
|
||||
# P9 — 反向通道(待排,P6.5 之后)
|
||||
|
||||
> 尚未开工。本页保存该阶段在路线图上的完整范围与出口门(2026-09-24 从 [`ROADMAP.md`](../../ROADMAP.md) 阶段表移入,原文照录);阶段开工后,计划、裁定与报告都放在本目录。
|
||||
> **2026-09-29:范围已按树里现状重划,实现交接见 [`HANDOFF-P9.md`](HANDOFF-P9.md)(先读它的 §2:路线图下面的原文有一半已经做完或前提已变)。** 尚未开工。本页保存该阶段在路线图上的完整范围与出口门(2026-09-24 从 [`ROADMAP.md`](../../ROADMAP.md) 阶段表移入,原文照录);阶段开工后,计划、裁定与报告都放在本目录。
|
||||
|
||||
## 摘要
|
||||
|
||||
|
||||
Reference in New Issue
Block a user