`notes/p7/f1-caps-placeholder.md`, per CONTRACT-P7 §9's "one red-once per package recorded in notes/p7/". It carries the call chain from `MobileGL::Initialize` down to `CapsMirror::ServerConsumes(0x400)`, the three corrections to the brief's reading (it is not a race; P6.5's fix already covers inproc; a bounded wait alone deadlocks), the three red-once arms, and the two things still owed to the integrator. The arm worth reading twice is A. On lavapipe the BASE build carries the R-8 line on every transport, with and without the delay knob, and OpenRA still scores ssim 1.000000 / mismatch 0. So the eleven dropped resource_creates cost nothing visible there, F1's red-once is the LOG LINE and the new Fatal rather than an SSIM, and this package must not be read as having explained the device's 0.976494 / 0.988998. That still needs the phone.
8.4 KiB
F1 — 占位 CapsMirror 回答「server 消费这个族吗」,并丢掉整整一个族的记录(P7 wave 2)
代码:
MobileGL/Init.cpp、MobileGL/MG_Backend/Init.cpp、MobileGL/MG_Remote/Client/CapsMirror.{h,cpp}、MobileGL/MG_Remote/Client/ClientSession.{h,cpp}、MobileGL/ConfigLoader.cpp、MobileGL/Config.h、MobileGL/MG_Remote/Server/ServerSession.cpp。 用例:MG_Test/Wire/RemoteClientTest.cpp的CapsMirrorTest.*(四条新的)。 Fatal 族:CapsBeforeFirstSnapshot(MG_Remote/FatalFamilies.def,投影ProtocolCorruption)。
0 发射者:不是竞态,是两条语句的顺序
设备日志里那行 R-8 警告
MG_Remote client: the server does not consume MGPipe subsystem 0x400 - this family emits
NOTHING and the legacy pull path runs for it (R-8). callMask=0x0, caps generation 0
的来源是一条确定的调用链,不是「主机上第一份 CapsSnapshot 赢了竞态、手机上输了」:
MobileGL::Initialize() Init.cpp:118 MG_ConfigLoader::Init() → "Config loaded"
└ MG_State::Init() Init.cpp:124
└ GLState::GLContext::GLContext() MG_State/GLState/Core.cpp:1291
└ TextureState::TextureState() MG_State/GLState/TextureState/TextureState.cpp:62-71
└ ×11(每个 TextureTarget 一个默认纹理,GL 3.3 core 3.8)
TextureObjectBase::TextureObjectBase()
MG_State/GLState/TextureState/TextureObject.cpp:168-169
├ MGPipeMintTextureHandle(*this) ← 无条件,槽位照拿
└ MGPipeEmitTextureResourceCreate(*this) ← 被丢弃
└ FamilyIsLive → P4aFamilyHasItsConsumer
MG_Impl/Pipe/PipeFill.cpp:1607
└ CapsMirror::ServerConsumes(0x400)
MG_Remote/Client/CapsMirror.cpp:137(修改前)
└ MG_Backend::Init() Init.cpp:126 ← 会话在这里才开始握手
MG_State::Init() 在 MG_Backend::Init() 之前,而启动会话的是后者。所以每一次 split 启动,
这 11 个默认纹理的 resource_create 都在 callMask=0、generation 0 上被判成「server 不消费」,
一条 WARN 之后再无痕迹。
主机上同样发生。 lavapipe 上重跑 OpenRA / DirectVulkan / inproc(rt-before-inproc.client.log:6)
与 DirectGLES 集成用例(redA-gles-inproc.client.log:6)都逐字带这一行,位置也一样(Config loaded 的下一行)。
计划 §3 与简报里「主机上第一份快照赢了竞态」的读法不成立:这从来不是竞态,两臂都输,
只是主机上的 OpenRA 仍然 ssim 1.000000 / mismatch 0。
另外两条与简报不同的事实:
- P6.5 的「第一份真 CapsSnapshot 落地前不算会话启动」已经覆盖 inproc。它在
ClientSession::FinishStartup第 7 步(ClientSession.cpp:1284-1311),而FinishStartup是 inproc / spawn / unix / tcp 四条路径共用的唯一漏斗。所以计划里的选项 (b)「把 P6.5 修复推广到 inproc」 已经是树上的现状,它修不了 F1:发射发生在会话还没开始的时候。 - 选项 (a)「第一次
Consumes()有界等待到 generation ≥ 1」单独用会死锁:MG_State::Init()执行时没有任何握手在飞,将来那次握手是同一个线程稍后去做的。等待只会烧掉预算然后 Fatal。
1 修在哪个缝上
顺序 + 规则,两件事各修一半。
- 顺序(真正的修复):
MobileGL::Initialize()在MG_State::Init()之前通过MG_Backend::InitSplitRolesBeforeState()把 split 两角色带起来(MobileGL/Init.cpp)。MG_Backend::Init()的 split 臂整体抽成InitSplitBackend(),两个调用点共用一份; monolith 臂原地不动(它必须留在MG_State::Init()之后,挪它会动 pull.text)。 整段在#if MOBILEGL_BUILD_DISAGGREGATED里,pull 构建里连语句都不存在。 - 规则(让回归变响):
CapsMirror::ServerConsumes()不再从占位镜像回答。RequireFirstSnapshot()先排除两种「问错了对象」的情形——server 臂(MGPipeServerArm(): apply 线程与 spawn server 进程)和「本进程的配置根本没要过 split 传输」 (MG_Config::SplitTransportRequestedByConfig,由MG_ConfigLoader::InitTransport()置位)—— 然后做一次有界等待,只有在别的线程确实在握手时才真的等 (ClientSession的BringUpScope+PublishedCapsGeneration()),否则立刻Fatal{CapsBeforeFirstSnapshot, "<族名>"}。
没有做的两件事,按简报的禁令:没有把警告调轻,没有给 callMask 一个全 1 的默认值。
读取型访问器(Renderer() / Dynamic() / Formats() / ApiVersion() / Backend())的 P5 裁定
原样保留——LogBackendInfo() 在 MG_Backend::Init() 里读 GetRendererInfo(),在那里 abort
不是更严格的镜像而是起不来的进程。改的只有那个决定。
2 red-once
三组,都在 lavapipe / Arch WSL 上,build-split(Release、clang、DISAGGREGATED+INPROC+PIPE_PUSH=ON)。
| # | 内容 | 结果 |
|---|---|---|
| A | 基线行为(把 Init.cpp、CapsMirror.{h,cpp}、ClientSession.cpp 退回 HEAD) |
redA-gles-inproc.client.log:6 与 rt-before-{inproc,inproc-delay200,spawn-delay200}.client.log:6 都带 R-8 那一行;OpenRA 仍 ssim 1.000000 / mismatch 0 |
| B | 只退回顺序修复(git checkout HEAD -- MobileGL/Init.cpp,保留新门) |
进程在 Config loaded 的下一行死于 Fatal{CapsBeforeFirstSnapshot, "TextureResources"}(redB2.client.log:6)——门是承重的 |
| C | 修复后 | R-8 那一行消失,CapsMirror generation 1 adopted 变成 Config loaded 之后的第一条 MG_Remote 行;OpenRA inproc / inproc+延迟 / inproc lockstep / spawn+延迟 / monolith 全部 ssim 1.000000、mismatch 0、Fatal{ 计数 0 |
A 组是这个包最重要的一条记录:主机上 OpenRA 的 ssim 与那 11 条丢掉的 resource_create
无关——丢掉它们,lavapipe 上的 OpenRA 依然 1.000000。所以 F1 是一个真缺陷、现在关掉了,
但它不能被 lavapipe 的图像差异证明,也就不能据此断言它就是真机 0.976494 / 0.988998 的原因。
真机验证仍然欠着(见 §4)。
3 MOBILEGL_TEST_DELAY_FIRST_CAPS_MS
ServerSession::Accept 第 6 步之前的一次性延迟(ServerSession.cpp),只在
MOBILEGL_BUILD_DISAGGREGATED 下存在,不设就完全不动;只延迟第一次发布,R-12 的重发不受影响。
它证明的东西分两臂:
- spawn / unix: / tcp://:发布在另一个进程里,所以这是一次货真价实的「快照迟到」。
实测
MOBILEGL_TEST_DELAY_FIRST_CAPS_MS=200的 spawn 臂,server 日志里有那行 WARN (rt-after-spawn-delay200.server.log:4,进程名libMobileGLServ),client 仍然在Config loaded之后第一条就CapsMirror generation 1 adopted——P6.5 第 7 步的等待在 spawn 上成立。 - inproc:
Accept就在ClientSession::Start里、同一个线程上,所以延迟只是让Start变慢。 这本身就是结论:inproc 下第一份快照相对会话启动不可能迟到,因此客户端能读到占位镜像的窗口 只有一个,就是Start被调用之前那一段——F1 的缺陷正是它,而它只能靠顺序关掉。
4 欠集成者的两件事
- 真机复跑:Redmi 2f7cbe2e / Adreno 830 / DirectVulkan /
--use-pbuffer上的 OpenRA, inproc(run-ahead ARMED 与MOBILEGL_IPC_RUN_AHEAD=0两臂)。预期是 R-8 那一行消失; ssim 是否从 0.976494 / 0.988998 回到 1.0 本包不预测——主机上这两者无关(§2 A 组)。 如果真机 ssim 不动,0.976494 / 0.988998 的病因仍未定位,wave 1 的 E4–E6 还得走。 - 一条裁定:
MG_Config::SplitTransportRequestedByConfig是新的「本进程配置过 split」闩。 它现在是门的武装条件;如果以后有哪条产品路径不经MG_ConfigLoader::Init()就手设MG_Config::Transport并建前端对象,那条路径会静默回到占位回答。树上只有单测这么做 (ServerLoopTest的main,已在那里写明原因)。