[Docs] (P8-E): the package note - the split read never touched a shadow, the store route and FollowCopy, the red-once table, the monolith defect for dev

This commit is contained in:
2026-09-29 22:43:02 -04:00
parent f823422bfa
commit 00d6c2480e
+102
View File
@@ -0,0 +1,102 @@
# P8-E:CopyImageSubData 由 server 的 staged store 跟随;死闩清理
> 2026-09-29,分支 `p8/e`(基线 `8d3e2317`)。简报 `~/w7/notes/p8/BRIEF-E.md`;证据 `~/w7/notes/p8/e-evidence/`。只在 Espryt;Magma 无代码改动,作对照。
## 1. 结论
- 审计的推断「split 下 copy 后 `glGetTexImage` 读到陈旧影子」不成立:split 的 `glGetTexImage` 不进 `DirectGLES::GetTexImage`,也不读任何影子。
- split 路径:client `EmitGetTexImage`(`MG_Remote/Client/TextureReadbackEmit.inc:158`)→ server `OnGetTextureImage`(`MG_Remote/Server/PipeApplier.cpp:443`)→ `ReadTextureImageWire`(`DirectGLES/WireTextureReadback.inc:422`)→ `ReadTextureLevelTight`(`DirectGLES/WireTextureReadback.inc:217`),只读 GPU(R32UI 字腿或 FBO)。
- 真实缺口:驱动不能 attach 的格式(Adreno:RGB16、16 位 SNORM)FBO 不完整(`DirectGLES/WireTextureReadback.inc:269`)→ `kStatusError`(`PipeApplier.cpp:462`)→ client `Fatal{ReplyError, "ReadPixels"}`(`EmitTables.cpp:903`)。与有没有 copy 无关,只上传过的层也结束会话。
- 主机 llvmpipe 能 attach 这些格式:不加旋钮时 split / spawn / tcp 与 monolith 全绿,所以要旋钮才能在主机上走到这条路(§4 R1)。
- 修法全在 server,不搬到 client:驱动拒绝时由 staged store 回答(store 路由),store 跟随 copy 做同一次 texel 块搬移(`FollowCopy`)。选「正确字节」,理由见 §2。
- monolith 同一缺陷(影子不随 copy 更新)记给 dev(§6)。
## 2. 设计
| 部件 | 位置 | 内容 |
|---|---|---|
| store 路由 | `DirectGLES/WireTextureReadback.inc:352` | 驱动拒绝回读时,store 仅在「整层覆盖、尺寸一致、纹理从未被驱动写过」时回答;转换同 `GetTexImageViaShadowConversion`(打包格式按字原样,其余解成宽 RGBA 逐行转) |
| 具名拒绝 / 武装标记 | `DirectGLES/WireTextureReadback.inc:411`,`:416` | 拒绝:`MGLOG_E_ONCE` `texture-readback-store-declined`,回复 error,client Fatal。回答:`MGLOG_W_ONCE` `texture-readback-from-store` |
| 驱动写标记 | `MG_Remote/Server/StagedTextureStore.h:479-514` | 按纹理、粘性;只有 `Drop` / `DropAll` 清,`ResetLevels` 不清(附件在重定义后照样写) |
| 标记来源 | `DirectGLES/Managers.cpp:10901` | `NoteDriverSideTextureWriteByHandle` 同时标 store:附件、可写 image、`CopyTexSubImage`、生成 mip 都经过它 |
| store 跟随 copy | `StagedTextureStore.h:519`,`DirectGLES/Managers.cpp:10924`,调用点 `DirectGLES/DirectGLES.cpp:13347-13365` | 两端同一内部格式(`DirectGLES/Managers.cpp:10946`)、单 upload target(非 cube / 1D array)、源整层覆盖且未被驱动写、目标整层覆盖或 copy 覆盖整层 → store 做同一搬移;否则给目标标驱动写 |
| copy 失败 | `DirectGLES/DirectGLES.cpp:13440` | 驱动拒绝 copy 时 store 已搬,补标驱动写 |
| 跟随后的 client 上传 | `DirectGLES/Managers.cpp:3193`,`StagedTextureStore.h:573` | client run 是 client 影子(没见过 copy),只落 record 声明写过的盒(regions,无 region 时 union box);放不下则照常收养并标驱动写;`AdoptRun` 兜底同样标(`StagedTextureStore.h:353`) |
| 测试旋钮 | `DirectGLES/WireTextureReadback.inc:328` | `MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT=<w>x<h>`,server 读;该尺寸颜色层的 FBO 腿视为驱动拒绝,R32UI 字腿不动 |
不选「标 GPU 脏」:
- Espryt 的 `GpuDirty` 语义是「需要从 store 重传」(`DirectGLES/Managers.cpp:8460`),标了会把 store 旧字节传回去盖掉 GPU 上的 copy。
- 跟随后其他整层读 store 的路径也拿到对的字节:重铸回读被拒时的 W2-b 重放(`DirectGLES/Managers.cpp:6888`)、整层重传。
- 跟随不了的形状(跨格式、renderbuffer 源、被驱动写过的源、cube / 1D array、视图)降为具名拒绝,不给旧字节。
旋钮按尺寸定界:
- tcp 的 server 是整条 lane 共用的 `TcpServer.Start` fixture,旋钮只能放进它的 ENVIRONMENT(`MG_IntegrationTest/CMakeLists.txt:3245`)。
- 场景只有自身环境里有旋钮时才建 13x7 的纹理,否则 12x6(`CopyImageStoreReadbackScenario.cpp:66`),所以 lane 级旋钮只碰 StoreRead 条目。
- 实测:tcp 上无旋钮条目的 server 日志没有 `texture-readback-from-store`,StoreRead 条目有。
## 3. 场景与登记
`CopyImageStoreReadbackScenario`(`MG_IntegrationTest/Scenarios/CopyImageStoreReadbackScenario.cpp:251-337`);格式 RGB16、RGBA16_SNORM、RGB16_SNORM、RGBA16,RGB9_E5 为正对照(R32UI 字腿)。
| 例 | 内容 |
|---|---|
| `AnUploadedLevelReadsBackExactly` | 上传后读回(对照) |
| `ACopiedWindowReadsBackOverTheUpload` | 可变纹理,5x3 窗口 copy 后读目标与源 |
| `ACopiedWindowReadsBackFromAnImmutableLevel` | 同上,`glTexStorage2D` |
| `APartialUploadAfterTheCopyKeepsTheCopiedWindow` | copy 后在同层别处 `glTexSubImage2D` |
| `ARenderbufferSourceLandsInTheTexture` | RGBA16 renderbuffer → 纹理(handle 臂,证明 `+RENDERBUFFER` 闩已死) |
| 条目 | 臂 | 登记 |
|---|---|---|
| `DirectGLES.*` / `DirectVulkan.*` | monolith | 整体发现 |
| `DirectGLES.{Split,Spawn,Tcp}.*` | 三臂,无旋钮 | `mgl_itest_register_split_arms`(`MG_IntegrationTest/CMakeLists.txt:3229`) |
| `DirectGLES.{Split,Spawn,Tcp}.StoreRead.*` | 三臂,旋钮 13x7,前 4 例 | `MG_IntegrationTest/CMakeLists.txt:3247-3273`,带 `integration-gpu` |
| `DirectVulkan.{Split,Spawn,Tcp}.CopyStore.*` | Magma 门控三臂 | `MG_IntegrationTest/CMakeLists.txt:3281`;另有 tier 2 信息层 |
- 三臂对称,`spawn_lane_parity.py` 不需例外行(`StoreRead.` 尾不被 Espryt 的 `CASE` 解析,`scripts/ci/spawn_lane_parity.py:39`)。
- store 单元用例 6 个:`MG_Test/Wire/StagedTextureStoreTest.cpp:330-475`。
## 4. red-once
| # | 变异 | 载体 | 红 |
|---|---|---|---|
| R1 | 基线代码 + 旋钮(无 store 路由) | StoreRead 12 条 | 12/12 `Fatal{ReplyError, "ReadPixels"}`,只上传的对照也死 |
| M0 | server 跳过 copy | 无旋钮 Espryt 三臂 15 条 | 12 红(4 例 × 3 臂),读到 copy 前字节;上传对照绿 |
| M1 | store 既不跟随也不标记 | StoreRead | 9/12 红,读到 copy 前字节(简报的「去掉即陈旧字节」) |
| M2 | 不跟随但标记 | StoreRead | 9/12 红,`Fatal{ReplyError}`(具名拒绝) |
| M3 | 关掉盒合并与 `AdoptRun` 兜底 | StoreRead 部分上传例 + 单元 | 3 条读到旧 copy 窗口 + `AWholeRunOverAFollowedCopyMarksTheTextureDriverWritten` 红 |
| M4 | store 路由忽略驱动写标记 | renderbuffer 例 + 旋钮 12x6(手动) | 旧字节;未变异时是 `texture-readback-store-declined` |
| U1 | `FollowCopy` 信任被驱动写过的源 | `ACopyTheStoreCannotVouchForIsNotFollowed` | 红 |
| U2 | `ResetLevels` 清驱动写标记 | `TheDriverWrittenMarkOutlivesAResetAndDiesWithTheKey` | 红 |
| U3 | `AdoptClientBoxes` 落整条 run | `AClientRunOverAFollowedCopyLandsOnlyItsBoxes` | 红 |
| MV | Magma 跳过 copy | Magma 35 条 | 28 红(4 例 × 7 条目),上传对照绿 |
| MONO | monolith `GetTexImage` 强制走影子(临时探针,不提交) | `DirectGLES.CopyImageStoreReadbackScenario.*` | 4 个 copy 例 × 4 个 16 位格式读到 copy 前字节;RGB9_E5 与上传对照绿 |
每次变异后按字节复制还原、`touch`、`cmp` 一致;日志在证据目录(`redonce-*.log`、`red1-head-knob-all.log`、`monolith-probe-shadow.log`)。
## 5. 死闩清理(无行为变化)
- `CopyImageSubData+RENDERBUFFER`:原在 `SyncRenderbufferObjectToBackend` 内,条件是 transport≠monolith;唯一调用点只在 monolith 臂(split 臂按句柄处理,`DirectGLES/DirectGLES.cpp:13134-13155`),删去并改写注释(`DirectGLES/DirectGLES.cpp:13110-13117`)。
- 镜像函数里过时的「client 侧镜像留给 P8」「由两个 Fatal 兜底」注释改写(`DirectGLES/DirectGLES.cpp:13239-13252`)。
- Fatal 普查:abort 点 79 → 78(`DirectGLES.cpp` 15 → 14),族 45 不变;基线用 `fatal_census.py --write-baseline` 重写。
## 6. monolith 缺陷(ID-P8-3,记给 dev,不在此修)
- `MirrorCopyImageIntoDestinationShadow` 只对 `HasRedundantPackedEncoding`(仅 RGB9_E5)调用(`DirectGLES/DirectGLES.cpp:13450`);其他格式 copy 后 CPU 影子不变。
- 驱动 attach 不了目标格式时 `GetTexImage` 退到影子(`DirectGLES/DirectGLES.cpp:15748`)→ 返回 copy 前字节。设备(Adreno:RGB16 / 16 位 SNORM / 无 norm16 时的 RGBA16)可达。
- red-once 见 §4 MONO;修法建议:镜像对所有非压缩同格式 copy 都做,或像 split 一样按纹理记「驱动写过」后拒绝。
## 7. 未做 / 发现未修
- `copy-image-shadow-mirror` 的 `MGPipeUnmigratedEmulation` 调用(`DirectGLES/DirectGLES.cpp:13261`)在 transport 下不可达(`:13253` 先返回),未删:名单在 `PipeCatalogueTest.cpp:1439`,归 B / F 统一改。
- `MGPipeTypes.h:1535` 的 P5b 历史注释仍提 `+RENDERBUFFER` 拒绝;是 wire 头注释,未动。
- 跨格式 copy 不跟随(store 存规范影子,16 位打包、RGB10 / RGB12 的规范表示不是 GL 位),降为具名拒绝。
- 具名拒绝仍是会话 Fatal(client 的 `RequireReadbackReplyComplete`);改成 `GL_INVALID_OPERATION` 需要协议变更,不在本包。
- 深度格式经 `BlitDepthTexture2D` 的 copy 由 store 按原样搬移;若该仿真非逐位,store 与 GPU 可能有精度差,只影响驱动拒绝深度回读的情形。
- 未上设备验证(简报:只有 S 包用手机)。
## 8. 门
(见 §8 补记)