10 KiB
P9:反向通道
2026-09-29。依据 docs/Disaggregated/notes/p9/(交接 HANDOFF-P9.md、裁定 INTEGRATOR-DECISIONS-P9.md)。本文只记实现所需的协议与行为;验证结果以 notes 里的运行证据为准。
范围
server → client 的结果回传在 split 下不阻塞、不丢、不乱序。本阶段落地:PACK-PBO 回读不等回复(§1);server 日志按级别转发、不阻塞(§2);OnGlError 的观察时点写成契约(§3);两条故障注入(§4);纹理重铸不需要拉取(§5)。事件量的批处理 / 收窄没有实测需求,不做(ID-P9-7)。路线图原文里已由前序阶段做完的(异步 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 暂存影子并排队范围,MagmaWriteWireBuffer与在飞读取排序;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 写<base>.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=<n>让 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 例)。
5. 纹理重铸(W2)
- 没有纹理拉取。重铸(为 image 绑定把纹理升级成可绑定存储)是 server 本地路径,反向通道不含任何"向 client 要纹素"的请求:
- Espryt(
RequireImageBindableStorageByHandle):只读回驱动上确实存在且尺寸相符的层(NativeTextureLevelHasExtent),pending 盒用StagedTextureStore合并;驱动没有的层——store 覆盖就重放 store,否则跳过(GL 里其内容本就未定义);驱动拒绝读回而 store 覆盖该层时退回 store(记一次remint-readback-fallback),两者都没有才Fatal;已 immutable 且无需加宽的核心格式原样保留。 - Magma:重建带
STORAGE用途的 VkImage,PreserveTextureContentsOnRecreateGPU→GPU 拷贝。
- Espryt(
TextureRemintPulls(trp=)在 server 路径计数(重铸了已持有的存储时;保留路径不计)。MGPipeCallbacks删去OnTexturePullRequest;op 50ResourceSubDataComplete留作退役行(解码仍拒收),protocol.fbs不变。两处Fatal{UnmigratedEmulation, "texture-remint-pull"}删除(EveryUnmigratedEmulationIsNamedOnce5 → 4)。- 测试旋钮
MGITEST_ESPRYT_FORCE_REMINT_READBACK_FAILURE(server 读)强制读回失败,驱动 fallback 用例。 - 门:
TextureRemintPullScenario(14 例)× 两后端 × monolith/Split/Spawn/Tcp,外加 Espryt Split/Spawn 的 4 条强制失败条目;monolith Espryt 上 7 个 GPU 写过的用例按名跳过,指向 dev 缺陷(ID-P9-8)。
不变量
- G1:pull 构建符号增 0 减 0、
.text不变(新代码全在MOBILEGL_BUILD_DISAGGREGATED下;GL_Texture.cpp的宏在 pull 构建里为空)。 - 目录只追加:
MGP_CALL_LIST_DOCUMENTED_COUNT81 → 83,kMGPipeVerifiedPayloadCount82 → 83,stamp 表 32 → 34 行。 - 回调 9 → 7(
OnLog、OnTexturePullRequest删除;剩OnGlError、OnGpuWritten、OnBufferWriteback、OnTextureWriteback、OnMipLevelsGenerated、OnSurfaceChanged、OnCapsInvalidated,其中后三个仍无生产者,不在本阶段)。 - 控制协议修订号不变(3);
protocol.fbs未改。