From 738b29f3e472a862035a1331a41cd7f7a01411e9 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 29 Sep 2026 03:03:03 -0400 Subject: [PATCH] [Docs] (P9): the contract's first three sections (pack-buffer readback, graded log forwarding, the glGetError observation point), both fault injections, the W1 note with its red-once evidence, and rulings ID-P9-1..10 --- MobileGL/MG_Remote/CONTRACT-P9.md | 59 +++++++++++++++++++ .../notes/p9/INTEGRATOR-DECISIONS-P9.md | 16 +++++ .../notes/p9/W1-PACK-BUFFER-READBACK.md | 46 +++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 MobileGL/MG_Remote/CONTRACT-P9.md create mode 100644 docs/Disaggregated/notes/p9/INTEGRATOR-DECISIONS-P9.md create mode 100644 docs/Disaggregated/notes/p9/W1-PACK-BUFFER-READBACK.md diff --git a/MobileGL/MG_Remote/CONTRACT-P9.md b/MobileGL/MG_Remote/CONTRACT-P9.md new file mode 100644 index 00000000..49c23480 --- /dev/null +++ b/MobileGL/MG_Remote/CONTRACT-P9.md @@ -0,0 +1,59 @@ +# P9:反向通道 + +2026-09-29。依据 `docs/Disaggregated/notes/p9/`(交接 [`HANDOFF-P9.md`](../../docs/Disaggregated/notes/p9/HANDOFF-P9.md)、裁定 `INTEGRATOR-DECISIONS-P9.md`)。本文只记实现所需的协议与行为;验证结果以 notes 里的运行证据为准。 + +## 范围 + +server → client 的结果回传在 split 下不阻塞、不丢、不乱序。本阶段落地:PACK-PBO 回读不等回复(§1);server 日志按级别转发、不阻塞(§2);`OnGlError` 的观察时点写成契约(§3);两条故障注入(§4)。路线图原文里已由前序阶段做完的(异步 reply、2 MiB 回复槽与分带回读、XFB 删除)不重做,见交接 §2。 + +## 1. 读进 pack buffer 的回读(W1) + +### 线上格式 + +- 追加两个 op(只追加,序号 82、83):`ReadPixelsToBuffer`(kCtxVerb,verb = ReadPixels)、`GetTextureImageToBuffer`(kCtxObject,verb = GetTextureImage)。标志 `kNone`(无回复槽、无 blob、无尾),等待类 `kWaitNone`。 +- 载荷 `MGPReadbackToBuffer`(104 字节):`Src` 是回复形式的 `MGPReadbackInfo` 原样(`Src.DstOffset = 0`、`Src.DstSize` = 紧排 w·h·d·bpp,只校验不定位);`Dst` 是 pack buffer 的资源句柄;`DstBase` = 应用偏移 + `GL_PACK_SKIP_{PIXELS,ROWS,IMAGES}`;`RowStride`(行长与对齐已算入);`ImageStride`(只在 `Src.Box.D > 1` 时读);`SwapGroup` = `GL_PACK_SWAP_BYTES` 要反转的字节组宽(2/4/8,0 = 不换)。布局算术只在 client(`ReadbackLayout`)。 + +### client + +- 目的是 pack buffer 且 `PackBufferReadbackTarget` 给出句柄时走新形式:先 `MarkReadPixelsPackBuffer()`(`GpuWritePending` 第 4 行,把 buffer 标成 GPU 写过),再发记录,**不等**。`glReadPixels` 仍按回复槽上限分带(为了 server 的 scratch 有界,不是为了线路);`glGetTexImage` 整级一条。 +- 不走新形式(回到回复形式:等回复、client 自己把行 `UploadSubData` 进 buffer)的五种情况:`MOBILEGL_IPC_PBO_READBACK_SYNC=1`;buffer 没有回读路径(`BufferWritebackIsReachable` 为假);buffer **已映射**(只有持久映射能跨读存活,它的读者直接读指针,永远不会触发同步);范围超出 `MGPSubData` 能表达的 buffer 偏移(2³¹−1);buffer 还没上过线(无句柄)。 +- 此后对该 buffer 的任何 CPU 读(`glGetBufferSubData`、`glMapBufferRange`、以它为 UNPACK 源的纹理上传)经 `SyncGpuWrites` → `resource_readback` → `OnBufferWriteback` 取回字节。**UNPACK 源读取在 split 下先 `SyncGpuWrites`**(`GL_Texture.cpp` 的 `MGL_SYNC_PIXEL_UNPACK_SOURCE`,pull 构建展开为空)。 +- monolith 不变:后端在自己的 `ReadPixels` 里映射驱动 PBO 回影子。 + +### server + +- 读取与回复形式逐字节相同(`ReadBoundFramebufferTight`:中性 PACK 读进 scratch;`ReadTextureImageTight`:两后端各自的 `ReadTextureImageWire`);`ReadPixelsToBuffer` 的紧排大小仍受本端 `maxReplyBytes` 约束(PH-3)。 +- 落地走 applier 的 buffer 写入口:按 `SwapGroup` 反转后,每段连续区间合成一条 buffer 半边的 `MGPSubData`(`HasLiveHostWrites = 0`)调 `MGPipeApplyResourceSubData`——布局紧排时整读一条、行紧排时每层一条、否则每行一条;行间 padding 不写。于是两后端都与"client 写了同样字节的 `glBufferSubData`"同形:Espryt 进 R-11 暂存影子并排队范围,Magma `WriteWireBuffer` 与在飞读取排序;serial、epoch 与后续记录的顺序都照旧。**不用驱动的 PBO 读**:它只写后端存储,下一次从暂存影子排空的范围会把旧字节盖回去。 +- **读之前**先查完全部形状:盒与格式、紧排大小是否溢出、PH-3 上限、`Src.DstOffset/DstSize`、布局(`RowStride` 不小于紧排行、`ImageStride` 不小于行、`SwapGroup` ∈ {0,2,4,8}、最后一行的起点 ≤ 2³¹−1、紧排宽度 ≤ 2³²−1)。任何一条不过 → 每个 sink 唯一的闩锁点 `Fatal{ProtocolCorruption, "ReadPixelsToBuffer.shape"}` / `"GetTextureImageToBuffer.shape"`(原因写在其后)。查过之后落地不会再遇到要拒绝的范围;范围是否落在 buffer 内由 applier 自己的写入口判。 +- 读失败是响亮的:无 `GL.ReadPixels` → `Fatal{ReadbackDeclined, "ReadPixelsToBuffer"}`;纹理级读不出 → `Fatal{ReplyError, "GetTextureImageToBuffer"}`。回复形式的这些情况在 client 端以同名 Fatal 结束,新形式没有回复可载,就在 server 端闩。三组闩锁点各有 `PeerLatchTest` 行或不可达论证(`ph_latch_sites.py`)。 +- 准入推导:一个 verb 只有在**盖它戳的每个 op**都等待时才算静态屏障(`gen_pipe_field_ownership.py`);`ReadPixelsToBuffer` 盖 ReadPixels 戳而不等,所以 ReadPixels 不再静态屏障(现有生产表无 BARRIER_PULLED 字段,只影响合成夹具)。 +- 计数:`ServerVerbSink::BufferReadbacks()` / `BufferReadbackWrites()`。 + +## 2. server 日志转发(W3) + +- **级别端到端**:`LogForwarder` 为 `void(*)(void* user, int level, const char* line)`(`SetLogForwarder` 与 `SetSessionLogForwarder` 两种),`level` 是 `MOBILEGL_LOG_LEVEL_*`,由 `Log()` 已收到的 Android 优先级映射(`Log()` 签名不变,G1)。线上用 `LogLine.level`(`protocol.fbs` 本来就有,**不改 schema、不动控制修订号**);未知线上级别按 ERROR 收,不降级。client 写 `.server.log` 时保留级别:server 格式化过的行原样,裸行加 `[peer/LEVEL]: `。 +- **不阻塞任何打日志的线程**:server 装的转发器是 `Transport/LogForward` 的队列 + 独立发送线程(`mgl-log-fwd`);打日志的线程只加锁、拷贝,不碰 socket。 + +| 级别 | 行为 | +|---|---| +| ≤ WARN | 有损:队列里这类行满 1 MiB 后丢弃并计数;每段缺口在流内补一行 `LogForward{Dropped} - N line(s) …` | +| ERROR | 无损:限速 256 行/s、突发 1024,超速的按序等待;超过 8 MiB 上限时多出的合成一行 ERROR `LogForward{Coalesced} - N …`,不静默消失 | +| FATAL | 最多等 2 s 把本行写出;若上一次 FATAL 等待已超时且其后无任何写出,后续 FATAL 不再等(树里 `MGLOG_F` 也用于可恢复的 EGL 加载消息) | + +- `LogFlush` 的 ack 经同一队列排在此前所有已转发行之后(`MGPipeSyncPeerLog` 的承诺不变)。会话每个出口:先清转发器,再最多 2 s 排空,发送线程仍卡在写上就切断控制 socket 让它返回。会话结束时 server 本地记一行计数(`forwarded / dropped(<=WARN) / coalesced(ERROR) / paced(ERROR) / notices / abandoned / fatal-flush-timeouts / transport-failed / max-offer-us`;有损失为 WARN,否则 DEBUG)。 +- `MGPipeCallbacks::OnLog` 删除(零生产者、零消费者、无 `EventKind`)。 + +## 3. `OnGlError` 的观察时点(W5) + +保持 P5e 的放宽(`CONTRACT-P5E.md` §"glGetError relaxation"、`CONTRACT-P5C.md` §4.2):run-ahead 下一个动词产生的 GL 错误在**下一个 drain 点**被 client 观察到,最迟晚一个 present credit;`glGetError` 不触发强制追平。收紧(`glGetError` 前 `WaitForApplyToCatchUp`)会新增同步点,需用户裁定。 + +## 4. 故障注入 + +- **F1**:pack buffer 读已发出、server 在落地前死亡,随后 client 读该 buffer。device lost 使 `resource_readback` 被 DECLINED,影子仍是读之前的字节;`AwaitBufferWriteback` 在 `ClientSession::DeviceLost()` 时以 `Fatal{ReadbackDeclined, "buffer-writeback"}` 结束,**不把旧字节当像素返回**(与回复形式的 `ReadbackDeclined` 同名)。门:`RemoteClientControls.APackReadOwedWhenTheDeviceIsLostNeverReadsBackAsTheOldBytes`。 + +- **F2**:client 停读控制 socket(`ClientSession::PauseControlReaderForTest`),server 以测试旋钮 `MOBILEGL_TEST_APPLY_LOG_FLOOD=` 让 apply 线程每条记录打 n 行 WARN + 1 行 ERROR。apply 线程不被日志卡住(约 16 MiB / 400 条记录 0.4 s 过完,单行交接最长 53–148 µs);≤ WARN 的丢弃全部有流内说明;≥ ERROR 全部按序到达。控制与 SEG_EVENT 都不排空时,会话经既有 `ReverseChannelForfeit{NotDraining}` 以 exit 0 结束,同一 supervisor 接下一个连接。门:`LogForwardTest`(15 例)、`EventForfeitPeer.StreamLogFlood*`(2 例)。 + +## 不变量 + +- G1:pull 构建符号增 0 减 0、`.text` 不变(新代码全在 `MOBILEGL_BUILD_DISAGGREGATED` 下;`GL_Texture.cpp` 的宏在 pull 构建里为空)。 +- 目录只追加:`MGP_CALL_LIST_DOCUMENTED_COUNT` 81 → 83,`kMGPipeVerifiedPayloadCount` 82 → 83,stamp 表 32 → 34 行。 diff --git a/docs/Disaggregated/notes/p9/INTEGRATOR-DECISIONS-P9.md b/docs/Disaggregated/notes/p9/INTEGRATOR-DECISIONS-P9.md new file mode 100644 index 00000000..93fcba1d --- /dev/null +++ b/docs/Disaggregated/notes/p9/INTEGRATOR-DECISIONS-P9.md @@ -0,0 +1,16 @@ +# P9 集成者裁定日志(反向通道) + +> 形式沿用 [`../p12/INTEGRATOR-DECISIONS-P12.md`](../p12/INTEGRATOR-DECISIONS-P12.md):一条裁定一个 ID,只记「定了什么、为什么、影响哪些文件」。交接与重划范围见 [`HANDOFF-P9.md`](HANDOFF-P9.md),协议见 `MobileGL/MG_Remote/CONTRACT-P9.md`。 + +| ID | 日期 | 裁定 | 理由 | 影响 | +|---|---|---|---|---| +| ID-P9-1 | 2026-09-29 | W1 取交接的 (a):读进 pack buffer 时 client 只标"GPU 写过",字节留在 server,CPU 读时经既有 `resource_readback` 取回;不做 server 主动推送 (b)。 | 读的那一刻两者都不等;(a) 零新机制(`GpuWritePending` 第 4 行 P5 已留好)、从不被 CPU 读的 PBO 零字节过线;(b) 要另一套在飞状态。 | `EmitTables.cpp`、`TextureReadbackEmit.inc` | +| ID-P9-2 | 2026-09-29 | 新形式是**两个追加 op**(82 `ReadPixelsToBuffer`、83 `GetTextureImageToBuffer`,共用 104 字节的 `MGPReadbackToBuffer`),不是给 `ReadPixels` 加每记录的免回复标志。 | 目的地要带句柄与 PACK 布局,改 `MGPReadbackInfo` 会同时改两个既有 op 的线上布局;追加 op 是目录的既定做法,且 verb 映射一 op 一 verb。 | `PipeCalls.def`、`MGPipeTypes.h`、`PipeFields.def`、`FieldOwnership.def`、`PipeWireCodec.{h,cpp}`、生成表 | +| ID-P9-3 | 2026-09-29 | server 端不用驱动 PBO 读:读紧排像素(与回复形式同一函数)后,按 client 算好的布局经 `MGPipeApplyResourceSubData` 逐段落进目的 buffer。 | Espryt 的 buffer 有两份存储,驱动写只到 GL buffer,之后从 R-11 暂存影子排空的范围会盖回旧字节;Magma 的 `ReadPixels` 没有 pack 路径。走写入口两后端同形。 | `PipeApplier.{h,cpp}` | +| ID-P9-4 | 2026-09-29 | client 在五种情况下退回回复形式:旋钮 `MOBILEGL_IPC_PBO_READBACK_SYNC=1`、无回读路径、buffer 已映射(持久映射的读者直接读指针)、范围超 2³¹−1、buffer 无句柄。split 下 UNPACK 源读取先 `SyncGpuWrites`。device lost 时欠着 GPU 写的 buffer 被读 → `Fatal{ReadbackDeclined, "buffer-writeback"}`(F1)。 | 每一条都是"标记兑现不了"的情形;UNPACK 源不同步会让"读进 PBO 再从它上传"上传旧字节;device lost 下返回影子就是把旧字节当像素。 | `TextureReadbackEmit.inc`、`GL_Texture.cpp`、`GpuWritePending.cpp`、`Config.h`、`ConfigLoader.cpp` | +| ID-P9-5 | 2026-09-29 | 两个新 sink 的全部形状检查在**读之前**做完,并归到每个 sink 唯一的闩锁点(`".shape"`,原因作参数);落地函数里不再有闩。准入推导改为"盖该 verb 戳的**每个** op 都等待才算静态屏障"(原实现取表中最后一个 op),自检里以 ReadPixels 为例的对照相应翻转,并加"把新 op 也屏障后该对重新准入"的正向对照。 | `ph_latch_sites.py` 要求每个闩锁点首参为字面量且有原始对端对照行;一个 sink 一个站点只需一行。准入表按 verb 存,一个不等待的 op 就让该 verb 的拉取对它的记录是撕裂读;"最后一个 op"让答案取决于表的顺序。 | `PipeApplier.{h,cpp}`、`PeerLatchTest.cpp`(3 行)、`ph_latch_sites.py`(1 条不可达论证)、`gen_pipe_field_ownership.py` | +| ID-P9-6 | 2026-09-29 | W2 取 A:**不要拉取协议**,两后端都是。Espryt split 的重铸已从旧 GPU 纹理读回每级、合并 `StagedTextureStore` 的待定盒(`RequireImageBindableStorageByHandle`,c1bf7e15);Magma 是 `vkCmdCopyImage` GPU→GPU。两处 `Fatal{UnmigratedEmulation, "texture-remint-pull"}` 在传输下不可达(普查 77 行 × 2 臂 = 154 次运行 0 次命中)→ 删除;`OnTexturePullRequest` 删除;`TextureRemintPulls` 在 server 路径计数。只修一个 split 缺陷:只定义未上传的级在重铸时读回失败(`Fatal{ResourceUnavailable,"image-promotion-readback"}`)→ 先问驱动该级是否存在,没有就用暂存或跳过;读回失败且暂存有该级 → 用暂存。 | 交接 §3 W2 的"改读 server 自己的 store"是错的:store 只有 client 上传过的字节,而语料里两次真实重铸(iris-photon、iris-derivative,GLES)都是纯 GPU 内容、store 为空。读 GPU 图像才对。只有确实需要拉取协议时才回来问用户,此处不需要。 | p9/w2 包(`Managers.cpp`、`MGPipeCallbacks.h`、`TextureRemintPullScenario`) | +| ID-P9-7 | 2026-09-29 | W4 不做:`OnBufferWriteback` 批处理 / epoch 排序、`OnGpuWritten` 收窄都没有实测需求。普查全语料 0 错误事件、0 环满阻塞、0 丢弃;唯一的大量是 Magma 在 create-indirect 第 1 帧发 7,093 条整 buffer 的 `OnGpuWritten`(client 自己已标同一批 buffer,Espryt 同 trace 为 0),冗余但无害,记为债。 | 交接 §3 W4:只有事件量在某个负载里成为问题才做。 | `DEBTS.md` | +| ID-P9-8 | 2026-09-29 | W2 普查查出 monolith Espryt 重铸用陈旧 / 零的 client 纹素覆盖 GPU 写过的级(14 例探针中 7 例错),即 P7 普查 §3 #6 未归因的 iris-derivative × DirectGLES ~26.9k px 差异。**不在 P9 修**,另开独立任务(改 pull 构建 `.text`,属 dev 线)。 | 交接 §6:不顺手修 dev 上的 bug,记成独立条目。 | 过程(独立任务) | +| ID-P9-9 | 2026-09-29 | W3:`OnLog` 删除(零生产者、零消费者、无 `EventKind`;日志一直走控制面 `LogLine`)。转发器带级别,由 `Log()` 已收的 Android 优先级映射,`Log()` 签名不变;server 装队列 + 独立发送线程:≤WARN 1 MiB 后丢并计数、每段缺口一行流内说明;ERROR 无损、256 行/s 突发 1024、8 MiB 外合并成一行;FATAL 有界 2 s 且每次卡顿只等一次。限值为常量,不设 `MOBILEGL_IPC_*` 旋钮;计数走会话结束的一行本地日志,不进 PIPE_STATS。F2 为两例(只停控制;控制 + SEG_EVENT 都停)+ server 旋钮 `MOBILEGL_TEST_APPLY_LOG_FLOOD`。 | 交接 §3 W3 与 §4 F2;`protocol.fbs` 的 `LogLine.level` 本来就在,不必改 schema。旋钮要动配置表与 spawn 环境白名单,无实测需求。计数的家当惯例是日志行(同 `ForfeitDrops`)。 | p9/w3 包:`Transport/LogForward.{h,cpp}`、`Log.{h,cpp}`、`ServerMain.cpp`、`ControlInbox.{h,cpp}`、`ServerLoop.{h,cpp}`、`MGPipeCallbacks.h`、`LogForwardTest.cpp`、`EventForfeitPeerTest.cpp` | +| ID-P9-10 | 2026-09-29 | W3 查出、**不在 P9 改**的两项转出为债:(1) `TCP_USER_TIMEOUT` = 5000 ms(`SocketTransport.cpp`)——客户端停读控制超过 5 s 且有待发日志字节时,内核仍会断开连接(W3 之后 apply 线程不再被卡,但会话仍会断);(2) `PublishSessionFault`(`FatalFunnel.cpp`)用阻塞写发 SessionFault,对停读的客户端会排在日志发送线程卡住的写之后最多等同样的 5 s。 | 两者都早于 W3,属于传输策略,改动需另行裁定。 | `DEBTS.md` | diff --git a/docs/Disaggregated/notes/p9/W1-PACK-BUFFER-READBACK.md b/docs/Disaggregated/notes/p9/W1-PACK-BUFFER-READBACK.md new file mode 100644 index 00000000..53d332d2 --- /dev/null +++ b/docs/Disaggregated/notes/p9/W1-PACK-BUFFER-READBACK.md @@ -0,0 +1,46 @@ +# W1:读进 pack buffer 的回读不再等回复 + +> 2026-09-29。协议与行为见 `MobileGL/MG_Remote/CONTRACT-P9.md` §1、§4;裁定 `INTEGRATOR-DECISIONS-P9.md` ID-P9-1..4。本页只记为什么这样做、门与红一次的证据。 + +## 选了哪条路 + +交接 §3 W1 给了两条:(a) client 只标"GPU 写过",数据留在 server,CPU 读时惰性取回;(b) server 读完主动发 `OnBufferWriteback`。取 (a): + +| 点 | (a) 标记 + 惰性取回 | (b) server 主动推 | +|---|---|---| +| client 在读的那一刻 | 不等 | 不等 | +| 此后 CPU 读 | 一次 `resource_readback` 往返(既有路径) | 要知道"推送还在路上"并等它,另一套在飞状态 | +| 从不读的 PBO(只作 UNPACK 源或丢弃) | 零字节过线 | 整块字节白推 | +| 新机制 | 无:`GpuWritePending` 第 4 行 P5 就留好了,只是没接线 | 新的排序与等待 | + +server 侧不用驱动的 PBO 读,而是读紧排像素后走 applier 的 buffer 写入口(契约 §1 server):Espryt 的 buffer 有两份存储(GL buffer 与 R-11 暂存影子),驱动写只到前者,后续从暂存影子排空的范围会把旧字节盖回;Magma 的 `ReadPixels` 本来就没有 pack 路径。走写入口两后端同形、零新后端代码。 + +## 顺带补上的洞 + +UNPACK 源读取(`glTexSubImage*` 等以 `GL_PIXEL_UNPACK_BUFFER` 为源,`GL_Texture.cpp` 8 处)直接读 client 影子、从不调 `SyncGpuWrites`。monolith 里 PBO 读完就回写影子所以无事;split 下读进 pack buffer 后立刻当 UNPACK 源上传(GPU→GPU 的常见写法)会上传旧字节。现在 split 下先同步。同一个洞以前对"着色器写过的 SSBO 当 UNPACK 源"也存在,一并关掉(monolith 不动)。 + +## 门与红一次 + +| 门 | 绿 | 红一次(怎么变红、哪条断言) | +|---|---|---| +| `PackBufferReadbackScenario`(6 条对等用例 × 单进程/Espryt split·spawn·tcp/Magma split·spawn·tcp) | 全绿 | 去掉 unpack 同步:`APackBufferReadFeedsAnUploadFromTheSameBuffer` 在 split 臂红(见下) | +| 计数门 `APackBufferReadPostsNoReplyAndMarksTheBuffer`(split 臂) | 回复槽记录增量 0,标记增量 = 读次数 | `MOBILEGL_IPC_PBO_READBACK_SYNC=1`:两条计数断言都红(见下) | +| F1 `RemoteClientControls.APackReadOwedWhenTheDeviceIsLostNeverReadsBackAsTheOldBytes` | 以 `Fatal{ReadbackDeclined, "buffer-writeback"}` 结束 | 去掉 `AwaitBufferWriteback` 的 device-lost 分支:abort 名为 `UnimplementedWritebackWait`,期望的族名找不到 → 红。(设 `MOBILEGL_IPC_PBO_READBACK_SYNC=1` 跑它**不红**:单元子进程不走 `ConfigLoader`,环境旋钮不生效;用例里的 `packReads != 1 → exit 151` 只是前提守卫。) | +| 既有 `BandedReadbackScenario` / `PixelStoreSweepScenario`(含 PBO 用例) | 全绿 | — | +| G1 | 增 0 减 0,`.text` 0xa52203 | — | +| verify 构建(`MOBILEGL_PIPE_VERIFY`)unit / integration-verify / integration-verify-split | 2626 / 1202 / 1136 全绿 | — | +| retrace(主机,39 例 × 两后端 × monolith/split/spawn,236 项) | 230 过;`iris-iterationrp-in-world` 6 项在**所有臂含 monolith**上挂于宿主 Mesa llvmpipe 的 JIT(`LLVM ERROR: Cannot select: intrinsic %llvm.x86.vcvtps2ph.256`),环境问题,与本包无关 | — | + +红一次的原始输出:`~/w7/logs/p9/redonce.log`(WSL),摘录: + +``` +RED (a) MOBILEGL_IPC_PBO_READBACK_SYNC=1, DirectGLES.Split + DirectVulkan.Split.Pbo: + PackBufferReadbackScenario.cpp:440 reply-slot records: 65, expected 0 + PackBufferReadbackScenario.cpp:444 pack-buffer marks: 0, expected 5 +RED (b2) device-lost arm removed: + MGPipe: Fatal{UnimplementedWritebackWait} - ... buffer 1 still has an outstanding GPU write ... +RED (c) unpack sync removed, DirectGLES.{Split,Spawn,Tcp} + DirectVulkan.{Split,Spawn,Tcp}.Pbo: + 493 texels of the uploaded copy differ from the pattern, first at (0,0) (6/6 arms) +``` + +两处临时改动从备份拷回、`git diff --stat` 只剩本包的改动、重编通过后再跑门。