[Docs] (Disaggregated): P5f f0 - the eight-way read-only census reports

Statics, reverse channel, Magma cross-role sites, EGL control plane,
registry rekey, build/gate mechanics, the 15 BARRIER_PULLED fields'
carrier rulings, and the dual-block mechanism's design recon.
This commit is contained in:
2026-09-19 09:08:56 -04:00
parent 8b68b92c76
commit e75e00cb7a
8 changed files with 2337 additions and 0 deletions
@@ -0,0 +1,428 @@
# P5f / f0 — f1 双块机制设计侦察(只读普查)
> 只读普查产物,未修改任何代码、未构建、未跑测试。所有 `file:line` 以
> `feat/disaggregated @ 8b68b92c`(代码头 `25fba0d5`)为准。
> 姊妹篇:`f0-env.md`(旋钮/车道环境一览)、`f0-statics.md`、`f0-magma.md` 等。
> 本文聚焦 `gPipeInputs` 的双块拆分(P5F-WIRE-COMPLETENESS.md §4)的设计侦察,
> 回答任务单 (1)–(7)。
---
## 1 `gPipeInputs` 的声明与全树引用点分类
### 1.1 声明
`MobileGL/MG_Backend/MGPipe/PipeInputs.h:835`:
```cpp
inline PipeInputs& gPipeInputs = *new PipeInputs();
```
- inline 引用变量,无 .cpp 定义;**leak-at-exit**(`PipeInputs.h:818-834`):O 类成员是指向前端
对象的 `SharedPtr`,退出时析构会沿 slot allocator / applier / twin 表触发不安全的销毁链,
所以永不释放。
- 尺寸预算 `static_assert(sizeof(PipeInputs) < 20 * 1024)`(`PipeInputs.h:843`)。
- 该头只在 `MOBILEGL_PIPE_PUSH` 构建里经由 `MG_Pipe/PipeInputsSwitch.h:17-21` 进入 include
闭包(`PipeInputs.h:39-43` 的论证);`PipeInputs.cpp` 也只在此条件下编译
(`PipeInputs.cpp:11-13`)。**pull 构建从符号层面就看不见它**,双块改动若同样关在
`MOBILEGL_BUILD_DISAGGREGATED` 里则对 G1 零扰动。
### 1.2 引用点全清单(非测试代码),按角色/线程分类
**写者 — client 角色(GL 线程,`MG_Impl`)**:
| 位置 | 写什么 |
|---|---|
| `MG_Impl/Pipe/PipeFill.cpp:3348`(`MGPipeValidateForVerb`) | 绑定 `PipeInputs& inputs = gPipeInputs;`;`:3385` SetIdentity(**每个 verb 都写**,含 run-ahead);`:3423` `++CurrentVerbSerial`;`:3437` `MGPipeServerClearVerbBoundary()`;`:3439` SetVerb;`:3746-3763` step 4 残余填充 `CopyField` + `FilledGen[i] = CurrentVerbSerial`(sticky 一次性盖 1,`:3753`) |
| `PipeFill.cpp:2204-2230`(`MGPipeLeaveVerb`) | `:2225` 再 bump serial(无 verb 时使旧戳过期);`:2228` clear;`:2230` SetVerb(kVerbCount) |
| `PipeFill.cpp:702-724`(`MGPipeNoteFrontendMutation`) | 单字段刷新(P3a D-H2 类:后端在自己 verb 内改了前端对象后的回填) |
| `PipeFill.cpp:1217`(`MGPipeReleaseResidualFillPins`) | 释放四个 O 类 SharedPtr |
| `PipeFill.cpp:565-566, 627, 653, 663, 679`(verify 臂) | `g_snapshot` / `g_readScratch` 与 `gPipeInputs` 的入口比较、compare-at-read hook(`MOBILEGL_PIPE_VERIFY` 构建限定) |
**写者 — server 角色(apply 线程 `mgl-srv-apply`)**:
| 位置 | 写什么 |
|---|---|
| `MG_Backend/MGPipe/PipeInputs.cpp:265-305`(`MGPipeServerStampVerbBoundary`) | bump serial、SetVerb、按 `AnswerableMaskForVerb` 盖 RECORD_SUPPLIED/APPLIER_DERIVED、**清零** BARRIER_PULLED/FATAL 的戳(`:300-303`)、置 `m_serverStampedVerb`(`:304`)。调用点:`MG_Remote/Server/PipeApplier.cpp:1255-1258`(仅 Clear/DrawVbo/ReadPixels/Blit 四个 verb 形 op) |
| `PipeInputs.cpp:307`(`MGPipeServerClearVerbBoundary`) | 清 `m_serverStampedVerb`。**双角色都调它**:server 侧 `PipeApplier::LeaveApplier`(`PipeApplier.cpp:1261`),client 侧 `PipeFill.cpp:3437` 与 `:2228` |
| `MG_Pipe/PipeApply.cpp`(经 `MGPipeApplyAccess` 门,`PipeInputs.h:732-738`) | `:1524-1534` BindRenderState、`:1552-1557` SetDynamicState、`:1561` SetPixelPackState、`:1573` SetContextValues、`:1584-1638` SetPatchState、`:1643-1690` SetVertexAttribDefaults;`:1724` 是读(trip wire 比较)。**故意不盖戳**(`PipeInputs.h:735-737`) |
**读者 — server 角色(apply 线程)**:
- **后端主通道**:`MG_Pipe/PipeInputsSwitch.h:19-21` 的 `MGB_CTX` 宏 = `&gPipeInputs`。
全树 `MGB_CTX` 共 **379 处 / 24 文件**;生产后端:DirectGLES.cpp 103、Managers.cpp 24、
MultiDraw.cpp 7、Utils.cpp 2、DirectVulkan.cpp 48、VulkanRenderer.cpp 133、
UniformManager.cpp 23 等。inproc 下这些读全部发生在 `PipeApplier::ApplyOne`
(`PipeApplier.cpp:1159-1243`)内部,即 apply 线程;monolith 下在 GL 线程。
- 直读(不经宏):`MG_Backend/DirectGLES/DirectGLES.cpp:267`(`gPipeInputs.CurrentVerb()`);
`PipeApplier.cpp:402`(GetPixelStoreParameters)、`:505`(GetMaxTouchedTextureUnit)、
`:858-859`(GetTextureObject)。
- 七个 F 类 sticky forward 的函数体在 `PipeFill.cpp:2131-2186`,**逻辑上被 server 调用、
物理上在 apply 线程执行**(经后端调用进入),体内 `MGP_STICKY_FORWARD_PULL` 调
`MGPipeStickyForwardPull`,后者读 `gPipeInputs.ServerStampedVerb()/CurrentVerb()`
(`PipeInputs.cpp:376-377`)。
**读者 — client 角色(GL 线程)**:
- `PipeFill.cpp:3016`(`VertexAttribDefaultsOf`,emitter 自查)、`:629`(Fatal 消息取名);
verify hook 的 `&self == &gPipeInputs` 判等(`:663`)。
- `EmitTables.cpp:1200` 的注释明确反面规则:client 侧**不得**经 MGB_CTX 问问题
("on this side of a split MGB_CTX is gPipeInputs, which is the SERVER's view")。
**测试引用**(非生产,但双块要决定它们落在哪块上):`MG_Test/Pipe/PipeInputsTest.cpp`、
`FieldOwnershipTest.cpp`(约 40 处)、`RenderStateSpansTest.cpp`、`MG_Test/Wire/
ServerLoopTest.cpp:572-586`、`PipeWireCodecTest.cpp:1366-1380`、`RemoteClientTest.cpp:
2053-2364`(角色守卫死亡测试)。测试均跑 monolith/单线程形态,knob 关闭时行为必须逐字节不变。
**纯注释引用**(无代码语义):`PipeMutation.h:223,248`、`PipeApply.h:21,802,1086`、
`ClientSession.h:25,150,191-199`、`WireTables.h:29`、`CapsMirror.cpp:37`、
`Init.cpp:80`、`PipeStats.h:172`、`SlotAllocator.cpp:428`、`EmitTables.cpp:256,812,1394`、
`PipeWireCodec.cpp:2257`、`Config.h:523`、`ConfigLoader.cpp:386`。
**今天为何能工作**:两角色同进程同对象,verb barrier 保证 {GL 线程, apply 线程} 同一时刻至多
一个可运行(`PipeInputs.h:904-908` 引 CONTRACT-P5 表 3 的裁定),所以单写者成立;client 填、
server 读,BARRIER_PULLED 就是"读对方留在同一块里的残余"。
**P5f 处置建议**:写侧/读侧的代码边界恰好是文件边界 —— **所有 client 写集中在
`MG_Impl/Pipe/PipeFill.cpp`,所有 server 写在 `MG_Backend/MGPipe/PipeInputs.cpp` +
`MG_Pipe/PipeApply.cpp`,所有后端读经 `MGB_CTX` 一个宏**。这意味着双块不需要碰 379 个后端
读点,只需要:(a) 在 `PipeInputs.h` 加第二块与选择函数;(b) 把 PipeFill.cpp 里约 10 处
`gPipeInputs` 拼写换成 client 块;(c) server 侧保持 `gPipeInputs` 拼写不变。详见 §7。
---
## 2 运行时如何知道当前角色
### 2.1 server 侧:线程身份
- `ServerLoop::OnApplyThread()`(`MG_Remote/Server/ServerLoop.h:246-252`):一次 relaxed
读 `Detail::g_applyThreadKey` + 与当前线程 key 比较;key 由 apply 线程自己在
`ApplyThreadMain` 第一行发布(`ServerLoop.cpp:337`),在与 `m_running` 清空的**同一个临界
区**内清零(`ServerLoop.cpp:519`,理由见 `:496-518` 的长注释——身份必须与 m_running 同死)。
key==0 时短路返回 false,所以 monolith/未启动/已停止都答"否"(`ServerLoop.h:248-251`)。
- 别名 `RunsAsTheServerRole() = OnApplyThread()`(`MG_Remote/Client/WireTables.cpp:87`),
WireTables 每条发射路径用它分流 monolith/split。
- 它是**全树每个角色守卫的谓词**(`ServerLoop.h:89-92` 列举:BufferObject 的 legacy-arm
守卫、PersistentMapTracker::OnServerRole、RefusePipeInputsTouchWhileApplierOwnsIt、
分配器守卫等)。
### 2.2 client 侧:会话标志 + 线程局部
- `thread_local Bool g_inBarrierWait`(`MG_Remote/Client/ClientSession.cpp:117`,由
`BarrierWaitScope` :120-122 举起)——"本线程是否在等 barrier",**必须** thread_local
(`ClientSession.cpp:1278-1284` 的论证)。
- `std::atomic<Bool> g_applyThreadInsideApplier`(`:118`),由 applier 侧的
`ScopedApplierEntry` 在整个 `ApplyOne` 期间举起(`PipeApplier.cpp:1173`;
Note* 函数在 `ClientSession.cpp:1381-1386`)。
- `ClientSessionInstance().RunAheadArmed()` / `BarrierArmed()` / `Started()`:
会话级标志,守卫链按"常量优先"顺序短路(`ClientSession.cpp:1292-1305`)。
### 2.3 P5c 角色守卫本体
`ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt`(`ClientSession.cpp:1290-1380`),
被 PipeFill 的四个写点调用:`:723`(NoteFrontendMutation)、`:2214`(LeaveVerb)、
`:3412`(ValidateForVerb phase 1)、`:3737`(ValidateForVerb/residual phase 2)。
run-ahead 臂(`:1318-1354`):除"barriered fill 且 applier 不在 applier 内"外,GL 线程触碰
即 `Fatal{RoleViolation, "gPipeInputs"}`;lockstep + BatchWaits==0 臂(`:1361-1379`)同理。
**读侧线程**:server 读全部在 apply 线程(`mgl-srv-apply`,`ServerLoop.cpp:338` 命名),
具体在 `PipeApplier::ApplyOne` 的 stamp→decode→LeaveApplier 区间内。
**填侧线程**:GL 线程(app 调 GL 入口 → `MGPipeValidateForVerb`)。
**今天为何能工作**:角色判定不靠"你在哪个进程",而靠"你在哪条线程 + 会话是否 started/armed"。
双块机制可以复用同一套谓词,但更稳的轴是**代码位置**(§1.2:填侧全在 PipeFill.cpp)而不是
线程——线程谓词在 monolith 下会把 client 和 backend 判成同一角色,恰好正确;在 inproc 下
分开,也恰好正确。两条轴等价,代码位置轴更便宜(无热路径谓词)。
**P5f 处置建议**:块选择函数内部**不需要**调 `OnApplyThread()`(它在 P5d 被压成一次 relaxed
load 但仍是每 draw 的成本,`ServerLoop.h:89-99`)。填侧站点是静态已知的(PipeFill.cpp 的
~10 处拼写),直接拼 `MGPipeClientInputs()`;读侧拼 `gPipeInputs`(server 块)。线程谓词只在
调试断言/新守卫里用。
---
## 3 poison / Fatal{UnmigratedPipeInput} / FilledGen 世代戳机制
### 3.1 戳的结构与判定
- `MGPipeFilledState { Uint64 CurrentVerbSerial; Uint64 FilledGen[63]; }`
(`MG_Pipe/generated/PipeFilled.inc:407-410`;63 = `kMGPipeInputFieldCount`,`:96`)。
- 新鲜判定 `MGPipeInputFieldIsFresh`(`PipeFilled.inc:421-426`):`gen==0` → false
("从未填过",两个分支同义);sticky → true;否则 `gen == CurrentVerbSerial`。
- **serial 从 1 起**(`PipeFill.cpp:3420-3423` 与 `PipeInputs.cpp:280-283` 两处同述):
0 保留给"从未填过",所以 server 的清零(`PipeInputs.cpp:300-303`)是真撤回而不是"碰巧旧"。
### 3.2 Fatal 形态
- 基础版:`MGPipeInputPoisonFatal`(`PipeFilled.inc:412-416`)——
`MGPipe: Fatal{UnmigratedPipeInput, "<field>@<verb>"}` + `std::abort()`;verb 取名经
`MGPipeInputPoisonFatalForVerb`(`PipeInputs.cpp:30-32`,首个 verb 前是 `"<none>"`)。
- split 四路裁定 `MGPipeInputUnfreshRead`(`PipeInputs.cpp:312-345`):
非 server-stamped → monolith 语义 Fatal;server-stamped + RECORD_SUPPLIED/APPLIER_DERIVED →
`Fatal{RoleViolation, "<field>"}`(P5c gt,`:333-341`);BARRIER_PULLED 且在本 verb 类内 →
`CountBarrierPull`;其余 → poison Fatal。
- `CountBarrierPull`(`PipeInputs.cpp:162-187`)三臂:**未设障记录 → 无条件 Fatal**
(`:163-165`,无旋钮可豁免,理由 `:122-132`——值按构造是 torn 的);设障 + strict + 未被
静态表或 escalation 承认 → `StrictBarrierPullFatal`(`:182-184`,消息尾
`[BARRIER-PULLED, MOBILEGL_IPC_STRICT_ERRORS=1, retires in <phase>]`);设障 + admitted →
每进程每 (field,verb) 一条 `Admitted{...}` MGLOG_W(`AdmittedBarrierPullOnce` :96-117,
静态位图去重)。
- 参数窄化:`MGPipeInputArgumentRead`(`PipeInputs.cpp:347-370`),今天唯一行是
GetPixelStoreParameters 的 unpack 半 → FATAL(`PipeInputs.h:439-447`)。
- sticky 七字段无 `MGP_INPUT_CHECK`(声明的例外,`PipeInputs.h:683-688`),由
`MGPipeStickyForwardPull`(`PipeInputs.cpp:372-378`)在 server-stamped verb 内手工计入
`rsp`/strict。
### 3.3 MOBILEGL_PIPE_POISON 旋钮在哪解析
**它不是运行时环境变量**:`PipeInputs.h:30-35` 的编译期派生宏——
`MOBILEGL_PIPE_PUSH && (LOG<=DEBUG || MOBILEGL_BUILD_DISAGGREGATED || MOBILEGL_PIPE_VERIFY)`。
运行时旋钮只有毒化家族的**测试**旋钮:`MOBILEGL_PIPE_POISON_OMIT=<Verb>:<Field>`
(阴性对照 B)→ `Features.PipePoisonOmit`(`Config.h:373-378` 定义、`ConfigLoader.cpp:278`
解析)→ `PipeFill.cpp:518-543` `ParsePoisonOmissionKnob` 在每次 Validate 时查对,
未知名 `BadKnob` Fatal。
**今天为何能工作**:戳是每 verb 的代际号而非位图(`PipeFilled.inc:19-23`:位图看不见
"上一 draw 填的、这一 verb 没重填"),stamp/withdraw 双写者靠 verb barrier 串行化。
**P5f 处置建议**:双块下 server 块的 BARRIER_PULLED 戳**已经被 server stamp 清零**——freshness
语义原样成立。关键缺口在**值**:knob 开时 server 块的 15 个 BARRIER_PULLED 字段没人填,
O 类 SharedPtr 字段(GetBoundVertexArray/GetProgramForDraw/GetTransformFeedbackProgram)
**没有 null 检查**(对比有检查的 `GetTextureUnitObject`,`PipeInputs.h:669-676`),直接返回
空 SharedPtr 给后端解引用 → 是**无名崩溃而不是具名 marker**。所以 f1 必须把
`CountBarrierPull` 在双块臂下改成无条件 `StrictBarrierPullFatal`(why 串填
`MOBILEGL_IPC_ROLE_SPLIT_STATE=1`,marker 语法与 `split_log_paths.py:44-46` 的
`MARKER_RE` 天然兼容)——这是"红就是清单"成立的前提,否则 O 类字段的红是段错误。
---
## 4 MOBILEGL_IPC_* 旋钮与新旋钮的最小接线
### 4.1 现有机制
- 声明:`Config.h:468-547` `struct IpcTable`(`MOBILEGL_BUILD_DISAGGREGATED` 内);
实例 `MG_Config::Ipc`(`ConfigLoader.cpp:35`)。
- 解析:`MG_ConfigLoader::InitIpc()`(`ConfigLoader.cpp:356-416`),由 `Init()` 在
`InitTransport()` 后调(`:428-429`)。env 收集是前缀制
(`IsAcceptedPrefix`,`ConfigLoader.cpp:42-44`),**任何 MOBILEGL_ 前缀自动可见,
无需登记**(`:257-259` 注释明说)。
- 工具函数:`QueryEnvFlag`(truthy:非空、非 "0"、非 "false")、`QueryEnvUint32`(带
min/max 与非法值 WARN 回落)、`QueryEnvVariable`。
- 纪律:**新旋钮必须进 Config 由集成者落地,不得在调用点现造**
(`EmitTables.h:109` 原文:调用点发明的 MOBILEGL_IPC_ 形名字是反模式;
`Config.h:463-467`:P5 只落自己包读的旋钮,后续阶段的旋钮由集成者加在这里)。
### 4.2 `MOBILEGL_IPC_ROLE_SPLIT_STATE=1` 的最小接线
1. `Config.h` IpcTable 尾部加 `Bool RoleSplitState = false;` + 注释(照 `StrictErrors`
`Config.h:534-537` 的形状:一句话说从什么升成什么)。
2. `ConfigLoader.cpp` `InitIpc()` 加 `ipc.RoleSplitState = QueryEnvFlag("MOBILEGL_IPC_ROLE_SPLIT_STATE");`,
并进 `:400-406` 的那行汇总日志。
3. 两个交互要写死:
- **monolith 下无效**:`:397` 已有 `if (Transport == Monolith) return;`,选择函数处再判一次
`Transport != Monolith`(monolith 的 backend 与 fill 同线程,分块会立刻饿死所有读)。
- **与 PipeVerify 互斥**:verify 的入口比较比的是填侧块、compare-at-read hook 以
`&self == &gPipeInputs` 判活块(`PipeFill.cpp:663`),双块下 hook 会在 apply 线程重读
活 context。照 `:388`(`if (PipeVerify) ipc.RunAhead = 0;`)的形状强制
`RoleSplitState = 0` 并留一行 WARN。
4. 消费点:`PipeInputs.h` 的选择函数(§7)。`PipeInputs.cpp` 已 include `Config.h`
(`:20`),无新依赖方向问题。
**今天为何能工作**(对"加旋钮"这件事):前缀收集 + IpcTable + InitIpc 三点一线,StrictErrors
/ Audit 两个 Bool 旋钮是逐字可照抄的样例。
---
## 5 CONTRACT-P5E §3.2 的 per-role stamp 存储:内容与归属裁定
### 5.1 内容(承诺了什么)
`MG_Remote/CONTRACT-P5E.md:224-229`(§3.2):`MGPipeServerStampVerbBoundary` 应把
`m_filled` / `m_currentVerb` / `m_serverStampedVerb` 写进 **applier 私有存储而绝不写共享块**;
client 侧的 `MGPipeServerClearVerbBoundary()`(PipeFill 的两处调用)在 transport 下删除;
`SetIdentity` 移到 `ApplyOne`、每会话一次。
### 5.2 落地状态与编号归属(任务单说"ID-120",需更正)
- §3.2 **UNLANDED**,且契约自己在 ra2 修正里点名了它(`CONTRACT-P5E.md:266-272`):
"§3.2 above, which is UNLANDED and was never marked as such",stamp 今天仍写进
`gPipeInputs` 本体。拆 stamp 在契约里被记为 **P11 项**,并附关键告诫:**只有 per-role
stamp 而不给 BARRIER_PULLED 值做版本化,比今天更坏**——会把具名 Fatal 变成静默旧值读
(`:270-272`)。
- 编号:`CURRENT_STAGE_PROGRESS.md:232` 把这一行挂为 "ID-120 / ID-135"。逐条核对后:
**ID-120 是 §8 修正 8**(`GetBufferBindingPointCount` 的 forward → FATAL 子句,
`CONTRACT-P5E.md:606-617` 标 UNLANDED、Lands with P7/P13);**§3.2 的 per-role stamp
对应 ID-135**(ra2 元数据竞态的裁定,`notes/p5e/FACTS-P5E.md:178`、ROADMAP.md:39
P11 行:"当时那次竞态在元数据标量上,修法是让守卫测事实而非断言未来(ID-135)……
契约 §3.2 的 per-role stamp 存储仍未落地")。任务单的"ID-120"应读作"ID-135(§3.2)"。
### 5.3 双块机制应不应该顺手落它
**应该,而且是 f1 的必然子集而非可选项**,理由:
1. 双块一落地,server 块**就是** applier 私有存储(client 再也摸不到它)——§3.2 第一句在
knob 开的臂上自动成立;knob 关的臂保持共享块旧语义(A/B 所需)。
2. §3.2 的另两条在双块下**变成 moot 而非要删的代码**:client 侧的
`MGPipeServerClearVerbBoundary`(`PipeFill.cpp:3437, 2228`)清的是自己块的 flag
(无人读过它,无害),不需要"删除",只需要让它作用于 client 块(见 §7 的参数化)。
3. **唯一真正要做的搬运是 `SetIdentity`**:今天 client 每个 verb 都写
`m_live`/`m_contextIdentity`(`PipeFill.cpp:3377-3385`,这是 §3.1 的具名偏离,因为
"本进程有没有活 GL context"不属于任何字段的归属类)。双块后 server 块的
`MGB_CTX_LIVE`/`MGB_CTX_IDENTITY`(`PipeInputsSwitch.h:20-21`)**永远答 false/nullptr**,
后端所有 null-context 守卫会立刻全部翻车。f1 必须在 server 侧给 server 块置一次身份
——契约 §3.2 末句"SetIdentity moves to ApplyOne, once per session"正是答案,
落点在 `PipeApplier::Attach`(`PipeApplier.cpp:1140-1149`)或首个 verb boundary。
4. 契约的告诫(§3.2 不配版本化就更坏)在双块下由 §3.3 的"CountBarrierPull 改无条件
Fatal"兑现:BARRIER_PULLED 值在 server 块里**不存在**(不是旧值),读它具名死——
恰好是契约要求的方向。
---
## 6 integration-split 车道的声明方式与双块车道形状
### 6.1 现有车道形态(样例)
- **标签纪律(ID-131)**:`ctest -L` 是**正则**,所以"必须从 integration-split 里搬出去的
条目"的标签不能含子串 `integration-split`。样例:
- `integration-magma-split`(`MG_IntegrationTest/CMakeLists.txt:2422-2450`,理由
`:2428-2433`:曾实测 `integration-split-magma` 拼法会被 `-L integration-split` 匹配中);
对应预期红 CI 步 `test.yml:1360-1381`:红 + 私有日志里必须有具名 marker,绿也是错。
- `integration-clientarrays-split`(`CMakeLists.txt:2216-2225`);CI 步
`test.yml:1405-1440` 是**三臂**:run-ahead 未武装却红 = 真回归;武装却绿 = 具名拒绝没
打响;红 + 日志带 `Fatal{UnmigratedVerb, "...+CLIENT_ARRAYS"}` = 通过。
- **strict 车道**:主要形态是**环境变量车道而非 ctest 标签**(`f0-env.md:196`:
`MOBILEGL_IPC_STRICT_ERRORS=1 ctest -L integration-split` + marker 直方图);另有少量
自带旋钮的注册条目(`StrictProgramArm.*`,`CMakeLists.txt:2327-2352`,LABELS 故意只写
`integration-split`,理由 `:2321-2326`)。
- **预期红的双态门**:`test.yml:1207-1317`——同一车道在翻转常量
(`kMGPipeP5eRunAheadReady`,grep 自 `Init.cpp`)前是"红即设计、跑 census",后是"硬绿 +
两侧棘轮"。
- **私有日志**:每条 split 条目一个 `MOBILEGL_LOG_FILE_PATH`,
`SplitLogPaths.PrivateAndDistinct`(`CMakeLists.txt:2360-2364` +
`Harness/split_log_paths.py` 的 `check` 模式 :183-188)断言私有且互不相同;
markers 模式的日志集来自 `ctest --show-only=json-v1` 而非目录猜测(:49-70,ID-119)。
- **棘轮**:`Harness/strict-expected-markers.txt`(52 行,10 对活着的
Admitted `<field>@<verb>`)+ `split_log_paths.py` 的 `markers` 模式(:116-178)——
**两侧都咬人**:出现了表里没有的对 → 红;表里的对不再出现 → 红(防"烂绿",:122-126)。
Fatal 侧在 `no-fatal` 臂下任何一对即红。
### 6.2 双块车道的具体形状
1. **标签**:`integration-dualblock-split`(不含 `integration-split` 子串,正则安全;
同样不带 `integration-gpu`,因为该标签下的车道必须能绿,`:2210-2211` 同论证)。
2. **条目**:`gtest_discover_tests` 同一批 split scenario,`TEST_PREFIX
"DirectGLES.Split.DualBlock."`——前缀仍在 `DirectGLES.Split.` 下,
`split_log_paths.py` 的 `paths()`(:19 的前缀判定)与 markers 模式继续覆盖,无需改工具。
env = split env + `MOBILEGL_IPC_ROLE_SPLIT_STATE=1` + 每条目私有
`MOBILEGL_LOG_FILE_PATH`(照 F1./Ct. 块形态,`CMakeLists.txt:2367-2404`)。
3. **CI 步**(test.yml 新增,照 :1360-1381 的双态形状):f1 落地时**预期红**——
步断言 (a) 车道非绿,(b) 私有日志带 `Fatal{UnmigratedPipeInput, "..."} [BARRIER-PULLED,`
形 marker(语法已在 `MARKER_RE`,`split_log_paths.py:44-46`),(c) 把 census 对表提交为
新的预期文件(见 §7.4)。收口包翻转一个常量(照 `kMGPipeP5eRunAheadReady` 的
grep-源码判臂法,`test.yml:1215`)后同一批条目转硬绿。
4. **阴性对照(§6 出口门,ID-122 教训:对照要证伪机制本身)**:关掉双块必须让至少一条具名
用例由绿转红。单纯"共享块恢复旧行为"是全绿的,所以对照用例必须是**为双块专门写的确定性
断言**,建议:`DualBlockScenario.TheTwoRolesHaveDistinctBlocks`——经新增 Harness peek
(照 `SplitRuntimePeek.h`/`PipeApplyPeek.h` 形态)断言两块地址不同、且 client 填一个 verb
后 server 块的 `CurrentVerbSerial` 不动。knob 开 → 绿;knob 关(`ROLE_SPLIT_STATE=0`)→
同一块 → 断言失败 → 红。确定性、无时序、不在 pre-flight 里 skip(ID-62),形状可直接进
`scripts/ci/split_negative_controls.sh` 的 `run_control`(该脚本现成的合同:基线必须绿、
红必须带被选条目自己的断言文本,`split_negative_controls.sh:60-99, 118-168`)。
---
## 7 f1 落地方案
### 7.1 块的分区形态(最小改动面)
`PipeInputs.h:835` 一带(`MOBILEGL_BUILD_DISAGGREGATED` 内,保持 pull/verify 构建符号不动):
```cpp
// server 角色的块:后端经 MGB_CTX 读的、applier 写的、stamp 落的那一块。
// gPipeInputs 的拼写不变,379 个 MGB_CTX 读点与 PipeApply.cpp 的写点零改动。
inline PipeInputs& gPipeInputsServerBlock = *new PipeInputs();
// client 角色的块:MG_Impl 的残余填充写的那一块。
inline PipeInputs& gPipeInputsClientBlock = *new PipeInputs();
inline PipeInputs& gPipeInputs = gPipeInputsServerBlock; // 读侧拼写,向后兼容
inline Bool MGPipeRoleSplitActive() {
return MG_Config::Ipc.RoleSplitState &&
MG_Config::Transport != MG_Config::TransportMode::Monolith;
}
// 填侧唯一新拼写。knob 关或 monolith:同一块,行为逐字节不变。
inline PipeInputs& MGPipeClientInputs() {
return MGPipeRoleSplitActive() ? gPipeInputsClientBlock : gPipeInputs;
}
```
- 两块都 `*new`(leak-at-exit,`PipeInputs.h:818-834` 的论证原样适用,每条目多一块几 KB)。
- `MGPipeRoleSplitActive` 是进程寿命内常量;若要省热路径读,可在首次调用处 latch
(照 `ArmVerify` 的 re-arm 形态,`PipeFill.cpp:598-624`),但 Config 读本身只是两次
全局 load,填侧每 verb 一次,可接受。
### 7.2 拼写替换清单(client 侧,全在 `MG_Impl/Pipe/PipeFill.cpp`)
`gPipeInputs` → `MGPipeClientInputs()`:`:627`(ReportDivergence 的 serial/verb 取名)、
`:653`(SnapshotFromGLContext 的 SetVerb)、`:703`(NoteFrontendMutation)、`:1217`
(ReleaseResidualFillPins)、`:2205`(LeaveVerb)、`:3016`(VertexAttribDefaultsOf 读)、
`:3348`(ValidateForVerb)。
**例外留着不动**:`:663` 的 `&self == &gPipeInputs` 判等——hook 只该钉在**读侧块**上
(server 块),knob 开时它与 verify 互斥(§4.2),knob 关时两块同物,两种臂都正确。
### 7.3 server 侧需要的三处配合
1. **`MGPipeServerClearVerbBoundary` 参数化**(`PipeInputs.cpp:307`):今天写死 gPipeInputs。
拆成 `MGPipeServerClearVerbBoundaryFor(PipeInputs&)`(或直接让两个调用角色各调各的):
applier 的 `LeaveApplier`(`PipeApplier.cpp:1261`)清 server 块;client 的两处
(`PipeFill.cpp:3437, 2228`)清 client 块。knob 关时同一块,语义不变。
2. **server 块身份**:`PipeApplier::Attach` 或首个 `StampVerbBoundary` 处给 server 块
`SetIdentity`/置 `m_live`(§5.3.3),否则 `MGB_CTX_LIVE` 恒 false。
3. **`CountBarrierPull` 的双块臂**(`PipeInputs.cpp:162-187`):`MGPipeRoleSplitActive()` 时
无条件 `StrictBarrierPullFatal(field, verb, "MOBILEGL_IPC_ROLE_SPLIT_STATE=1")`——
这是把 §3.3 的"无名崩溃"变成"具名红"的一行,也是计划 §4 "立刻变成 Fatal" 的机制落点。
`rsp` 计数语义不变(knob 开时 BARRIER_PULLED 读都死了,rsp 恒 0,与出口门 3 的
"rsp 逐帧读零"自洽)。
### 7.4 "红就是清单":红怎么收成清单
1. f1 落地当天跑双块车道,每条目在自己的私有日志里留下
`Fatal{UnmigratedPipeInput, "<field>@<verb>"} [BARRIER-PULLED, MOBILEGL_IPC_ROLE_SPLIT_STATE=1, retires in <phase>]`。
2. `split_log_paths.py markers` 已能解析并按条目归类 Fatal 对(:83-113, :133-136)。
需要一个小扩展:一个 `expect-fatal` 臂,把 **Fatal 集合**与提交的预期文件做与
`strict-expected-markers.txt` 完全同构的双向棘轮(出现新对 → 红;旧对消失 → 红)。
新文件建议 `Harness/dualblock-expected-fatals.txt`,首版内容由 census 生成,
**不由手写**(strict 文件 `:15-20` 的刷新规程照抄)。
3. 之后每个 f 包(fm/fc/fs/fr/fv)退掉自己名下字段的载体,删对应行;文件空 + 车道绿 =
收口(对照 P5f §5 的包图与 §6 出口门 1/2)。strict 车道的
`strict-expected-markers.txt` 棘轮继续独立存在——双块车道管"值不在对面",
strict 车道管"读没被承认",两者是同一债务的两个探针。
### 7.5 守卫与测试的兼容
- `RefusePipeInputsTouchWhileApplierOwnsIt`(`ClientSession.cpp:1290-1380`)保持原样:
它守的是"GL 线程碰 server 拥有的块",knob 开时填侧不再碰 server 块,守卫自然不再触发;
knob 关时行为不变。RemoteClientTest 的死亡测试(:2071, :2295, :2347)不受影响。
- 单测/集成 GPU 车道 knob 默认关 → 单块,逐字节不变;FieldOwnershipTest/PipeInputsTest
直读 `gPipeInputs`(server 块拼写),monolith 下同物。
- G1:新增符号全部关在 `MOBILEGL_BUILD_DISAGGREGATED` 内(该头本就只在 PUSH 闭包,
`PipeInputs.h:39-43`),pull 构建符号集不动。
---
## 8 风险与待确认
1. **O 类 SharedPtr 字段无 null 检查**(§3.3)——f1 的第一批红若不走 §7.3.3 的 Fatal 化,
会以段错误形态出现,census 收集不到 marker。**这是 f1 落地顺序里必须先落的一行。**
2. **`m_live`/`m_contextIdentity` 不属于任何字段归属类**(`PipeFill.cpp:3377-3385` 的具名
偏离):双块后 server 块的身份由 §7.3.2 置一次,但**失语境**(context destroy)时谁把
server 块的 `m_live` 打回 false——契约 §3.2 没说,f1 设计时要点名(候选:applier_reset
控制记录,P5c ct 已让它过线)。
3. **Magma 臂**:Magma 不发布 `kCapRunAheadApply`、跑 lockstep,九个 BARRIER_PULLED 字段
全是它的读者。双块车道建议**双后端各注册一份**(照 `NamedBlit` 的双后端循环,
`CMakeLists.txt:2423-2449`),Magma 侧的红就是 fm 包的清单——这与 §6 出口门 4
"Magma 与 Espryt 跑同一套判据"直接对齐。
4. **verify 构建**:`MOBILEGL_PIPE_VERIFY` 强制 lockstep + 全填(`ConfigLoader.cpp:377, 388`),
与双块互斥的建议已写进 §4.2;若未来要 verify 双块臂,compare-at-read hook 的块归属要重做。
5. `MG_Test` 里直接调 `MGPipeServerStampVerbBoundary` 的用例(PipeWireCodecTest :1366-1380、
FieldOwnershipTest 约 15 处、RemoteClientTest :2393-2413)stamp 的都是 server 块拼写,
knob 关时不变;若有人想在单测里开 knob,需要新 peek——目前无此需求,标为**未知/待 f1 定**。
+228
View File
@@ -0,0 +1,228 @@
# f0-egl — P5f §2.3 EGL / surface 控制面普查(只读)
> 基线 `feat/disaggregated @ 8b68b92c`(代码头 `25fba0d5`)。路径在 `MobileGL/` 下。
> 本文件只读普查,不改任何代码。对应 P5c 审计 G4 行(`docs/Disaggregated/ROADMAP.md:71`),
> 原计划记给 P6(`MobileGL/MG_Remote/CONTRACT-P5C.md:537`),P5f 计划 §2.3
> (`docs/Disaggregated/P5F-WIRE-COMPLETENESS.md:181-186`)将其收回 P5f 的包 `fc`。
---
## 1. 单槽邮箱机制(现状)
**机制本体。** 十二个 forwarder 共享同一条过线通道:
- `ServerLoop.h:220`:`using ControlWork = MobileGLResult (*)(void* user);` —— 一个**裸函数指针**
加一个 `void*`。注释(`ServerLoop.h:217-219`、`ServerLoop.cpp:784-787`)说明为何不是
`std::function`:析构路径不得分配(ID-8)。
- `ServerLoop.cpp:788-793`:每个 forwarder 用同一个无捕获 trampoline
`+[](void* user) { return static_cast<Args*>(user)->Run(); }` 和**指向调用者栈上 `Args` 的
指针**调用 `RunOnApplyThread`。
- `ServerLoop.cpp:645-707`(`RunOnApplyThread`):`m_callerMutex` 串行化多个投递者(:653);
在 `m_controlMutex` 临界区内发布 `{m_controlWork, m_controlUser, m_controlPending}` 四元组,
影子位 `m_controlPosted` 最后发(:685-694);发布后再敲门铃(:702-704,先发布后敲门是
Doorbell 的硬规则);然后阻塞在 `m_controlDone.wait`(:705),返回 `m_controlResult`(:706)。
- `ServerLoop.cpp:533-568`(`PumpControlRequest`):apply 线程在 drain 批之间取走请求、
执行 `work(user)`(:560)、回填结果并 notify(:561-567)。
- 泵点:`ApplyThreadMain` 主循环 :397-398 与退出前 :453;park 谓词 `ready`(:391-395)把
`ControlIsPending()` 列为唤醒条件之一。
- 重入臂:`RunOnApplyThread:651`——apply 线程自己投递就内联执行(拆构路径
`~BackendObject_DirectGLES` → `ReleaseEGLResources` 走这里)。
- 无线程时的行为:backend 活着而线程不在 → `Fatal{ApplyThreadNotRunning}`(:670-676);
backend 已不在 → 返回 `MOBILEGL_ERR_NOT_INITIALIZED`(:678-683)。
**邮箱字段**在 `ServerLoop.h:367-389`:`m_callerMutex` / `m_controlMutex` /
`m_controlPosted`(一位影子)/ `m_controlDone` / `m_controlWork` / `m_controlUser` /
`m_controlResult` / `m_controlPending` / `m_controlFinished`。
**今天为何能工作**:两个角色同进程、同地址空间。函数指针是双方都能执行的代码地址;
`&args`、`major`/`minor`、`&handle` 指向的栈帧由阻塞握手(:705 的 wait)保证在整个执行期间存活;
单槽够用是因为"verb barrier 同一时刻只留一个 client 线程可运行"(`ServerLoop.h:367-368`
注释),加上 `m_callerMutex` 兜底。跨进程后这三样全部失效:代码地址、栈地址、以及"阻塞等待
同进程另一个线程"本身。
**P5f 处置建议**:邮箱机制在 inproc 下整体保留(它是 apply 线程归属语义的一部分),但在其上
叠一层"帧化"接缝:每个 forwarder 的参数先收敛成一个**纯值结构**(见 §4 草案),inproc 臂把该
结构放在栈上传指针(现状等价),spawn 臂把同一结构编码进 `SurfaceOp` 帧。这样 fc 包不动
线程模型,只动载荷的形状。
## 2. 十二个 forwarder 逐个点名
声明在 `ServerLoop.h:459-475`,定义在 `ServerLoop.cpp:802-1033`。下表"载荷"一列标出
**值**(可直接序列化)/ **栈上指针**(指向调用者存储)/ **句柄值**(client 进程的 EGL 句柄,
跨进程无意义)。
| # | forwarder(定义行) | 签名 | 语义 | 载荷分类 |
|---|---|---|---|---|
| 1 | `ServerInitializeEGLDisplay`(`ServerLoop.cpp:802-816`) | `Bool (EGLDisplay dpy, EGLint* major, EGLint* minor)` | 初始化 server 后端的 EGL display,回写主/次版本 | dpy 句柄值;**major/minor 是指向 client 栈的 out 指针**(:805-806) |
| 2 | `ServerCreateEGLWindowSurface`(:818-834) | `Bool (EGLSurface surface, const WindowHandle& handle)` | 建窗口 surface;成功后 `ForgetCurrentTuple()`(:829,N-3) | surface 句柄值;**`&handle` 是指向 client 存储的指针**(:821/:832),且 `WindowHandle::Handle` 本身是 `void*`(`BackendObject.h:561-566`) |
| 3 | `ServerResizeEGLWindowSurface`(:836-850) | `Bool (EGLSurface, Uint32 w, Uint32 h)` | 重建 swapchain | 全为值。**最干净的一个** |
| 4 | `ServerCreateEGLPbufferSurface`(:852-870) | `Bool (EGLSurface, EGLint w, EGLint h)` | 建 pbuffer;成功后 `ForgetCurrentTuple()`(:865) | 全为值 |
| 5 | `ServerMakeEGLCurrent`(:872-918) | `Bool (EGLDisplay, EGLSurface draw, EGLSurface read, EGLContext ctx)` | 经 `ApplyMakeCurrent`(:269-316)三分支:NativeBind / RepeatNoOp / ClientRelease(ID-54);真绑定后重发布 caps 快照(:899-913,R-12) | 四个全是句柄值 |
| 6 | `ServerSwapEGLBuffers`(:920-933) | `Bool (EGLDisplay, EGLSurface draw)` | 直接在 server 上 present | 句柄值。**无生产调用者**(见 §5 发现 F8) |
| 7 | `ServerSetEGLSwapInterval`(:935-946) | `void (Int interval)` | 设置交换间隔;返回值被丢弃(:945) | 值 |
| 8 | `ServerReleaseEGLSurface`(:948-965) | `void (EGLSurface)` | 释放 surface;`ForgetCurrentTupleIfItNames`(:960,N-3) | 句柄值 |
| 9 | `ServerReleaseEGLResources`(:967-989) | `void ()` | **按契约阻塞**(:967-973 注释:之后 `MobileGL::Destroy()` 立即走);`ForgetCurrentTuple()`(:984) | 无参数 |
| 10 | `ServerInitCapabilities`(:991-1007) | `Bool ()` | 跑 server 端 `InitCapabilities` 并 `PublishCapsSnapshot()`(:999-1002) | 无参数 |
| 11 | `ServerInitWindowSurface`(:1009-1020) | `Bool ()` | server 端 `InitWindowSurface` | 无参数。**无任何调用者**(见 §5 发现 F8) |
| 12 | `ServerSetWindowHandle`(:1022-1033) | `void (const WindowHandle& handle)` | 把窗口句柄交给 server 后端 | **`&handle` 是指向 client 存储的指针**(:1024/:1031),内含 `void*` |
参数里的**函数指针**:每一条都是同一个 trampoline(:790-791),它承载的唯一信息是"调哪个
`Args::Run`",即**操作的身份**。参数里的**栈上 `void*`**:`&args` 永远存在(:792),其内部再
含 client 栈指针的是 #1(major/minor)、#2 与 #12(handle)。
**今天为何能工作**:同进程;阻塞握手(`RunOnApplyThread:705`)使调用者栈帧在整个 RPC 期间
存活,所以 out 指针与引用参数安全。EGL 句柄值两边通用是因为同一个驱动在同一进程里认它们。
**P5f 处置建议(共性)**:
- 函数指针 → `SurfaceOp.kind` 枚举(操作身份显式化)。
- out 指针 → 帧的 reply 字段(#1 已被 `SurfaceReply.eglMajor/eglMinor` 覆盖)。
- `WindowHandle` → `windowKind` + `nativeToken` + `width`/`height` 值字段;`Handle` 指针在
Android 上是 `ANativeWindow*`(`P6-SPAWN-PLAN.md:38`),**子进程无意义,真窗口到达是 P12**,
P5f 只在 server 侧落具名拒绝 `Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"}`
(`P6-CONTRACT-DRAFT.md:43` 已预写这个 Fatal 名)。
- EGL 句柄值 → client 铸造的 token(`DisplayToken/SurfaceToken/ContextToken`,
`P6-CONTRACT-DRAFT.md:41` 已裁定:client 铸造、从 1 起稠密、session 内不复用,server 持
token → native 映射,永远不见 client 的 EGL 值)。`protocol.fbs:189-190` 的
`display/surface: ulong` 已经就是 token 的宽度。
## 3. client 侧调用面(`BackendObject_Remote.cpp`)
九个 EGL 虚函数的路由(:196-310),核心纪律是"先转发,再跑基类"(:198-201 注释):
- `InitializeEGLDisplay`(:203-207)→ forwarder #1。
- `CreateEGLWindowSurface`(:209-225)→ 先 #12(:214,"唯一告诉 server 窗口是哪扇的途径"),
再 #2(:215),随后 `DrainPublishedEvents()`(:223)——surface-changed 事件走 SEG_EVENT。
- `ResizeEGLWindowSurface`(:227-234)→ #3 + drain(:232)。
- `CreateEGLPbufferSurface`(:236-243)→ #4 + drain(:241)。
- `MakeEGLCurrent`(:245-276)→ #5,成功后 `PumpControlPlane()` + drain + 刷新格式能力
(:265-274,codex 12:把 R-12 的"第二次到达即失效"闭合在造成它的那次 make-current 上)。
- `SwapEGLBuffers`(:278-287)→ **不转发**:present 走记录(class-B `Present` emitter),
注释明确 #6 "在 P5 没有调用者,这是刻意的"(:284-285)。
- `SetEGLSwapInterval`(:289-297)→ #7。覆写的理由:基类会调 class-C 的 `SetSwapInterval`
槽而 Fatal(:291-295 注释)。
- `ReleaseEGLSurface`(:299-302)→ #8。
- `ReleaseEGLResources`(:304-310)→ #9,按契约阻塞。
- 另有 `InitCapabilities`(:106-142)调 #10(:125);`InitWindowSurface`(:144-149)是
**纯 client no-op,不调 #11**。
**每个 forwarder 调用前的 fence**:`WaitForApplyBeforeEglForwarder`(:189-193),对除
#6 外的每一次转发先 `WaitForApplyToCatchUp`。理由(:176-187 注释、CONTRACT-P5E §2.5
`CONTRACT-P5E.md:177-180`):邮箱在 drain 批之间被泵,run-ahead 下控制请求会插进两条
在飞记录之间。client 入口侧:九个虚函数由 `MG_Impl/EGLImpl/EGLImpl.cpp` 的 EGL 入口调
(如 eglMakeCurrent → :284、eglSwapBuffers → :178、eglSwapInterval → :448、eglTerminate 路径
→ :328)。
**P5f 处置建议**:这个 fence 在 spawn 下**仍然是必要的**——socket 控制通道与 SEG_CMD 是两条
通道,控制帧一样可以超过在飞记录。fc 包的帧化必须保留"发帧前先等 `appliedSeq` 追上
`LastPublishedSeq`"这一步;它不属于 wire 格式而属于发送纪律,应写进 fc 的契约行。
## 4. schema 对照与 wire 帧设计草案
### 4.1 现有 schema(`Protocol/protocol.fbs`,已定但无人使用)
- `SurfaceOpKind`(:166-175):`None / InitializeDisplay / CreateWindowSurface /
CreatePbufferSurface / ResizeWindowSurface / ReleaseSurface / MakeCurrent / ReleaseCurrent`,
共 7 个有效枚举。
- `WindowKind`(:177-184):`None / AndroidNativeWindow / X11 / Win32Hwnd / Surfaceless /
Pbuffer`。
- `SurfaceOp`(:186-196):`seq, kind, display, surface, windowKind, nativeToken, width,
height, swapInterval`。`nativeToken` 注释自述"Android transfers the window out of band"。
- `SurfaceReply`(:198-204):`seq, ok, eglMajor, eglMinor, defaultFb`。
- 信封:`CtrlMsg` union(:273-284)已含 `SurfaceOp/SurfaceReply`;`CtrlEnvelope`(:286-288)。
### 4.2 映射表:12 forwarder → op
| forwarder | 映射 | 缺的枚举 / 字段 |
|---|---|---|
| #1 InitializeEGLDisplay | `InitializeDisplay`;reply 的 `eglMajor/eglMinor` 正是 major/minor out 参数的线形 | 无 |
| #2 CreateEGLWindowSurface | `CreateWindowSurface` + `windowKind`/`nativeToken`/`width`/`height` | 无(P12 前 server 具名拒绝 `AndroidNativeWindow`) |
| #3 ResizeEGLWindowSurface | `ResizeWindowSurface` + `width`/`height` | 无 |
| #4 CreateEGLPbufferSurface | `CreatePbufferSurface` + `width`/`height` | 无 |
| #5 MakeEGLCurrent | `MakeCurrent`;其 ClientRelease 臂(`ClassifyEglMakeCurrent`,:257-267)落 `ReleaseCurrent` | **`SurfaceOp` 缺 `readSurface` 与 `context` 两个字段**:MakeCurrent 的语义单元是 (dpy, draw, read, ctx) 四元组,schema 只有一个 `surface` 且没有 context 字段 |
| #6 SwapEGLBuffers | **不配帧**:present 走记录(`BackendObject_Remote.cpp:278-287`) | — |
| #7 SetEGLSwapInterval | 用 `swapInterval` 字段 | **缺 `SetSwapInterval` 枚举** |
| #8 ReleaseEGLSurface | `ReleaseSurface` | 无 |
| #9 ReleaseEGLResources | 无参数,阻塞 | **缺 `ReleaseResources` 枚举** |
| #10 InitCapabilities | **不配 SurfaceOp**:应答物是 `CapsSnapshot` 帧(server 已在 :999-1002 发布;`P6-CONTRACT-DRAFT.md:88-89` 同此裁定) | —(但 client 需要一个"等下一帧 CapsSnapshot"的同步点,见 4.4) |
| #11 InitWindowSurface | **不配帧**:client 侧 no-op(:144-149),无调用者 | — |
| #12 SetWindowHandle | 用 `windowKind`/`nativeToken`/`width`/`height` | **缺 `SetWindowHandle` 枚举** |
缺的三个枚举与 `P6-SPAWN-PLAN.md:76` 的预测("预期缺 `SetSwapInterval`/`ReleaseResources`/
`SetWindowHandle`")**逐字吻合,本普查确认**。9 个需映射的 forwarder = 7 个现有枚举 + 3 个新增
枚举 - 1 个复用(ReleaseCurrent 由 #5 的释放臂复用),与 `P6-CONTRACT-DRAFT.md:87-90` 的
"the remaining nine map onto the seven plus §1's additions" 一致。追加枚举必须 append-only
(schema 自述纪律在 :268-272 的 union 注释与 `CapsSnapshot` 的 deprecated 先例 :115-127)。
另外发现 **schema 缺的字段**(P6 计划未预写):
1. `SurfaceOp` 缺 `readSurface`(MakeCurrent 需要 draw≠read 的场景)。
2. `SurfaceOp` 缺 `context` token 字段(MakeCurrent/ReleaseCurrent 都要指名 ctx)。
3. `WindowKind` 与 `WindowBackend`(`BackendObject.h:551-559`:Android/X11/MetalLayer/Win32)
**不互洽**:`WindowKind` 缺 `MetalLayer`,多 `Surfaceless`/`Pbuffer`(那两个不是窗口后端)。
需要一张显式映射表,不能按整数值直译。
### 4.3 帧设计草案(逐 forwarder)
公共规则:`seq` 由 client 单调铸造,`SurfaceReply.seq` 回声;携带 `ok=false` 等价今天的
`Bool false` 或 void 丢弃结果;server 侧答不出来等价 `ServerBackendOrNull()` 为 null
(:798)时的 `MOBILEGL_ERR_NOT_INITIALIZED`。token 化按 `P6-CONTRACT-DRAFT.md:41`。
| op 帧 | 载荷 | reply | 阻塞性 |
|---|---|---|---|
| `InitializeDisplay` | `display`(token) | `ok` + `eglMajor/eglMinor` | 阻塞(今天返回 Bool) |
| `CreateWindowSurface` | `surface`(token) + `windowKind` + `nativeToken` + `width/height` | `ok` + `defaultFb`(见 4.4 的路线冲突) | 阻塞。可并入 #12(见下) |
| `ResizeWindowSurface` | `surface` + `width/height` | `ok` + `defaultFb` | 阻塞 |
| `CreatePbufferSurface` | `surface` + `width/height`(`windowKind=Pbuffer`) | `ok` | 阻塞 |
| `MakeCurrent` | `display` + `surface`(draw) + **新字段 `readSurface`** + **新字段 `context`** | `ok` | 阻塞 |
| `ReleaseCurrent` | `display`(其余 token 为 0 合法,`P6-CONTRACT-DRAFT.md:41` 已许) | `ok` | 阻塞(今天经同一个 Bool 通道) |
| `SetSwapInterval`(新枚举) | `surface` + `swapInterval` | 可无 reply | **可改 fire-and-forget**:今天虽经阻塞邮箱但结果被丢弃(:945) |
| `ReleaseSurface` | `surface` | 可无 reply | 可 fire-and-forget(:964 丢结果);但须保序 |
| `ReleaseResources`(新枚举) | 无 | `ok` | **必须阻塞**:`ServerLoop.cpp:967-973` 的契约,`Destroy()` 在它返回后立即走 |
| `SetWindowHandle`(新枚举) | `windowKind` + `nativeToken` + `width/height` | 可无 reply | 可 fire-and-forget,**但必须在后续 `CreateWindowSurface` 之前生效**——同一有序通道即保证 |
两个可合并项(供 fc 包定夺,本普查只摆出):① `CreateWindowSurface` 的 schema 字段已自带
`windowKind/nativeToken/width/height`,#12 可以并进 #2,`SetWindowHandle` 枚举只在"先给窗口
后建 surface"的时序必须拆开时才需要——今天的代码就是拆开的(:214-215),保守做法是两者都
落;② `ReleaseCurrent` 也可用 `MakeCurrent` + 全 0 token 表达,现有枚举分开更好,保留。
### 4.4 三个与 schema 相邻的副作用,必须在 fc 的契约里点名
1. **caps 重发布**:#5 的真绑定臂(:899-913)和 #10(:999-1002)都会让 server 主动推一帧
`CapsSnapshot`。跨进程后这是 server 自发帧;client 今天的吸收点(RPC 返回后
`PumpControlPlane()`,:266-272 与 :130)天然对应"reply 之后泵控制通道"。schema 无需加
字段,但 fc 应写明"MakeCurrent reply 到达后、下一帧 CapsSnapshot 到达前"的语义窗口。
2. **`SurfaceReply.defaultFb` 与 SEG_EVENT 是重复路线**:P5c 已把默认 FBO 形状做成
surface-changed 事件,client 在 create/resize reply 后 `DrainPublishedEvents()`
(:223/:232/:241)。`defaultFb` 字段(:154-160, :203)若启用是第二载体。建议 fc 二选一
并写明,不要两个都活(`CapsSnapshot` 的 deprecated 字段教训,:101-127,就是两种拼法
并存的代价)。
3. **server 死亡的具名化**:今天的 `Fatal{ApplyThreadNotRunning}`(:670-676)在 spawn 下
对应 `FatalCode::ServerCrashed`(:240 已分配,`P6-CONTRACT-DRAFT.md:44` 已点名 P6 是
第一个能产生它的阶段)。fc 的 client 侧发送路径要把"发不出去/等不到 reply"接到这个码上。
## 5. 其他发现
- **F8:两个 forwarder 没有生产调用者。** `ServerSwapEGLBuffers` 只有单测调用
(`MG_Test/Wire/ServerLoopTest.cpp:1549`、`:1568`);`ServerInitWindowSurface` 全树无调用者
(client 的 `InitWindowSurface` 是纯 no-op,`BackendObject_Remote.cpp:144-149`)。
处置建议:fc 不为它们配帧(§4.2 已按此映射);`ServerLoop.h:469`/`:474` 的声明与
实现在 spawn 世界只剩 inproc 测试价值,fc 可具名删除或标注 inproc-only。
- **F9:文档行号漂移。** `ROADMAP.md:71`(P5c 审计 G4 行)引 `ServerLoop.cpp:543-598`,
当前 HEAD 的邮箱是 `:533-568`(PumpControlRequest)+ `:645-707`(RunOnApplyThread);
`CONTRACT-P5C.md:537` 把控制面记给 P6,P5f 计划 §2.3(`P5F-WIRE-COMPLETENESS.md:181-186`)
已改记 P5f 的包 `fc`(`P5F-WIRE-COMPLETENESS.md:252`)。处置建议:fc 落地时顺手刷新
ROADMAP G4 行的行号与去向。
- **F10:`ServerLoop.h:436` 与 `ServerLoop.cpp:968` 引的 `EGLImpl.cpp` 行号**(:284/:326)
与当前 `MG_Impl/EGLImpl/EGLImpl.cpp` 基本相符(:284 是 eglMakeCurrent;ReleaseEGLResources
实际在 :328)。仅为记录,不构成缺陷。
## 6. 结论汇总
1. 十二个 forwarder 的过线载荷里,真正"不可序列化"的只有两类:**trampoline 函数指针**
(操作身份 → `SurfaceOp.kind`)和**三处 client 栈/堆指针**(#1 的 major/minor → reply 字段;
#2/#12 的 `WindowHandle*` → windowKind+nativeToken 值字段,Android 的 `ANativeWindow*`
归 P12,P5f 只落具名拒绝)。其余参数全是值或 client 句柄值(→ token)。
2. schema 缺口清点确认:**缺 3 个枚举**(SetSwapInterval / ReleaseResources /
SetWindowHandle,与 P6-SPAWN-PLAN 的预测逐字一致)+ **缺 2 个字段**(MakeCurrent 的
readSurface 与 context token)+ WindowKind/WindowBackend 需要显式映射表。
3. #6、#10、#11 不配 SurfaceOp 帧:present 走记录、InitCapabilities 走 CapsSnapshot、
InitWindowSurface 无调用者。需要帧化的是 9 个 forwarder、10 种 op(含 ReleaseCurrent)。
+309
View File
@@ -0,0 +1,309 @@
# P5f / f0 — 构建与门禁机制侦察(实现代理 cheatsheet)
> 只读普查产物。仓内引用以 `feat/disaggregated @ 8b68b92c`(代码头 `25fba0d5`)为准;
> WSL 侧文件不在仓内,引用给绝对路径。未发起任何构建。
---
## 1 WSL 侧布局:树在哪、是什么关系
所有 WSL 树都是**同一个仓的 git worktree**(不是拷贝)。主 gitdir 在 `/home/swung/mgl/MobileGL`,
`git worktree list` 在任何一棵树里都能看到全部(证据:在 `~/w7/p5e-int` 里执行
`git worktree list` 列出 pipe / p5-* / p5e-* / land 等 20 棵树)。
| 树 | 分支 @ HEAD | 角色 |
|---|---|---|
| `~/w7/pipe` | `feat/disaggregated @ 11ac3de6` | **集成树**。比 Windows HEAD 落后 105 个提交(`11ac3de6..HEAD`),P5c 开工时的位置。`docs/Disaggregated/notes/p0/BRIEF-P0.md:13`:"DO NOT edit `~/w7/pipe` or `~/w7/base` - they are read-only references" |
| `~/w7/base` | `dev @ 81b17c0b` | **性能锚点**,dev 分支,不动(`docs/Disaggregated/notes/p1/BRIEF-P1.md:3`) |
| `~/w7/land` | `dev @ 81b17c0b` | 与 base 同 commit,带 `build-android/` 目录。用途未见诸阶段文档——**未知**(从目录形态看是 dev 的 Android 构建树;P5f 用不到) |
| `~/w7/p5e-int` | `p5e-wave2-landed @ f23fbc1b` | P5e 集成树,**已过时**,见 §6.1 |
| `~/w7/p5e-<slug>` | `p5e/<slug>` | P5e 各包树(c0e/fb/fix1/gl/id/mv/pa/pg/ra/ra2/sb/tx2/vi),每棵一个包 |
| `~/w7/p5e-logs/` | — | p5e_gate.sh 的日志输出目录(每步一个 `<slug>-<step>.log`) |
每棵树的 remote 都有两个:`origin = /home/swung/mgl/MobileGL`(WSL 内部主仓),
`winrepo = /mnt/c/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL`(Windows 侧)。
**Windows 侧关系**:工作区 `MobileGL-disagg` 是 `FoldCraftLauncher` 仓里 `MobileGL` 子模块的
worktree(`MobileGL-disagg/.git` 文件指向
`C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/.git/modules/MobileGL/worktrees/MobileGL-disagg`)。
Windows 侧(在该 worktree 执行 `git remote -v`)配了 **`wslpipe = //wsl.localhost/Arch/home/swung/w7/pipe`**,
以及 `origin = github.com/MobileGL-Dev/MobileGL.git`、`hitmoe`、`fu3`。
### 1.1 代码怎么同步(非对称,两个方向机制不同)
- **Windows → WSL**:按 SHA fetch。`~/w7/notes/tools/p5e_new.sh` 的核心三行:
```bash
cd ~/w7/pipe
git fetch -q /mnt/c/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/.git/modules/MobileGL "$SHA"
git branch -f "p5e/$SLUG" "$SHA" && git worktree add ~/w7/p5e-$SLUG "p5e/$SLUG"
```
前提是**先在 Windows 侧 commit**——WSL 看到的是 gitdir 里的对象,工作区改动过不去。
p5e_new.sh 随后做 `git submodule update --init --recursive`、补拷
`3rdparty/glslang/External`(worktree 不带子模块的嵌套 External)、写
`.git/info/exclude` 加 `.p5e/`,然后直接调 `p5e_gate.sh $SLUG configure` + `build`。
- **WSL → Windows**:在 Windows worktree 里 `git fetch wslpipe <branch>`。
出处:`docs/Disaggregated/notes/p5e/FLIP-CHECKLIST.md:3-5`——"Integration tree
`~/w7/p5e-int`(branch `p5e-wave2-landed`, fetched to the Windows worktree
`MobileGL-disagg` via remote `wslpipe`, pushed to `origin/feat/disaggregated`)"。
- **LFS 纪律**(trace fixture):`git worktree add` 不 hydrate LFS,新树里 41 个 fixture 有 40 个是
130 字节指针。从 `~/w7/pipe/tools/trace_replay/fixtures` **拷贝**,并标 assume-unchanged;
**永远不在 WSL 里 `git lfs pull` / `git lfs checkout`**(额度是既有规矩,且 checkout 会把真文件
覆盖成指针)。出处:`~/w7/notes/tools/wsl_p5_gate.sh` Part 2 头注释("FIXTURES FIRST…"段)。
- `~/w7/notes/` 本身已搬家:`~/w7/notes/MOVED-TO-REPO.txt`——415 篇 .md 已入仓
`docs/Disaggregated/notes/<phase>/`(commit `2acea1b1`);留在 WSL 的只有脚本、日志、census
数据、patch、APK、nm dump(含绝对路径与设备序列号,不入仓)。
### 1.2 现成脚本(`~/w7/notes/tools/`)
| 脚本 | 作用 |
|---|---|
| `p5e_new.sh <slug> <sha>` | 按 SHA 建 `~/w7/p5e-<slug>` 包树并 configure+build(见 §1.1) |
| `p5e_gate.sh <slug> <step> [regex]` | **P5e 门禁本体**,step ∈ configure/build/unit/isplit/gpu/strict/flavours/gens/one,逐条见 §4 |
| `wsl_p5_gate.sh [quick]` | P5 时代的五段式大门(env `MGL_P5_TREE` 指定树),四 flavour 全建 + retrace 扫描;`flavours` 的 verify 臂只在它这里有(§3) |
| `wsl_p4a_gate.sh` / `wsl_p3a_gate.sh` / `wsl_p2_gate.sh` 等 | 各阶段同构老门,仅供考古 |
| `wsl_integrate.sh` / `p5_merge_*.sh` / `p5_land_*.sh` | 各阶段合并/落地脚本,阶段专用 |
WSL 工具链事实(`docs/Disaggregated/notes/p1/BRIEF-P1.md:3`):clang 22.1.6 在
`/usr/sbin/clang++`(没有 `clang++-20`);28 核;ccache 配 `CCACHE_BASEDIR=/home/swung/w7`。
---
## 2 仓内门脚本(`scripts/` 与 `scripts/ci/`)
全部入口都是 `python3 scripts/xxx.py` / `bash scripts/ci/xxx.sh`,在仓根跑。P5e gate 的 `gens`
步(`~/w7/notes/tools/p5e_gate.sh`)就是这套的最小全集:
```bash
python3 scripts/gen_pipe.py --check # G1..G7 七个生成器,产物已提交,重生成有 diff 即红
python3 scripts/gen_pipe.py --self-test # 生成器自己的阴性对照
python3 scripts/gen_pipe_field_ownership.py --check # 70 行归属表(63 字段 + 7 sticky)每行恰好一类,有洞即红
python3 scripts/gen_pipe_field_ownership.py --self-test
python3 scripts/gen_pipe_field_ownership.py --print-admitted # 打印"允许 admitted 的 <field>@<verb>",strict 棘轮的另一臂
python3 scripts/check_include_closure.py # 三个头的 include 闭包(value/artifacts/wire 三 probe)
python3 scripts/gen_pipe_dirty_surface.py --check # 脏面扫描:mutator→aggregate generation 映射
python3 scripts/gen_pipe_dirty_surface.py --self-test
python3 scripts/check_doc_citations.py --rev HEAD --strict docs/Disaggregated/*.md MobileGL/MG_Remote/CONTRACT-P5*.md
```
细节:
- `scripts/gen_pipe.py`(文件头 docstring):读 `PipeCalls.def / PipeFields.def / Coverage.def /
FillPoints.def` + `scripts/data/backend_read_inventory.md`,写 `MobileGL/MG_Pipe/generated/*.inc`,
产物是**提交的**,CI 重生成比对。无参 = 写文件;`--check` = 只校验;`--self-test` = 阴性对照。
- `scripts/gen_pipe_field_ownership.py`(文件头):RECORD_SUPPLIED 是**推导**出来的
(Coverage.def emitted − PipeFill.cpp 拒绝表),所以映射文件不可能与 emitter 漂移还保持绿。
- `scripts/check_include_closure.py`(文件头):`--mode text|clang|both`,ctest 里跑的是 text
(CI test job 的 runner 没子模块);`--require-all` 把 SKIP 变红(棘轮)。
- `scripts/gen_protocol.py`:flatc 解析顺序 `--flatc` / `MOBILEGL_FLATC_EXECUTABLE` → 从
`3rdparty/flatbuffers` 源码建到仓外 `<repo>/../flatc-build`(`MOBILEGL_FLATC_BUILD_DIR` 可改)。
- `scripts/check_doc_citations.py`:解析 md 里的 `path:line` 引用对 git rev 校验,只有
`--strict` 才非零退出。
- `scripts/ci/split_negative_controls.sh [--self-test]`:E1(verb barrier)与 E3(a)(persistent-map
push)的阴性对照,是 `.github/workflows/test.yml:1097` 那个 step 的本体。环境变量
`CTEST`(默认 ctest)等覆盖点在文件头 usage 段。两条规矩(文件头):基线必须绿(数 PASSED)、
红必须带被选条目自己的失败文本。
- `scripts/ci/control_smoke_test.sh`:用 stub ctest 跑上面真脚本,验证"该红时红"(R-16)。
- `scripts/ci/redcheck_control_smoke_test.sh`:smoke test 自己的 red-once(临时 revert 证据检查)。
- `scripts/ci/retrace_pull_library_control.sh` / `retrace_drop_draw_control.sh`:retrace-split
车道的两个阴性对照(pull 库必须红于 transport 断言;丢 draw 必须红于 SSIM)。retrace 本体是
WSL 侧 `~/w7/retrace_gate.py`(不在仓内)。
- `scripts/ci/census_junit.py` / `junit_tally.py`:JUnit XML 统计辅助。
- CI 接线点:`.github/workflows/test.yml:1097`(negative controls)、`:1249`
(`--print-admitted`)、`:2568-2576`(gen_pipe)、`:2616-2617`(dirty surface)、
`:2637-2638`(field ownership)、`:2736`(doc citations,`|| true` 非 strict)。
---
## 3 四个构建 flavour 与 CMake 强制规则
开关定义在仓根 `CMakeLists.txt`:
| 开关 | 行 | 含义 |
|---|---|---|
| `MOBILEGL_BUILD_DISAGGREGATED` | `CMakeLists.txt:23` | 编译 MG_Remote 传输层(两进程形态) |
| `MOBILEGL_BUILD_DISAGGREGATED_INPROC` | `CMakeLists.txt:30` | 编译 inproc 角色隔离 shim;蕴含上一项(`:460-463`) |
| `MOBILEGL_PIPE_PUSH` | `CMakeLists.txt:35` | 后端经 PipeInputs 块读前端状态 |
| `MOBILEGL_PIPE_VERIFY` | `CMakeLists.txt:36` | 编译 SnapshotFromGLContext + G4 逐 verb 影子比对器;蕴含 PUSH(`:506-508`),永不发布 |
**强制规则(全是普通变量遮蔽,不写 cache——所以 CMakeCache 不可信,见 ID-124)**:
- `DISAGGREGATED=ON` 缺 flatbuffers 子模块 → 静默降级为 OFF(`:471-486`)。
- **`DISAGGREGATED=ON` 强制 `PIPE_PUSH=ON`(`:496-500`)——不存在 "disaggregated pull"**。
同时传 `-DMOBILEGL_PIPE_PUSH=OFF` 会被静默覆盖,cache 里仍显示 OFF。
- `PIPE_PUSH=OFF` 强制 `PIPE_LEGACY_MEMOS=ON`(`:513-517`)——pull 构建里 legacy 臂是唯一臂。
- 生效宏落点:`:652-659`(`-DMOBILEGL_BUILD_DISAGGREGATED=1` / `-DMOBILEGL_PIPE_PUSH=1` /
`-DMOBILEGL_PIPE_VERIFY=1`)。
四个 flavour 的配法(`~/w7/notes/tools/wsl_p5_gate.sh:63-66` 与 `p5e_gate.sh` 的 flavours 步):
| flavour | 构建目录 | 开关 |
|---|---|---|
| pull | `build-linux`(老门)/ `build-pull`(p5e) | `DISAGGREGATED=OFF INPROC=OFF PIPE_PUSH=OFF` |
| push | `build-push` | `DISAGGREGATED=OFF INPROC=OFF PIPE_PUSH=ON` |
| verify | `build-verify` | `PIPE_VERIFY=ON` + `MOBILEGL_ITEST_REQUIRE_GPU=ON`(只在 wsl_p5_gate.sh 里有;p5e_gate.sh 不建它) |
| split | `build-split` | `PIPE_PUSH=ON DISAGGREGATED=ON DISAGGREGATED_INPROC=ON` |
**ID-124 教训(p5e_gate.sh flavours 步头注释原文)**:每个 flavour 配完后必须先从
`compile_commands.json` 里 grep 生效宏再放行构建——cache 条目会说谎:
```bash
got=$(grep -o 'MOBILEGL_PIPE_PUSH=1' "build-$fl/compile_commands.json" | head -1)
# pull 要 absent,push 要 1,不等就 ARM CHECK FAILED
```
公共 configure 串(p5e_gate.sh 的 `$COMMON`):`-G Ninja`、clang/clang++、ccache、
`-DCMAKE_BUILD_TYPE=Release`、`-DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO`、
`-DMOBILEGL_BUILD_TEST=ON -DMOBILEGL_BUILD_INTEGRATION_TEST=ON -DMOBILEGL_BUILD_BENCHMARK=OFF`、
`-DMOBILEGL_ITEST_EGL_VENDOR=/usr/share/glvnd/egl_vendor.d/50_mesa.json`、
`-DMOBILEGL_ITEST_VK_ICD=/usr/share/vulkan/icd.d/lvp_icd.json`、
`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5`。
**C-10 教训**(wsl_p5_gate.sh:8-11 头注释):构建结果要用产物的存在来证明——`cmake --build`
拿错目录时 `$?` 也是 0(cmake 打 usage),所以每个 build 目录先查 `CTestTestfile.cmake`
再采信(`wsl_p5_gate.sh:74`)。
**`~/w7/p5e-int` 实测**(只读查 compile_commands.json):`build-pull` 无任何宏、`build-push`
有 `MOBILEGL_PIPE_PUSH=1`、`build-split` 有 `PUSH=1 + DISAGGREGATED=1`、`build-nodisagg` 无宏;
**没有 build-verify**。`verify` flavour 在 P5e 只在 retrace 扫描里以
`MOBILEGL_PIPE_VERIFY=1` 环境变量的形式出现(wsl_p5_gate.sh:270)。
---
## 4 车道与 P5e gate 脚本
### 4.1 ctest 标签
| 标签 | 注册处 | 说明 |
|---|---|---|
| `unit` | `MobileGL/MG_Test/**/CMakeLists.txt`(gtest_discover_tests … LABELS unit,如 `MobileGL/MG_Test/Pipe/CMakeLists.txt:215`) | 单测 |
| `integration-split` | `MobileGL/MG_IntegrationTest/CMakeLists.txt:1917-2364` | 拆分车道。大多数条目同时带 `integration-gpu`(`:1917` 等 `LABELS "integration-gpu\;integration-split"`);**`integration-split` 不等于 "split transport"**——`:1944` 注释明说 monolith 臂也带这个标签。strict 专用条目只带 `integration-split`(`:2339, :2349`)。`integration-split-clientarrays` 会被 `-L integration-split` 匹配(子串,`:2206-2209`) |
| `integration-gpu` | 同上 | GPU 车道。`MOBILEGL_ITEST_REQUIRE_GPU` 把"无 GPU 全 skip"从绿变红,否则该标签不可证伪(`MobileGL/MG_IntegrationTest/CMakeLists.txt:280-285`) |
| `integration-verify` | `:1695-1726` | verify comparator 车道(`-DMOBILEGL_PIPE_VERIFY=ON` 构建里跑) |
### 4.2 p5e_gate.sh 各 step(`~/w7/notes/tools/p5e_gate.sh`,逐字摘录的命令)
用法:`bash ~/w7/notes/tools/p5e_gate.sh <slug> <step> [regex]`,在 `~/w7/p5e-<slug>` 里跑,
日志落 `~/w7/p5e-logs/<slug>-<step>.log`。集成树就是 slug=int(FLIP-CHECKLIST.md:5:
"`G` = `bash /home/swung/w7/notes/tools/p5e_gate.sh int <step>`")。
| step | 命令本体 |
|---|---|
| `configure` | `cmake -S . -B build-split $COMMON -DMOBILEGL_BUILD_DISAGGREGATED=ON -DMOBILEGL_BUILD_DISAGGREGATED_INPROC=ON -DMOBILEGL_PIPE_PUSH=ON` |
| `build` | 无 cache 先 configure;`cmake --build build-split -j $(nproc)` |
| `unit` | `GLIBC_TUNABLES=glibc.malloc.tcache_count=0 ctest --test-dir build-split -L unit -j $(nproc) --output-on-failure`(GLIBC_TUNABLES 是 ID-18/21:让 exit-order UAF 可复现) |
| `isplit` | `ctest --test-dir build-split -L integration-split --no-tests=error -j 4 --output-on-failure` |
| `gpu` | `ctest --test-dir build-split -L integration-gpu --no-tests=error -j 4 --output-on-failure`。**不是可选**:它是 push 构建的 monolith 运行时臂(ID-109 的洞——137 个 SEGFAULT 在 38 个 scenario 上无门通过,fix1/ID-107);-j4 约 10 分钟,定为集成分支步骤而非每包步骤 |
| `strict` | `MOBILEGL_IPC_STRICT_ERRORS=1 ctest --test-dir build-split -L integration-split --no-tests=error -j 4 --output-on-failure`,然后 `grep -ho 'Fatal{UnmigratedPipeInput, "[^"]*"}'` 做首错直方图。**strict 是环境变量车道,不是独立 ctest 标签** |
| `flavours` | rm -rf 重建 build-pull / build-push,每个先过 compile_commands.json 宏断言再构建(§3 ID-124) |
| `gens` | §2 列的那串 + `check_doc_citations.py --rev HEAD --strict` |
| `one <regex>` | `ctest --test-dir build-split -R <regex> --output-on-failure`,附 Fatal 上下文摘录 |
关键运行时环境变量:
| 变量 | 取值/出处 |
|---|---|
| `MOBILEGL_TRANSPORT` | `monolith \| inproc \| spawn \| unix:<path> \| pipe:<name>`(`MobileGL/Config.h:425`);pull 库里 `inproc` 被静默忽略(`Config.h:436`)——这就是 retrace pull 阴性对照存在的理由 |
| `MOBILEGL_IPC_STRICT_ERRORS` | `MobileGL/ConfigLoader.cpp:393` 读取;BARRIER-PULLED 读由计数升为 Fatal(`MobileGL/Config.h:534`、`MobileGL/MG_Backend/MGPipe/PipeInputs.cpp:183`) |
| `MOBILEGL_IPC_RUN_AHEAD` | `MobileGL/Config.h:508`(P5e,`MG_Remote/CONTRACT-P5E.md` §1) |
| `MOBILEGL_IPC_AUDIT` | `=1` 时读 `0xDD` 毒化即红(BRIEF-P5E.md §3.7) |
| `MOBILEGL_IPC_VERB_BARRIER=0` | **不要拿它当对照**(ID-114,FLIP-CHECKLIST.md Phase E):它是 `RunAheadArmed()` 的第一合取项,副作用是关掉 run-ahead,且秒死于 `Fatal{UnmigratedPipeInput, "IsCapabilityEnabled@ClientWaitSync"}` |
| `MOBILEGL_ITEST_REQUIRE_GPU` | 见 §4.1;configure 期 option 会注入公共 ENVIRONMENT(`MobileGL/MG_IntegrationTest/CMakeLists.txt:303-306`) |
| `MOBILEGL_LOG_FILE_PATH` | 每个 split 条目一条私有库日志(split_log_paths.py 的职责就是校验私有且互不重名);库的 Fatal 只在私有日志里——**ctest 控制台 transcript 对库输出是假零**(split_log_paths.py evidence 模式错误信息原文) |
### 4.3 P5e 的落地流程(FLIP-CHECKLIST 摘要)
`docs/Disaggregated/notes/p5e/FLIP-CHECKLIST.md`:Phase A 按 gl→pa→mv 序合并,每次合并后
`G build && G unit && G isplit && G gpu && G gens`;Phase B 翻转前记录四条基线
(`RUN_AHEAD=0` / `IPC_AUDIT=1` 各 116/116、`STRICT_ERRORS=1 ctest -L unit` 2250/2250、
默认臂日志含 "run-ahead requested … running lockstep");Phase D 翻转是一个 commit 同时落
`kMGPipeP5eRunAheadReady`(`MG_Backend/Init.cpp:98`)与
`kMGPipeP5eClientWaitRuleLanded`(`MG_Pipe/PipeApply.h:969`)——分开落会死。
---
## 5 strict-expected-markers.txt 刷新流程
文件:`MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt`。当前 HEAD 上活着
**10 对** `<field>@<verb>`(`:34-52`:8 对表 admission + 2 对 escalation-only 的
client-vertex-array 条目,P8 的)。
它是双向棘轮(`:11-13`):表里有的对子若不再出现 → 在退役它的那个 commit 里删行;
出现新对子 → 加行并注明归哪个阶段,或直接退役它。
**刷新流程(`:15-20` 原文,禁止手猜)**:
```bash
ctest -L integration-split --show-only=json-v1 > lane.json
MOBILEGL_IPC_STRICT_ERRORS=1 ctest -L integration-split -j 4
python3 MobileGL/MG_IntegrationTest/Harness/split_log_paths.py markers lane.json . \
<(python3 scripts/gen_pipe_field_ownership.py --print-admitted) strict-expected-markers.txt
# 把它打印的 census 贴回文件
```
配套事实:
- marker 语法在写出处定义(`split_log_paths.py` 的 `MARKER_RE`):
`Fatal{UnmigratedPipeInput, "<F>@<V>"}` 与 `Admitted{UnmigratedPipeInput, "<F>@<V>"}
[BARRIER-PULLED, ADMITTED[-ESCALATED], retires in <phase>]` 两种形态。
- 日志集来自 `ctest --show-only=json-v1` 而不是目录扫描(ID-119:目录是猜测,F1. readback
块、NamedBlit、Ct.、PersistentMapArm、DirectVulkan 条目都不在 split-logs/ 目录里)。
- census 是**首错直方图不是清单**(FLIP-CHECKLIST.md Phase B.2):退役一行后那 62 个条目不会
变绿,会推进到各自的下一个 pull。
- 重测 census 要按 `ctest -L integration-split -N -V` 逐条驱动环境——`PersistentMapArm.` /
`SmallRing.` / `NamedBlit.` 前缀带额外 ENVIRONMENT,固定环境的 census 对 14/109 是错的
(FLIP-CHECKLIST.md Phase B.1)。
---
## 6 P5f 开工前必须知道的三件事
### 6.1 `~/w7/p5e-int` 不能直接用
p5e-int 停在 `f23fbc1b`(branch `p5e-wave2-landed`),它**不是** Windows HEAD `8b68b92c` 的
祖先(`git merge-base --is-ancestor` 判定否)——P5e 收官后 Windows 侧分支被重述过
(同一批提交、中英文两版 message、不同 SHA;`f23fbc1b` 对象本身在 Windows 仓里存在,
`git cat-file -t f23fbc1b` = commit)。P5f 的正确姿势是从当前代码头 `25fba0d5`(或 HEAD
`8b68b92c`,其后的提交只动文档)按 §1.1 新建树:`p5e_new.sh` 的形态照抄,slug 换 `p5f-<x>`。
### 6.2 gate 脚本要复制改造,不要直接复用
`p5e_gate.sh` 硬编码 `~/w7/p5e-$SLUG` 与 `~/w7/p5e-logs`。P5f 应复制为 `p5f_gate.sh`
(树改 `~/w7/p5f-$SLUG`)。P5f 新增的双块车道(P5F-WIRE-COMPLETENESS.md §4,旋钮建议名
`MOBILEGL_IPC_ROLE_SPLIT_STATE=1`)应加成新 step,形态照 `strict` 步(环境变量 + 同一标签 +
marker 直方图)。**阴性对照要写进脚本**:关掉双块必须让至少一条具名用例由绿转红
(P5F §6,ID-122 的教训)——参照 `scripts/ci/split_negative_controls.sh` 的"红必须带被选条目
自己的失败文本"规矩。
### 6.3 门禁的已知盲点都是"未被构建/未被运行的臂"
ID-107/109(运行时臂无门:push 构建的 monolith 臂漏跑 → 137 SEGFAULT)、ID-123(gate 只配
build-split,pull flavour 根本没建)、ID-124(cache 与生效宏不符的伪 pull 构建)、ID-122
(对照要证伪机制本身)。P5f 加任何新车道时按这个清单自查:**配了吗、建了吗、跑了吗、宏对吗、
红过一次吗**。
---
## 7 常用命令速查(Windows 侧发起,WSL 执行)
```bash
# 建 P5f 包树(先在 Windows worktree commit,拿到 SHA)
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_new.sh <slug> <sha>' # 脚本内路径写死 p5e-,P5f 需先复制改造
# 集成树门禁(P5e 形态,P5f 复制后替换 slug 前缀)
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int build'
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int unit'
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int isplit'
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int gpu' # 集成分支步骤,非每包
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int strict'
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int flavours'
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int gens'
wsl.exe -e bash -lc 'bash ~/w7/notes/tools/p5e_gate.sh int one <regex>'
# 仓内门(Windows 侧 Git Bash 也能跑,与 WSL 等价)
python3 scripts/gen_pipe.py --check && python3 scripts/gen_pipe.py --self-test
python3 scripts/gen_pipe_field_ownership.py --check && python3 scripts/gen_pipe_field_ownership.py --self-test
python3 scripts/gen_pipe_dirty_surface.py --check && python3 scripts/gen_pipe_dirty_surface.py --self-test
python3 scripts/check_include_closure.py
python3 scripts/check_doc_citations.py --rev HEAD --strict docs/Disaggregated/*.md MobileGL/MG_Remote/CONTRACT-P5*.md
bash scripts/ci/split_negative_controls.sh # 需要 build-split 与 bin 目录环境,一般在 WSL 跑
```
+408
View File
@@ -0,0 +1,408 @@
# f0 普查 · §2.1 BARRIER_PULLED 15 字段逐字段载体判定
> 基线:`feat/disaggregated @ 8b68b92c`(代码头 25fba0d5 之后的提交只动文档)。行号均以该 HEAD 为准。
> 范围:`MobileGL/MG_Pipe/FieldOwnership.def` 的 21 行 / 15 个 BARRIER_PULLED 字段,逐字段回答
> (a) 存储形态 (b) 后端读者与 verb (c) 现有记录载体 (d) P5f 处置 (e) 依赖 / 工作量 / 包归属。
> 只读普查:本文件是唯一产物,未改任何代码、未构建、未跑测试。
> 姊妹篇:`f0-magma.md`(Magma 侧逐站点清单,本报告与之交叉引用而不重复)、`f0-reverse-channel.md`。
## 0. 总判断
1. **15 个字段全部涉及 client 地址空间,无一例外**。9 个非 sticky 行的存储全部是前端堆引用——
4 个 `SharedPtr`(`m_boundVertexArray` / `m_programForDraw` / `m_programForDispatch` /
`m_transformFeedbackProgram`,PipeInputs.h:807、:812-814)+ 5 个裸指针基址/槽
(`m_bufferBindingSlot[15]` :808、`m_bufferBindingPointBase[15]` :809、`m_framebufferBindingSlot[2]`
:810、`m_imageTextureBindingBase` :811、`m_textureUnitBase` :815);6 个 sticky forward 里
3 个直接交出前端对象或写前端(GetProgramObject / GetTextureObject / RecordError),
3 个是值类查找(GetBufferBindingPointCount / HasOpenTransformFeedbackSpan / ValidateProgramName)。
21 行 = 15 字段行 + 6 行 forward 行(FieldOwnership.def:53-165 与 :178-192)。
2. **P5e 的教训("数据已在线上,读者没跟上")在 15 个里命中 11 个**。已有记录供给或线上已带等价
句柄的:GetBoundVertexArray(`bind_vertex_elements`→`BoundVertexElements`)、GetBufferBindingPoint
(`set_shader_buffers`→`BoundShaderBuffers`,**XFB 目标除外**)、GetFramebufferBindingSlot
(`set_framebuffer_state`→`BoundFramebuffer`)、GetImageTextureBinding(`set_shader_images`)、
GetProgramForDraw / GetProgramForDispatch(`set_draw_program` / `set_dispatch_program`→
`DrawProgram` / `DispatchProgram`)、GetTextureUnitObject(`set_sampler_views`→`BoundSamplerViews`)、
GetTextureObject(`MGPCopyRegion.Src/Dst` 句柄就在 GL 名旁边,MGPipeTypes.h:1468-1476)、
GetProgramObject + ValidateProgramName(`MGPStorageBlockBinding.ShaderCso` 就在 GlName 旁边,
MGPipeTypes.h:1791-1792)、RecordError(`kEventGlError` 已接线,PipeFill.cpp:2166-2178)。
**需要扩记录或新载体的只有 4 个**:GetTransformFeedbackProgram(`begin_stream_output` 只带
PrimitiveMode,CONTRACT-P5B.md:215)、GetBufferBindingSlot 的 7 个无载体 target
(Coverage.def:47-57)、HasOpenTransformFeedbackSpan(需 applier 自推)、GetBufferBindingPointCount
(需 applier 自推或 constexpr 表)。
3. **包归属有一个真实的缺口**:计划 §5 的 fm 字面只覆盖"Magma 的 9 个字段",而 15 个里
GetBufferBindingPointCount / HasOpenTransformFeedbackSpan / GetProgramObject / ValidateProgramName /
RecordError / GetBufferBindingSlot 六个不在其字面范围;fm 9 字段的 **Espryt 残余**(strict 车道
活着的 8 对非 escalation 标记里的 6 对,见 §2 各字段)也无家。建议 fm 改写为"对象类字段
(双后端)",或新增一个字段包;RecordError 归 fv 是计划 §2.6 文本直接支持的唯一年配。
4. **顺序约束(ID-112 的绊线仍然有效)**:`PipeFill.cpp:2789-2809` 的两个 `static_assert`
(`NoPointerBackedRowIsWhollySupplied` 等)把 7 个指针残余行钉在"仍被拉"状态;退役任一行的
`EmittedCallSuppliesTheWholeField` FALSE 臂(PipeFill.cpp:2582-2602)必须与**该字段最后一批
后端读者的迁移同提交落地**,否则 Magma 第一帧 draw 就解引用空镜像——注释 :2773-2781 明说
"This residual fill is MAGMA'S ONLY SOURCE"。这不是建议,是构建期强制。
5. **strict 车道活标记 10 对与字段的映射**(strict-expected-markers.txt:34-52):8 对表驱动
(disjunct 1/2)+ 2 对仅 escalation(disjunct 3)。出口门要求该文件清空,而
`GetBoundVertexArray@DrawArrays` / `GetBufferBindingPoint@DrawArrays` 两对的退役题材是 P8 的
client vertex arrays(P5F 计划 §2.1 自注),**P8 不在 P5f 的六个包里**——要么 P5f 把这两对的
载体(client VA staging)拉进来,要么出口门 2 需要为 escalation 对留一个具名豁免。这是
计划文本内部的张力,f0 只记账不裁决。
## 1. 方法与口径
- **三重证据**:(i) `MG_IntegrationTest/Harness/strict-expected-markers.txt` 的 10 对活标记
(车道实测,含 admitted 判据);(ii) `rsp` 计数路径——非 sticky 读经 `MGP_INPUT_CHECK` →
`MGPipeInputUnfreshRead` → `CountBarrierPull`(PipeInputs.cpp:344),sticky forward 经
`MGPipeStickyForwardPull`(PipeInputs.cpp:372-378,七个本体在 PipeFill.cpp:2119-2186 各自
第一行调 `MGP_STICKY_FORWARD_PULL`);(iii) 全树 grep `MGB_CTX-><Field>` / sink 侧
`gPipeInputs.<Field>`。
- **推导规则**(scripts/gen_pipe_field_ownership.py):RECORD_SUPPLIED 是推导值 =
Coverage.def 的 `MGP_COVERAGE_EMITTED_LIST`(48 行,Coverage.def:256-304)减去
`EmittedCallSuppliesTheWholeField` 的 8 个拒绝(PipeFill.cpp:2582-2602,解析见
gen 脚本 `parse_supplies_whole_field` :157-175;self-test 钉住"恰为 8 个",:972-974)。
BARRIER_PULLED 手维护且每行必须有退役相(gen 脚本 :460-462 无相即构建失败)。
admitted 表是 ID-116 推导:BARRIER_PULLED ∧ 在 verb 类 may-read 罩内 ∧(verb 的 op 静态设障
∨ 退役相不含本阶段)(gen 脚本 `build_admitted` :312-375),再加运行时的 disjunct 3
(escalation,PipeInputs.cpp:170-174 起)。
- **"今天为何能工作"的公共答案**(下文各字段只写增量):inproc 下两角色共享一份
`gPipeInputs`(PipeInputs.h:835),client 的第 4 步残余填充(PipeFill.cpp:3712-3762,
:3759 的 `CopyField`)写入、server 在同一块上读,verb barrier 保证同一时刻只有一个写者
(PipeInputs.h:902-908 的裁定)。strict 下这类读要么具名 admitted 要么 Fatal。
- **行号漂移记录**(内容判断不受影响,但引用者须知):FieldOwnership.def:68-71 引的
`PipeFill.cpp:1902-1905` 对应现为 `CopyField` 的 GetBoundVertexArray 臂 :128-130;
FieldOwnership.def:82-84 引的 `SyncCurrentFBO (:2995)` 现为 DirectGLES.cpp:4465,
`BindCurrentFBO (:4303-4353)` 现为 :5860 起;计划 §1.1 引的 PipeFill.cpp:245/:274 现为
:244-245/:272-274,:2591-2592 现为 :2591-2597(均只差一两行)。P5F 计划 §2.2 引的
T5 站点 `VulkanRenderer.cpp:1562-1590` 据 f0-magma.md §2 已漂移到 :1590-1637。
## 2. 逐字段判定
### 2.1 GetBoundVertexArray
- **(a) 形态**:`SharedPtr<VertexArrayObject> m_boundVertexArray`(PipeInputs.h:807;
accessor :597-601)。O 类,`ReleaseObjectPins` 四行之一(PipeFill.cpp:95-100)。
- **(b) 读者**:Espryt 普通 draw 已走记录——`fromRecords` 三元 DirectGLES.cpp:2275、:2442、
:6185,`VertexInputReadsRecords()` 门 :2195;**残余读点**全在 escalation 路径:
`SyncClientSideVertexArraysForDrawArrays` :2207/:2213(client VA,P8)、:8539、
MultiDraw.cpp:223。Magma 6 站:DirectVulkan.cpp:805、:929,VulkanRenderer.cpp:6446、:6852、
:12550、:12651(逐站分析见 f0-magma.md §4.1;:805 的 LINE_LOOP 臂还叠加
`buffer-legacy-arm` Fatal)。**strict 标记**:`GetBoundVertexArray@DrawArrays`
(markers:51,仅 disjunct 3 escalation;普通 draw 对已在 P5e mv 退役,markers 头注 :29-30
与 ROADMAP P5e 行 item 5 的 red-once 证明:"倒掉 mv 的臂得 GetBoundVertexArray@DrawArrays")。
- **(c) 载体**:`bind_vertex_elements` → applier `BoundVertexElements`(PipeApply.h:662;
写入 PipeApply.cpp:2348)+ `VertexElementsCsos` 记录表。Espryt 读者已跟上;Magma 的
fast-path 句柄臂存在但句柄仍由前端对象推出(f0-magma.md §4.1,VulkanRenderer.cpp:6452-6458)。
- **(d) 处置**:Magma 六站改读 `BoundVertexElements` → 升 RECORD_SUPPLIED(Coverage.def:36、
:262 的 emitted 行早已在;删 PipeFill.cpp:2585 的 FALSE 臂,与 ID-112 绊线同提交)。
Espryt 残余的 client-VA 对是 P8 题材——见 §0.5 的门张力。
- **(e)** 依赖:Magma vertex-input 句柄化(与 fm 的 GetBufferBindingPoint 同源)。
工作量:中。包:fm;escalation 对无包(缺口)。
### 2.2 GetBufferBindingPoint
- **(a) 形态**:裸指针基址 `BindingSlotRange1D<BufferObject>* m_bufferBindingPointBase[15]`
(PipeInputs.h:809;accessor :615-627,`base[index]` 即前端活槽 :624-626)。填充
PipeFill.cpp:141-147(只填 `BufferBindPointTargets`)。
- **(b) 读者**:Espryt 的记录臂已落——`SyncBufferBindingPointsByRecord` 读
`st.BoundShaderBuffers`(DirectGLES.cpp:670-713,:684)、`SyncAtomicCounterBuffers` 记录臂
:873-926、UBO 循环读 `BoundShaderBuffers[Uniform]`(:7295;CONTRACT-P5E.md:476)。
legacy 臂仍在:SyncBufferBindingPoints :724-761(:737)、:928-936(:935)。
**XFB 目标无载体**:SyncTransformFeedbackBindingPoints :796、StartPendingTransformFeedback
:1891(kXfbSpan 罩住这两个字段,FillPoints.def:315-316)。Magma 3 站:
UniformManager.cpp:1205、:2035,VulkanRenderer.cpp:11764(XFB)。**strict 标记**:
`GetBufferBindingPoint@DrawArrays`(markers:52,仅 disjunct 3)。该对的具体读点本普查
不能从静态证据唯一确定——候选是 XFB-span draw 的 :1891(escalation (i),open span)或
client-VA draw;计划 §2.1 把两对都记为 client-VA(P8)。双块演习会直接报名字,f0 不裁决。
- **(c) 载体**:`set_shader_buffers` → `BoundShaderBuffers[3][84]`(PipeApply.h:776-779;
写入 PipeApply.cpp:2918)。**XFB 目标明确不搭这条记录**(CONTRACT-P5E.md:770:
"XFB does NOT ride this state");`set_stream_output_targets` 今天"has no applier and no
producer"(CONTRACT-P5B.md:215)。
- **(d) 处置**:Magma 三站改读 `BoundShaderBuffers`;XFB 目标的载体是 `set_stream_output_targets`
的实体化(P9 题材,落入 P5f 范围)。之后删 PipeFill.cpp:2597 的 FALSE 臂、字段翻
RECORD_SUPPLIED(Coverage.def:71、:263 已在)。
- **(e)** 依赖:同 2.1 + XFB 记录实体化。工作量:中。包:fm + XFB 半无包(缺口)。
### 2.3 GetFramebufferBindingSlot
- **(a) 形态**:裸指针 `BindingSlot<FramebufferObject>* m_framebufferBindingSlot[2]`
(PipeInputs.h:810;accessor :628-637,空槽即 poison Fatal)。填充 PipeFill.cpp:190-195。
- **(b) 读者**:Espryt——记录臂 `SyncCurrentFBOByRecord`(DirectGLES.cpp:4479)+ 强制臂具名
abort `RefuseFramebufferBindingSlotRead`(:4490-4492,P5e fb);pre-handle 回落臂仍在
SyncCurrentFBO(:4465 起,:4507 的 `GetFramebufferBindingSlotChecked`,helper 在 :184-202),
直接站点 :3261、:3491、:4212、:4426、:4507、:5625、:5923、:6096、:8349、:10696-10697,
Managers.cpp:9903、:10299。FieldOwnership.def:82-84 的"8 Espryt + 13 Magma"与本次 grep
量级一致。**strict 标记**:`GetFramebufferBindingSlot@ReadPixels`(markers:40,disjunct 1——
`read_pixels` 是 kWaitReply);读点是 ReadPixels 内的 `FramebufferImpl::SyncCurrentFBO()`
(DirectGLES.cpp:14275)回落臂。Magma 13 站:UniformManager.cpp:560;VulkanRenderer.cpp:3158、
:5510、:5730、:6360、:6465、:6845、:7648、:8016、:9024-9025、:9660、:10371。
- **(c) 载体**:`set_framebuffer_state` → `BoundFramebuffer[2]` + `FramebufferRecords`
(PipeApply.h:714;写入 PipeApply.cpp:2558-2562),P5e fb 已给 Espryt 记录臂。
- **(d) 处置**:Espryt 的 ReadPixels 回落为何仍命中(记录臂 decline 的路径)需 f1 双块演习
定位——静态看 :4479 应先试记录臂,标记存活说明 ReadPixels 场景走了回落;Magma 13 站改读
`BoundFramebuffer`/`FramebufferRecordFor`(PipeApply.cpp:2609-2613)。之后删
PipeFill.cpp:2586 FALSE 臂、翻 RECORD_SUPPLIED(Coverage.def:84、:274 已在)。
- **(e)** 依赖:fm 的 FBO 记录消费是 Espryt 已铺好的路。工作量:中(Magma 站点多但形状单一)。
包:fm。
### 2.4 GetImageTextureBinding
- **(a) 形态**:裸指针 `ImageTextureBinding* m_imageTextureBindingBase`(PipeInputs.h:811;
两个 const 重载 :638-653,null 基址即 poison Fatal)。填充 PipeFill.cpp:196-199。
- **(b) 读者**:Espryt——pre-handle 形式 SyncImageTextureBinding :3842(注释 :3831-3837 自明
"what the pull build compiles and what a push-monolith build runs");transport 下已删的
MarkWritableImageBufferTexturesGpuWritten :3891(门 :3885-3887);Managers.cpp:11963(.Format)。
Magma 4 站:UniformManager.cpp:1021、:1312、:1867、:1955。**strict 标记:无**。
- **(c) 载体**:`set_shader_images` → `BoundShaderImages`(PipeApply.h:751;写入
PipeApply.cpp:2857);Espryt 的记录重载已在(DirectGLES.cpp:3810-3829,`ResolveShaderImageRecord`
+ `SyncTextureToBackendByHandle`)。`bind_shader_image` verb 记录的参数全景也在线上
(PipeApplier.cpp:889-900 的注释)。
- **(d) 处置**:Magma 四站改读 `BoundShaderImages`;Espryt 残余是 monolith 臂,不动。
删 PipeFill.cpp:2587 FALSE 臂、翻 RECORD_SUPPLIED(Coverage.def:85、:275 已在)。
- **(e)** 依赖:无超出 fm 的。工作量:小-中。包:fm。
### 2.5 GetProgramForDraw
- **(a) 形态**:`SharedPtr<ProgramObject> m_programForDraw`(PipeInputs.h:813;accessor
:659-663)。填充 PipeFill.cpp:244-245(计划 §1.1 的 :245 引文)。O 类 pin 四行之一。
- **(b) 读者**:Espryt——PrepareForDraw 的提升读 :6215(`programFromRecords` 三元;
:6132-6134 的注释记了这条提升本身的历史);记录臂:`GetCurrentBackendProgram` 读
`MGPipeApplier().DrawProgram`(:7659-7670),legacy 臂 :7672/:7744 只在
`ProgramHandleArm()==false` 时跑;:7029 同样带三元。Magma 3 站:VulkanRenderer.cpp:6384、
:6815、:6853。**strict 标记:无**——P5e pa 退役,markers 头注 :25-27 记了棘轮曾指名它消失,
ROADMAP P5e 行 item 5 的 red-once 即此对。
- **(c) 载体**:`set_draw_program` → `DrawProgram`(PipeApply.h:759)+ `bind_shader_state` →
`BoundShaderCso`(:761;写入 PipeApply.cpp:3208-3214);ShaderCso 记录带逐 link 的
ProgramArchive(ROADMAP P5e 行:"最大的那次迁移没有改动任何线上结构:记录早就带着逐 link
的 ProgramArchive,缺的是仍去问前端对象的读者"——本字段就是那句话的原主)。
- **(d) 处置**:Magma 三站改读记录即毕。删 PipeFill.cpp:2589 FALSE 臂、翻 RECORD_SUPPLIED
(Coverage.def:100、:289 已在)。
- **(e)** 依赖:无。工作量:小-中。包:fm。
### 2.6 GetProgramForDispatch
- **(a) 形态**:`SharedPtr<ProgramObject> m_programForDispatch`(PipeInputs.h:812;accessor
:654-658)。填充 PipeFill.cpp:241-243。O 类 pin 四行之一。
- **(b) 读者**:Espryt——PrepareForCompute :8142(记录三元)+ :8154-8157 的
`DispatchProgram` 句柄臂。Magma 2 站:VulkanRenderer.cpp:7424、:7476。**strict 标记:无**
(markers 头注 :28:pa 在它被写进表之前就退役了它)。
- **(c) 载体**:`set_dispatch_program` → `DispatchProgram`(PipeApply.h:760)。
- **(d) 处置**:与 2.5 同形。删 PipeFill.cpp:2590 FALSE 臂、翻 RECORD_SUPPLIED
(Coverage.def:99、:288 已在)。
- **(e)** 依赖:无。工作量:小。包:fm。
### 2.7 GetTextureUnitObject
- **(a) 形态**:裸指针 `TextureUnit* m_textureUnitBase`(PipeInputs.h:815;accessor :669-676,
null 基址即 poison Fatal——计划 §1.1 的 :669 引文)。填充 PipeFill.cpp:272-274(计划的 :274)。
- **(b) 读者**:FieldOwnership.def:89-90 记"13 Espryt + 8 Magma",与 grep 一致。Espryt 13 站:
DirectGLES.cpp:2679、:2707、:2742、:2755、:3451、:6425、:6754、:7535、:10760、:11771、
:11887、:12120、:14451。Magma 8 站:UniformManager.cpp:511、:787、:826、:852、:1679;
VulkanRenderer.cpp:9652、:11157、:11401。**strict 标记**:`GetTextureUnitObject@CopyTexImage2D`
(markers:43,disjunct 1——`copy_framebuffer_to_texture` 记为 verb CopyTexImage2D,
FieldOwnership.def:285)。读点值得点名:`UpdateTextureBindingAtTarget` 的
`MGB_CTX->GetTextureUnitObject(unit)`(DirectGLES.cpp:10760)**在 transport 分流检查
(:10769-10774 的 `VerbCopyTexDst` 句柄臂)之前无条件执行**——句柄臂已经在了,读却先发生;
这正是"数据已在线上,读者顺序没跟上"的最小样本,也是双块演习下第一批会红的站点之一。
- **(c) 载体**:`set_sampler_views` → `BoundSamplerViews`(PipeApply.h:741;写入
PipeApply.cpp:2828)+ `BoundSamplerStates`(:746)。
- **(d) 处置**:Espryt 13 站 + Magma 8 站逐个改读记录(:10760 这类"读在分流前"的站点要
把读取移进各自臂内)。删 PipeFill.cpp:2588 FALSE 臂、翻 RECORD_SUPPLIED(Coverage.def:115、
:295 已在)。
- **(e)** 依赖:无超出 fm 的;站点数量是 fm 里最大的一批。工作量:中-大。包:fm。
### 2.8 GetTextureObject
- **(a) 形态**:sticky forward,无存储;按 GL 名返回 `const SharedPtr<ITextureObject>&`
(PipeInputs.h:691;本体 PipeFill.cpp:2131-2135,null context 时返回 `NullShared`)。
- **(b) 读者**:Espryt 后端本体**无站点**(DirectGLES 全树 grep 无 `MGB_CTX->GetTextureObject`);
读者在 **server sink**:`ServerVerbSink::OnResourceCopyRegion` 用 GL 名重建两端点
(PipeApplier.cpp:853-859,注释明说"`rsp` counts every one of these and P7 is what retires
them")。Magma:VkTextureManager.cpp:810。**strict 标记**:`GetTextureObject@CopyImageSubData`
(markers:42,disjunct 2 退役相 P7;gen 脚本 self-test :998-999 与 :1039-1041 钉住它同时被
disjunct 1/2 覆盖)。
- **(c) 载体**:`MGPCopyRegion.Src/Dst` 句柄与 GL 名**并排在线**(MGPipeTypes.h:1468-1476;
注释 :1455-1464 自明"A GL name is NEVER an identity; it is the lookup key of a pull the
handle beside it will replace")。
- **(d) 处置**:sink 改按 `Src/Dst` 句柄经 server 纹理 twin 表解析(FieldOwnership.def:183-184
的 forward 机制"a server-side texture handle table"),forward 删除、字段行出表。
Magma 的 :810 同批。无法升 RECORD_SUPPLIED——sticky forward 没有存储可供"供给",退役形态
是 accessor 死亡(仿 InvalidateCompileEnv 的 P5c ev 处置,FieldOwnership.def:157-163:
字段行改 FATAL + forward 删除)。
- **(e)** 依赖:server 端纹理句柄表(fm 的公共底座,与 2.7 共用)。工作量:小-中。
包:fm 边缘——读者在"后端无关的 sink"而非 Magma 本体,fm 的字面范围需要为此扩写(§0.3)。
### 2.9 GetBufferBindingPointCount
- **(a) 形态**:sticky forward,值类 `SizeT`,按 target 查前端绑定点的**容量**(不是绑定状态)。
Coverage.def:145 的 sticky 理由原文:"keyed by target: a constexpr capacity table, not verb
state"。本体 PipeFill.cpp:2119-2123。
- **(b) 读者**:Espryt 1 站:SyncAtomicCounterBuffers 的 legacy 臂 :928——它的记录臂
(:873-877)已改读 applier 窗口,注释 :871-872 自明"the bound is the applier's window
instead of GetBufferBindingPointCount's sticky forward, which is the row this site was the
Espryt reader of"(即 Espryt 侧读者已退役,legacy 臂只在非记录配置跑)。Magma 2 站:
UniformManager.cpp:1200、:2030。**strict 标记:无**。
- **(c) 载体**:`set_shader_buffers` 的窗口(`ShaderBufferStart/Count`,PipeApply.h:778-779)
回答"用到哪";容量本身是常量(线上 84,`kMGPipeMaxBufferBindingPoints`;CONTRACT-P5E.md:650)。
- **(d) 处置**:升 **APPLIER_DERIVED**——applier 从 `BoundShaderBuffers` 窗口/容量表回答,
读者改读之(FieldOwnership.def:179-180 的 forward 机制就是"a server-side binding-point
table")。注意它与 2.2 同源:Magma 的 UniformManager 两站把 count 与 point 成对读
(:1200/:1205、:2030/:2035),应同批迁移。
- **(e)** 依赖:同 2.2。工作量:小。包:fm(字面外,缺口)。退役相标 P7/P13——P13 的半边
(transfer target 的容量)本普查未见读者,未见不等于没有,标**未知**。
### 2.10 HasOpenTransformFeedbackSpan
- **(a) 形态**:sticky forward,值类 `Bool`,按 lifetime id 查前端 XFB span 开闭
(PipeInputs.h:692;本体 PipeFill.cpp:2137-2141;前端实现 Core.cpp:1299)。
- **(b) 读者**:仅 Magma 1 站:VulkanRenderer.cpp:11684(`m_xfbCounterSlotOwner` 的归并检查)。
Espryt 无后端读者。**strict 标记:无**。
- **(c) 载体**:无现成字段级载体,但 applier 已具备自推的全部输入:`begin/end/pause/resume
_stream_output` 四个 verb 记录(FieldOwnership.def:267-270 的 stamp 行)+ `bind_stream_output`
(带 `LifetimeId`,MGPipeTypes.h:1773-1778)+ 已有的 `MGPipeApplierState::IsTransformFeedbackActive`
镜像(PipeApply.h:805,P5e §2.1 escalation (i) 的两侧同值论证是先例)。注意
CONTRACT-P5B.md:215 记 `begin_stream_output` 的 sink 今天是 stub——自推依赖 t2 sink 实体化。
- **(d) 处置**:升 **APPLIER_DERIVED**(FieldOwnership.def:185-186 的机制"server-side XFB span
state"):applier 从 stream-output 记录序列维护 per-lifetime-id 的 span 开闭集。
- **(e)** 依赖:t2 sink 实体化(P9 题材的边缘)。工作量:小-中。包:无明确归属(缺口;
读者在 Magma,可挂 fm)。
### 2.11 GetTransformFeedbackProgram
- **(a) 形态**:`SharedPtr<ProgramObject> m_transformFeedbackProgram`(PipeInputs.h:814;
accessor :664-668)。填充 PipeFill.cpp:285-287。O 类 pin 四行之一。
- **(b) 读者**:Espryt 1 站:StartPendingTransformFeedback(DirectGLES.cpp:1871,PrepareForDraw
尾部,kDraw 罩住——FieldOwnership.def:102-105 的"XFB itself is off the reduced path, but
kDraw's may-read mask carries it"即此);同一函数 :1891 还读 GetBufferBindingPoint(2.2 的
XFB 半)。Magma 2 站:VulkanRenderer.cpp:11725、:11824。**strict 标记**:
`GetTransformFeedbackProgram@DrawArrays`(markers:44;gen 脚本 self-test :996-997 钉住它由
disjunct 2 承载,CONTRACT-P5E §5.7 把 XFB 整体划出 P5e)。
- **(c) 载体**:**今天没有**。`begin_stream_output` 只带 `PrimitiveMode`(CONTRACT-P5B.md:215
原文,且同文明说 capture program 靠的就是 kXfbSpan/kDraw 的 BARRIER-PULLED 读);
`set_stream_output_targets` 无 producer 无 applier。这是 15 个里"线上没数据"的代表。
- **(d) 处置**:升 RECORD_SUPPLIED 需要先造数据:给 `begin_stream_output` 增加 capture
program 的 ShaderCso handle(或实体化 `set_stream_output_targets` 并让它带上),两端读者
(Espryt :1871、Magma 两站)改读 applier 状态。**不能降 FATAL**:kDraw 罩内有活读者,
缩减路径(OpenRA 的 XFB 用例)会读。
- **(e)** 依赖:记录 schema 扩展(线上行,须与 gen 管线同步)+ 双后端读者。工作量:中。
包:fm(Magma 半);Espryt 半的退役相标的是 P3b/P4b(与 fr 的 registry 重键同源不同事,
归属需在 §5 定包时裁决)。
### 2.12 GetProgramObject
- **(a) 形态**:sticky forward,按 GL 名返回 `const SharedPtr<ProgramObject>&`
(PipeInputs.h:690;本体 PipeFill.cpp:2125-2129)。
- **(b) 读者**:Espryt 2 站,成对出现在 ValidateProgramName 之后:GetBackendProgramId :8204
(+:8199)、ShaderStorageBlockBinding :12600(+:12599;:12603-12606 还背着 G6 的
`MGPipeFrontendKeyedRegistryScope`)。Magma 1 站:DirectVulkan.cpp:278(+:275)。
sink 侧注释 PipeApplier.cpp:921-923 自明"Both backends resolve the PROGRAM through the
barrier-pulled GetProgramObject(GlName) … retired by P9"。**strict 标记**:
`GetProgramObject@ShaderStorageBlockBinding`(markers:41;`SetStorageBlockBinding` 是
kWaitApplied,PipeCalls.def:297,disjunct 1/2 皆覆盖)。
- **(c) 载体**:`MGPStorageBlockBinding.ShaderCso` 已在记录里(MGPipeTypes.h:1791-1792:
"the identity, and what P7 dispatches on"),名字 blob 走 SEG_STAGE。数据已在线上。
- **(d) 处置**:两端 backend 的 `ShaderStorageBlockBinding` slot 改按 ShaderCso 句柄解析
(esp. Espryt :12597-12620 的"只推给已建成 program"优化路径,其 frontend-keyed `Find` 一并
随 fr 的 registry 重键消失);forward 删除,字段行参照 2.8 的 ev 形态出表。无法升
RECORD_SUPPLIED(无存储),处置形态是 accessor 死亡。
- **(e)** 依赖:fr 的 program registry 句柄化(:12603 的 scope 是 P5c G6 债,两包有交集)。
工作量:小-中。包:无明确归属(缺口;可挂 fm 或并入 fr 的 program 半边)。
### 2.13 ValidateProgramName
- **(a) 形态**:sticky forward,值类 `Bool`,前端名表探测(PipeInputs.h:694;本体
PipeFill.cpp:2158-2162;Coverage.def:127 记 kClientResolved——"the server is never asked"
是设计答案,今天的实现恰恰相反)。
- **(b) 读者**:与 GetProgramObject 完全同点成对(2.12 的四处;另 DirectGLES.cpp:8199)。
**strict 标记**:`ValidateProgramName@ShaderStorageBlockBinding`(markers:45)。
- **(c) 载体**:不需要载体——发射端 client 在发 `set_storage_block_binding` 时名字必然已过
前端 validator;server 按 ShaderCso 解析到记录即隐含合法(与 2.12 是同一笔迁移)。
- **(d) 处置**:随 2.12 一起退役;字段行出表(机制"a client-resolved program name probe",
FieldOwnership.def:187-188)。
- **(e)** 依赖:同 2.12。工作量:小(与 2.12 同提交)。包:同 2.12。
### 2.14 RecordError
- **(a) 形态**:sticky forward,**写前端**(错误队列)(PipeInputs.h:695-697;本体
PipeFill.cpp:2164-2186)。15 个里唯一的写方向。
- **(b) 读者(写者)**:Espryt:Managers.cpp:14858、DirectGLES.cpp:11124(另有
Managers.cpp:14949-14951 自明"NO RecordError HERE"的反向先例)。Magma:DirectVulkan.cpp:735、
VulkanRenderer.cpp:1367、:1371、:1427。**strict 标记:无**(车道不制造 GL 错误)。
**rsp 口径注意**:`MGPipeStickyForwardPull(RecordError)` 在分流**之前**(PipeFill.cpp:2165),
所以即使 transport 臂根本不碰前端(:2166-2178 直接 `PostGlError` 后 return),server-stamped
verb 内的每次调用仍被记一次 pull——这是机制的保守,不是残余的债。
- **(c) 载体**:**已存在且已接线**:P5c ev 把 transport 臂改成了 `kEventGlError` 事件
(CONTRACT-P5C §4.2;`ServerSessionInstance().PostGlError`,PipeFill.cpp:2174-2177),
错误消息经 `info->toString()` 过线。剩在 P9 的只是**有序化**(:2170-2173 的注释:
"preserves per-thread program order of error-then-read but not cross-verb interleaving")。
- **(d) 处置**:这是 15 个里唯一"载体已通、剩记账口径"的字段。建议仿 InvalidateCompileEnv 的
P5c ev 处置(FieldOwnership.def:157-163 的先例与 :160-161 的"FieldOwnership 只描述 split
server 的读"论证):把 `MGP_STICKY_FORWARD_PULL` 移到分流之后(或 transport 臂豁免计数),
字段行降 FATAL——论据是"transport 下分流臂已是完整答案,server 侧再落到 monolith 臂即真
缺陷"。需先逐写者核 `OnGlError` 覆盖(消息保真已由 :2174 保证,有序化仍是 P9 的)。
若 f1 双块演习显示monolith 臂在 split 下仍被合法触达,则退而维持 BARRIER_PULLED 到 P9——
双块的红就是判据。
- **(e)** 依赖:fv 的反向通道有序化口径(P9 的边界划分)。工作量:小。包:**fv**——计划
§2.6 明列"OnGlError 有序化",其中触碰 client 内存的部分归 P5f,而本字段 transport 下已不
触碰,剩余正好是有序化与计数口径。
### 2.15 GetBufferBindingSlot
- **(a) 形态**:裸指针 `BindingSlot<BufferObject>* m_bufferBindingSlot[15]`(PipeInputs.h:808;
accessor :605-614,空槽/越界即 poison Fatal)。填充 PipeFill.cpp:131-140——只填
`GlobalBufferTargets`,**Index 恒 null**(:132-136 的注释:GLContext 经绑定 VAO 的
element-buffer 槽推导,"a derivation no FillPoints.def row can copy")。
- **(b) 读者**:按 target 分片(Coverage.def:37-70 的权威拆分:8 个 target 有记录载体,7 个
没有——CopyRead/CopyWrite/PixelPack/PixelUnpack/Texture/DispatchIndirect/Query)。
Espryt:PixelPack 读回 6 站(DirectGLES.cpp:12809、:13791、:14028、:14338、:14769,
Utils.cpp:2353);DrawIndirect/Parameter 族 :440、:976、:1422、:8921、:8999-9000、:9078、
:9151-9152、:9332、:9381,MultiDraw.cpp:308。Magma:DirectVulkan.cpp:283、:407、:470、:538、
:591;VulkanRenderer.cpp:7520(DispatchIndirect)、:11126、:12560-12561、:12661-12662、:12731。
**strict 标记两对**:`GetBufferBindingSlot@ReadPixels`(markers:34,disjunct 1——读点是
ReadPixels 的 PBO 槽 :14337-14338 一带)与 `GetBufferBindingSlot@DrawArrays`(markers:36-39,
wave-3 新:multi-draw 五档被强制后 indirect 档浮出,注释 :35-38 记了它"was named before it
could fire")。
- **(c) 载体**:`set_indirect_buffers` 只带 DrawIndirect+Parameter 对(Coverage.def:44-45,
MGPIndirectBuffers 的自明范围);`resource_readback`(MGPReadback)带目标 buffer 句柄
(MGPipeTypes.h:1439-1443)但 PixelPack 槽的读在 verb 内、不在记录解析点。PixelPack 等
7 target **无任何 call**(Coverage.def:47-57 逐 target 点名)。
- **(d) 处置**:本字段不能一次翻类,只能按 target 渐进——而字段按 Coverage.def:61-69 的裁定
必须保持一行,所以 P5f 的工具是 **`MGP_FIELD_OWNERSHIP_ARG_LIST` 的按参数例外行**
(GetPixelStoreParameters,1 已是先例,FieldOwnership.def:214-216):有载体的 target 逐批窄出
BARRIER_PULLED,Query target 两端都无读者(Coverage.def:56-57)可率先窄为 FATAL,
DispatchIndirect(Coverage.def:50-55 点名"两端都读且无 call")需要扩 `set_indirect_buffers`
或新记录(P13 题材),PixelPack 随 readback 记录改造(P9 题材)。全部窄完后字段行出表。
- **(e)** 依赖:readback 记录化(P9)+ indirect 扩 target(P8/P13)——**三个退役相分属三个
后续阶段,是 15 个里唯一需要 P5f 主动把外阶段题材拉进来的字段**。工作量:大
(15 target × 双后端 × 三类载体)。包:无归属(缺口最大的一行)。
## 3. 包归属汇总与缺口
| 字段 | 处置方向 | 载体状态 | 包(计划 §5 字面) | 建议包 |
|---|---|---|---|---|
| GetBoundVertexArray | 升 RECORD_SUPPLIED | bind_vertex_elements 已在 | fm(Magma 半);escalation 对无包 | fm + P8 议题 |
| GetBufferBindingPoint | 升 RECORD_SUPPLIED | set_shader_buffers 已在(XFB 除外) | fm;XFB 半无包 | fm |
| GetFramebufferBindingSlot | 升 RECORD_SUPPLIED | set_framebuffer_state 已在 | fm | fm |
| GetImageTextureBinding | 升 RECORD_SUPPLIED | set_shader_images 已在 | fm | fm |
| GetProgramForDraw | 升 RECORD_SUPPLIED | set_draw_program 已在 | fm | fm |
| GetProgramForDispatch | 升 RECORD_SUPPLIED | set_dispatch_program 已在 | fm | fm |
| GetTextureUnitObject | 升 RECORD_SUPPLIED | set_sampler_views 已在 | fm | fm |
| GetTextureObject | accessor 死亡(FATAL 形态) | MGPCopyRegion 句柄已在 | fm 边缘(读者在 sink) | fm |
| GetBufferBindingPointCount | 升 APPLIER_DERIVED | applier 窗口可推 | 无 | fm |
| HasOpenTransformFeedbackSpan | 升 APPLIER_DERIVED | 需 applier 自推(依赖 t2 sink) | 无 | fm 或 P9 边界 |
| GetTransformFeedbackProgram | 升 RECORD_SUPPLIED(**须先造数据**) | 无——begin_stream_output 需扩 | fm(Magma 半);Espryt 半标 P3b/P4b | fm + fr 裁决 |
| GetProgramObject | accessor 死亡 | MGPStorageBlockBinding.ShaderCso 已在 | 无 | fm 或 fr |
| ValidateProgramName | accessor 死亡 | 随 2.12,无需载体 | 无 | 同 2.12 |
| RecordError | 降 FATAL(计数口径修正后) | kEventGlError 已接线 | fv(唯一字面匹配的) | fv |
| GetBufferBindingSlot | 按 target 渐进(ARG 例外行) | 8/15 有载体,7/15 无 | 无(P8/P9/P13 混合) | 缺口最大 |
**三个给 §5 定包的输入**:
1. fm 的字面"Magma 的 9 个字段"应扩写为"对象类字段的双后端退役"——否则 6 个字段无家可归,
且 fm 内部还混着 sink 侧读者(GetTextureObject)与 Espryt 残余读者(GetTextureUnitObject@
CopyTexImage2D、GetFramebufferBindingSlot@ReadPixels、GetTransformFeedbackProgram@DrawArrays)。
2. 两个 escalation 对(client VA,P8 题材)与 GetBufferBindingSlot 的 P8/P9/P13 混合退役,
是出口门 2(markers 清空)与范围表 §3(不取 P8 staging)之间唯一的正面冲突,需要计划
层面裁决:拉进来,或为 escalation 对留具名豁免。
3. 顺序约束:7 个指针行的 `EmittedCallSuppliesTheWholeField` FALSE 臂删除(PipeFill.cpp
:2582-2602)与对应最后一批改者必须同提交,ID-112 的两个 `static_assert`
(PipeFill.cpp:2789-2809)是构建期强制执行者;`FieldOwnershipTest` 钉住非 sticky
BARRIER_PULLED 清单(FieldOwnership.def:66-67),gen 脚本 self-test 钉住拒绝集恰为 8
(scripts/gen_pipe_field_ownership.py:972-974)——每退役一行,这三处与
strict-expected-markers.txt 的同提交更新是机械义务。
+175
View File
@@ -0,0 +1,175 @@
# f0 普查 · §2.2 Magma(DirectVulkan)跨角色读写
> 基线:`feat/disaggregated @ 8b68b92c`(代码头 25fba0d5 之后的提交只动文档)。行号均以该 HEAD 为准。
> 范围:`MobileGL/MG_Backend/DirectVulkan/**` 一侧所有直接读前端对象 / 读 client 镜像 / 写 client 存储的站点。
> 只读普查:本文件是唯一产物,未改任何代码、未构建、未跑测试。
## 0. 总判断
1. **Magma 在 transport 下的 draw/dispatch 路径今天根本跑不通**,这不是推断而是守卫的直接后果:顶点/索引/UBO/SSBO/indirect 缓冲的同步都汇到 `VkBufferManager::AcquireResidentSlice` / `AcquireStreamedSlice`,两者第一动作就是 `bufferObject->SyncPersistentMappedRange()`(VkBufferManager.cpp:657、:708),而该方法在 transport + apply 线程下无条件 `Fatal{RoleViolation, "buffer-legacy-arm"}`(MG_State/GLState/BufferState/BufferObject.cpp:415-440,守卫条件 `PushIsArmed() && OnServerRole()`,PersistentMapTracker.cpp:526-535)。CI 侧与之一致:`integration-magma-split` 只有两条 `DirectVulkan.Split.NamedBlit.*`,且**预期红**(在 Clear 的 `GetFramebufferBindingSlot` 读处就 abort,MobileGL/MG_IntegrationTest/CMakeLists.txt:2411-2437,.github/workflows/test.yml:1353-1381)。所以"今天为何能工作"对本目录绝大多数站点的诚实回答是:**monolith / pull 臂下同线程直接成立;inproc split 下只有不设缓冲的 verb(Clear/Blit/GenerateMipmap 等)能走到,凡是 draw 带缓冲即具名 Fatal**。lockstep(Magma 永不发布 `kCapRunAheadApply`,CONTRACT-P5E.md:68、§6.1)保证每条记录设障,使未加守卫的 BARRIER_PULLED 读在非 strict 下作为 admitted pull 计数(`rsp`)而非 Fatal——但 guard 类站点不吃这一套,任何 strictness 下都 abort。
2. **T5 属实且已被 P5c tx 处理**:transport 下生成 mip 改写 server 的 staged shadow,不再写 client 的 level 存储;但留了两处具名残余(见 §2)。
3. **C1 四处 caps 读属实已改走 server backend**(见 §3)。
4. 对象类 BARRIER_PULLED 字段的 Magma 读点全部在 draw/dispatch/blit/readback/mipmap/XFB 路径上,逐字段清单见 §4。其中 8 行的退役标签是 "P5e (Espryt unbarriered), P7 (Magma)",第 9 个 `GetTextureObject` 标的是裸 "P7"(Espryt 半边已在 P5c tx 退役),合计 9 个 Magma 相关行——与任务书的"9 个"一致。
5. 除字段读外,Magma 还有三类结构性跨角色耦合:前端地址键控的 twin registry / memo(§5)、server 在 apply 线程上创建与销毁前端类型对象(§6)、以及对前端 BufferObject 的直接方法调用(§7)。
## 1. 术语与机制前提(本次普查用到的)
- `MGB_CTX` 在 push 构建下 = `&MG_Pipe::gPipeInputs`(MG_Pipe/PipeInputsSwitch.h:17-21);在 pull 构建下 = `MG_State::pGLContext`。Magma 全树的对象访问器读都经此宏。
- lockstep:Magma 臂的 `CallMask` 不含 `kCapRunAheadApply`,每条记录都设障,apply 时 client 停在 `WaitForApplied`。
- 双层守卫(均在 `MOBILEGL_BUILD_DISAGGREGATED` 下):`Fatal{RoleViolation, "MGPipeSlots"}`(MG_Impl/Pipe/SlotAllocator.cpp:32-70,挂在 Acquire/FindByLifetimeId/Free,:330/:317/:339);`Fatal{RoleViolation, "buffer-legacy-arm"}`(BufferObject.cpp:47-66,挂在 MappedData :992 / ShadowAllocationBytes :1002 / GetChangeSerial :1025 / HasDefinedContent :1034 / IsMapped :1049,以及 SyncPersistentMappedRange :434-437);`Fatal{RoleViolation, "texture-legacy-arm"}`(MipmapStorage.cpp:30-60,被 `MGPipeTextureLegacyArmScope` 豁免)。
- Magma 的具名豁免 scope:`MagmaP7AllocatorDebtScope`(MG_Impl/Pipe/SlotAllocator.h:223-248,P5e ruling 12 改名后专指 Magma 的四笔 apply 线程分配器债,且按后端种类 keyed——Espryt 借用即 Fatal,SlotAllocator.cpp:47-55)。
## 2. T5(P5c 审计行,ROADMAP.md:57)核验:属实,已迁移,但有残余
**原状**(ROADMAP 记的行号已漂移):`EnsureGenerateMipmapStorageAllocated` 现在位于 `Renderer/VulkanRenderer.cpp:1590-1637`(非 :1562-1590),其中 `texture.AllocateStorage(...)` :1632、`texture.MarkStorageDirty(...)` :1633 直接写前端 `TextureObjectMipmap` 的 level 存储。调用点在 `GenerateMipmap`(def :11378,非 :11317)。
**现状(P5c tx 已落地)**:`GenerateMipmap` 在 transport 下改走 `EnsureGenerateMipmapShadowAllocated`(:1655-1682),把生成的链定义在 server 的 `StagedTextureStore` 上、以 twin 地址为键(`KeyForTwinAddress`,:1670);monolith 臂保持原样(:11464-11474 的三元)。**跨进程:成立。**
**残余两处,均已被代码注释自认**:
- 形状读:`EnsureGenerateMipmapShadowAllocated(*resource, baseMipLevel, texture->GetUploadTargets())`(:11470)——upload-target 列表仍读前端对象。注释 :1653-1654 明说:"The upload-target list still comes from the frontend object - a shape READ, the P7 registry's residual"。
- `GenerateMipmap` 头部仍经 `MGB_CTX->GetTextureUnitObject(...)`(:11401,BARRIER_PULLED)取得前端纹理,并把它传给 `m_textureManager->NeedsMipChainGrowth(*texture)`(:11420)与 `SyncTextureAndGetDescriptor(*texture)`(:11442、:11477)——后者进入 §5.1 的前端地址键控同步,读 client mip shadow(见 §5.2 的 texture-legacy-arm)。
**P5f 处置建议**:T5 本体无需再做;残余随 §4 的 `GetTextureUnitObject` / §5.1 的纹理 twin 迁移一起消失(upload-target 列表可随纹理描述符/记录供给)。
## 3. C1(ROADMAP.md:67)核验:四处全部属实,已改走 server backend
四处均在 `MG_Config::Transport != Monolith` 时读 `MG_Remote::Server::ServerLoopInstance().Backend()->GetDynamicParameters()`(server 自己的 backend),monolith 臂保持 `pActiveBackendObject`:
| # | 站点(当前 HEAD) | 函数 |
|---|---|---|
| 1 | `DirectVulkan.cpp:714-733`(split 臂 :723-728) | `ShaderStorageBlockBinding`,`MaxShaderStorageBufferBindings` 越界检查 |
| 2 | `Renderer/VulkanRenderer.cpp:676-712`(split 臂 :682-693) | `ApplyLineWidthState`,aliased line-width clamp |
| 3 | `Renderer/VertexInputStateFactory.cpp:252-264` | `narrowFloat64Arrays` 判定(`SupportsFloat64VertexAttributes`) |
| 4 | `Renderer/VertexInputStateFactory.cpp:515-527` | `ToVkVertexFormat` 的 Long 臂同一 flag 判定 |
(ROADMAP 记的 :713-715 / :667-676 / :249-250 / :502-503 均略有漂移,指向相同代码。)`DirectVulkan.cpp:20` 起的 `#if MOBILEGL_BUILD_DISAGGREGATED` include 即为此。**跨进程:成立**(server 读自己的 backend 对象)。Espryt 回落臂(`DirectGLES/Utils.cpp:50`)不在本普查范围。
## 4. 九个 "P7 (Magma)" BARRIER_PULLED 字段的 Magma 读点
字段归属以 `MG_Pipe/FieldOwnership.def:53-107`(非 sticky 行)与 :178-192(sticky forward 行)为准。"draw 路径"= 经 `SetupDraw`(:6801) / `TrySetupDrawFastPath`(:6375) / `GetOrCreatePipeline`(:5250) 到达;"dispatch 路径"= 经 `DispatchCompute`(:7419) / `DispatchComputeIndirect`(:7472) 到达。
### 4.1 `GetBoundVertexArray` — 6 站,全部在 draw 路径
- `DirectVulkan.cpp:805`(`BuildClosedLineLoopIndices`,LINE_LOOP 重写):读出 VAO 的索引缓冲绑定后 **`indexBufferShared->SyncPersistentMappedRange()` + `MappedData()`**(:814-815)——后者在 transport 下是 `Fatal{buffer-legacy-arm}`。即 Magma 的 LINE_LOOP indexed draw 在 split 下今天是具名 Fatal,不是静默错。
- `DirectVulkan.cpp:929`(`MultiDrawElementsImpl` :913):只读 EBO 绑定存在性以决定逐条回放。
- `VulkanRenderer.cpp:6446`(`TrySetupDrawFastPath`)、`:6852`(`SetupDraw`):每次 draw 的对象读;fast path 还有 handle 臂(`ResolveVaoHandle`,:6452-6458,`MagmaPipeTrackHArmIsHandles`),但 handle 由 `vao.GetLifetimeId()` 推出——仍以前端对象为源。
- `VulkanRenderer.cpp:12550`(`MultiDrawElementsIndirectCount` :12528)、`:12651`(`MultiDrawElementsIndirect` :12629)。
**今天为何能工作**:同地址空间 + 每条记录设障;值是 client 残余填充的 `SharedPtr<VertexArrayObject>`(PipeFill.cpp:1902-1905 一带,见 FieldOwnership.def:68-71 的引文)。**跨进程:不成立**(前端堆 SharedPtr)。
### 4.2 `GetBufferBindingPoint` / `GetBufferBindingPointCount` — 3 + 2 站,draw 与 dispatch 都到
- `UniformManager.cpp:1205` + `:1200`(`ResolveStorageBufferDescriptor` :1157,SSBO/原子计数器绑定解析,draw/dispatch 共享)。
- `UniformManager.cpp:2035` + `:2030`(`ResolveUniformBufferPayload` :1979,UBO 绑定解析,draw/dispatch 共享)。
- `VulkanRenderer.cpp:11764`(`BeginXfbCaptureForDraw` :11714,`BufferTarget::TransformFeedback`,XFB 捕获,draw 路径)。
三处取到绑定点后都继续 `bindingPoint.GetBoundObject()` 拿前端 `BufferObject`,再走 §7 的 buffer legacy arm。**跨进程:不成立**(`BindingSlotRange1D` 前端基址指针,PipeFill.cpp:2591-2592 的 "four raw bases" 同类)。`GetBufferBindingPointCount` 是 sticky forward(FieldOwnership.def:147-148),退役标签 "P7/P13"。
### 4.3 `GetFramebufferBindingSlot` — 13 站
- draw 路径:`VulkanRenderer.cpp:3158`(`GetShaderTransformFlags`,判 default FBO 决定 Y-flip 编译位)、`:5510`、`:5730`(`GetOrCreatePipeline`,深度/模板附件门控与 render-pass 相容性)、`:6360`(`GetBaseTransformFlagsRaw` 的 MOBILEGL_ASSERT——**注意:INFO 级别下断言被编译掉,这站不总是执行**)、`:6465`(fast path)、`:6845`(`SetupDraw`)、`UniformManager.cpp:560`(`ResolveSamplerDescriptor` 的 feedback-loop 诊断)。
- 其余 verb:`VulkanRenderer.cpp:7648`(`Clear`——**这就是 integration-magma-split 预期红的那一对 `GetFramebufferBindingSlot@Clear`,CMakeLists.txt:2411**)、`:8016`(`QueueClearBufferPayload`)、`:9024`/`:9025`(`BlitFramebuffer` 的 bound 臂;具名臂见 §5.4)、`:9660`(`CopyTexSubImage2D`,Read 靶)、`:10371`(`ReadPixels`,Read 靶)。
读到的都是前端 `BindingSlot<FramebufferObject>` 指针再 `GetBoundObject()`。**跨进程:不成立**。
### 4.4 `GetImageTextureBinding` — 4 站,draw 与 dispatch 都到
- `UniformManager.cpp:1021`(`ResolveStorageTexelBufferDescriptor` :991)、`:1312`(`ResolveStorageImageDescriptor` :1276)、`:1867`(`CollectStorageImageTextures` :1818)、`:1955`(`CollectSamplerImageFeedback` :1900,sampler/image 子资源重叠危害探测)。
返回前端 `ImageTextureBinding` 基址后的元素。**跨进程:不成立**。
### 4.5 `GetTextureUnitObject` — 8 站(与 FieldOwnership.def:90 的 "8 Magma sites" 吻合)
- `UniformManager.cpp:511`(`ResolveSamplerDescriptor`,随后 `textureUnit.GetSamplerObject()` :512 取前端 SamplerObject)、`:787`(`ProgramSamplesOnlySingleLevelTextures`)、`:826`(`ResolveSamplerTexture`)、`:852`(`ResolveSamplerTextureRaw`)、`:1679`(`ResolveSampledBinding`)。
- `VulkanRenderer.cpp:9652`(`CopyTexSubImage2D`)、`:11157`(`GetTexImage`——class C,split 下 client 侧已具名拒绝,EmitTables.cpp:1673-1675)、`:11401`(`GenerateMipmap`)。
指针指进 client 的纹理单元数组(PipeInputs.h:669 的解引用 + PipeFill.cpp:274 的裸指针填充)。**跨进程:不成立**。
### 4.6 `GetProgramForDraw` / `GetProgramForDispatch` — 3 + 2 站
- draw:`VulkanRenderer.cpp:6384`(`TrySetupDrawFastPath`)、`:6815`、`:6853`(`SetupDraw`)。
- dispatch:`VulkanRenderer.cpp:7424`(`DispatchCompute`)、`:7476`(`DispatchComputeIndirect`)。(FieldOwnership.def:97 注里写的 :7327 已漂移。)
拿到前端 `SharedPtr<ProgramObject>` 后整个 program 内容都从前端对象读:`ProgramFactory::ComputeHash`/`GetOrCreateProgram` 读 `program.GetGeneratedSpirv()`(ProgramFactory.cpp:2469-2473、:3472);`ResolveUniformBufferPayload` 读 `program.GetUBOData()/GetUBOSize()`(UniformManager.cpp:1992-1993,具名 UBO host payload,ROADMAP.md:142 D-B8 的已知形状)、`GetUniformBlockBinding`/`GetUniformBlockName`/`GetUBOSizeAt`(:2028/:2033/:2063)。**跨进程:不成立**。Espryt 在 P5e pa 的证明("记录早就带着逐 link 的 ProgramArchive",ROADMAP.md:31)对 Magma 尚未接线——Magma 的 program 记录载体是否存在,本次未核实到(**未知**,fm 包第一天要回答)。
### 4.7 `GetTransformFeedbackProgram` — 2 站,draw 路径
- `VulkanRenderer.cpp:11725`(`BeginXfbCaptureForDraw`)、`:11824`(`EndXfbCaptureForDraw`)。前端 `SharedPtr<ProgramObject>`。**跨进程:不成立**。配套读还有 `IsTransformFeedbackActive/Paused`(:11716/:11722,值类,记录供给,成立)、`GetBoundTransformFeedbackLifetimeId`(:11667,值类)、`GetTransformFeedbackGeneration`(:11801,值类)、`HasOpenTransformFeedbackSpan`(:11684,sticky forward,"P7/P9"——前端 XFB span 表查询,**不成立**)。
### 4.8 `GetTextureObject` — 1 站
- `VkTextureManager.cpp:810`(`SyncTextureAndGetDescriptor` 内):按 GL name 查前端纹理对象表以注册 alive 追踪;查不到就 `texture.weak_from_this()`(:821)——把前端对象的 control block 存进 server 侧 map。sticky forward,退役标签裸 "P7"。**跨进程:不成立**。
### 4.9 相邻但非 "P7 (Magma)" 的字段读(列出以免漏网)
- `GetBufferBindingSlot`("P8/P9/P13",15 靶合一数组)13 站:`DirectVulkan.cpp:283`(`ResolveIndirectCommandBytes`,DrawIndirect)、`:407`(`MultiDrawArraysIndirect`)、`:470`(`MultiDrawArraysIndirectCount`,Parameter)、`:538`(`DrawElementsIndirect`)、`:591`(`DrawArraysIndirect`);`VulkanRenderer.cpp:7520`(`DispatchComputeIndirect`,DispatchIndirect)、`:11126`(`GetTexImage`,PixelPack)、`:12560`/`:12566`、`:12661`、`:12731`(multi-draw indirect 各站);`UniformManager.cpp:910`(`ResolveTexelBufferDescriptor`)、`:1053`(`ResolveStorageTexelBufferDescriptor`——这两站是 `textureBuffer->GetBufferBindingSlot()`,前端 TextureBuffer 对象自身的方法)。indirect 路径取到前端 buffer 后普遍跟 `SyncPersistentMappedRange()`(如 DirectVulkan.cpp:285、VR:7525)——transport 下具名 Fatal。
- `GetTransformFeedbackPausedPrimitiveCounter`(FATAL 行):`DirectVulkan.cpp:1256`(`GetQueryResult64` :1228)、`:1303`(`BeginXfbPrimitivesQuery` :1294)。两个入口都是 class C(EmitTables.cpp:1690-1693),split 下 client 侧先拒绝,**今天不可达**;行为一致,归 §2.7,不归 P5f。
- `GetPixelStoreParameters(false)`(pack 半,APPLIER_DERIVED):`VulkanRenderer.cpp:11127`。**跨进程成立**。注意 FieldOwnership.def:208 注里写的 ":10980" 已漂移(该行现在是 MOBILEGL_ASSERT),真身在 :11127——文档漂移,供 fm 顺手修注。
## 5. 前端地址 / 前端身份键控的 twin registry 与 memo
### 5.1 `VkTextureManager`(纹理 twin)
- `TextureIdentity{ ITextureObject* texture, Uint64 lifetimeId }`(VkTextureManager.h:126-145),`m_textureResources` / `m_aliveObjects` 均以此为主键;hash 混入前端对象地址(:144)。`m_syncedTextureMemo[8]` 直接以 `const ITextureObject*` 比较(:708-715,"A dead-then-reused texture address cannot false-hit: the new object carries a new lifetime id")。
- `SyncTexture`(VkTextureManager.cpp:1745-1796+)整体裹在 `MGPipeTextureLegacyArmScope`(:1755)里——注释 :1748-1754 自认:"Magma's texture sync still reads - and clears - the CLIENT's mip shadow: the dirty scan below, and UploadDirtyMipLevels' texel reads / region reads / MarkStorageDirty(false) clears"。**这是 §2.2 意义上最大的一块:读 client 纹素字节 + 写(清脏)client 存储**,guard 是 texture-legacy-arm,scope 内豁免。
- `ToStorageMipLevel/ToStorageArrayLayer(ITextureObject*)`(VkTextureManager.h:113-126)全树散布,读前端 view 父链。
**跨进程:不成立**。P5c 只迁了 Espryt 的 sync(走 StagedTextureStore 句柄键),Magma 的 server 侧 sync 是 P7 的活。
### 5.2 `VkBufferManager`(缓冲 twin)
- twin 指针直接**写进前端对象**:`bufferObject->SetBackendResource(resource)`(VkBufferManager.cpp:305、:601),读出对称(:291、:299)。`GetBackendResource/SetBackendResource` 是前端 BufferObject 上的 mutable 槽——server 写 client 内存。
- `AcquirePersistentMap`(:598-646)seed 时读 `bufferObject.MappedData()`(:631)。
- `AcquireResidentSlice`(:649-699)/ `AcquireStreamedSlice`(:701-):每 draw 调用,含 `SyncPersistentMappedRange()`(:657/:708)、`GetSize()`、`GetChangeSerial()`(:726)、`MappedData()` 整缓冲上传(:682/:768)。transport 下第一行即 `Fatal{buffer-legacy-arm}`(见 §0.1)。
**跨进程:不成立**;且今天是 guarded Fatal,不是静默读。
### 5.3 draw 路径上的地址键控小缓存
- `ConvertedVertexStreamKey.buffer = sourceBufferShared.get()`(VulkanRenderer.cpp:4106-4116)+ `GetChangeSerial()`(:4107)+ `MappedData()` 转换读(:4127)——`UploadAndBindVertexStreams` 内,前端 buffer 地址做 memo 键 + 读 client 字节。
- `UploadAndBindIndexBuffer` 的 restart-index 替换臂:`indexBufferShared->SyncGpuWrites()` + `MappedData()` + `GetSize()`(:4421-4423)——`SyncGpuWrites` 在 transport 下有反向通道臂(BufferObject.cpp:571-590 起),但那是 client 线程语义;apply 线程调它的行为本次未逐行走通(**未知**,大概率在 `MappedData()` 的 guard 处先 Fatal)。
- `SetupDrawSnapshot`(VR:6388-6470 一带)以 `program.GetLifetimeId()`、前端 VAO/FBO 的地址与 lifetime id 做键(:6446-6468)。
- `UniformManager.h` 的 memo 族(`SamplerResolveMemo` :418 起、`FastRebindMemo` :363、`m_globalUboMemo` :392、`m_descriptorReuseMemo` :341)以 binding + program lifetime id / 版本号做键——键本身是值,但值全部来自前端对象读出。
- `ProgramFactory::ProgramLookupCache.program` 存前端 `ProgramObject*`(ProgramFactory.h:582-587)。
- `g_programResourceCaches`(DirectVulkan.cpp:103-108)以 **GL name** 为键、lifetime id 校验(:166-189)——`GetShaderStorageBlockIndex/Binding`(:313/:332,client 侧查询路径)与 `ShaderStorageBlockBinding`(:714,P5b 的 `set_storage_block_binding` 记录会经 sink 在 apply 线程到达)都会进 `TryGetDirectVulkanProgram`(:274-280),后者调 sticky forward `ValidateProgramName` + `GetProgramObject`("P9" 行)——**server 按名字探前端名字表,跨进程不成立**;GL-name 键控的 cache 本身是 server 内存,成立。
### 5.4 具名 blit 端点解析(P5c G6 / P5e ruling 12)
- `VulkanRenderer.cpp:8974-9022`:split 臂从 applier 的 verb 句柄工作区取 `ReadFbo/DrawFbo` 句柄(:8989-8991),随后 `resolveEndpoint` lambda 里**直接拼写 `MG_State::pGLContext`**(:8997、:9004、:9005)与 `MG_Pipe::MGPipeSlots()`(:8999、:9003),全程裹 `MagmaP7AllocatorDebtScope`(:8995)。这是 DirectVulkan 全树仅有的 `pGLContext` 与 `MGPipeSlots()` 直接拼写。**跨进程:不成立**(前端 FBO 池 + client 分配器)。P7 的退役方式注释已写明(:8983-8984:"P7 retires it by carrying the object identity in the record")。
- 附带发现:**ARCHITECTURE.md:460 的 C 门原文是 `grep -c 'pGLContext' MG_Backend/ == 0`,当前 HEAD 实测非零**——`VulkanRenderer.cpp` 3 处代码命中(:8997/:9004/:9005)+ `MGPipe/PipeInputs.h:822` 一处注释。该门是否仍在 CI 以 ==0 执行、还是已改为白名单,本次未核实(**未知**);若仍按字面执行则门已破,值得在 f1 之前确认。
### 5.5 `MagmaPipeIdentityTables`(MagmaPipeArms.h:370-529)
Magma 自己的 {slot, gen} mint,按 lifetime id 把前端 VAO/Buffer 映到 server 侧 slot 表(`HandleOf` :515-518);`VertexInputStateFactory::VaoBackendMemos` 以该 handle 为键(VertexInputStateFactory.h:149-171)。表本身是 server 私有内存,**成立**;但 lifetime id 的获取仍靠读前端对象(`vao.GetLifetimeId()`),身份源是 §4.1 的字段读—— mint 干净、水源脏。P5f 无需动表,动的是喂它的字段。
## 6. server 在 apply 线程上创建 / 销毁前端类型对象
- 隐藏 blit 资源:`VulkanRenderer.cpp:4475-4491`(`ShaderObject`/`ProgramObject`)、`:4527-4540`(两个 `SamplerObject`);隐藏 depth-mipmap 资源:`:4567-4617`。销毁走 `ShutdownBlitResources`/`ShutdownDepthMipmapResources`(:4544-4562、:4634-4643),已裹 `MagmaP7AllocatorDebtScope`(:4559、:4640)——注释自认析构会触发 client death helper 的 lifetime-id 探测。注释 :4552-4553 明说:"P7 gives these resources storage that is not a frontend object"。
- UniformManager 占位纹理:`MakePlaceholderTextureObject`(UniformManager.cpp:167-189,`GetFallbackTexture` :1410/:1435、`GetFallbackMultisampleTexture` :1451/:1497-1499 调用),以及 `m_unboundStorageImageTextures`(UniformManager.h:317)。**每构造一个纹理对象都会 `MGPipeMintTextureHandle` + `MGPipeEmitTextureResourceCreate`**(TextureObject.cpp:143-170,push 构建下无条件 mint)——apply 线程上的 `MGPipeSlots().Acquire` 无 scope 即 `Fatal{RoleViolation, "MGPipeSlots"}`(SlotAllocator.cpp:330、:47-59)。也就是说 **Magma 的占位纹理在 split 下首次需要时即具名 Fatal**(与 ROADMAP.md:127 "占位纹理原生化 → P7" 互证)。ProgramObject/ShaderObject/SamplerObject 的构造不 mint(grep 无命中),故隐藏 program/sampler 的创建本身今天不触发守卫,只有析构的探测被 scope 兜住。
**跨进程:不成立**(server 进程里没有 MG_Impl/MG_State 的 client 语义,构造出的对象也不该存在)。P5f 处置:随 P7 的"内部 shader 烘焙 / 占位纹理原生化"换成 server 原生资源;f1 双块演习时这些点会以 Fatal 形式自己报出来。
## 7. 对前端 BufferObject 的直接方法调用(写 client 存储的边)
- `bufferObject->MarkGpuWritten()` 直写臂已按 P5c ev 改掉:三处都是 `#if MOBILEGL_PIPE_PUSH → MGPipeAnnounceBufferGpuWritten(...) / #else 直写` 双臂(UniformManager.cpp:1077-1085、:1241-1247;VulkanRenderer.cpp:11774-11781)。announce 内部经 `MagmaP7AllocatorDebtScope` 做 client 分配器的 lifetime-id 反查(ResourceTracker.h:710-743,:728 是"第四笔债"),找到句柄则走 `OnGpuWritten` → SEG_EVENT;找不到句柄则**回退直写 client 对象**(:738)。**跨进程:announce 主臂成立;回退臂不成立;scope 内的分配器探测不成立**。
- `bufferObject->EnsureGpuResidentStorage()`(UniformManager.cpp:1077、:1239;VulkanRenderer.cpp:11774):transport 下早退 `return false`(BufferObject.cpp:877-880,R-6 的第二道门),但早退之前已读 `m_resource.IsGpuResident()` / `m_isMapped`(:856、:866)——**无守卫的 client 内存读**,今天靠同地址空间成立,跨进程不成立。
- `indexBufferShared->SyncGpuWrites()`(VR:4421):见 §5.3。
## 8. 写 client 存储 / 读 client 镜像的其他站点
- `RecordError`(写前端错误队列):`VulkanRenderer.cpp:1367`、`:1371`(`RecordClearBufferError`/`RecordTextureCopyError` 帮手)、`:1427`(`RecordUnsupportedFramebufferError`)、`DirectVulkan.cpp:735`(`ShaderStorageBlockBinding`)。transport 下 `PipeInputs::RecordError` 改走 `ServerSessionInstance().PostGlError` → SEG_EVENT `kEventGlError`(PipeFill.cpp:2164-2178),client 在 drain 点入账。**跨进程:成立**(排序是 P9 的已知债)。
- `InvalidateCompileEnv`(写前端编译环境):`BackendObject_DirectVulkan.cpp:390`(`InitCapabilities`)、`:788`(`ApplyVulkanCapabilitiesForTesting`)。transport 下 `PipeInputs::InvalidateCompileEnv` 已删除为空操作(PipeFill.cpp:2143-2156,失效改由 caps 重发布完成)。**跨进程:成立**。
- `SwapchainObject` 默认 FBO 附件写:transport 下走 `OnSurfaceChanged` → SEG_EVENT(SwapchainObject.cpp:323-339),monolith 臂仍直写 `pDefaultFramebufferInfo` 的 `AllocateStorage/SetInternalFormat`(:342-358)。**跨进程:成立**(P5c ev / R3 已处理,ROADMAP.md:64)。
- `GetTextureImage`(VR:11162-11209+):入口签名直接吃前端 `SharedPtr<ITextureObject>`(DirectVulkan.h:101 声明、DirectVulkan.cpp:768 入口),内部 `SyncTextureAndGetDescriptor(*textureObject)`(:11175)+ 无 GPU 存储时回退 `MG_Impl::GLImpl::CopyTextureImageToClientOrPBO_State`(:11205)——直接调 MG_Impl 函数读 client shadow 写 client 目标缓冲。但两个 GL 入口都是 class C(EmitTables.cpp:1673-1675),split 下 client 先拒绝,**今天不可达**;若 P9 要放开,这是整条臂的跨进程形状问题,先记着。
## 9. 值类读取(非债,列出备查)
以下 accessor 在 DirectVulkan 下被读、但属 RECORD_SUPPLIED / APPLIER_DERIVED,跨进程成立,不需 P5f 动作:`IsCapabilityEnabled(Indexed)`、`GetRenderStateParameters(Version)`、`GetPipelineStateVersion`、`GetStencilState`、`GetColorMaskIndexed`、`GetDepthMask`、`GetBlendColor/Func/Equation`、`GetScissorBox`、`GetViewportIndexed`、`GetDepthRangeIndexed`、`GetClearColor/Depth/Stencil`、`GetActiveTextureUnit`、`GetCurrentVertexAttribute`、`GetPatch*`、`GetPolygon*`、`GetLineWidth`、`GetLogicOp`、`GetCullFaceMode`、`GetDepthFunc`、`GetMinSampleShadingValue`、`GetClampReadColor`、`GetProvokingVertexMode`(VR:13229)、`IsTransformFeedbackActive/Paused`、`GetTransformFeedbackGeneration`、`GetBoundTransformFeedbackLifetimeId`,以及三 shutter `GetTextureBindGeneration`/`GetSamplingResolutionGeneration`(VR:6477/:6659/:6907/:6913/:7031/:7050/:7361/:7387 等,P5c rv 起 APPLIER_DERIVED,server 自答)。`g_dynamicStateShadow`(VR:290-409)是纯 server 侧 memo,成立。
## 10. 未知 / 留给 fm 的问题
1. Magma 的 program 内容(SPIR-V、UBO 初值)是否已有记录载体(类似 Espryt 的 ProgramArchive)——本普查未在 wire schema 侧核实;若没有,4.6 是 fm 最大的一块。
2. `SyncGpuWrites` 从 apply 线程在 transport 下的实际行为(BufferObject.cpp:590 起的臂是否假设 GL 线程)。
3. ARCHITECTURE.md:460 的 C 门是否仍以 ==0 执行(当前实测非零,见 §5.4)。
4. indirect draw 无绑定缓冲(client 指针)时,记录在 wire 上如何携带命令字节、server 端 `ResolveIndirectCommandBytes` 的 client 指针解引用在严格双块下的归类(指针随记录过来,值无意义——大概率走 client-memory 升级的具名拒绝,待 f1 的红来确认)。
5. `GetTexImage`/`GetTextureImage` 若 P9 放开,VR:11162 起的整条前端对象签名链需要重新设计(§8)。
+225
View File
@@ -0,0 +1,225 @@
# f0 普查子项:§2.5 registry 重键普查(`MGPipeFrontendKeyedRegistryScope`)
> 基线 `feat/disaggregated @ 8b68b92c`(代码头 `25fba0d5`,其后仅文档提交)。只读普查,未改动任何代码。
> 路径省略前缀 `MobileGL/`。行号均在当前 HEAD 实测。
## 0. 范围与实际 grep 结果
计划 §2.5 写"约 12 个活跃站点(:2542、:4505、:5540、:5659、:5940、:6490、:6576、:7092、:7682、:8213、:11231 …)"。
当前 HEAD 实测(`rg MGPipeFrontendKeyedRegistryScope`,全树):
- **`MG_Backend/DirectGLES/DirectGLES.cpp`:13 处构造点**(比计划列举多 `:12606`、`:14461`),另有 `:2090` 一处注释记录 vi 家族已删除的 VAO 站点(非构造点)。
- `MG_Backend/DirectGLES/Managers.cpp`:**11 处**(`:3063`、`:4689`、`:6101`、`:8934`、`:9762`、`:9816`、`:10953`、`:10974`、`:14102`、`:14463`、`:14760`)。
- `MG_Test/Wire/RemoteClientTest.cpp:1930`、`:1948`:测试夹具,非生产路径。
本报告主体是 DirectGLES.cpp 的 13 处;Managers.cpp 的 11 处在 §4 作附注(同一豁免家族,超出本子项口径但处置同类)。
## 1. 机制回顾:scope 保护的是什么,P5e id 已经重键了什么
**scope 的语义**(`MG_Impl/Pipe/SlotAllocator.h:250-282`、`:272-282`;实现 `SlotAllocator.cpp:146-157`):
P5c G6 的具名豁免——豁免的探测是"server 私有、但以**前端身份**键控的 twin registry 查询"。
P5e(id,CONTRACT-P5E §4.4)把豁免条件收窄为**仅当前记录设障**(`MGPipeApplierCurrentRecordIsBarriered()`):
设障时 client 停在 `WaitForApplied` 里,其分配器与 lifetimeId→slot 映射静止,只读探测无撕裂;
未设障记录下 scope 不豁免任何东西,探测即 `Fatal{RoleViolation, "MGPipeSlots"}`(`SlotAllocator.cpp:32-70`)。
monolith 下守卫首行即返回(`SlotAllocator.cpp:33`),scope 只是标记。
**被保护的"键"是什么**。七张 twin 表全部是 `BackendSlotTable`/`TwinRegistry`(`Managers.h:1579` VAO、`:2118` 纹理、`:2297` FBO、`:3110` 程序、`:3298` 采样器、`:3383` sampler-view、`:3449` renderbuffer)。
P5e(id)之后,**每张表都已按 {slot,gen} 键控**:`FindByHandle` / `GetOrCreate(MGPipeHandle)` / `ReleaseByHandle` 全部存在
(`SlotTables.h:351`、`:461`、`:486`),Gen 检查、composite band、ABA 语义齐备(CONTRACT-P5E §4.2/§4.3)。
scope 站点走的是**前端身份半边**:
- `Find(StateObject*)` ≡ `FindByHandle(HandleOf(stateObj))`(`SlotTables.h:477-480`),
`HandleOf` = `stateObj->GetLifetimeId()` → `MGPipeSlots().FindByLifetimeId(kind, lifetimeId)`(`SlotTables.h:501-517`)——
即"前端对象指针 → lifetime-id → client 分配器探测"。这是 **lifetime-id 键**,不是裸地址键;
- `GetOrCreate(const StatePtr&)` 在 miss 时经 `Acquire` **铸造**句柄(`SlotTables.h:263-298` 一带,守卫在 `:298`);
- legacy-memos 臂(`MOBILEGL_PIPE_LEGACY_MEMOS`)才是裸前端地址键 + weak_ptr owner-equality(`DirectGLES.cpp:75-153`)。
所以每张表"按句柄重键缺什么"的答案**不是缺表的句柄能力**——表早就有了——缺的是**句柄到达该站点**:
要么记录已带句柄但 sink/后端没用它(:2542、:12606),要么该站点整个是 monolith glue,transport 下根本不该到达(其余 11 处)。
**P5e id 已删的**:draw path 上约 20 处 scope 构造点连同其包裹的前端探测(CONTRACT-P5E §4.4 列旧行号;
当前 HEAD 的 `DirectGLES.cpp:2086-2093` 注释记录了 VAO 站点是"删除而非收窄",删除使回退变响)。
**存活的口径**(CONTRACT-P5E §4.4 末段):设障行站点(CopyTex / GetTexImage / mipmap-shape /
set_storage_block_binding / detach-walk)、两个 verify 臂、`BufferImpl::HandleOfBuffer`。
注意该列举写于 P5e 基线,当前 HEAD 已有漂移:CopyTex 与 mipmap 已改走句柄(见下),GetTexImage 在 wire 上已是 class-C 具名拒绝(见 :14461 条)。
## 2. 总结论
13 个站点分两类:
- **A 类(P5f 跨进程必需,2 处)**:transport 下真实活着、被设障行保护的前端键探测——
**`:2542`(resource_copy_region 的 CopyImage 端点同步)** 与 **`:12606`(set_storage_block_binding)**。
这两处的记录**早已携带句柄**(`MGPCopyRegion::Src/Dst`,`MGPipeTypes.h:1469`;`MGPStorageBlockBinding::ShaderCso`,`:1792`),
sink/后端没用而已。
- **B 类(P3b/P4b 债 / monolith glue,11 处)**:在受支持的 transport 配置下不可达——或前面已有 transport 具名拒绝/abort
(:6576、:7682),或臂选择器先行分流到 by-handle 臂(:4505、:5540、:5659、:5940、:6490、:7092),
或 wire 槽位本身是 class-C 拒绝(:14461),或仅 monolith 路径调用(:11231),或**全树无调用者**(:8213)。
这些在"拆进程后还成立吗"的判据下**不成立也无妨**,因为它们到达不了 server 进程;
它们退役的正确归属是 P3b/P4b 的 monolith-glue 清理,而不是 P5f 的 wire 完备性。
## 3. DirectGLES.cpp 逐站点
### A 类:transport 下活着的设障行站点
#### :2542 — `TextureImpl::SyncTextureObjectToBackend(textureObject, imageBindable)`
- **保护的 registry**:`TextureImpl::g_backendTextureObjects`(纹理 twin 表)。
- **今天的键**:`Find(textureObject.get())` / `GetOrCreate(textureObject)`(`:2544-2546`),slot 臂上经 lifetime-id 探测;
注释自己写明(`:2538-2541`)。
- **对象是否已有 {slot,gen}**:有。纹理的句柄由 `resource_create/respecify` 先行(CONTRACT-P5E §4.2 纹理行)。
- **transport 下谁到达这里**:`DirectGLES::CopyImageSubData` → `MakeGLESCopyImageEndpoint`(`:12301`),
其端点由 sink `ServerVerbSink::OnResourceCopyRegion`(`MG_Remote/Server/PipeApplier.cpp:856-859`)
用 **BARRIER_PULLED 的 `gPipeInputs.GetTextureObject(GlName)`** 重建出前端对象再传入。
`resource_copy_region` 是 `kWaitApplied`(`PipeCalls.def:254`),client 停车,故探测合法。
(视图嵌套同步 `SyncTextureViewToBackend` 也调它,但 transport 臂已改为 `SyncTextureViewToBackendByRecord` 走 `Desc.ViewOf` 句柄,`Managers.cpp:7178-7195`。)
- **按句柄重键缺什么**:不缺载体——`MGPCopyRegion` 已同时携带 `Src/Dst` 句柄与 `SrcGlName/DstGlName`(`MGPipeTypes.h:1468-1476`)。
缺:(1) sink 用 `Src/Dst` 句柄而非 `GetTextureObject` 重建端点(顺带退役 FieldOwnership 的 `GetTextureObject` P7 行);
(2) `CopyImageEndpoint` 增加句柄形态或后端函数表加 by-handle 入口;(3) `MakeGLESCopyImageEndpoint` 改 `SyncTextureToBackendByHandle`。
- **今天为何能工作**:设障(kWaitApplied)+ scope 豁免,client 停着,lifetime-id 探测读的是静止的 client 分配器。
- **P5f 处置建议**:归 fr 包。这是 §2.1 `GetTextureObject`(P7 行)在 Espryt 侧的**实际消费点**,句柄已在记录里,属纯接线;
做完后 `:2542` 的 scope 在 transport 下失去最后一个到达者,可随 P3b/P4b 的 monolith-glue 清理一起删。
#### :12606 — `ShaderStorageBlockBinding(program, storageBlockName, binding)`
- **保护的 registry**:`PrgramImpl::g_backendProgramObjects`(程序 twin 表,kind ShaderCso)。
- **今天的键**:sink `OnSetStorageBlockBinding`(`PipeApplier.cpp:910-928`)按 GL 名调后端表项,
后端先 `MGB_CTX->ValidateProgramName` / `GetProgramObject(program)`(两个 BARRIER_PULLED 行,P9),
再 `Find(programObject.get())`(`:12608`)→ lifetime-id 探测。
- **对象是否已有 {slot,gen}**:有;且**记录已带**——`MGPStorageBlockBinding::ShaderCso`(`MGPipeTypes.h:1791-1796`)。
CONTRACT-P5E §2.2 本就写着"trailing: pg resolves through `MGPStorageBlockBinding::ShaderCso` and flips it",未落。
- **按句柄重键缺什么**:sink/后端按 `ShaderCso` 解析 twin(`ResolveProgramTwin`/`FindShaderCsoRecord` 已在),
跳过 `GetProgramObject` 全程;该行 `kWaitApplied`(`PipeCalls.def:297`)即可翻 `kWaitNone`。
注意当前实现的"不同步即返回"语义(`:12614-12617`,link version 落后则留给 reseed)在 by-handle 臂需用
`m_syncedShaderCsoSerial` vs `record->Serial` 重述。
- **今天为何能工作**:同上——设障 + scope。
- **P5f 处置建议**:归 fr 包(或与 §2.1 的 `GetProgramObject`/`ValidateProgramName` P9 行一起:这两行的 Espryt 消费点主要就是这里)。
契约已预定、记录已带句柄,是 13 处里**收益最直接**的一处。
### B 类:monolith glue / transport 下不可达(P3b/P4b 债)
#### :4505 — `SyncCurrentFBO()` 的 legacy 循环
- registry:`FramebufferImpl::g_backendFramebufferObjects`;键:前端 FBO 指针(经 `HandleOf`)。
- transport 下:`SyncCurrentFBOByRecord()` 先行(`:4479`),decline 且 fb 位开启时直接
`RefuseFramebufferBindingSlotRead()` abort(`:4490-4492`)。fb 位关 + transport 的组合里,
环绕它的 `GetFramebufferBindingSlot` 读本身已是 BARRIER_PULLED 具名红——**非受支持配置**。
- 处置:P3b/P4b 删 monolith glue 时连同 `GetOrCreate(currentFBO)` 臂一起删;P5f 不需要动它。
#### :5540 — `ResolveGlobalConstantsRecord(program)`(monolith-glue 半边)
- registry:`g_backendProgramObjects`;键:`HandleOf(program)`(`:5546`)。
- 注释自带结论(`:5530-5533`):"Reached only when `Transport == Monolith`";transport 的唯一调用点
走 `ResolveGlobalConstantsRecordForHandle`(`:7139`)。
- 处置:P3b/P4b。P5f 无需动。
#### :5659 — `SyncCurrentProgram(SharedPtr<ProgramObject>)`
- registry:`g_backendProgramObjects`(另含两张表的 `CollectGarbageIfNeeded`,`:5661-5662`);键:前端程序指针(`:5679-5680`)。
- transport 下:调用点全部按 `ProgramHandleArm()`(`Managers.h:714-716`)分流到
`SyncCurrentProgramByHandle`(draw `:6260-6265`,dispatch `:8156-8161`)。
- 处置:P3b/P4b。
#### :5940 — `BindCurrentFBO(target)` 的 legacy 臂
- registry:`g_backendFramebufferObjects`;键:前端 FBO 指针 `Find(currentFBO.get())`(`:5947`)。
- transport + fb 位:记录臂 `FindByHandle(record.Fbo)`(`:5889`)先行返回;decline 则 abort(`:5917-5919`)。
- 处置:P3b/P4b。
#### :6490 — `ResolveAndBindUnitTextures()` 的 legacy 单元绑定 pass
- registry:`g_backendTextureObjects`;键:前端纹理指针 `Find(textureObject.get())`(`:6492`)。
- transport + 纹理位:`UnitTexturesByHandle()` 的记录臂在 `:6397` 返回,走不到这里(`:6341-6398`)。
- 处置:P3b/P4b。该 legacy pass 的外层读 `MGB_CTX->GetTextureUnitObject(unit)`(`:6425`)本身是 §2.1 的
BARRIER_PULLED 行(Magma 的那半归 fm),scope 只是其中 registry 探测的标记。
#### :6576 — `ResolveUnitSamplerBackend(unit, samplerObject)`
- registry:`SamplerImpl::g_backendSamplerObjects`;键:`HandleOf(samplerObject)`(`:6582`)。
- transport 下:**scope 之前就已 abort**——`Fatal{ProtocolCorruption, "BindSamplerStates.Count"}`(`:6565-6573`),
P5e(tx2)按 CONTRACT-P5E §4.2 把 identity sampler 家族在 transport 下整个删除。
- 处置:scope 已形同虚设(只为 monolith 编译期保留),随 P3b/P4b 删。
#### :7092 — `BindCurrentProgramWithResources` 的 else 臂
- registry:`g_backendProgramObjects`;键:前端程序指针 + stash 比较(`:7096-7104`)。
- transport 下:`handleArm` 走句柄 stash / `ResolveProgramTwin`(`:7080-7083`)。注释自标"monolith glue only"(`:7088-7091`)。
- 处置:P3b/P4b。
#### :7682 — `GetCurrentBackendProgram()`
- registry:`g_backendProgramObjects`;键:前端程序指针 `Find(currentProgram.get())`(`:7684`)。
- transport 下:`ProgramHandleArm()` 臂(`:7659-7670`)在 scope 之前返回,scope 不可达(注释 `:7680-7681` 自明)。
- 处置:P3b/P4b。
#### :8213 — `GetBackendProgramId(GLuint program)`
- registry:`g_backendProgramObjects`;键:前端程序指针(`:8215-8217`,Find-or-create + 按需 `SyncToBackend`)。
- **特殊发现:当前 HEAD 全树无任何调用者。** 该函数无头文件声明(`DirectGLES.h`、`Managers.h` 均无),
不在后端函数表(`BackendObject.h` 无此槽),MG_Impl/MG_Remote/MG_Pipe 无引用;唯一同名物是
`BackendProgramObjectImpl::GetBackendProgramId()` 成员(`Managers.h:2894`)。CONTRACT-P5E §5.5 称它
"is a monolith entry",但至少在当前 HEAD 它是**死代码**(唯一旁证:`MG_Test/Program/ProgramInterfaceTest.cpp:1411`
的注释提到这个名字描述一种既有危害)。
- 处置:先确认删除(或恢复声明与调用者),与 P5f 无关;若保留则归 P3b/P4b。**建议 fr 包顺手删掉它连带这处 scope**——
这是普查发现的唯一"scope 保护着没人调的函数"。
#### :11231 — `ScopedDetachedTextureFramebufferAttachments(SharedPtr<ITextureObject>)`(detach-walk 前端构造)
- registry:`g_backendTextureObjects`(`Find(texture.get())`,`:11233`)+ `g_backendFramebufferObjects.ForEachLive`
内经 `StateForHandle` 取回前端 FBO 对象(注释 `:11243-11255`)。
- transport 下:唯一 transport 调用者是 `GenerateMipmapByRecord` 的**句柄构造**(`:12076`,走 `:11162` 的
`MGPipeHandle` 重载,纯记录走查);前端构造只被 monolith 的 `GenerateMipmap` 调用(`:12151`),
而 transport 的 `GenerateMipmap` 在 `:12105-12117` 已整臂分流到 `VerbMipRes`。
- 处置:P3b/P4b(fb 包的 reverse index 已是句柄臂的替代)。注意 `StateForHandle` 这半边由
`MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply` 单独把守(`SlotAllocator.cpp:76-86`),
scope 删后该守卫仍在。
#### :14461 — `GetTexImage(target, level, format, type, pixels)`
- registry:`g_backendTextureObjects`;键:前端纹理指针(`:14463`),其前端对象来自
`GetActiveTextureUnit`/`GetTextureUnitObject`/`GetBindingSlot` 三连 BARRIER_PULLED 读(`:14448-14453`)。
- transport 下:**不可达**——wire 槽 `GetTexImage` 在 `MGR_UNMIGRATED_TAIL_SLOTS`(`EmitTables.cpp:1673`),
即 class-C 具名拒绝 `Fatal{UnmigratedVerb, "GetTexImage"}`;DSA 的 `GetTextureImage` 由前端自己的 CPU 读回
应答(`SlotCaps.h:57`),其后端槽同样在拒绝名单(`EmitTables.cpp:1674-1676`)。
即 CONTRACT-P5E §4.4 当年列举的"GetTexImage 设障行站点"在当前 HEAD 已漂移成 class-C。
- 处置:本分两个阶段看——P9 若把 GetTexImage 接上 wire,`MGPReadbackInfo::Res`(`MGPipeTypes.h:1555`)已带句柄,
届时按 :2542 同款模式重键;在那之前此 scope 仅 monolith 可达,归 P3b/P4b。**当前不属于 P5f 跨进程必需。**
## 4. 附注:Managers.cpp 的 11 处(同一豁免家族,本子项口径外)
均为 G6 注释标记的同类探测,除注明外均为 monolith-glue 性质:
| 行 | 函数 | registry / 键 |
|---|---|---|
| `:3063` | `BufferImpl::HandleOfBuffer` | buffer twin 表;lifetime-id。CONTRACT-P5E §4.4 点名的残留("until vi/sb carry the handle");当前调用者在 monolith 臂(`DirectGLES.cpp:1076`、`:1366`) |
| `:4689` | `SamplerViewImpl::HandleOfSamplerViewForTexture` | sampler-view 表;lifetime-id |
| `:6101` | 纹理 remint-pull 的 rearm(`RequireImageBindableStorage` 族) | 纹理表 `HandleOf` |
| `:8934` | `BackendTextureObject::ResolveOwnRecord` | 纹理表 `HandleOf`;by-handle 臂靠 `m_pushedSyncHandle` 不探测 |
| `:9762` / `:9816` | `SyncAttachmentObject` | 纹理 / renderbuffer 表 Find-or-Create;legacy FBO 附件走查 |
| `:10953` / `:10974` | FBO verify 臂(`glGetFramebufferAttachmentParameteriv` 断言走查) | 纹理 / renderbuffer 表 Find;debug 断言代码 |
| `:14102` | 程序 `SyncToBackend` 的 serial 盖章 | 程序表 `HandleOf`;注释自标 "MONOLITH GLUE ONLY" |
| `:14463` | 采样器参数同步的"未注册"判别 | 采样器表 `HandleOf`;后端自产采样器(raw-depth-fetch)靠它区分 |
| `:14760` | renderbuffer `SyncToBackend` 的记录解析 | renderbuffer 表 `HandleOf`;注释自标 monolith glue |
建议 fr 包把 Managers.cpp 这 11 处与 DirectGLES.cpp 的 B 类一起列入"随 P3b/P4b 删除"清单,避免 P5f 范围漂移。
## 5. 对 P5f(fr 包)的处置清单(汇总)
1. **fr 的真工作只有两处接线**:`:2542`(resource_copy_region 改吃 `MGPCopyRegion::Src/Dst` 句柄,
顺带退役 `GetTextureObject` 拉取)与 `:12606`(set_storage_block_binding 改吃 `MGPStorageBlockBinding::ShaderCso`,
顺带退役 `GetProgramObject`/`ValidateProgramName` 拉取,WaitClass 可翻 `kWaitNone`)。
两者记录都已带句柄,registry 都已是句柄键——**是接线,不是重键**。
2. `:8213`(`GetBackendProgramId`)疑似死代码,建议确认后随 fr 删除。
3. 其余 11 处(DirectGLES.cpp)+ 11 处(Managers.cpp)是 monolith glue / P3b/P4b 债,
**不应计入 P5f 的跨进程判据**;双块演练(§4)不会让它们变红,因为它们根本不被 transport 到达。
计划的 fr 包若按"删全部 scope"来写会虚胖;按本普查,fr 的 red-once 判据应落在 A 类两处。
4. 口径修正建议:P5F-WIRE-COMPLETENESS.md §2.5 写"registry 仍按前端地址 / lifetime-id 键控"——
实测 **registry 本体已全部按 {slot,gen} 键控**(P5e id 完成),残存的是"调用点仍用前端身份解析",
且 13 处中 11 处在 transport 下不可达。建议把 §2.5 措辞改为"两个设障行调用点仍经前端身份解析"。
## 6. 未知项(如实记录)
- `:2542` 的 `SyncTextureObjectToBackend` 在 transport 下是否还有 CopyImageSubData 之外的到达者,
本普查核对了其全部 8 个调用点(`DirectGLES.cpp:3323`、`:3458`、`:3543`、`:3852`、`:10363-10364`、
`:12131`、`:12134`、`:12301`,加 `Managers.cpp:7104`):其余均在 monolith/legacy 臂或被
`FramebufferRecordArmIsMandatory` 之类的分流挡住;若臂选择器被操作员改出非默认组合,可达性会变化——未逐组合穷举。
- `:8213` "无调用者"的结论基于全树文本检索;若存在经函数指针表以外的动态到达(如 dlopen 符号),未能覆盖——未发现任何迹象。
@@ -0,0 +1,259 @@
# f0-reverse-channel — P5f §2.6 反向通道逐站点普查(只读)
> 基线 `feat/disaggregated @ 8b68b92c`(代码头 `25fba0d5`)。路径在 `MobileGL/` 下。
> 本文件为只读普查,未改任何代码、未发起构建、未跑测试。对应 P5f 计划 §2.6
> (`docs/Disaggregated/P5F-WIRE-COMPLETENESS.md:200-204`)与 P5c 审计表 R1–R5 / B4 行
> (`docs/Disaggregated/ROADMAP.md:61-66`)。
>
> **行号漂移提醒**:任务与 `ROADMAP.md:61`(B4 行)引用的 XFB scatter 旧行号
> (`DirectGLES.cpp:950/1060/1077/1081/1172/1185`)在当前 HEAD 已落在
> `SyncVaoAttributeBuffersByHandle/ByRecord`(VBO memo)区域,与 XFB 无关。XFB 机制现位于
> `DirectGLES.cpp:1508-2072`(`XfbImpl` 命名空间),scatter 本体在 `:1726-1839`,
> 非 scatter 回读臂在 `:1602-1684`。下文一律以当前 HEAD 行号为准。
---
## 0 总览:十个回调,四类有接线
`MGPipeCallbacks` 共 10 个成员(`MG_Pipe/MGPipeCallbacks.h:27-51`,个数有 static_assert 把守)。
逐个数 producer / consumer / 事件种类:
| 回调 | EventKind | server producer | client consumer | 状态 |
|---|---|---|---|---|
| `OnBufferWriteback` | `kEventBufferWriteback`(`EventRing.h:49`) | `ServerSession.cpp:264-292` | `ClientSession.cpp:266-291` → `ResourceTracker.h:575-638` | **已接线** |
| `OnGpuWritten` | `kEventGpuWritten`(`EventRing.h:50`) | `ServerSession.cpp:294-312` | `ClientSession.cpp:292-314` → `ResourceTracker.h:645-667` | **已接线** |
| `OnSurfaceChanged` | `kEventSurfaceChanged`(`EventRing.h:51`) | `ServerSession.cpp:314-330` | `ClientSession.cpp:315-329`(→ `:211-252`) | **已接线** |
| (`OnGlError`) | `kEventGlError`(`EventRing.h:52`,P5c 新增) | **不经回调表**:`PipeInputs::RecordError` 直调 `ServerSession::PostGlError` | `ClientSession.cpp:330-365` | **已接线,但绕过回调表**(见 §5) |
| `OnTextureWriteback` | 无 | 无 | 无 | 零接线 |
| `OnTexturePullRequest` | 无 | 无 | 无 | 零接线(终止符见 §7) |
| `OnMipLevelsGenerated` | 无 | 无 | 无 | 零接线(见 §6) |
| `OnXfbScatterReady` | 无 | 无 | 无 | 零接线(见 §3) |
| `OnCapsInvalidated` | 无 | 无 | 无 | 零接线,由 R-12 caps 重发布取代(`CapsMirror.cpp:78-80`) |
| `OnLog` | 无 | 无 | 无 | 零接线 |
安装/卸载:`ServerSession::Accept` 只装三个(`ServerSession.cpp:538-542`),`Close` 对称卸载
(`:622-632`);双重安装是 `Fatal{RoleViolation,"callback-double-install"}`(`:337-347`)。
consumer 侧按名直调、绝不回走 `gMGPipeCallbacks`(`ClientSession.cpp:203-206` 注释,防
layer-2 反调)。这套所有制关系本身**没有跨进程问题**,P5f 无需动。
---
## 1 XFB scatter(Espryt)——字节路径已迁移,对象身份仍指回 client
这是 B4 行的本体。当前 HEAD 的传输臂形状(P5c 已落):
- **非 scatter 臂** `ReadbackCapturedRanges`(`DirectGLES.cpp:1602-1684`):transport 下
(`:1616-1656`)按 handle 解析 server 资源(`:1622-1623`)、map 的是 **ES 驱动 buffer**
(`:1626-1629`)、字节经 `OnBufferWriteback` 事件回传(`:1638-1643`),serial/epoch 的
序按 Ops_H_Readback 同款规则(`:1650-1654`)。monolith 臂(`:1658-1680`)仍直接
`target.buffer->WritebackFromBackend`(`:1675`),被 transport 分支的 `continue` 隔开。
- **scatter 臂** `ScatterCapturedRecords`(`:1726-1839`):transport 下预捕获字节读的是
**server 自己的 staged shadow**(`:1776-1794`,含 `RequireStagedCoverage` 覆盖断言
`:1789-1790`),拼好的范围同样经 `OnBufferWriteback` 回传(`:1807-1819`),不再读改写
client shadow。monolith 臂 `target.buffer->MappedData()` / `WritebackFromBackend`
(`:1794`/`:1826`)保留。
**仍触碰 / 依赖 client 内存的残余**(都在 transport 臂上,逐条列):
1. **`scatterProgram` 是 client `ProgramObject` 的 `SharedPtr`,并在 apply 线程上直接调它的
方法。** 来源:`MGB_CTX->GetTransformFeedbackProgram()`(`:1871`,BARRIER_PULLED 字段,
`FieldOwnership.def:106` 记 "P3b/P4b (Espryt), P7 (Magma)"),存进
`XfbObjectState::scatterProgram`(`:1538`、`:1925`)。scatter 时读
`GetTransformFeedbackPackedStride()`(`:1731`)、`GetTransformFeedbackStride()`(`:1766`)、
`GetTransformFeedbackVaryings()`(`:1796`)——**这些都是对 client 对象体的读**,
字节不走 client shadow 了,但 layout 元数据仍从 client 对象上现取。
- 今天为何能工作:同地址空间 + barrier/run-ahead 窗口内对象存活(P5e 的 `ReleaseFillPins`
机制钉住 O 类 SharedPtr,`ClientSession.cpp:1073-1082`)。
- P5f 处置:这是 §2.1 `GetTransformFeedbackProgram` 字段退役(P3b/P4b 债)与 fv 的交集——
scatter 所需的三个 layout 答案应随 XFB span 记录上 wire(值类,可推导),server 侧只留
快照。**分类:P5f(fv,依赖 §2.1 字段的载体判定先行)。**
2. **`XfbCaptureTarget::buffer` 持有 client `BufferObject` 的 `SharedPtr` 跨整个 span**
(`:1511`,填充于 `StartPendingTransformFeedback` 的 `:1890-1901`,后者同时读
BARRIER_PULLED 的 `GetBufferBindingPoint`,`:1891`)。transport 臂只取
`HandleOfBuffer(target.buffer.get())`(`:1622`/`:1779`)——**用 client 裸地址当 registry
键**,落在具名豁免 `MGPipeFrontendKeyedRegistryScope` 内(`Managers.cpp:3063`)。
- 今天为何能工作:同地址空间下地址即身份;scope 把债记名。
- P5f 处置:targets 快照应改持 `{handle, range}` 纯值,地址键探针随 §2.5 registry 重键
(包 `fr`)一起退。**分类:P5f(fr ∩ fv;本身不是字节读写,但跨进程后"client 地址"无意义)。**
3. **`MGB_CTX->GetTransformFeedbackCapturedVertices()`(`:1733`)是 RECORD_SUPPLIED**
(`PipeFieldOwnership.inc:85`,emitter `SetContextValues`,`PipeFill.cpp:3064`),
**不是债**,列出以免后人重复普查。
另外 XFB 整条机制读 `GetBufferBindingPoint`(BARRIER_PULLED,`FieldOwnership.def:80`)的退役
属 §2.1 字段包,fv 不应重复记账,只登记依赖关系。
## 2 `OnBufferWriteback` —— 已接线,blobref 约定闭环(无 P5f 债)
**producer(后端侧,3 处)**,全部以 `Seg = kMGHostSpanSegNone` + host 地址形态交出:
`Managers.cpp:2451-2455`(`Ops_H_Readback`,readback verb 的字节)、
`DirectGLES.cpp:1638-1643`(XFB 非 scatter 臂)、`DirectGLES.cpp:1809-1814`(XFB scatter 臂)。
三处都带"无通道则 MGLOG_E_ONCE 丢弃"的显式降级(如 `:1644-1648`),不是静默。
**producer(会话侧)** `ServerOnBufferWriteback`(`ServerSession.cpp:264-292`):
入参 `Seg != kMGHostSpanSegNone` 即 `Fatal{ProtocolCorruption,"OnBufferWriteback.Seg"}`
(`:266-276`)——**后端边界只接受 host span,段拷贝是这个函数自己的活**;字节 memcpy 进
SEG_EVENT 记录内联尾(`:285-290`)。满环经 `ReserveEventOrBlock`(`:211-262`):
run-ahead 服务器park 等 client 排空(CONTRACT-P5E §2.6 流控),lockstep 服务器
`Fatal{EventRingOverflow}`。整 buffer 大于半环的切片在 client 侧发起处完成
(`BufferObject.cpp:593-612`,`BufferWritebackSliceBytes`)。
**consumer** `DrainEventRing`(`ClientSession.cpp:266-291`):在此构造
`Seg = kSegEvent` + `OffsetInSegment` 的 blobref(`:282-285`),交给
`MGPipeClientOnBufferWriteback`(`ResourceTracker.h:575-638`)——三臂校验齐备:
kSegEvent 经 client 自己的 SegmentTable 界检解析(`:596-617`)、transport 下 host span 即
Fatal(`:584-593`)、其他 Seg 即 Fatal(`:618-624`)。写 client shadow 的是 GL 线程上的
`WritebackFromBackend`(`:636-637`),归属正确。
- 今天为何能工作:约定是**双向 Fatal 执法**的,不只是文档;wire 形态(`Seg=kSegEvent`)
在跨越点被强制。
- P5f 处置:**无**。剩余课题(批处理、epoch 排序、`eventRingFull`/`eventDropped` 的丢弃
策略)都是 P9 的,`EventRing.h:14-16` 与 `CONTRACT-P5C.md §4.4` 已记名。
## 3 `OnXfbScatterReady` —— 声明存在,全仓零接线
仅有声明(`MGPipeCallbacks.h:50`,注释写明设计意图:server 交回 packed scratch、
**client 自己 scatter**,`:48-49`)。grep 全仓无 producer、无 consumer、无对应 EventKind;
`CONTRACT-P5C.md:536` 把它与 `OnTexturePullRequest` 并列记给 P9。
- 今天为何能工作:P5c 选的是另一条路——scatter 留在 server,读 server staged shadow、
结果走 `OnBufferWriteback`(§1)。两条路在语义上互斥地覆盖同一个需求。
- P5f 处置:**无**。是否改走"client scatter"是纯设计/异步化选择(P9 的题材,
`ROADMAP.md:34` P3b/P4b 行也记了"XFB scatter 搬到 client")。若 P9 采纳,§1 的
残余 1/2 自然消解(layout 与 targets 都回到 client 侧)。**分类:P9。**
## 4 `OnGpuWritten` —— Espryt 已净,Magma 有一处响亮但真实的直捅兜底
**Espryt**:`MarkBufferGpuWritten`(`Managers.cpp:3072-3103`)在资源族臂上只发事件
(`:3101-3102`,单条 `kMGPipeWholeBuffer` 全量 range——收窄是 P8/P9 的,
`ResourceTracker.h:646-656` 有 assert 把守形状)。它在 transport 下的三个原调用点已被
P5e 删除或门控:`MarkShaderStorageBuffersGpuWritten` 整体早退(`DirectGLES.cpp:845`,
注释 `:828-844` 说明 client 的 `MarkShaderStorageBindings` 已覆盖同集);
`SyncAtomicCounterBuffers` 的记录臂不标(`:918-924`);
`MarkWritableImageBufferTexturesGpuWritten` transport 下整函数 return(`:3884-3887`)。
client 侧保守集在 `MG_Remote/Client/GpuWritePending.{h,cpp}`(GL 线程自标,窄化通道反向
运行)。**Espryt 无 P5f 债。**
**Magma**:三个站点经 `MGPipeAnnounceBufferGpuWritten` 走回调——
`UniformManager.cpp:1082`(storage texel buffer,Access != READ_ONLY)、
`UniformManager.cpp:1244`(storage block)、`VulkanRenderer.cpp:11778`(XFB capture)。
但 helper 本体(`ResourceTracker.h:710-743`)有两个跨进程不成立的点:
1. handle 来自 **`MGPipeSlots().FindByLifetimeId`(client 分配器探针)**,套在
`MagmaP7AllocatorDebtScope` 具名豁免里(`:717-730`)——P7 债,记名在册。
2. **handle 缺失时的兜底是直捅 client 对象**(`:732-739`:MGLOG_E_ONCE 之后
`bufferObject->MarkGpuWritten()`——apply 线程写 client 内存)。注释说这是"响亮而非
静默"的故意选择;在 P5f 双块演练下这一支会成为真实违规。
- 今天为何能工作:同地址空间;Magma 跑 lockstep(不发布 `kCapRunAheadApply`),
client 停在 barrier 后,直捅恰好安全——ID-132 的那类掩盖。
- P5f 处置:**分类 P5f(fm/fv)**。把兜底从"直捅"改成具名 Fatal 或携带性降级
(例如记 pending 由 client 侧保守集兜底——反正 GpuWritePending 已按绑定走查标过同集,
这个兜底在语义上是冗余保险而非正确性来源);lifetime-id 探针随 Magma 的 §2.2/fm 一并退。
## 5 `OnGlError` —— 已接线,但绕过了回调表;顺序保证是"下一个 drain 点"
**路径不经 `gMGPipeCallbacks.OnGlError`**:该成员声明于 `MGPipeCallbacks.h:29`,
**全仓零安装、零调用**。实际路径是 `PipeInputs::RecordError`(`PipeFill.cpp:2164-2186`)
在 transport 下直接调 `ServerSessionInstance().PostGlError`(`:2175`),后者产
`kEventGlError`(`ServerSession.cpp:709-739`,消息内联、NUL 随带、producer 侧截断到
1024,`EventRing.h:108-115`)。consumer `ClientSession.cpp:330-365` 校验
`MessageBytes != 0` 且 NUL 在位(否则 `Fatal{ProtocolCorruption,"kEventGlError"}`),
在 GL 线程写 client 自己的 `pGLContext->RecordError`(`:353-357`)。
**顺序保证的现状**(任务点名项):所有 producer 都在 apply 线程上、共用同一个 SEG_EVENT
环,所以 **错误事件与 writeback/gpu-written/surface 事件之间保 FIFO**;client 侧的
保证是"**下一个 drain 点观测到**"——保住的是同线程的 error-then-read 程序序,
不保跨 verb 交错。这是写明并接受的局面,不是漏洞:`PipeFill.cpp:2168-2173`、
`ClientSession.cpp:350-352`、`CONTRACT-P5C.md §4.2`(表格第 4 行,`:281`)、
run-ahead 下的松弛在 `CONTRACT-P5E.md:181`。`FieldOwnership.def:164` 的 `RecordError`
行仍挂 BARRIER_PULLED "P9"(`MGP_STICKY_FORWARD_PULL` 宏仍执行、计数仍走 `rsp`),
有序化退役排在 P9。
- 今天为何能工作:错误队列是 client 状态,写它发生在 GL 线程 drain 时(归属正确);
序的保证靠 drain 点分布(§8)而非锁。
- P5f 处置:**本体分类 P9**(有序化、`OnGlError` 回调成员的正式接线)。P5f 只需登记一个
不对称事实:**错误事件不走回调表,因此 §4.1 的双重安装守卫与"十个成员"的记账方式
盖不住它**;若 fv 收口时顺手把 `gMGPipeCallbacks.OnGlError` 也接到
`ServerSession::PostGlError`(或显式标注该成员永不经表),可消除"将来有人调
`OnGlError` 而静默空转"的坑。这是可选的整洁项,不是跨进程缺陷。
## 6 `OnTextureWriteback` / `OnMipLevelsGenerated` —— 零接线,且今天的 Espryt 不需要它们
全仓零 producer / consumer / EventKind。核查它们"本该服务"的路径在 transport 下的现状:
- `glGenerateMipmap`(Espryt):transport 下走记录臂(`DirectGLES.cpp:12093-12117` →
`GenerateMipmapByRecord :12037-12081`),纹理由 `VerbMipRes` 句柄解析,存储形状由
**前端自己分配**、server 侧只做描述符核验(`EnsureGenerateMipmapStorageDescribed`,
`:11040`起)——这正是 `OnMipLevelsGenerated` 注释里"SHAPE ONLY, client owns the CPU
shadow"(`MGPipeCallbacks.h:39-41`)想要的形态,只是由"前端先行分配"实现而非事件通知。
- 唯一真正需要 texel 回流的是 RGB16F/RGB32F 的 CPU filter
(`GenerateThreeChannelFloatMipmapOnCpu` 读改写 client level shadow):transport 下已被
具名拒绝 `MGPipeUnmigratedEmulation("generate-mipmap-cpu-filter")`(`:12051-12056`),
记录保持 barriered(`:12030-12036` 注释)。**它就是 `OnTexturePullRequest` /
`OnTextureWriteback` 的未来用户**,`CONTRACT-P5B.md:255/:379` 也这么记。
- Magma 侧 mip 直写 client level 存储是 T5(`ROADMAP` §2.2 引用
`VulkanRenderer.cpp:1562-1590` 一带),属包 `fm`,此处只登记交叉。
- P5f 处置:**无**。**分类:P9**(纹理拉取与 texel 回写通道的建设);T5 归 fm。
## 7 `OnTexturePullRequest` 与终止符 `ResourceSubDataComplete` —— 整条未建
零 producer。终止符 `ResourceSubDataComplete` 有 wire 位(opcode 50,
`PipeCalls.def:229`、`PipeWire.inc:122`)但 **applier 是显式空 stub**:
`PipeWireCodec.cpp:2070-2073`("There is no client producer and no applier; the
reverse channel is P7/P9",`return false`)。`PipeCatalogueTest.cpp:1425` 注释同样记为
later phase。
- 今天为何能工作:缩减路径上没有纹理重铸(recast)场景;唯一会需要的 CPU filter 已被
具名拒绝(§6)。
- P5f 处置:**无**。**分类:P9**(`ROADMAP.md:37` P9 行"纹理拉取四条缓解 + 终止符")。
## 8 排空点清单(顺序保证的现实基础,P5f 不需要动)
`kEventGlError` 与所有事件的"下一个 drain 点"具体是(全部在 GL 线程):
`ClientSession.cpp:170`(事件积压唤醒的二级退出)、`:908`(SEG_CMD 空间等待出口)、
`:1071`(`EmitAndWait` barrier 之后——主排空点,注释 `:1066-1070`)、`:1206`
(`WaitForApplyToCatchUp`,run-ahead 强制对齐)、`:1256`/`:1269`(present credit 等待)、
`:1456`(公共 `DrainEvents`);另有第二类:阻塞式 EGL 生命周期 RPC 返回处
(`CONTRACT-P5C.md §4.1`,`:263-272`)。每个等待出口都排空是 P5e 流控死锁论证的支柱
(`:1202-1206`)。**这些是全值的拆分机语义,跨进程成立,P5f 无处置。**
## 9 SEG_EVENT blobref 约定核验结论(任务点名项)
约定(`CONTRACT-P5C.md:54`):wire 上的 `OnBufferWriteback` blobref 必须
`Seg = kSegEvent` + 段内偏移,**永不**是 host 地址;monolith 下保持 `kMGHostSpanSegNone`。
逐环核验:**约定被所有 producer 遵守,且每一环都有执法**——
1. 后端 → 会话:3 处 producer 全部交 `kMGHostSpanSegNone` host span(§2);
2. 会话入口:`Seg` 检查是 Fatal(`ServerSession.cpp:266-276`);
3. wire 形态构造:只有 `DrainEventRing` 一处造 `kSegEvent` blobref(`ClientSession.cpp:282-285`);
4. consumer 三臂:kSegEvent 界检解析 / host span 在 transport 下 Fatal / 其他 Fatal
(`ResourceTracker.h:583-624`)。
**无违规、无缺口。**
## 10 汇总
| # | 站点 | 触碰 client 内存? | 分类 |
|---|---|---|---|
| 1 | XFB scatter 字节路径(Espryt) | 否(P5c 已改走 server shadow + writeback 事件) | 无债 |
| 2 | XFB `scatterProgram`(client ProgramObject 的 SharedPtr,apply 线程直读 layout) | **是(读)** | **P5f(fv,依赖 §2.1 `GetTransformFeedbackProgram` 载体判定)** |
| 3 | XFB `targets` 持 client BufferObject SharedPtr + `HandleOfBuffer` 地址键探针 | 身份依赖(不读字节) | **P5f(fr ∩ fv)** |
| 4 | `OnBufferWriteback` 全链 | 否 | 无债(批处理/排序归 P9) |
| 5 | `OnXfbScatterReady` | —(零接线) | P9 |
| 6 | `OnGpuWritten` Espryt 臂 | 否 | 无债(收窄归 P9) |
| 7 | `OnGpuWritten` Magma 兜底(handle 缺失时直捅 `MarkGpuWritten`) | **是(写,响亮兜底)** | **P5f(fm/fv)** |
| 8 | `OnGlError`(`RecordError` → `kEventGlError`) | 否(GL 线程写 client 队列) | 有序化归 P9;回调表不对称是可登记的小项 |
| 9 | `OnTextureWriteback` / `OnMipLevelsGenerated` | —(零接线;唯一用户被具名拒绝) | P9 |
| 10 | `OnTexturePullRequest` + 终止符 | —(整条未建) | P9 |
| 11 | `OnSurfaceChanged` 全链(Espryt `:14959-14970` / Magma `SwapchainObject.cpp:324-339` → `ClientSession.cpp:211-252`) | 否(GL 线程写 client `pDefaultFramebufferInfo`,R3 归属正确) | 无债 |
| 12 | `OnCapsInvalidated` / `OnLog` | —(零接线;前者由 R-12 重发布取代) | P9/无需 |
**P5f 实收三条**(#2、#3、#7),全部集中在 XFB 对象身份与 Magma 兜底;反向通道的
**字节面在 P5c 之后已经干净**。monolith 臂上的直写点(`DirectGLES.cpp:1675/:1826/:12820/
:14391/:14806`、`Utils.cpp:2398`、`Managers.cpp:2025`)逐一核过:transport 下或被
`#else` 臂隔开,或被 client 侧具名拒绝把门(`EmitTables.cpp:976-980` 的
`UnmigratedVerbFatal("ReadPixels+PACK_BUFFER")`、`PipeWireCodec.cpp:2105-2108` 的
`GetTextureImage` class-C 拒绝),均不可达,不构成 P5f 项(其中 `ReadPixels` 服务器臂对
`GetBufferBindingSlot(PixelPack)` 的残余读,`DirectGLES.cpp:14337`,属 §2.1
`GetBufferBindingSlot` 行 "P9 (readback)",不在本清单重复记账)。
+305
View File
@@ -0,0 +1,305 @@
# P5f f0 普查(只读):进程级静态量中语义属于 per-context / per-session 的成员
> 基线:`feat/disaggregated @ 8b68b92c`(代码头 `25fba0d5`,其后提交仅动文档)。
> 范围:`MobileGL/` 下 `MG_Backend`(DirectGLES / DirectVulkan / MGPipe)、`MG_Impl`、`MG_Pipe`、
> `MG_Remote`、`MG_State`。方法:`g_`/`s_` 前缀可变全局、函数级 `static`、`inline X& = *new X()`
> 单例、命名空间级非常量变量的全面 grep,再逐族读代码确认键控方式与重置时机。
> 本报告只读产生;未修改任何代码、未构建、未跑测试。
## 0. 判定框架
P5f 关心的是「换一个地址空间还成立吗」的一个变体:**这个静态量的语义寿命跟谁走**。
| 分类 | 含义 |
|---|---|
| **【A】P5f 必须按角色+世代分区** | 语义属于某个 GL context / session,且当前的重置时机挂在错误的钟上(或不挂任何钟),f1 双块演练下必须暴露 |
| **【B】进程级即可** | 拆进程后每个进程自然各有一份,且持有者在 spawn 后唯一(server 独有 / client 独有),或纯诊断/配置 |
| **【C】已是 per-context / per-session** | 已有世代键控自失效或已有 per-session 重置机制,只需 f1 确认 |
| **【D】未知** | 只读普查不足以判定,标出待 f1/后续包回答的问题 |
一条贯穿全报告的事实先记住:**拆进程后 Espryt/Magma 后端代码只在 server 进程运行,MG_Impl 只在
client 进程运行**(P5F-WIRE-COMPLETENESS.md §1.4:「MG_Impl 根本不在 server 进程里」)。所以
`MG_Backend` 下的静态量天然只需 server 一份、`MG_Impl` 下的天然只需 client 一份——**单看拆进程,
绝大多数成员是安全的**。真正的缺陷集中在两类:(1) 重置时机挂错钟(挂 server 自己的钟,而语义属于
client context 世代);(2) 静态量里存着 client 对象的指针/SharedPtr。
## 1. 已知成员的核实与重置时机
### S1 `g_syncedRenderStateParameters` 家族 —— 【A】
- 证据:`MG_Backend/DirectGLES/DirectGLES.cpp:4584-4617`,同族 `g_syncedRenderStateVersion`、
`g_hasSyncedRenderState`、`g_syncedBackendViewport`、`g_syncedBackendScissorBox`、
`g_syncedSrgbFramebufferWrites`、`g_forceFullRenderStateResync`、`g_syncedColorMaskAlphaWidenMask`、
`g_dualSourceDeclinedBlendStates`。
- 语义:「后端 ES context 上当前已推下去的渲染状态」的影子,diff 对象是**前端**
`RenderStateParameters` 的版本与字节(`DirectGLES.cpp:4629-4660`)。即 per-(client context ×
server ES context) 的乘积状态。
- **重置时机**:`RenderStateImpl::InvalidateSyncedRenderState()`(`DirectGLES.cpp:4618-4623`,置
`g_forceFullRenderStateResync=true` 并清空四个影子)。调用点两个:
(a) `DirectGLES::MakeCurrent()` 尾部(`DirectGLES.cpp:15375`)——每次 backend make-current;
(b) viewport-routing 回放收尾(`DirectGLES.cpp:7930`)。注意 `DestroyEGLContext()`
(`DirectGLES.cpp:15896-15912`)的清理清单**不含**它——靠「新 context 必经 MakeCurrent」兜底。
- 今天为何能工作:inproc 单进程,ES context 与 client context 同在;ID-67 裁定「相同 tuple 的
make-current 跳过 native bind」,但 `BackendObject_DirectGLES.cpp:40-42` 的注释写明不同 virtual
context 仍进 `MakeCurrent`,七个缓存在那里统一失效。
- P5f 处置建议:拆进程后只有 server 持有、重置挂在 server 自己的 MakeCurrent 上,跨进程方向成立;
但 (1) f1 双块下必须把它归入 server 角色的块,使 push-monolith 臂与 server 臂不共享;(2) 待验证点:
server ES context 换代(`g_backendContextGeneration` 在 `DirectGLES.cpp:15912` 自增)而 client
tuple 不变时,`MakeCurrent` 是否仍被调用——若是则靠 :15375 兜住,若否则此家族缺一条挂
`g_backendContextGeneration` 的失效线。**该点只读无法定论,标为待 f1 双块回答。**
### S2 `ScopedDefaultUnpackState::s_synced` 家族 —— 【A】(全清单里最弱的一个)
- 证据:`MG_Backend/DirectGLES/Managers.cpp:6258-6325`。类内 `static inline` 成员:
`s_synced`(:6319)、`s_alignment/s_rowLength/s_skipRows/s_skipPixels/s_imageHeight/s_skipImages`
(:6320-6325)。
- 语义:后端 ES context 的 GL_UNPACK_* 像素存储状态影子(纹理上传路径的 save/restore)。
- **重置时机:没有。** `s_synced` 在 `EnsureShadowSynced()`(:6283-6300)里一次性置 true 后**永不
清除**;`grep` 确认它不在 `DestroyEGLContext()`(`DirectGLES.cpp:15896-15912`)的清理清单里,
也不在任何 `Invalidate*` 里(`PixelStoreImpl::InvalidatePackStateCache`
(`Managers.cpp:11565-11567`)只动 PACK 侧的 `g_packState`)。
- 今天为何能工作:两层巧合——(a) 本类是唯一写者且析构恒恢复到 resting default (4,0,0,0,0,0)
(类注释 :6250-6257);(b) 新 ES context 的 GL 默认值恰好等于该 resting default,故 ES context
销毁重建后影子「恰好仍对」。CONTRACT-P5C.md:538-539 已把它与 S1 一起记为 P6 的债,P5f §2.4 收回。
- P5f 处置建议:把 `s_synced=false` 挂进 `DestroyEGLContext` 的清理清单(或挂
`g_backendContextGeneration` 比较),这是「进程级静态无世代重置」的最小确定样本;f1 双块下归入
server 块。
## 2. 新发现
### S3 后端 memo/影子大家族:键控 (client 上下文 id/serial, `g_backendContextGeneration`),自失效 —— 【C】
Espryt 的 per-draw memo 几乎全部自带双键:前端侧键(`ContextId`/`ContextSerial`/内容哈希)加
`g_backendContextGeneration`(`DirectGLES.cpp:15912` 处自增)。全部成员(grep 命中的完整枚举):
| 成员 | 位置 | 键 / 重置 |
|---|---|---|
| `g_observedUnitBindings` 等 5 个 | DirectGLES.cpp:2767-2771 | (contextId, bind generation, maxUnit) 三重门,见 :2773-2782 注释 |
| `g_unitTextureSyncListByHandle*` 7 个 | DirectGLES.cpp:3002-3008 | 记录臂:(ContextSerial, ViewsSerial, g_backendContextGeneration),:3372/:3404 |
| `g_unitTextureSyncList*` 7 个 | DirectGLES.cpp:3010-3016 | 前端臂:(ContextId, epoch, generation),:3420/:3464 |
| `g_fboTextureSyncList*` 8 个 | DirectGLES.cpp:3046-3066 | {Fbo, ContentHash, ContextSerial, g_backendContextGeneration},:3522/:3550 |
| `g_fboAttachmentSyncList*` 6 个 | DirectGLES.cpp:3073-3078 | 同上,:3231/:3236 |
| `g_readFboAttachmentSyncList*` 6 个 | DirectGLES.cpp:3082-3087 | :3249/:3254 |
| `g_readFboTextureSyncList*` 8 个 | DirectGLES.cpp:3182-3189 | :3299/:3330 |
| `g_imageSweep*` 7 个 | DirectGLES.cpp:4013-4024 | 记录臂三 serial + generation,:4074-4094;前端臂 :4088 |
| `g_unitSamplerWalk*` 5 个 | DirectGLES.cpp:6627-6631 | (contextId, epoch, maxUnit, generation) + 行 memcmp,:6640/:6770 |
| `g_resolvedTextureBindingMemos` + cursor | DirectGLES.cpp:6859-6860 | 每 program 4 槽 round-robin memo,键含 lifetime id(:6921-6940) |
| `g_fboSyncedSerials` / `g_fboRecordsTrusted` | DirectGLES.cpp:4137 / :4146 | `InvalidateFramebufferHandleArmMemos()`(:4161-4164),挂 `InvalidateFramebufferBindingCache`(Managers.cpp:9726)→ MakeCurrent(:15367)与 DestroyEGLContext(:15903-15905) |
| broadcast memo 7 个 | DirectGLES.cpp:5324-5350 | `PrgramImpl::InvalidateBroadcastMemo()`(:5358),挂 MakeCurrent(:15364)与 DestroyEGLContext(:15909) |
| `g_currentDrawFrontendProgram`/`g_currentDrawBackendProgram`/`g_currentDrawProgramHandle` | DirectGLES.cpp:5300-5313 | 每次 PrepareForDraw/Compute 顶部重写(:5291-5298 注释) |
| `g_passes`/`g_passCount`/`g_routedProgram` | DirectGLES.cpp:7779-7780 | 每次 replay 收尾清零(:7915-7924) |
- 今天为何能工作:双键俱备且失效方向保守(键不符即重建);`MGPipeApplierReset()` 在 context 切换
时清空 applier 的记录表,令 `ContextSerial`/记录哈希侧的键同时移动(DirectGLES.cpp:1022-1029
的注释即此论证)。
- P5f 处置建议:**进程级即可,不新增机制**。spawn 后只有 server 跑 Espryt;记录臂的键全部来自
applier 自己的 serial(APPLIER_DERIVED),不依赖 client 内存。f1 双块只需验证 client 角色下无人
读写它们(双块会把这种访问变成具名红)。
### S4 纯 server-ES-context 状态(绑定影子 / scratch / 池 / 环 / EGL 句柄)—— 【B】
描述的是 server 自己的 ES context 或 renderer,不携带任何 client 身份。重置统一挂在
`DestroyEGLContext()`(`DirectGLES.cpp:15896-15912`,依次调 `BufferImpl::OnBackendContextDestroyed`
(Managers.cpp:3146)、`XfbImpl::OnBackendContextDestroyed`(DirectGLES.cpp:2067-2074)、
`MultiDrawImpl::OnBackendContextDestroyed`(MultiDraw.cpp:1086)、`OnRestartSubstitutionContextDestroyed`、
`ScratchFBOImpl::OnBackendContextDestroyed`(Managers.cpp:11509)、`ReleasePackedWordScratchTexture`
(DirectGLES.cpp:14150-14155))与 `MakeCurrent()` 的 Invalidate 序列(:15363-15375):
- DirectGLES.cpp:`g_Display/g_Context/g_Surface/g_Config`(:14834-14837,后端唯一 ES context
句柄)、`g_requestedSwapInterval`(:15229,per-surface,surface 重建时重放)、
`g_backendContextOwnerThread`(:15289)、`g_syncContextGeneration`(:15295)、frame fence 环
(:15304-15312)、`g_eglVerifiedFrameSerial/g_eglVerifiedContextGeneration`(:15415-15416)、
resolve scratch(:9462-9467,自含世代守卫 :9488)、`ReplicateBlitImpl`(:9777-9789,守卫 :9851)、
`DepthStencilSamplingReadImpl`(:12876-12911,守卫 :12941)、packed-word scratch
(:14101-14103)、restart scratch(:8395-8396)、`g_writableImageBufferUnits/g_writableImageBufferUnitCount`
(:3605-3607)与 `g_imageUnitHighWaterMark`(:3633,自恢复语义见 :3600-3604 注释)。
- Managers.cpp:`g_backendContextGeneration`(:68)、绑定影子(:811-822、:3777-3791、
:4790-4791、:9578-9582 `g_activeTextureUnit`/`g_boundTexturesCache`、:11520-11521 pack 影子)、
`g_bufferContextGeneration`(:826)、deferred releases(:852-857)、buffer pool(:873-875)、
三条 persistent ring(:1017-1039)、`g_backendBufferResources`(:2873)、`g_bufferBackendIdGeneration`
(:3108)、`g_backendSamplerViews`(:4649)、`g_pendingFetchBaseInstance`(:4850)、alpha/integer
mask(:9929-9930)、`g_attachmentBackendIdGeneration`(:11277)、scratch FBO(:11283-11287)、
program 影子(:11571-11574)。
- MultiDraw.cpp:scratch 组(:329-344)、tier 解析(:464-466)、`g_announcedTiers`(:483)。
- BackendObject_DirectGLES.cpp:54-58:native bind 影子,注释自述「Process-wide like the native
state it mirrors; under split exactly one DirectGLES object exists (the server's)」。
- 处置建议:进程级即可;唯一待办是 f1 双块下确认 client 角色不触碰(EsprytSlotTablesEnabled 等
latch 已在拆分臂下拒绝)。
### S5 `TwinLookupMemo` 三件套 + `g_fbSlotCache*`(legacy 臂)—— 【B】,含一条 D
- 证据:DirectGLES.cpp:143-152(`g_vaoTwinLookupMemo`/`g_programTwinLookupMemo`/`g_fboTwinLookupMemo`,
仅 `MOBILEGL_PIPE_LEGACY_MEMOS` 编译)、:167-168(`g_fbSlotCacheContext`/`g_fbSlotCache`,pull 或
legacy 臂编译)。
- 语义:以前端对象地址为键的 memo——**per-client-context 语义**,因为键是 client 堆地址。
- 今天为何能工作:memo 注释(:88-102)论证 registry 条目跨 ES context 存活、弱指针防地址复用;
`g_fbSlotCache` 以 context 裸指针为键、指针比较即失效(:155-163)。
- P5f 处置建议:拆进程后 server 进程没有 client 对象,这两条臂在 server 上根本不编译/不被选中
(句柄臂取代);进程级即可。但留一条【D】:push-monolith 构建下它们仍在运行,若 f1 双块让
monolith 臂与 server 臂同进程共存,这两个 memo 属于 client 角色块——f1 的归属判定要覆盖它们。
### S6 XfbImpl 家族:map 里存 client 对象的 SharedPtr —— 【A】
- 证据:`g_xfbObjects`(`DirectGLES.cpp:1548`)、`g_currentXfbName`/`g_currentXfbState`
(:1549/:1558)、`g_scatterBufferId/g_scatterBufferSize`(:1545-1546)。`XfbObjectState` 持有
`SharedPtr<MG_State::GLState::BufferObject> buffer`(:1511)与
`SharedPtr<MG_State::GLState::ProgramObject> scatterProgram`(:1538)。
- 语义:按前端 XFB 对象名键控的捕获状态——per-client-context 语义,且**值里嵌着 client 对象指针**。
- 重置时机:`XfbImpl::OnBackendContextDestroyed()`(:2067-2074)清表——只随 server ES context
销毁走,**不随 client context 世代走**。
- 今天为何能工作:同地址空间 + lockstep;P5c 已把 XFB scatter 的读改到 server staged shadow,但
这个 map 的 SharedPtr 成员本身仍在。
- P5f 处置建议:此项同时落在 §2.6(反向通道触碰 client 内存)的 fv 包里;从静态量角度它必须
按角色+世代分区,且成员里的 `SharedPtr` 要在 spawn 前换成句柄或 server 侧对象——与 T5 同类的
跨角色持指针。
### S7 `g_rawDepthFetchSamplerState`/`g_rawDepthFetchSamplerBackend` —— 【D】
- 证据:DirectGLES.cpp:72-73;惰性构造于 `GetRawDepthFetchSampler()`(:301-313),**无重置点**。
- 语义:raw-depth-fetch 替代的 server 自建 frontend SamplerObject 及其 backend twin(P4a D-F3 具名
保留,:284-300 注释)。
- 今天为何能工作:内容是不可变常量(构造即定型),`SyncToBackend` 每次调用重同步。
- P5f 处置建议:跨进程后 server 自建自持有,方向成立;但 `BackendSamplerObject` 内部是否持有
driver sampler id(若是,则 ES context 换代后失效无人清)只读未确认——**未知**,留给 fs 包顺手
回答(一行 grep `BackendSamplerObject` 的成员即可)。
### S8 `g_anyProgramRoutesViewportIndex` —— 【B】,附注
- 证据:Managers.cpp:87;置位点 :14028(program link),**只升不降,无重置**。
- 语义:「本进程编译过的 program 里是否有任何一个路由 viewport index」的 latch。
- 今天为何能工作:方向保守——latch 卡在 true 只会多做 viewport routing 检查,不会错画面。
- P5f 处置建议:进程级即可(per-server-进程的程序集合属性);不需修,仅记录它是「无重置 latch」
家族的一员。
### S9 `g_bufferMutationEpoch` —— 【C】(已被角色守卫钉在 server 侧)
- 证据:Managers.cpp:834;bump 点全在 `Ops_*Tracked` 与 DirectGLES 的 6 处
(DirectGLES.cpp:1654/:1679/:1828/:12828/:14393/:14808);P5e 已加
`Fatal{RoleViolation, "CurrentBufferMutationEpoch"}` 守卫(Managers.cpp:2853)把 GL 线程 bump
钉死(仅 bring-up 例外,:2811-2830 注释),并有 `g_bufferMutationEpochBumpsOffTheApplyThread`
(:2803)计数供测试断言为零。
- 语义:memo 干净探针的跳过时钟;语义上「谁的 buffer 变了」是 client 事件,但 P5e 已裁定 bump 只
发生在 apply 线程(server 角色)。
- P5f 处置建议:已是角色正确;进程级即可。f1 双块下它归入 server 块。
### S10 Magma(DirectVulkan)—— 【B】
- `g_rendererGeneration`(DirectVulkan.cpp:44):per-renderer 世代,renderer 重建时
`BumpRendererGeneration()`(:51-53);server 独有。
- `g_activeBufferManager`(Renderer/VkBufferManager.cpp:56):当前 renderer 的 manager 指针;
server 独有。
- `g_dynamicStateShadow`(Renderer/VulkanRenderer.cpp:409):Vulkan 动态状态影子,per-renderer。
- `s_warnedHalfTessellatedPipeline`(PipelineFactory.cpp:528)、`s_warnedInstanceIndexUnsupported`
(ProgramFactory.cpp:2635):log-once,进程级。
- 注:VulkanRenderer.h:718-730 的 `s_vkResetQueryPool` 等是**类成员**(无 `static`),不在普查范围。
- 处置建议:进程级即可。Magma 臂的 lockstep 掩盖问题属 §2.2(fm 包),静态量本身无跨角色语义。
### S11 MG_Impl/Pipe 的 client 侧单例群(16 个 Instance() 泄漏单例 + PipeFill 静态)—— 【B】
- 证据:`MG_Impl/Pipe/` 下 `CompositeResolver.h:266`、`CsoCache.h:202`、`FramebufferEmit.h:656`、
`ImageEmit.h:194`、`PipeFill.cpp:1711`(publication latch)、`ProgramEmit.h:725`、
`ResourceTracker.h:550`、`SamplerEmit.h:522`(cso cache)/`:679`(opaque units)/`:1082`、
`SetHashSuppressor.h:130`、`ShaderBufferEmit.h:273`、`SlotAllocator.cpp:433`、`TextureEmit.h:1344`、
`Tracker.h:932`、`VertexInputEmit.h:441`;`CsoCache.h:129` `s_hashForTest`(测试钩子)。
- PipeFill.cpp 的命名空间静态:`g_lastFillWasBarriered`(:486)、`g_omission` 三件套(:509-511,
verify 旋钮)、`g_snapshot`/`g_readScratch`(:565-566,verify 双臂)、`g_verify`(:591)、
`g_deferredDestroy*`(:1166-1168)、`g_fillPlan`(:2822)、`g_attribDefaultRepairs`(:2942)、
`g_attribDefaultLastHeader`(:2949)、`g_residualDue`(:3152)、verify 探针 `probe`(:2664)。
- 语义:per-client-context/session 的发射与追踪状态。
- 重置时机:make-current 边沿(`tracker.FreshlyPrimed()`,`PipeFill.cpp:2654-2667`;该处同时是
`applier_reset` 记录的 producer,并做 client 侧的 CsoCache / hash suppressor / vertex-input
emitter 重置,CONTRACT-P5C §5.1)。
- 今天为何能工作:单 client、单 context/session(P5c 裁定每 session 恰一个 context)。
- P5f 处置建议:进程级即可——spawn 后它们只存在于 client 进程;f1 双块时归入 client 块。多
context 是 P6+ 的形状,`MGPApplierReset::ContextSerial` 的注释(CONTRACT-P5C §1)已写明。
### S12 `MG_Impl/GLImpl` 的 query/sync 注册表 —— 【B】,含一条多 context 语义债
- 证据:`GL_Query.cpp:55-66`(`g_queryObjectsMutex`/`g_liveQueryObjects`/`g_nextQueryId` 与四个
active-query id + pipeline-statistics map)、`GL_Sync.cpp:34-37`(`g_syncObjectsMutex`/
`g_liveSyncObjects`)。
- 语义:GL 对象(query/sync)按规范是 per-context 的,这里存成进程级注册表。
- 今天为何能工作:单 context/单 session;mutex 防多 JVM 线程迁移(:50-54 注释)。
- P5f 处置建议:拆进程后天然在 client 进程,进程级即可;但「语义 per-context、存储 per-process」
这一事实记入 P6+ 多 context 债(与 S11 同一形状)。
### S13 MG_Remote 会话单例与跨角色标量 —— 混合
- `ClientSession::g_active`(Client/ClientSession.cpp:48)、`ServerSession::g_active`
(Server/ServerSession.cpp:349)、`ServerLoop` 单例(Server/ServerLoop.cpp:774):每进程一个
session 是设计本身——【B】。
- `g_applyThreadInsideApplier`(Client/ClientSession.cpp:118):**inproc 下 server 写、client 读的
跨角色共享原子**,语义 per-session。CONTRACT-P5C §6(A1)已具名其为 barrier 簿记。拆进程后
client 那份恒 false,但它是 wait-rule 机制的一部分而非状态内容——【A】:f1 双块/角色分区时必须
按角色拆开,否则会漏掉「client 读自己那份永远 false」的假绿。同址的 `thread_local
g_inBarrierWait`(:117)是 client 线程本地,【B】。
- `WireTables.cpp:91-104`(`g_emitted`/`g_declined`/`g_clientTablesUninstalled`)、
`WireTables.cpp:642`(`g_applierResetContextSerial`,per-session 计数器,client 侧)、
`EmitTables.cpp:91-96`(drop 旋钮与 `g_presentOrdinal`/`g_publishedMaxRecordBytes`)、
`EmitTables.cpp:1543`(`g_fenceMutex`)、`EmitTables.cpp:711`(发射 scratch)、
`CapsMirror.cpp:34-40`(hook + 计数器)、`CapsMirror.cpp:169`(单例)、
`GpuWritePending.cpp:171`(producer marks)、`PersistentMapTracker.cpp:85-136`(SIGSEGV 追踪):
全部 client 侧,【B】。
- `PipeWireCodec.cpp:347` `g_processResolverTable`:本进程段表指针,每进程各一份即正确,【B】;
同文件 :355 的 `t_activeDecoder` 是 thread_local,已是 per-thread。
- `ServerLoop.h:126` `g_applyThreadKey`:server 线程身份戳,【B】。
- `StagedShadowStore` 单例(Managers.cpp:1160-1165):server 侧暂存影子,注释自述「ONE PER
PROCESS」,【B】。
- Transport 的 `g_spinItersPerUs`(Doorbell.cpp:37)、`g_shmCounter`(ShmSegmentPosix.cpp:47)、
`g_sectionCounter`(ShmSegmentWin32.cpp:36):进程级校准/命名计数器,【B】。
### S14 MG_Pipe 核心 —— 已知项定位
- `gPipeInputs`(MG_Backend/MGPipe/PipeInputs.h:835):P5f §1 的主对象,双块机制(§4)就是为它造
的;不重述。
- `g_applier`(MG_Pipe/PipeApply.cpp:433):server-private(CONTRACT-P5C §5.1 裁定),重置经
`applier_reset` 记录(opcode 77,`MGPipeApplierReset()`,`PipeApply.cpp:1202-1300`)——【C】
已是 per-session。
- `PipeRoute.cpp:63-69`(`g_arm`/`g_monolithScreen`/`g_monolithContext`/`g_monolithEscapes`):
路由表与配置,【B】。
- `PipeInputs.cpp:55` `g_residualPulls`:诊断计数器(rsp),server 侧读,【B】。
### S15 MG_State —— 【B】
- 七个 lifetime-id 分配器(BufferObject.cpp:31、RenderbufferObject.cpp:25、
FramebufferObject.cpp:20、ProgramObject.cpp:27、SamplerObject.cpp:20、TextureObject.cpp:26、
VertexArrayObject.cpp:18)与 `s_nextTextureStateContextId`(TextureState.cpp:24):client 侧单调
分配器;spawn 后只存在于 client 进程,句柄经记录过线,进程级即可。
- `g_stateObjectDeathOps`(StateObjectDeathNotice.h:52)、`g_bufferBackendOps`
(BufferObject.cpp:79):进程级函数表,【B】。
- `g_textureLegacyArmScopeDepth`(MipmapStorage.cpp:112)、`g_magmaP7AllocatorDebtScopeDepth`
(SlotAllocator.cpp:118)、`g_frontendKeyedRegistryScopeDepth`(SlotAllocator.cpp:143):P5c 具名
豁免 scope 的深度计数,守卫簿记而非状态,【B】。
- `ProgramSpirvTask.cpp:35-36`:log-once atomic_flag,【B】。
- `MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp:32-33`:macOS IMP 缓存,与拆分无关,【B】。
## 3. 汇总
| 分类 | 条目 |
|---|---|
| 【A】P5f 必须按角色+世代分区 | S1(g_syncedRenderStateParameters 家族)、S2(s_synced 家族)、S6(XfbImpl)、S13 中的 `g_applyThreadInsideApplier` |
| 【B】进程级即可 | S4、S5(附一条 D)、S8、S10、S11、S12(附多 context 债)、S13 大部、S14 的 PipeRoute/rsp、S15 |
| 【C】已是 per-context/per-session | S3(自失效双键)、S9(角色守卫已钉)、S14 的 `g_applier` |
| 【D】未知 | S7(BackendSamplerObject 是否持 driver id);S1 的「ES context 换代且 tuple 不变时 MakeCurrent 是否仍被调用」 |
**最重要的三条结论**:
1. **两个已知成员里,`ScopedDefaultUnpackState::s_synced` 比 `g_syncedRenderStateParameters` 弱得多**:
前者没有任何重置点(`Managers.cpp:6319` 一次置位永不清,不在 `DestroyEGLContext` 清单里),正确性
靠「新 ES context 的 GL 默认值恰好等于影子值」的巧合;后者有 `MakeCurrent()`(DirectGLES.cpp:15375)
的每次强制全量重推兜底,但兜底链条上「tuple 相同而 ES context 换代」一格只读无法确认(D 项)。
2. **Espryt 的 per-draw memo 大家族(S3)已经自带 (client 键, `g_backendContextGeneration`) 双键
自失效,是本普查中最大的一类,且全部不需要新机制**——f1 双块对它们只需证明 client 角色不触碰。
P5f §2.4 的「完整清单未知」可以收口为:真正缺世代钟的只有 S2、S6、S7 三个小点。
3. **S6(`g_xfbObjects` 持有 client 对象 `SharedPtr`,DirectGLES.cpp:1511/:1538/:1548)是本次普查
新发现的、唯一一个「进程级 map 的值里嵌 client 指针」的成员**,重置只挂 server ES context 销毁
(:2067-2074),同时属于 §2.6 fv 包的范畴——两个包的清单里都应有它。
### 附:普查方法的可复现性
```
grep -rn -E '^\s*(inline\s+)?(static\s+)?[A-Za-z_][A-Za-z0-9_:<>,\s\*&]*[ \*&](g_|s_)[A-Za-z_0-9]+\s*(=[^=]|\[|\{|;)' \
MG_Backend MG_Impl MG_Pipe MG_Remote MG_State --include='*.cpp' --include='*.h'
# 加上 '= *new ' / 'Instance()' 单例模式的第二轮 grep;全部命中均已人工过目并归入上表。
# 3rdparty/ 子模块不在范围内(其 git 报错为既有噪音)。
```