[MG_Pipe, MG_Backend, MG_Remote, docs] (P7): ID-P7-30 - D3's two review fixes: the bit-8 and bit-13 rows are pinned, and deletion ordering is guaranteed by the Live/Gen test

The vertex-input and buffer-bindings rows of SubsystemDeps.def became live
behaviour when every reader started reading the table; two static_asserts
now pin them the way the texture and programs rows already were. The
"safe by construction" story about destroy ordering was inverted for the
case where a view holds its owner's last reference - the owner's destroy
reaches the wire first - so the three places that told it now say that the
walk's Live/Gen test is the guarantee.
This commit is contained in:
2026-09-22 18:27:43 -04:00
parent 498eb66a14
commit 22761a692d
5 changed files with 44 additions and 23 deletions
+12 -8
View File
@@ -809,14 +809,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
// may not depend on a client invariant to terminate: a chain the wire could make cyclic ends
// as a null answer rather than as a hang.
//
// DELETION ORDERING IS SAFE BY CONSTRUCTION, AND CHECKED ANYWAY. A view holds a strong
// SharedPtr to its storage owner (TextureObjectView::m_storageOwner) and the client emits
// resource_destroy from the DESTRUCTOR - "the last SharedPtr to this object dropping, not the
// glDelete* that only marks the name and leaves a still-bound object very much alive"
// (MG_State/GLState/TextureState/TextureObject.cpp:63) - so an owner's record cannot die
// while a record that views it is live, however the application orders its glDeleteTextures.
// If one ever did, PipeTextureRecordForHandle tests Live and Gen, so a recycled slot answers
// null rather than a stranger's record.
// DELETION ORDERING: THE LIVE/GEN TEST IS THE GUARANTEE, NOT THE EMIT ORDER. A view holds a
// strong SharedPtr to its storage owner (TextureObjectView::m_storageOwner) and the client
// emits resource_destroy from the DESTRUCTOR - "the last SharedPtr to this object dropping,
// not the glDelete* that only marks the name and leaves a still-bound object very much alive"
// (MG_State/GLState/TextureState/TextureObject.cpp:63). So the owner's storage cannot go away
// under a live view, whatever order the application deletes the two names in. But the two
// destroys are emitted back to back from one destructor chain, and when the view holds the
// owner's LAST reference the member is destroyed before the view's base destructor runs, so
// the wire carries resource_destroy(owner) and then resource_destroy(view): for one apply the
// view record is live with Desc.ViewOf naming a freed slot. No draw can land in between, and
// PipeTextureRecordForHandle tests Live and Gen, so that window answers null (= not clean)
// rather than a recycled stranger's record.
const MG_Pipe::MGPipeResourceRecord* PipeTextureStorageRecordForRecord(
const MG_Pipe::MGPipeResourceRecord& record);
+8
View File
@@ -234,6 +234,14 @@ namespace MobileGL::MG_Pipe {
return "";
}
// Rows 1 and 6 became live behaviour when D3 made every reader read the table (before that
// the bit-7 tests were hard-coded in Managers.cpp and DirectGLES.cpp and these rows were
// dead data). An edit that drops either row now makes BOTH readers agree, so nothing
// downstream can catch it; these two lines can.
static_assert(MGPipeSubsystemRequires(kMGPipeSubsystemVertexInput) == kMGPipeSubsystemResources,
"D-K2's vertex-input row (bit 8 requires bit 7) has gone missing");
static_assert(MGPipeSubsystemRequires(kMGPipeSubsystemBufferBindings) == kMGPipeSubsystemResources,
"D-K2's buffer-bindings row (bit 13 requires bit 7) has gone missing");
static_assert(MGPipeSubsystemRequires(kMGPipeSubsystemTextureResources) ==
(kMGPipeSubsystemResources | kMGPipeSubsystemSamplers),
"D-K2's fourth row (bit 10 requires bit 11) has gone missing again");
+8 -5
View File
@@ -413,11 +413,14 @@ may not depend on a client invariant to terminate), and a storage record that ca
**not clean**: the direction that re-syncs, not a refusal, and it raises nothing.
`SyncTextureViewToBackendByRecord` stamps that same storage serial — or 0, which reads as never
clean — at both its arms, so the stamp and the gate name one quantity. Deletion ordering cannot
strand the walk: a view holds a strong reference to its storage owner and the client emits
`resource_destroy` from the DESTRUCTOR rather than from `glDeleteTextures`
(`TextureObject.cpp:63`), so an owner's record outlives every record that views it whatever order
the application deletes the two names in; `Live`/`Gen` are checked anyway, so a recycled slot
answers null rather than a stranger's record.
strand the walk, and the guarantee is the check, not the emit order: a view holds a strong
reference to its storage owner and the client emits `resource_destroy` from the DESTRUCTOR rather
than from `glDeleteTextures` (`TextureObject.cpp:63`), so the storage cannot go away under a live
view whatever order the application deletes the two names in — but the two destroys leave one
destructor chain back to back, owner FIRST when the view held the owner's last reference, so for
one apply the view record is live with `ViewOf` naming a freed slot. No draw can land in that
window, and the walk tests `Live`/`Gen`, so it answers null (not clean) rather than a recycled
stranger's record.
The four in-body live reads (`GetTarget` at
`Managers.cpp:8876-8877, 8620-8621`; `IsTextureView` `:7060`; the TexBuffer backing `:8223-8231`)
+15 -10
View File
@@ -112,17 +112,22 @@ cost to discover later:
`PipeTextureRecordForHandle` tests `Live` and `Gen`, so a recycled slot answers null rather
than a stranger's record.
**Deletion ordering for the chosen shape, checked rather than assumed.** A view may outlive its
owner's GL NAME — `glDeleteTextures(owner)` while the view is alive is legal and the storage
must survive. It cannot outlive the owner's RECORD, and that is by construction on the client:
`TextureObjectView::m_storageOwner` is a strong `SharedPtr`, and
**Deletion ordering for the chosen shape: the walk's `Live`/`Gen` test is the guarantee, not
the emit order** (the review corrected an earlier draft of this paragraph that claimed the
ordering alone was enough). A view may outlive its owner's GL NAME — `glDeleteTextures(owner)`
while the view is alive is legal and the storage must survive — and it cannot outlive the owner's
STORAGE: `TextureObjectView::m_storageOwner` is a strong `SharedPtr`, and
`MGPipeEmitTextureDestroyAndFree` is called from `TextureObjectBase`'s DESTRUCTOR, whose own
comment says why — *"the last SharedPtr to this object dropping, not the glDelete* that only
marks the name and leaves a still-bound object very much alive"* (`TextureObject.cpp:63`). So
`Desc.ViewOf` cannot dangle while the record that carries it is live, whatever order the
application deletes the two names in. The walk validates anyway, and **a null answer is NOT
clean** — the direction that re-syncs, never the direction that shows stale texels. It is not a
refusal and raises nothing (rule I: no new abort site, no new `Fatal` family word).
marks the name and leaves a still-bound object very much alive"* (`TextureObject.cpp:63`). But
`m_storageOwner` is a member of the derived class and there is no user destructor on the view,
so when the view holds the owner's last reference C++ destroys the member first: the wire
carries `resource_destroy(owner)` and then `resource_destroy(view)`, and for exactly one apply
the view record is live with `Desc.ViewOf` naming a freed slot. No draw can land between two
destroys from one destructor chain, and the walk tests `Live`/`Gen`, so that window answers
null — and **a null answer is NOT clean**, the direction that re-syncs, never the direction that
shows stale texels. It is not a refusal and raises nothing (rule I: no new abort site, no new
`Fatal` family word).
**Views of views resolve transitively.** One hop always reaches storage in practice —
`glTextureView` composes a view-of-a-view onto the ROOT at creation, which is what the spec's
@@ -215,7 +220,7 @@ prose statements and the estimate counted two:
| `MG_Impl/Pipe/PipeFill.cpp` `P4aFamilyDependencyBits` | its own `kMGPipeP4aFamilyDependencies[]`, four rows | `MGPipeSubsystemRequires()`; the local table is deleted |
| `MG_Impl/Pipe/PipeFill.cpp` `P5eFamilyIsLive` | hand-coded `(pushMask & kMGPipeSubsystemResources) == 0` | `MGPipeSubsystemDependenciesAreSet(bit 13, mask)` |
| `MG_Backend/DirectGLES/Managers.cpp` `PipeSubsystemDependencyMissing` | took a literal dependency BIT per call site | takes the FAMILY and asks the table |
| …its four `Resolve<Family>SubsystemArm` call sites | five calls with five hand-written sentences | four calls, one per family, naming only the family |
| …its `Resolve<Family>SubsystemArm` call sites | four calls with four hand-written sentences (`ResolveProgramSubsystemArm` never called it) | three calls, one per family that has a row, naming only the family |
| `MG_Backend/DirectGLES/Managers.cpp` `ResolveVertexInputSubsystemArm` | hand-rolled `bitSet && !resourcesBitSet` | the table |
| `MG_Backend/DirectGLES/DirectGLES.cpp` `ResolveBufferBindingSubsystemArm` | hand-rolled `bitSet && !resourcesBitSet` | the table |
@@ -32,4 +32,5 @@
| ID-P7-27 | 2026-09-22 | bsl-esc-menu-854 **不排除**:monolith 807 MiB 通过、spawn 的 server 进程 ~1.2 GiB 死于 scudo `internal map failure`(client 543 MiB、系统余 8 GB)、inproc 1.6 GiB 死——wire 臂的内存缺陷(≈2.1× 字节或映射数上限),派 **M1** 归因(host 复测、逐 store 归因、泄漏/平台、`max_map_count`),修复包随归因定分区。 | `W4-verify/README.md` §3c 的采样表;ID-P7-12 的规则是「monolith 自己红才排除」。 | `CONTRACT-P7.md` §12 |
| ID-P7-28 | 2026-09-22 | wave 2-D3 落地(`6eaaf492 de6fb063 25b586d0`;其 `8274dcab` 与集成者的 `6da19f71` 重复,不取):view 陈旧缺陷取「谓词解析到 storage record」而非反向索引(删除顺序:反向索引要在 view 死亡时剪枝,否则 owner 上传会推到被回收给无关纹理的槽——静默跨纹理串号;且把可变状态放进两后端共读的 `MG_Pipe`);删除顺序由构造保证(`TextureObjectView::m_storageOwner` 强 `SharedPtr`,`resource_destroy` 在析构发出,`TextureObject.cpp:63`)。D-K2 「两处一行」实为六处读者,全部改读 `SubsystemDeps.def`,client 侧参数按 `kMGPipeP4aFamilySubsystems` 掩码(bit 8 的 vertex-input 发射归 P3a 规则,`TextureEmit.EveryDKTwoDependencyRowGatesItsOwnFamilyAndTheMirrorPairsStayLive` 抓到);**批准**「`SubsystemDepsTest` 从比两份副本变成比两个读者」的减弱,编译期一半(`MGPipe.h:225/:230`)承担。门:pull rc 0、`.text` 0xa52203、nm 0/0;unit 2426 / split 303 / spawn 219 / tcp 222 / magma 88/67/69 / full 530;census 79、ratchet 186、parity 0。门脚本模板自此对 `linux build rc≠0` 打 `GATE-RED`。 | `notes/p34b/espryt-d3.md`;两处基线引文更正(KHR-GL46 非 GL43;`texture_view` 1/7 红是 `view_sampling` 不是 `coherency`)。 | `Managers.{h,cpp}`、`PipeFill.cpp`、`DirectGLES.cpp`、`SubsystemDeps.def`、`CONTRACT-P5E.md` §5.2、`CONTRACT-P7.md` §12 |
| ID-P7-29 | 2026-09-22 | wave 2-D2 返工落地(`3cdaa5ee ec56644f 623d6641 c13ba36b`,叠在 D3 之上;`SubsystemDepsTest` 取 D2 版守卫 + 两条注记,`split-scenario-census.md` 与 D3 的附记手工合并):TUS 散点内缩后 gold 不变(`emit=9 box=9 rect=0 jobs=9` 四臂同)但对象对了——**零补丁 red-once** `MOBILEGL_ESPRYT_DISABLE_UNPACK_RING=1` 给 `box=3 rect=6 jobs=183`(两张散点纹理都动,26 像素例仍绿);顺带抓到 `ASSERT_TRUE(clientWindow.found)` 被「counters ON」横幅满足的假读,改为带 `tex[]` 的窗口;`espryt_memo_purity.py` 锚点/allow 活性断言后 6 条 allow 剩 1;`+76`/72 数字改正、每片 Gates 表。门:pull rc 0、G1 恒等、unit 2426 / split 303 / spawn 219 / tcp 222(`F1WireScenario.TextureReadbackRejectsPackedDestinationOverflowBeforeWriting` 首跑 1 红、rerun 绿——tcp 车道已知 flake 形状,非本包文件)/ magma 88/67/69 / full 530。 | D2 报告;tcp flake 记 §12 的既有条。 | `TextureUploadShapeScenario.cpp`、`PipeStatsWindow.h`、`espryt_memo_purity.py`、`test.yml`、`split-scenario-census.md`、`MEASUREMENTS.md` §13.1 |
| ID-P7-30 | 2026-09-22 | D3 的 fable 审查判「带修复落地」,两处修复由集成者直接落在 pipe:(1) `SubsystemDeps.def` 第 1/6 行(bit 8 / bit 13 各 requires bit 7)自 D3 起是活行为、却无任何钉子(`MGPipe.h` 的 static_assert 只钉纹理行/程序行/0x7ff/0x1fff,`SubsystemDepsTest` 只扫四个 P4a resolver)——加两条 `static_assert`;(2) 「删除顺序由构造保证」的说法在常见情形下反了:`m_storageOwner` 是派生类成员、destroy 从基类析构发出,view 持有 owner 最后一个引用时 wire 上先 `resource_destroy(owner)` 后 `(view)`,一次 apply 内 view 记录活着而 `ViewOf` 指向已释放槽——保证是 walk 的 `Live`/`Gen` 检查,不是顺序;`Managers.h`、`CONTRACT-P5E.md` §5.2、`espryt-d3.md` 三处改写,call-site 计数「五→四」改「四→三」。审查确认:门代数对非 view 逐字节同旧;G1 每处改动都在 `PIPE_PUSH`/`BUILD_DISAGGREGATED` 守卫内;red-once 在 tcp 臂真跑真红;引文(KHR-GL46、`view_sampling`)属实。**批准** D3 改 `CONTRACT-P5E.md` §5.2(既非 R-5 也非 R-3,但是 D2 普查行点名的修正)。 | 审查报告;`~/w7/pipe` 上 `range-diff` 三个提交 `=`。 | `MGPipe.h`、`Managers.h`、`CONTRACT-P5E.md`、`espryt-d3.md` |
| ID-P7-19 | 2026-09-22 | OpenRA 真机分歧改判为 **wire 臂的 GPU 时序竞态**(冷驱动缓存 = 慢 pipeline 创建 → 最终帧回读在最后几批 draw 完成前采样),归 wave 2-B 第 0 片;主机 red-once 用 pipeline 创建延迟旋钮复现;真机验收 = 三次 `pm clear` 冷跑 1.0。F1 保留(机制正确)但不再被当作分歧原因。 | E7:`pm clear` 三遍 0.976493989 / 14658 逐字相同、热跑 1.0、冷 monolith 1.0、`FRAMESINFLIGHT=1` 一绿一红;两臂 client/server 日志逐行相同;diff 只缺最后画的对象。 | `WireFramebuffer.inc`、`VulkanRenderer.cpp`、`F1-verify/README.md` |