Files
MobileGL/docs/Disaggregated/notes/p9/W2-REMINT.md
T

102 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P9 W2:`texture-remint-pull`——重铸不需要拉取
> 2026-09-29,分支 `p9/w2`(基线 `ad705a73`)。交接:[`HANDOFF-P9.md`](HANDOFF-P9.md) §3 W2 / W4。
> 裁定(集成方):采纳 A——不做拉取协议,删 `OnTexturePullRequest`;monolith Espryt 的缺陷不在 P9 修(§6 单列);W2-b 只退回 store;W4 不做(§5 只记测量)。
## 1. 结论
两个后端在 split 下重铸都只用 server 自己的字节,client 从不参与:
| 后端 | split | monolith |
|---|---|---|
| Espryt | `RequireImageBindableStorageByHandle`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:6289`,`c1bf7e15` 落地):驱动上存在的层逐层读回旧 GPU 纹理(`ReadTextureLevelTight`),pending 盒用 `StagedTextureStore` 合并后整层重放;已 immutable 且无需加宽的原样保留(keep) | 前端臂 `RequireImageBindableStorage`(`Managers.cpp:6459`)重放 client 影子 |
| Magma | 重建带 `STORAGE` 的 VkImage,`PreserveTextureContentsOnRecreate`(`MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp:289`)GPU→GPU;wire 臂 `SyncWireTextureShape` 的 `preserve`(`:2254`) | 同一套(`SyncTextureResource`,`:3313`) |
- **只读 store 不够**:store 只有 client 上传过的字节;GPU 写过的层(渲染目标、clear、生成的 mip)在 store 里是旧的或没有。普查里 Espryt 的两次重铸都是 store 无字节的渲染目标(§4)。
- `Fatal{UnmigratedEmulation, "texture-remint-pull"}` 的两处标记都在前端臂里,transport 下不可达(P7 与本次普查都是 0 次),随本包删除。
## 2. 改了什么
| 包 | 内容 |
|---|---|
| W2-a | 回读前问驱动这一层在不在、尺寸对不对(`NativeTextureLevelHasExtent`,`DirectGLES/WireTextureReadback.inc:290`,`glGetTexLevelParameteriv`)。不在:store 覆盖就重放 store,否则跳过(GL 定义为未定义内容)。修的是:在最后一次 sync 之后才定义(空数据,或空数据 + 局部上传)的层,旧代码去读它 → FBO 不完整 → `Fatal{ResourceUnavailable, "image-promotion-readback"}`,合法 GL 让会话死掉 |
| W2-b | 驱动拒绝回读(设备上 SNORM / 16 位 norm / 无 `EXT_color_buffer_float` 的浮点不可渲染)而 store 覆盖该层 → 重放 store(= monolith 的答案),`MGLOG_W_ONCE` 带 `remint-readback-fallback`;store 也不覆盖才保留原 Fatal。测试旋钮 `MGITEST_ESPRYT_FORCE_REMINT_READBACK_FAILURE`(server 读) |
| W2-c | 删两处 `texture-remint-pull` 标记(transport 专用的提前标记块 + 循环内标记);`PipeCatalogue.EveryUnmigratedEmulationIsNamedOnce` 5 → 4;「remint stall class」注释改写 |
| W2-d | `TextureRemintPulls`(`trp=`)在 server 路径也计:ByHandle 重铸已有存储时 +1(keep 不计),名字不改 |
| W2-e | 删 `OnTexturePullRequest`,`kMGPipeCallbackCount` 9 → 8;op 50 `ResourceSubDataComplete` 留作退役行(解码仍拒收),不改 `protocol.fbs` |
| W2-f/g | `TextureRemintPullScenario`:14 例 × 两后端 × {monolith, Split, Spawn, Tcp};另有 `DirectGLES.{Split,Spawn}.RemintFallback.` 4 例(W2-b 旋钮,tcp 不带,理由同 `MAGMA_SERVER_ENV_KNOB_NO_TCP`) |
没改:Magma 无功能改动;`MOBILEGL_PIPE_TEXEL_RETAIN_MB`(`Config.h:400-403`)已无消费者但不在 PUSH 守卫内,删它动 pull 的 `Features` / `ConfigLoader`(G1),留到 P13。
## 3. 覆盖矩阵(场景 14 例)
内容 → 采样 draw(让后端持有存储)→(按例)GPU 写 → `glBindImageTexture` → compute `imageLoad` 拷到一张「sync 之前就 image 绑定」的 RGBA8 → FBO 回读,比 GL 正确答案。
| 例 | 内容来源 | store | 驱动上的层 | Espryt mono / pull | Espryt split / spawn / tcp | Magma 全部臂 |
|---|---|---|---|---|---|---|
| A 可变,上传 | 上传 | 有,= GPU | 有 | ✓ | ✓ | ✓ |
| B 可变,上传 → GPU clear | GPU | 旧 | 有 | **✗ 旧影子** | ✓ | ✓ |
| C immutable RGBA8,上传 → clear | GPU | 旧 | 有 | **✗** | ✓(keep) | ✓ |
| D 空定义 → clear | GPU | 无 | 有 | **✗ 零** | ✓ | ✓ |
| E clear → 未 sync 的局部上传 | GPU + 盒 | 盒 | 有 | **✗** | ✓(合并) | ✓ |
| F `glGenerateMipmap` 的 level 1 | GPU | 无 | 有 | **✗ 零** | ✓ | ✓ |
| F2 上传的 level 1 → clear | GPU | 旧 | 有 | **✗** | ✓ | ✓ |
| G sync 后空定义 level 1,绑 level 0 | — | 无 | **无** | ✓ | ✓(W2-a 前 Fatal) | ✓ |
| G2 sync 后以更大尺寸空定义 level 0 | — | 无 | 旧尺寸 | 不 Fatal | 不 Fatal | 不 Fatal |
| G3 G + 局部上传,绑 level 1 | 上传 | 整层 | **无** | ✓ | ✓(W2-a 前 Fatal) | ✓ |
| H 加宽 rg8 immutable,上传 → clear | GPU | 旧 | 有 | **✗** | ✓(promote) | ✓ |
| H2 / I / J 加宽 rg8 / RGBA8_SNORM / RGBA16_SNORM,仅上传 | 上传 | 有 | 有 | ✓ | ✓ | ✓ |
「谁都没有字节」只剩 G / G3,而那里 GL 不要求字节(G)或字节就在 store(G3)。Espryt 重铸代价:一次 `glFinish` + 逐层回读 + 整层重传,主机 0.3–6.5 ms / 次;GPU→GPU 是优化,只记录。
## 4. 普查(主机 lavapipe/llvmpipe,77 行 × {monolith, inproc} = 154 次,`MOBILEGL_PIPE_STATS=1`)
口径同 P7 E1(CI 旋钮、每条单独 `ctest`、`-j 2`);154 次 rc 全 0、`Fatal{` 0 行。只有下表非零,其余 71 行全零:
| case | 后端 | monolith 重铸已有存储(`trp`) | split | mono / split SSIM |
|---|---|---|---|---|
| `iris-photon-v1.3b` | GLES | 1,RGBA16F 1 层,GPU ≠ 影子 | promote 1,回读 165,888 B,store 无字节,0.3 ms | 0.998774617 / 同(纹理随后被整张覆盖) |
| `iris-derivative` | GLES | 1,RGBA16F 1 层,GPU ≠ 影子 | promote 1,回读 3,279,360 B,store 无字节,6.5 ms | **0.996196552 / 0.996376198**(§6) |
| `create-indirect` | GLES | 0(首次分配,9 层) | 0(首次分配) | 0.999961275 / 同 |
| `iris-photon-v1.3b` / `iris-derivative` / `iris-iterationrp` | Vulkan | STORAGE 升级 1 / 1 / 4 | 1 / 1 / 5 | 全 `preserve`(GPU→GPU) |
与 P4a 的「780 个统计窗口 2 次」同两条 trace。测量用的是临时插桩(未提交),跑器与原始数据在 `~/w7/notes/p9/w2census/`。
## 5. W4:反向通道事件量(inproc,server 侧逐事件计,同一次普查)
| 量 | 读数 |
|---|---|
| `OnBufferWriteback` | 只有 `create-indirect`:两后端各 6 片、7,180 B,最大单片 6,656 B(上限 SEG_EVENT / 4 = 64 KiB) |
| `OnGpuWritten` | 只有 `create-indirect` × Vulkan:7,093 条(每条 1 个 whole-buffer range,几乎全在第一帧);Espryt 同 trace 0 条 |
| `OnGlError` / 阻塞(ring 满)/ 丢弃 | 0 / 0 / 0 |
Magma 的 7,093 条是冗余的:transport 臂每次 draw / dispatch 对每个可写 SSBO / texel buffer / XFB 目标发一条 whole-buffer `OnGpuWritten`(`VkBufferManager.cpp` 的 `MarkWireBufferGpuWritten`),而 client 早已按同一组绑定自标保守集(`MG_Remote/Client/GpuWritePending.h`);Espryt 的同名生产者 P5e fb 已在 transport 下删掉。**裁定:没有阻塞、没有丢弃 = 没有实测需求,不做。** 以后要做,改动是「transport 下只保留 server 簿记、不发事件」。今天没有逐 EventKind 的计数器,`eventDropped` 也不进 stats 行。
## 6. dev 缺陷:monolith Espryt 重铸重放过期影子(不在 P9 修,单独立项)
> **2026-09-29 已修**:dev `568090f0`(重铸前先把驱动上的各层读回影子,immutable 且无需加宽的分配直接保留),P11 合并进本线(`notes/p11/INTEGRATOR-DECISIONS-P11.md` ID-P11-6);`TextureRemintPullScenario` 对 monolith DirectGLES 的 7 例 skip 已撤,iris-derivative 的 monolith SSIM 0.996196817 → 0.996376498,与 split 各臂相同。下文是当时的记录。
- **一句话原因**:前端臂 `RequireImageBindableStorage`(`Managers.cpp:6459`)把每个已定义层标脏、从 client `MipmapStorage` 重放进新 carrier,而影子从没见过 GPU 写的内容(渲染目标、clear、`glGenerateMipmap` 的层、`imageStore`);immutable RGBA8 也走这条(monolith 没有 split 的 keep)。
- **复现**:本场景的 B、C、D、E、F、F2、H 在 `DirectGLES.TextureRemintPullScenario.*`(monolith)和 pull 构建(`build-linux`)逐例同错(B:读到上传的图案而不是 clear 的绿;D、F:读到零)。Magma 两臂全对。场景对 monolith DirectGLES 这 7 例按名 skip,理由指向本节。
- **真实证据**:`iris-derivative` × DirectGLES,P7 普查 §3 #6 那个「未归因」的 ~26.9k px 系统差。monolith 在该 trace 重铸一张 854×480 RGBA16F(GPU ≠ 影子);把前端臂改成重放 GPU 字节(临时实验)后 monolith 的 `actual.png` 与 split 逐字节相同(sha `ebbe8e9d…`),SSIM 0.996196552 → 0.996376198(更近 golden)。`iris-photon-v1.3b` 同样重铸一次,但纹理随后被整张覆盖,无可见差。
- **建议修法**:前端臂在重放前,对 `hadBackendStorage` 且无 pending 的层从旧驱动纹理读回(Espryt 已有同一把工具 `ReadTextureLevelTight`,目前只编进 `MOBILEGL_BUILD_DISAGGREGATED`)写进影子再重放;或更干净地学 Magma:`glCopyImageSubData` / 着色器拷贝 GPU→GPU,影子不参与。
- **G1**:函数主体是 pull 构建共用代码;任何修法都会动 pull 的 `.text`(`ReadTextureLevelTight` 还得搬出 disaggregated 守卫)。按规矩在 dev 上单独修,不在 P9。
## 7. 未决 / 观察
- **keep 路径的一个排序缺口(非重铸问题)**:C 在 Espryt split 上不加 barrier 时 30 次里 3 次读到 clear 之前的纹素(加 `glMemoryBarrier(GL_ALL_BARRIER_BITS)` 后 0 / 30),失败与通过两种运行里 server 的每个同步决定完全一样(临时日志:keep、无 regen、无上传)。GL 不要求 framebuffer 写之后、imageLoad 之前加 barrier;promote 路径因 `glFinish` 回读被串行化,所以只有 keep 暴露。怀疑 llvmpipe 的 compute image 读没有等待挂起的光栅场景,未归因。场景在 dispatch 前加了 barrier(注释写明),这件事单独记。
- **设备格式风险**:Adreno 830 上 SNORM 等格式的回读是否被拒没测(本包不碰设备);W2-b 让这种情况退回 store,只有「GPU 写过 + 不可读 + store 无字节」才 Fatal。
- 多重采样:`ReadTextureLevelTight` 对 MS 直接失败;ES 没有 MS image 单元,只有「MS + 需加宽 + 已有存储」才进 promote。理论可达,没见过。
## 8. 门与 red-once(R-16)
| 门 | 红的方式(临时改动,已还原,真跑过) | 结果 |
|---|---|---|
| G:`ALevelDefinedWithoutDataAfterTheLastSyncDoesNotEndTheSession` | 去掉 W2-a 检查 | Split / Spawn / Tcp 3 / 3 `Subprocess aborted` |
| G3:`APartialUploadIntoALevelDefinedAfterTheLastSyncReachesTheImage` | W2-a 与 W2-b 一起去掉(G3 被两者各自兜住) | 3 / 3 aborted(G 同) |
| `RemintFallback.*`(W2-b) | 退路改回 Fatal | 8 / 8 aborted |
| `trp=`(W2-d) | 删 server 路径的 +1 | inproc 上 A、H 红,C(keep)保持绿 |
| Espryt 读 GPU 是承重的 | 不改代码,整组 split 场景带 `MGITEST_ESPRYT_FORCE_REMINT_READBACK_FAILURE=1`(= 只读 store) | B、E、F2、H 读到旧字节,D、F aborted(store 无字节);C(keep)、A、G、G2、G3、H2、I、J 绿 |
| Magma 的 GPU→GPU 保留 | 两臂 `preserve` 恒 false | 4 臂 × 12 例红(G2、G3 除外) |
| 回调数、名单 | `static_assert` + `PipeCatalogue.ReverseChannelHasTenCallbacks`(断言 8)、`EveryUnmigratedEmulationIsNamedOnce`(断言 4) | 编译期 / 名单钉 |