From 47bbf5e6fda806095adb9445ef87df395a9553dd Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 29 Sep 2026 04:34:16 -0400 Subject: [PATCH] [Docs] (P10 A): CONTRACT-P10 section 1 - the fence-report protocol, the present-time report, the gates and red-once - with the re-scoped plan and A-FENCE-POLL, the paired host bench and the llvmpipe finding --- MobileGL/MG_Remote/CONTRACT-P10.md | 44 ++++++++++++++++++++ docs/Disaggregated/notes/p10/A-FENCE-POLL.md | 37 ++++++++++++++++ docs/Disaggregated/notes/p10/PLAN-P10.md | 38 +++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 MobileGL/MG_Remote/CONTRACT-P10.md create mode 100644 docs/Disaggregated/notes/p10/A-FENCE-POLL.md create mode 100644 docs/Disaggregated/notes/p10/PLAN-P10.md diff --git a/MobileGL/MG_Remote/CONTRACT-P10.md b/MobileGL/MG_Remote/CONTRACT-P10.md new file mode 100644 index 00000000..fd1bce71 --- /dev/null +++ b/MobileGL/MG_Remote/CONTRACT-P10.md @@ -0,0 +1,44 @@ +# P10:sync / query / present 节奏 + +2026-09-29。依据 `docs/Disaggregated/notes/p10/`(计划与范围重划 [`PLAN-P10.md`](../../docs/Disaggregated/notes/p10/PLAN-P10.md)、裁定 `INTEGRATOR-DECISIONS-P10.md`)。本文只记实现所需的协议与行为;验证结果以 notes 里的运行证据为准。 + +## 范围 + +fence 轮询不再是往返(§1)。路线图原文里已做完或前提已变的项(client 铸造 query handle、present 1:1、credit 默认 1、无 present 负载的 `SEG_STAGE` 饥饿)见计划 §1。 + +## 1. fence 轮询从 server 的报告作答(A) + +### 线上格式 + +- `EventKind` 追加 `kEventFenceSignaled = 5`,载荷 `EventFenceSignaledHead { EventHandle Fence; }`(8 字节):server 看到该 client fence 已 signaled,每个 fence 至多一条。opcode 与控制协议修订号不变。 + +### server + +- `ServerVerbSink` 按**创建顺序**记下尚未报告的 fence(`FenceCreate` 入队;被删的出队时跳过)。`ReportSignaledFences(flush)` 从最老的一个开始查,查到第一个未完成的就停(同一 context 的 GPU 按提交顺序完成),每个已完成的发一条 `kEventFenceSignaled` 并立即发布。 +- `OnPresent` 在归还 present credit **之前**调用一次(状态查询,不提交):credit 放行的 client 会立刻去等一两帧前的 fence,报告必须先于放行。 +- apply 线程在**每个 drain 批次之后**调用(后端表 `GL.GetSyncStatus`,不提交);**将要空闲**且还有未报告的 fence 时先调一次 `flush=true`——最老的 pending fence 改用 `GL.ClientWaitSync(GL_SYNC_FLUSH_COMMANDS_BIT, 0)`,这是两个后端都会提交所持命令的唯一后端无关方式——仍有未报告的就**限时停车 1 ms**(`kFenceIdlePollMs`)后再查,否则照旧无限期停车。`SEG_EVENT` 满时不报告(与既有"环满不 apply"同一规矩)。 +- 与后端无关:两个后端都实现 `GL.FenceSync / GetSyncStatus / ClientWaitSync`。没有原生 fence 的(`FenceSync` 为空或返回空)视为已 signaled。 + +### client + +- 事件排空时记下已报告的 fence(`{slot → gen}`,独立互斥锁:排空发生在持有 `g_fenceMutex` 的发射函数内部);往返答"已 signaled"的也记下;`glDeleteSync` 时忘掉。 +- `glClientWaitSync` / `glGetSynciv(GL_SYNC_STATUS)` 先做一次**轮询入口**(`ClientSession::PollEntry`:刷出已发布的记录、排空反向通道),然后: + - 已知 signaled → 本地答 `GL_ALREADY_SIGNALED` / `GL_SIGNALED`; + - 未知且调用不能等(timeout = 0、`glGetSynciv`)→ 本地答 `GL_TIMEOUT_EXPIRED` / `GL_UNSIGNALED`;同一 fence 连续第 N 次(`MOBILEGL_IPC_POLL_ESCALATE`,默认 64)改为一次真往返,计数归零; + - 未知且调用可以等(timeout > 0)→ 真往返,语义不变。 +- `MOBILEGL_IPC_POLL_ESCALATE=0`:每次轮询都往返,即 P10 之前的行为(A/B 臂)。设备丢失后一律走往返臂(其 DECLINED 答复是既有的 no-op)。 +- 计数:`ReadFencePollCounters()`(本地作答、升级、往返、server 报告),经 `SplitRuntimePeek` 给车道。 + +### 门 + +- `FencePollScenario`(两后端 × 单进程 / Split / Spawn / Tcp):零超时轮询循环有界结束(带 / 不带 flush 位,后者仅 split);已 signaled 的 fence 轮询不产生回复槽记录;只轮询不发记录时,空闲 server 仍会报告后来完成的 fence(无升级)。单元控制 `RemoteClientControls.AnUnreportedFencePollCrossesOnlyEveryPollEscalateTimes` 钉住"第 N 次才过线"。 +- red-once:`POLL_ESCALATE=0` → 200 条回复槽记录 / 200 次往返;去掉空闲复查 → Espryt 需 1 次升级、Magma 升级 15 次仍等不到(Magma 的 fence 不会自己提交);client 关掉本地作答 → 单元控制 exit 163。 + +### 测量 + +`PacedFrameFenceWaitsBench`(手动开启,不设门)的配对结果在 [`A-FENCE-POLL.md`](../../docs/Disaggregated/notes/p10/A-FENCE-POLL.md):Magma 走 tcp 时每帧耗时降 37%、fence 往返为 0;Espryt 在主机 llvmpipe 上没有收益,因为只有阻塞等待才能让 fence 可见。 + +## 不变量 + +- G1:新代码全在 `MOBILEGL_BUILD_DISAGGREGATED` 下(`MG_Remote`、`Config` 的 IPC 段)。 +- `EventKind` 只追加;控制协议修订号不变(3)。 diff --git a/docs/Disaggregated/notes/p10/A-FENCE-POLL.md b/docs/Disaggregated/notes/p10/A-FENCE-POLL.md new file mode 100644 index 00000000..93bf66f8 --- /dev/null +++ b/docs/Disaggregated/notes/p10/A-FENCE-POLL.md @@ -0,0 +1,37 @@ +# P10 A:fence 轮询本地作答——配对测量与 Espryt 发现 + +2026-09-29,`p10/main@8d0a1b6c`,主机 WSL Arch(Espryt = mesa llvmpipe ES 3.2;Magma = lavapipe)。协议与门见 [`CONTRACT-P10.md`](../../../../MobileGL/MG_Remote/CONTRACT-P10.md) §1。只记录、不设门。 + +## 负载 + +`FencePollScenario.PacedFrameFenceWaitsBench`(`MGITEST_FENCE_PACE_BENCH=1`):每帧 clear + `glFenceSync` + 交换,再对 N 帧前(`MGITEST_FENCE_PACE_LAG`,默认 2)的 fence 做 `glClientWaitSync(FLUSH, 1 s)`;600 帧。也就是 Minecraft 的形状,回放语料里没有 trace 带它。A/B = `MOBILEGL_IPC_POLL_ESCALATE=0`(每次都往返,P10 之前的行为)对默认 64。 + +## 结果(3 轮,ms/帧 与 600 帧里的 fence 往返次数) + +| 后端 · 传输 | esc=0 ms/帧 | esc=64 ms/帧 | esc=64 往返 / 本地作答 | +|---|---|---|---| +| Magma · inproc | 0.201 / 0.206 / 0.217 | 0.200 / 0.206 / 0.214 | 0 / 598(回复槽记录 601 → 3) | +| Magma · tcp | 1.017 / 1.007 / 1.004 | **0.631 / 0.651 / 0.606**(−37%) | 0 / 598 | +| Espryt · inproc | 0.816 / 0.672 / 0.816 | 0.808 / 0.843 / 0.848 | 595–598 / 0–3 | +| Espryt · tcp | 0.891 / 0.939 / 0.908 | 0.796 / 0.891 / 0.800 | 497–519 / 79–101 | + +表中是 `625f4db4` 的配置。证据:`~/w7/notes/p10/evidence/bench-after-present-report.log`;改动前的一组在 `bench-before-present-report.log`,数字相近。 + +## present 时报告(`625f4db4`) + +server 在 `OnPresent` 里、归还 credit **之前**查一次未报告的 fence(状态查询,不提交)。原因:credit 放行的 client 立刻去等一两帧前的 fence,批次结束后才发的报告会输掉这场竞争。实测只有 Espryt·tcp 有变化,本地作答从 36–54 升到 79–101;Magma 在改动前就已经是 0 往返。 + +## Espryt 发现:llvmpipe 上 fence 只有被阻塞等待过才可见 + +Espryt 的 timed wait 几乎全部过线,这**不是**报告晚到: + +| 探针 | 结果 | +|---|---| +| lag 1 / 2 / 3 / 4 / 6(`benchlag-present-status.log`) | inproc 本地作答都在 0–6,与 lag 无关——六帧前的 fence 也看不到 | +| server 端追踪(临时 `P10DBG`) | 队首 fence 在约 4 帧里的每次检查(含 flush 检查)都是 pending;client 的 timed wait 过线、server 执行 `ClientWaitSync(…, 1 s)` 之后,下一批才报告它。Magma 同一追踪里报告全部早于等待 | +| flush 检查改成冲刷最新的 fence | 不变(0–24) | +| flush 检查改用 1 ns 的 timed wait | 不变(本地 17 / 596) | + +结论:在这台主机的 mesa llvmpipe(ES 3.2)上,`glGetSynciv(GL_SYNC_STATUS)` 与零超时 `glClientWaitSync` 看不到早已完成的 fence,只有真正阻塞的等待才能让它变成 signaled。这是宿主驱动的行为,与传输无关。A 在这里只是**没有收益**,并没有错:过线的等待语义不变,esc=64 与 esc=0 持平。探针都已撤掉,`ReportSignaledFences` 保持门禁通过时的原样。 + +**真机上的答案归 C**:红米(Adreno 830、Espryt)FCL + Minecraft,esc 0/64 配对,读 `fence-local / round-trips` 计数。 diff --git a/docs/Disaggregated/notes/p10/PLAN-P10.md b/docs/Disaggregated/notes/p10/PLAN-P10.md new file mode 100644 index 00000000..fcb5647d --- /dev/null +++ b/docs/Disaggregated/notes/p10/PLAN-P10.md @@ -0,0 +1,38 @@ +# P10 计划:sync / query / present 节奏(重划范围) + +> 2026-09-29,基于 `feat/disaggregated@de33d401`(P9 收官头)。路线图行(原文在 [`README.md`](README.md))写于 09-22,与树的现状逐项核对如下;**先读 §1**。 + +## 1. 路线图每一项的现状 + +| 路线图写的 | 现状(核对) | 处置 | +|---|---|---| +| 轮询入口成门铃点 + `MOBILEGL_IPC_POLL_ESCALATE` | **没做**:`glClientWaitSync` / `glGetSynciv` 都是 `kWaitReply`(`FenceWait` op 9、`FenceStatus` op 8),每次都要等 server 把它之前**所有**记录 apply 完再回复——run-ahead 下等于一次全管线同步。FCL 里的 Minecraft 稳态每帧约 1 次(P12 验收 A);TCP 上再加一个 RTT。旋钮 0 处 | **A**(核心) | +| DirectGLES fence 完成度逐 fence 退休;非 present fence tick | 线上**不依赖**帧水位:server 每批 apply 后 `RetireThrough(appliedSeq)`(`ServerLoop.cpp`),`SEG_STAGE` 不会在无 present 负载里饿死。帧水位只喂 Espryt 自己的 buffer 池回收,与 monolith 同 | 不单独做;A 需要"空闲时也检查 fence",那一点就是线上的非 present tick | +| `present` 1:1、credit 默认 1、client 铸造 query handle | 已是 | 关闭 | +| `PRESENT_CREDIT > 1` 从未量过 | 跨机 Wi-Fi 已量(P12:rd12 稳态 1→2 +26%、2→3 +4%,[`../p12/CROSSHOST-ACCEPTANCE.md`](../p12/CROSSHOST-ACCEPTANCE.md));**TCP loopback 未量**([`../perf-five-arm-20260928`](../perf-five-arm-20260928/README.md) 建议 #2 "未实测,待 A/B") | **C** | +| roundtrip 计数器与输入延迟直方图 | 部分已有:`P65LinkMetrics` 逐 op 回包等待 + 回复延迟直方图(P12);trace 稳态回包等待 0(五臂报告)。缺:present credit 等待的直方图、逐 trace 的往返普查记录 | **C**(记录);直方图若缺则补 | +| `SetSwapInterval`(最后一个手写 class-C) | `eglSwapInterval` 实际走 `BackendObject_Remote::SetEGLSwapInterval` → 控制帧 `SurfaceControlOp::SetSwapInterval`,跨进程可用;发射表槽 `SetSwapInterval_Unmigrated` 是永远到不了的具名 Fatal | **B**:槽改为同一转发,class-C 清零 | +| 门:query / XFB / `AsyncCompile` 场景在 split 下绿 | `SyncWire`、四个 XFB 场景已登记 split;**`AsyncCompileScenario`、`XfbPrimitiveQueryScenario`、`PrimitivesGeneratedNoXfbScenario` 未登记任何 split 臂** | **B** | +| 门:零 timeout 轮询有界退出 | 今天靠每次往返保证进度;A 之后由升级规则保证 | **A** 的门 | + +## 2. 工作包 + +### A — fence 轮询本地作答(核心) + +- **server**:apply 线程在每个 drain 批次之后、以及空闲等待时(有未报告的 fence 就限时停车,默认 1 ms 再查)按创建顺序用后端表 `GL.GetSyncStatus` 查尚未报告的 fence;新近 signaled 的发一条 `kEventFenceSignaled{handle}`(EventKind 只追加)。后端无关(两个后端都实现 `GL.FenceSync/GetSyncStatus/ClientWaitSync`)。 +- **client**:排空事件时记下已 signaled 的 fence;`glClientWaitSync` / `glGetSynciv(GL_SYNC_STATUS)`:已知 signaled → 本地答 `GL_ALREADY_SIGNALED` / `GL_SIGNALED`;未知且 timeout = 0 → 先 publish + 敲门铃(轮询入口是门铃点;`GL_SYNC_FLUSH_COMMANDS_BIT` 无条件 flush),排空事件,仍未知则本地答 `GL_TIMEOUT_EXPIRED` / `GL_UNSIGNALED`;同一 fence 连续 N 次(`MOBILEGL_IPC_POLL_ESCALATE`,默认 64;0 = 关闭本地作答,即 A/B 控制)无进展 → 升级成一次真往返;timeout > 0 且未知 → 真往返(语义不变)。 +- **门**:零 timeout 轮询循环在 split / spawn / tcp × 两后端有界退出;轮询不产生回复槽记录(计数门,red-once = `POLL_ESCALATE=0`);`SyncWireScenario` 不变绿;停车的 server 仍会报告后来才 signaled 的 fence(red-once = 去掉空闲检查)。 + +### B — split 覆盖与最后一个 class-C + +- `AsyncCompileScenario`、`XfbPrimitiveQueryScenario`、`PrimitivesGeneratedNoXfbScenario` 登记到两个后端的 Split / Spawn / Tcp 臂,红的修(两后端都修)。 +- `SetSwapInterval` 发射表槽改为转发,删 `SetSwapInterval_Unmigrated`。 + +### C — 测量(A 落地之后) + +- 红米 `2f7cbe2e`:`PRESENT_CREDIT` 1 / 2 / 3 × {spawn+tcp localhost, WSL → 手机 Wi-Fi},rd12 与 openra,每档 ≥ 3 次交错;`POLL_ESCALATE` 0 / 64 的配对。只记录、不设门(性能只记录,用户 09-08)。 +- 主机 retrace 语料 `MOBILEGL_PIPE_STATS=1`:逐 trace 的稳态回包等待按 op 列表,draw / state / upload 路径应为 0。 + +## 3. 门与规矩 + +同 P9([`../p9/HANDOFF-P9.md`](../p9/HANDOFF-P9.md) §5–§6):`~/w7/notes/p9/gate.sh` 全套 + verify 构建三车道;G1 同机比较;改 wire 按规矩(EventKind / opcode 只追加);每个新门 red-once;设备只用红米;不拉 LFS;提交一行、无署名尾注。