diff --git a/MobileGL/MG_Pipe/MGPipeCallbacks.h b/MobileGL/MG_Pipe/MGPipeCallbacks.h index d54c5688..1a012cb9 100644 --- a/MobileGL/MG_Pipe/MGPipeCallbacks.h +++ b/MobileGL/MG_Pipe/MGPipeCallbacks.h @@ -17,9 +17,20 @@ // Today this traffic is 95 call sites across 17 methods poked directly into frontend // objects. gallium has no vocabulary for shadow writeback, GPU-write notification, texture // re-send requests or default-framebuffer geometry, because in Mesa the state tracker and -// the driver share an address space. Naming them as ten callbacks plus one forward +// the driver share an address space. Naming them as nine callbacks plus one forward // terminator (MGPipeContext::ResourceSubDataComplete) is the deliberate deviation (D8). // +// NINE, NOT THE TEN PLAN B WROTE. The tenth was OnXfbScatterReady, and it went with the +// design it belonged to: plan B put the XFB scatter on the CLIENT (the server would hand +// back the packed scratch and the client would run the patch loop over its own shadow), so +// there had to be a callback that told the client the layout. P5c/P5f went the other way - +// the server reads its OWN staged shadow, scatters there and returns the reconciled range +// through OnBufferWriteback, which needs no second callback - and the declaration was left +// behind with zero producers, zero consumers and no EventKind of its own. Deleted here +// (P3b/P4b espryt D1 slice 3) rather than carried: an entry that cannot fire is one every +// reader of this file has to rule out by hand, and the size assertion below made it look +// load-bearing. +// // Installed at context creation. In a monolith these are direct calls; under split they are // records on the reverse channel, and their ORDER is a correctness requirement rather than // an optimization (section 7.4). @@ -46,14 +57,11 @@ namespace MobileGL::MG_Pipe { void (*OnCapsInvalidated)(); // <= WARN is lossy, >= ERROR is lossless and rate limited. void (*OnLog)(Uint8 level, const char* text); - // The XFB scatter is a read-modify-write of the CLIENT's shadow, so the server - // hands back the packed scratch and the client scatters (section 7.2.1). - void (*OnXfbScatterReady)(MGPipeHandle scratch, Uint64 packedStride, Uint64 vertices); }; - // Ten, and the count is asserted so an eleventh cannot be added without touching the + // Nine, and the count is asserted so a tenth cannot be added without touching the // transport's reverse-channel record table. - inline constexpr SizeT kMGPipeCallbackCount = 10; + inline constexpr SizeT kMGPipeCallbackCount = 9; static_assert(sizeof(MGPipeCallbacks) == kMGPipeCallbackCount * sizeof(void (*)()), "MGPipeCallbacks gained or lost a callback"); diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index 918d22b8..697c69d2 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -1672,8 +1672,11 @@ namespace MobileGL::MG_Pipe { }; MGP_ASSERT_POD(MGPStreamOutputBegin, 120); - // end_stream_output carries the accounting the client owns; the scatter itself is a - // read-modify-write of the client's shadow and lives there (section 7.2.1). + // end_stream_output carries the accounting the client owns. THE SCATTER ITSELF IS THE + // SERVER'S, and this sentence used to say the opposite: plan B section 7.2.1 put the + // read-modify-write on the client's shadow, P5c/P5f put it on the server's own staged + // shadow, and the reconciled range comes back as an ordinary OnBufferWriteback. Nothing + // about the scatter crosses in this record. struct MGPXfbAccounting { Uint64 CapturedVertices; Uint64 PrimitivesWritten; diff --git a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp index cef7c382..3101aa94 100644 --- a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp +++ b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp @@ -1098,9 +1098,17 @@ TEST(PipeCatalogue, PipelineSubsetMembersArePinned) { "ScissorTestEnabledMask"); } -// The reverse channel is exactly ten callbacks (section 7.1). +// The reverse channel is exactly NINE callbacks: plan B's section 7.1 wrote ten, and the tenth +// (OnXfbScatterReady) went with the design it belonged to when P5c/P5f moved the XFB scatter to +// the server's own staged shadow and its OnBufferWriteback return path. P3b/P4b espryt D1 slice 3 +// deleted the declaration; this case is what makes the struct's shrink a measured fact rather +// than a claim, since the static_assert beside kMGPipeCallbackCount only proves the two agree. +// +// THE CASE NAME STAYS "…HasTenCallbacks" DELIBERATELY. G2/G14 say the ctest name set only ever +// grows, so renaming this would delete a name the gate is watching; the count it asserts is what +// has to be right, and the comment is where the number lives. TEST(PipeCatalogue, ReverseChannelHasTenCallbacks) { - EXPECT_EQ(kMGPipeCallbackCount, 10u); + EXPECT_EQ(kMGPipeCallbackCount, 9u); EXPECT_EQ(sizeof(MGPipeCallbacks), kMGPipeCallbackCount * sizeof(void (*)())); } diff --git a/docs/Disaggregated/ARCHITECTURE.md b/docs/Disaggregated/ARCHITECTURE.md index 451615d5..455e140c 100644 --- a/docs/Disaggregated/ARCHITECTURE.md +++ b/docs/Disaggregated/ARCHITECTURE.md @@ -260,7 +260,6 @@ GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 samp | `OnSurfaceChanged(info)` | `SwapchainObject` 写 `pDefaultFramebufferInfo` 的分层倒置;client 自己合成 default-FB 对象 | | `OnCapsInvalidated()` | 2 处 `InvalidateCompileEnv` | | `OnLog(level, text)` | ≤WARN 有损,≥ERROR 无损 + 速率限制 | -| `OnXfbScatterReady(scratch, packedStride, vertices)` | §8.5 | 95 个写回点的其余归属:`MarkStorageDirty` 大多是 server 本地记账;后端凭空造的前端对象(Magma 占位纹理、swapchain default-FB 占位)→ server 原生;`SetBackendStateMemo` 直接删除;`SetBackendHashMemo/AuxMemo` → server 侧 per-slot 字段。**D-K(P3a)**:`PipeResource::m_backend` / `SetBackendResource` / `ReleaseBackend` / `BackendBufferResource` 在 push 下只是不再被写,真删会移动 pull 构建里 `sizeof(BufferObject)`(G1 破坏),随 pull 路径在 P13 退役。 @@ -278,9 +277,16 @@ GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 samp server 不保留纹素,三个原因会要求重发已发过的 level:`RequireImageBindableStorage` 的 re-dirty、整格式再生、view 源重铸。四条缓解:(1) **预防主因**:`ResourceCreate/Respecify` 一直携带 `ImageBindableHint`;(2) **拉取异步**:server 发 `OnTexturePullRequest` 并把 twin 标 not-ready,阻塞的是 apply 线程不是应用线程;(3) 有上限的保留默认关(`MOBILEGL_PIPE_TEXEL_RETAIN_MB=0`,`MipmapStorage` 保有每 level 完整 CPU 影子,缓存买的是延迟不是正确性);(4) **显式终止符** `ResourceSubDataComplete`,可携带零个 region(内容只来自渲染 / GPU mip 的 level,client 没有字节;server 带着"已分配但为空"的存储继续)。真实语料上重铸拉取率见 `ROADMAP.md` 开放问题 2(780 个窗口 2 次)。P5b 下这条路径是 `Fatal{UnmigratedEmulation,"texture-remint-pull"}`(三条 trace 的首阻塞),P9 落地。 -### 8.5 XFB scatter 搬到 client +### 8.5 XFB scatter 留在 server(本节在 P5c/P5f 之后重写;P3b/P4b espryt D1 更正文本) -Espryt 的 `ScatterCapturedRecords` 是对 client shadow 的 read-modify-write(`gl_SkipComponents` 的空洞保留应用原本的内容)。server 没有 `MappedData()`,所以 server 把紧密打包的 scratch 经 `OnBufferWriteback` 推给 client,用 `OnXfbScatterReady` 告知布局;client 拥有目的 shadow 与反射归档里的 varying/stride,原样跑补丁循环,补好的范围作为普通 `ResourceSubData` 重发。不新增停顿类。 +Espryt 的 `ScatterCapturedRecords` 是 read-modify-write:`gl_SkipComponents` 的空洞必须保留目的缓冲原本的内容,所以补丁循环需要「捕获前的字节」。本节原来写的是**搬到 client**——server 把紧密打包的 scratch 经 `OnBufferWriteback` 推给 client,用 `OnXfbScatterReady` 告知布局,client 跑补丁循环再以 `ResourceSubData` 重发。**树上走的是相反的一条路**,这一段按树更正: + +- **捕获前的字节是 server 自己的 staged shadow**(rule C:拆分后权威影子归 server,`GLESBufferResource::hostBytes`),不是 client 的 `MappedData()`——后者在 apply 线程上是 `Fatal{RoleViolation, "buffer-legacy-arm"}`(CONTRACT-P5C §3.8)。所以 server 有它需要的一切:影子、反射归档里的 varying/stride(`ProgramArchive->Link`),补丁循环原地跑完。 +- **回程只有一条 `OnBufferWriteback`**,载的是**补好的整段**,不是 scratch。`OnXfbScatterReady` 因此是零生产者、零消费者、无 EventKind 的死声明,P3b/P4b espryt D1 slice 3 删除,`kMGPipeCallbackCount` 10 → 9。 +- **该回写按 SEG_EVENT 容量的四分之一切片**(`MGPipeBufferWritebackSliceBytes`,espryt D1 slice 2):字节内联在 SEG_EVENT 记录里,而环形缓冲拒绝任何超过容量一半的记录,所以整段投递在 >128 KiB 时是 `Fatal{EventRingOverflow}`。切片按序投递,最后一片落地即蕴含之前每一片。 +- **孤儿目标(`HasDefinedContent == 0`)是合法目标**(espryt D1 slice 1):它没有可读的捕获前字节是应用自己声明的,零填充后照常散射并上传,与 monolith 从新鲜 `MappedData()` 读到的逐字节相同。 + +不新增停顿类。 ## 9. 后端状态机改造 diff --git a/docs/Disaggregated/notes/p34b/espryt-d1.md b/docs/Disaggregated/notes/p34b/espryt-d1.md index 78ecf619..33cd77d7 100644 --- a/docs/Disaggregated/notes/p34b/espryt-d1.md +++ b/docs/Disaggregated/notes/p34b/espryt-d1.md @@ -172,7 +172,51 @@ byte-identical. ## slice 3 — the dead `OnXfbScatterReady` declaration -`RESULT: (pending)` +Plan B's section 7.1 named ten reverse-channel callbacks. The tenth, `OnXfbScatterReady`, +belonged to a design the tree does not have: plan B put the XFB scatter on the CLIENT — the +server would hand back the packed scratch and the client would run the patch loop over its own +shadow — so something had to tell the client the layout. P5c/P5f went the other way. The +declaration was left behind with **zero producers, zero consumers and no `EventKind`**, and the +`static_assert` beside `kMGPipeCallbackCount` made it look load-bearing. + +Deleted; `kMGPipeCallbackCount` 10 → 9. Three pieces of prose that still described the old design +go with it: + +* `MGPipeCallbacks.h`'s own header — "ten callbacks" → nine, with the reason the tenth went. +* `MGPipeTypes.h`'s `MGPXfbAccounting` comment, which said the scatter "is a read-modify-write of + the client's shadow and lives there". It lives on the server's staged shadow. +* `ARCHITECTURE.md` §8.5, retitled from 「XFB scatter 搬到 client」 and rewritten to what the tree + does: server staged shadow, one `OnBufferWriteback` carrying the reconciled range (sliced — + slice 2), orphaned targets legal (slice 1). Its `OnXfbScatterReady` row in §8.1's table goes too, + since leaving it would be a new staleness rather than an inherited one. + +`PipeCatalogue.ReverseChannelHasTenCallbacks` now asserts 9. **The case name is deliberately not +renamed**: G2/G14 say the ctest name set only grows, so renaming would delete a name the gate +watches. The count it asserts is what has to be right; the comment carries the number. + +**RESULT — red once, then green.** With the member and the count restored (everything else as +committed), `PipeCatalogue.ReverseChannelHasTenCallbacks` fails: + +``` +PipeCatalogueTest.cpp:1111: Failure +Expected equality of these values: + kMGPipeCallbackCount Which is: 10 + 9u Which is: 9 +``` + +which is what makes the struct's shrink a measured fact rather than a claim — the `static_assert` +beside the constant only proves the two agree with each other. + +**Gates.** `integration-split` 202/202, `integration-spawn` 118/118, `integration-tcp` 121/121, +`ctest -L unit` 2408/2408, parity / census / ratchet (186) green. **G1 run for this slice in +particular**, because `MGPipeCallbacks.h` is included from MG_Backend and the struct is *not* +behind `MOBILEGL_BUILD_DISAGGREGATED`, so `sizeof(gMGPipeCallbacks)` really does shrink by 8 bytes +in the pull build: `.text` still `0xa52203` and both nm lists still byte-identical. The shrink is +in `.bss` and nothing indexes the table by count. + +*(One unrelated flake seen and cleared: `DirectGLES.Tcp.PrimitiveRestartScenario.TheFixedIndex +ValueTakesTheForwardingPath` failed once in the tcp lane and passed on re-run, and the full lane +was re-run clean at 121/121.)* ## slice 4 — readback close-out