[MG_Backend, scripts, docs] (P7): in a disaggregated build the monolith arm blits and mips with the baked modules, and the ratchet's p7-magma bucket falls to 88

CONTRACT-P7 §5.2's (B'), which §4.2 needs.

WHAT MOVES. In a disaggregated build the monolith arm now drives the SAME baked
modules the wire arm does:
  * InitializeBlitResources / InitializeDepthMipmapResources return true - there is
    nothing to build on any transport;
  * TryBlitToDefaultFramebufferWithShader turns its two BlitImageBindings into
    WireImages and calls BlitWireColorToDefault (WireColorBlit.inc);
  * GenerateDepthMipmapWithShader loops GenerateWireDepthMipLevel
    (WireDepthMipmap.inc) and hands the image back in the finalLayout its caller
    promised;
  * GetOrCreateBlitPipeline, the three GLSL strings, the eight kHidden* ids and the
    frontend members of BlitResources / DepthMipmapResources compile out.
The `#else` is the pull build's code, statement for statement.

WHY AN `#if` AND NOT A RUNTIME BRANCH - this is the whole of §4.2. P5f fv already
gave both initializers a non-monolith early return, and the ratchet counted the
symbols anyway, because a reference is a reference whether or not the branch runs.
And the MEMBER has to go with the construction: a SharedPtr member's destructor is
itself a reference to ~ProgramObject.

THE BUCKET TABLE (scripts/link_ratchet.py --bucket, build-split):

  bucket             before   after   a6
  p6-core                11      11    6
  p7-magma              101      88  101
  p3b-p4b-espryt         13      18   14
  both-backends          61      56   60
  total                 186     173  184

p3b-p4b-espryt's +5 is not a regression: those five MOVED out of both-backends
because DirectVulkan stopped referring to them and only DirectGLES still does.
Thirteen symbols went away outright, and they are exactly the three classes §4.2
names: ProgramObject::{AllocateLifetimeId, AttachShader, Link, ~ProgramObject},
SamplerObject::{SetLodRange, SetWrapR, SetWrapS, SetWrapT} and
ShaderObject::{Compile, DropCompileNode, JoinPendingCompile, ReleaseCompileNode,
SetShaderSource}. Re-baselined in this commit with --write-baseline; the 7 `# P13`
annotations carried over.

§4.2's "down to 0" DOES NOT HOLD, and the gap is not (B') left half-done - it is
that 88 of the 101 were never about the hidden programs. 62 come from
UniformManager.cpp.o and 14 more from VkSamplerManager.cpp.o: the MONOLITH
descriptor binder and sampler cache reading the application's own GL texture and
sampler objects (TextureObjectBase 35, TextureObjectWithOneMipmap 20 with its
vtable and typeinfo, SamplerObject's fourteen getters). Under a transport
SetupDraw branches to SetupWireDraw and not one of those lines runs - but that
branch is a RUNTIME one, which ID-P7-5 deliberately leaves to the optimisation
phase. notes/p7/magma-b2.md §4.3 names all eleven referring-object groups and what
each is.

NONE OF THEM IS ANNOTATED `# P13`, on purpose: that mark is for a symbol that
cannot fall without moving pull `.text`, and nothing above has shown that.
UniformManager's and VkSamplerManager's monolith arms are pull-build code, and
`#if`-ing them is the same operation this commit performed on VulkanRenderer.cpp.
They are wave 3's work, not P13's boundary problem, and marking them P13 early
would record an untried thing as a tried-and-impossible one.

Gates: integration-gpu 1705 (every monolith `DirectVulkan.` entry is in there),
integration-magma-split 93, -spawn 72, -tcp 74, integration-magma-full-split 524,
ctest -L unit 2423, all green; fatal_census 79 abort sites / 44 family words / 0
unmarked; spawn_lane_parity green; link_ratchet --assert-monotone unchanged at 173
against the re-baselined file, and --self-test OK.

RETRACE, which is this slice's load-bearing reading because the monolith arm
changed implementation: OpenRA.DirectVulkan (monolith) ssim 1.000000,
mismatchPixels 0, twice; OpenRA.DirectVulkan.SPLIT ssim 1.000000, 0. The whole
DirectVulkan matrix is 39/40 monolith and 38/39 split, and both reds are the SAME
case, minecraft-1.21.4-fabric-iris-iterationrp-in-world (ssim 0.027915), which is
PRE-EXISTING - it fails identically on ~/w7/p7-magma-b (this branch's parent),
because it needs CONTRACT-P7 §2.4's three iterationRP knobs and those are exported
by the CI job rather than by the ctest entry.

G1 pull .text 0xa52203, nm --defined-only identical to p7-before/pull-syms.txt
(30570 lines, 0 diffs).

Device debt: the monolith arm in the Android disaggregated package now runs the
baked blit, and its ROTATE_90/180/270 geometry has never executed - every surface
on this host is IDENTITY. notes/p7/magma-b2.md §5 lists it with the package's three
other device items.
This commit is contained in:
2026-09-22 18:29:22 -04:00
parent a50d50df60
commit 3c2867d34d
4 changed files with 473 additions and 25 deletions
@@ -1452,6 +1452,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
static constexpr Uint32 kDescriptorSetsPerFrame = 64;
#if !MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 2-B2, CONTRACT-P7 §5.2 (B'): THE HIDDEN GL PROGRAMS ONLY EXIST IN THE PULL
// BUILD NOW. In a disaggregated build the monolith arm drives WireColorBlit.inc and
// WireDepthMipmap.inc - the baked modules the wire arm already uses - so nothing below
// this line is compiled: no GLSL text, no hidden object ids, no ShaderObject,
// ProgramObject or SamplerObject construction on a DirectVulkan path. That is what
// takes those three classes out of the link-closure ratchet's `p7-magma` bucket (§4.2),
// and the reason it is an `#if` and not a runtime branch is exactly that: a runtime
// early return leaves the symbols referenced.
//
// The pull build keeps every statement below byte for byte, which is what G1 pins.
static constexpr Uint kHiddenBlitProgramId = 0xFFFFFFF0u;
static constexpr Uint kHiddenBlitVertexShaderId = 0xFFFFFFF1u;
static constexpr Uint kHiddenBlitFragmentShaderId = 0xFFFFFFF2u;
@@ -1530,6 +1541,7 @@ void main() {
gl_FragDepth = 0.25 * (depth0 + depth1 + depth2 + depth3);
}
)";
#endif // !MOBILEGL_BUILD_DISAGGREGATED
static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) {
@@ -4521,11 +4533,18 @@ void main() {
Bool VulkanRenderer::InitializeBlitResources() {
#if MOBILEGL_BUILD_DISAGGREGATED
// P5f fv: both window and headless transports use WireFramebuffer's native
// operations. These hidden programs are frontend objects, so even creating
// them on the server would retain a client compiler/allocator dependency.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
#endif
// P5f fv gave this an early return for every non-monolith transport, because the hidden
// programs are frontend objects and creating them on the server would retain a client
// compiler/allocator dependency. P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')) takes the last
// step: in a disaggregated build there is NOTHING TO INITIALIZE on any transport,
// because the monolith arm now blits with WireColorBlit.inc's baked module too.
//
// AN `#if` AND NOT A RUNTIME BRANCH, which is the whole point (§4.2): the early return
// left every ShaderObject / ProgramObject / SamplerObject symbol REFERENCED by this
// object file, so the link-closure ratchet counted them whether or not the branch ever
// ran. Compiling the construction out is what makes them fall.
return true;
#else
ShutdownBlitResources();
auto vertexShader = MakeShared<MG_State::GLState::ShaderObject>(ShaderStage::Vertex, kHiddenBlitVertexShaderId);
@@ -4595,6 +4614,7 @@ void main() {
m_blitResources.nearestSampler = createSampler(kHiddenBlitNearestSamplerId, SamplerFilterMode::Nearest);
m_blitResources.linearSampler = createSampler(kHiddenBlitLinearSamplerId, SamplerFilterMode::Linear);
return true;
#endif
}
void VulkanRenderer::ShutdownBlitResources() {
@@ -4625,11 +4645,12 @@ void main() {
Bool VulkanRenderer::InitializeDepthMipmapResources() {
#if MOBILEGL_BUILD_DISAGGREGATED
// P5f fv: both window and headless transports use WireFramebuffer's native
// operations. These hidden programs are frontend objects, so even creating
// them on the server would retain a client compiler/allocator dependency.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
#endif
// P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')): the depth-mip half of the same step. In a
// disaggregated build the monolith arm generates its depth chain with
// WireDepthMipmap.inc's baked pass, so there is no hidden GL program to build on any
// transport. See InitializeBlitResources above for why this is an `#if`.
return true;
#else
ShutdownDepthMipmapResources();
auto vertexShader = MakeShared<MG_State::GLState::ShaderObject>(ShaderStage::Vertex,
@@ -4697,6 +4718,7 @@ void main() {
MOBILEGL_ASSERT(foundSamplerBinding,
"InitializeDepthMipmapResources: failed to resolve reflected binding for uSource");
return true;
#endif
}
void VulkanRenderer::ShutdownDepthMipmapResources() {
@@ -4749,6 +4771,11 @@ void main() {
m_deferredDepthMipmapCleanup.clear();
}
#if !MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')): the pull build's blit pipeline, built out of the
// hidden GL program through the program/pipeline factories. A disaggregated build has no
// such program on either arm - WireColorBlit.inc owns its pipeline - so this is compiled
// out with its only caller's body.
VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) {
MOBILEGL_ASSERT(m_blitResources.program != nullptr, "GetOrCreateBlitPipeline: blit program is null");
MOBILEGL_ASSERT(m_programFactory != nullptr, "GetOrCreateBlitPipeline: program factory is null");
@@ -4803,6 +4830,7 @@ void main() {
}
return m_pipelineFactory->GetOrCreatePipeline(payload);
}
#endif // !MOBILEGL_BUILD_DISAGGREGATED
Bool VulkanRenderer::GenerateDepthMipmapWithShader(FrameContext::FrameData& frame,
MG_State::GLState::ITextureObject& texture,
@@ -4812,6 +4840,52 @@ void main() {
const IntVec3& storageBaseTexelSize,
VkImageLayout originalLayout,
VkImageLayout finalLayout) {
#if MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')): THE MONOLITH ARM ON THE BAKED PASS. Every
// level below is WireDepthMipmap.inc's GenerateWireDepthMipLevel - the same 2x2
// texelFetch box, the same clamp, the same average, the same half-texel offset, since
// that shader is a PORT of the one this function used to compile at startup. What goes
// away with the GL program is the four frontend objects and the compiler call; what the
// texture ends up holding is the same chain.
//
// `texture`, `storageBaseTexelSize` and `originalLayout` are the pull build's; the pass
// reads its shape from `resource`, whose tracked layout IS `originalLayout` at entry.
(void)frame;
(void)texture;
(void)storageBaseTexelSize;
(void)originalLayout;
if (generateMipLevelCount <= 1) return true;
if (resource.aspect != VK_IMAGE_ASPECT_DEPTH_BIT) return false;
for (Uint32 level = baseMipLevel + 1; level < baseMipLevel + generateMipLevelCount; ++level) {
WireImage source{};
source.image = resource.image;
source.format = resource.format;
source.aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
source.layout = resource.layout;
source.trackedLayout = &resource.layout;
source.levels = resource.mipLevels;
source.level = level - 1;
source.extent = {std::max(resource.extent.width >> (level - 1), 1u),
std::max(resource.extent.height >> (level - 1), 1u)};
WireImage destination = source;
destination.level = level;
destination.extent = {std::max(resource.extent.width >> level, 1u),
std::max(resource.extent.height >> level, 1u)};
if (!GenerateWireDepthMipLevel(source, destination)) return false;
}
// The caller's contract is that the image is left in `finalLayout`; the per-level pass
// leaves it in the GENERAL it uses for a same-image source and destination.
WireImage whole{};
whole.image = resource.image;
whole.format = resource.format;
whole.aspect = resource.aspect;
whole.layout = resource.layout;
whole.trackedLayout = &resource.layout;
whole.levels = resource.mipLevels;
whole.extent = resource.extent;
TransitionWireImage(whole, finalLayout);
return true;
#else
MOBILEGL_ASSERT(m_depthMipmapResources.program != nullptr,
"GenerateDepthMipmapWithShader: depth mipmap program is null");
MOBILEGL_ASSERT(m_blitResources.nearestSampler != nullptr,
@@ -5113,6 +5187,7 @@ void main() {
MOBILEGL_ASSERT(finishedReady, "%s: failed to transition mip level %u to sampled layout", __func__, level);
}
return true;
#endif
}
// Boost-style hash combine. The inputs are tiny enum ordinals and bit masks, so
@@ -9173,6 +9248,53 @@ void main() {
MOBILEGL_ASSERT(clearReady,
"TryBlitToDefaultFramebufferWithShader: failed to materialize pending clear for textureId=%d",
sourceTexture->GetExternalIndex());
#if MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')): THE MONOLITH ARM ON THE BAKED MODULE. Below
// this line the pull build binds a hidden GL program, writes three default-block
// uniforms into its global UBO and hands the sampler to the application's descriptor
// binder. A disaggregated build has no such program on either arm: the same pass, with
// the same vertex shader (WireColorBlit.vert IS this one, ported), takes its rects on a
// push constant and its sampler from its own descriptor set.
//
// The pre-amble above stays shared because it is not about the program: resolving the
// two bindings, ending an open render pass and materializing a pending clear on the
// source are what makes the blit legal, whichever pass performs it. What the baked path
// does NOT need is the sampling transition, the descriptor sync and the cached sampled
// view - it makes its own view and moves the layouts through TransitionWireImage, whose
// tracker is the same `trackedLayout` these bindings carry.
{
const auto toWire = [](const BlitImageBinding& binding) {
WireImage out{};
out.image = binding.image;
out.format = binding.format;
out.extent = {static_cast<Uint32>(binding.extent.x()), static_cast<Uint32>(binding.extent.y())};
out.samples = binding.sampleCount;
out.aspect = binding.aspectMask;
out.level = binding.mipLevel;
out.levels = binding.mipLevelCount;
out.layer = binding.baseArrayLayer;
out.layers = binding.layerCount;
out.viewType = binding.layerCount > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
out.trackedLayout = binding.trackedLayout;
out.layout = binding.trackedLayout ? *binding.trackedLayout : VK_IMAGE_LAYOUT_UNDEFINED;
return out;
};
WireImage source = toWire(srcBinding);
WireImage destination = toWire(dstBinding);
// The default framebuffer's layout lives in the swapchain object, not in a
// per-resource slot, which is what ResolveWireImage does for the same surface and
// what TransitionWireImage writes back through.
destination.isDefault = true;
destination.swapchainImageIndex = drawDefaultImageIndex;
destination.trackedLayout = nullptr;
destination.layout = m_swapchainObject.GetImageLayout(drawDefaultImageIndex);
// `false` here means "not serviced by the shader pass", and the caller falls
// through to the native vkCmdBlitImage arm - the same contract the pull build's
// early returns above have.
return BlitWireColorToDefault(source, destination, srcX0, srcY0, srcX1, srcY1,
dstX0, dstY0, dstX1, dstY1, filter);
}
#else
const Bool ready = m_textureManager->TransitionTextureForSampling(frame.commandBuffer, *sourceTexture);
if (!ready) {
MGLOG_E_ONCE("BlitFramebuffer skipped: failed to transition source textureId=%d for sampling",
@@ -9283,6 +9405,7 @@ void main() {
MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed");
vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0);
return true;
#endif
}
// A single-aspect depth/stencil copy between two images of the same format. An image copy
@@ -405,7 +405,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Int padding[3] = {0, 0, 0};
};
// P7 wave 2-B2, CONTRACT-P7 §5.2 (B'): THE FRONTEND MEMBERS ARE PULL-BUILD ONLY. In a
// disaggregated build the monolith arm blits and generates depth mips with the baked
// modules (WireColorBlit.inc, WireDepthMipmap.inc), so no DirectVulkan object holds a
// ProgramObject, a ShaderObject or a SamplerObject any more - which is what takes
// ProgramObject::{AllocateLifetimeId, AttachShader, Link, ~ProgramObject},
// ShaderObject::{SetShaderSource, Compile, JoinPendingCompile, ReleaseCompileNode,
// DropCompileNode} and SamplerObject::{SetWrapS, SetWrapT, SetWrapR, SetLodRange} out
// of the link ratchet's `p7-magma` bucket (§4.2). The destructor of a SharedPtr member
// is a reference too, so the MEMBER has to go and not only its construction.
//
// The structs stay, empty, so ShutdownBlitResources and the two `= {}` assignments read
// the same on both builds.
struct BlitResources {
#if !MOBILEGL_BUILD_DISAGGREGATED
SharedPtr<MG_State::GLState::ProgramObject> program;
SharedPtr<MG_State::GLState::SamplerObject> nearestSampler;
SharedPtr<MG_State::GLState::SamplerObject> linearSampler;
@@ -413,15 +426,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Int dstRectLocation = -1;
Int surfaceTransformLocation = -1;
Uint32 samplerBinding = 0;
#endif
};
struct DepthMipmapResources {
#if !MOBILEGL_BUILD_DISAGGREGATED
SharedPtr<MG_State::GLState::ProgramObject> program;
Int srcRectLocation = -1;
Int dstRectLocation = -1;
Int surfaceTransformLocation = -1;
Int srcTexelSizeLocation = -1;
Uint32 samplerBinding = 0;
#endif
};
// A single-sample staging image for multisample-resolve blits that also have to change
@@ -1817,7 +1833,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool MaterializePendingDepthStencilClearForDefaultFramebuffer(
VkCommandBuffer commandBuffer, const MG_State::GLState::FramebufferAttachmentObject& attachment,
const ClearAttachmentPayload& payload);
#if !MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')): the hidden blit program's pipeline. A
// disaggregated build has no hidden program on either arm, so this has neither a body
// nor a caller there.
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
#endif
Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame,
MG_State::GLState::ITextureObject& texture,
VkTextureManager::TextureResource& resource,
+317
View File
@@ -0,0 +1,317 @@
# P7 wave 2-B2:Magma blit / copy / mip 簇里包 B 没有走完的部分(分支 `p7/magma-b2`)
> 规范见 [`CONTRACT-P7.md`](../../../../MobileGL/MG_Remote/CONTRACT-P7.md) §0(规则 I / J)、
> §3.2(`multisample-blit-shape` / `-aspect` 行)、§4.2(棘轮目标与 (B′) 路线)、§5.2;
> 前一包见 [`magma-b.md`](magma-b.md)。基线 = `feat/disaggregated` 上 `~/w7/pipe` 的头
> `ac8065a3`(X1、F1、C、A、B 全部落地)。
>
> 主机口径与包 B 相同:WSL Arch + lavapipe(`/usr/share/vulkan/icd.d/lvp_icd.json`),
> `build-split` = Release / clang / ccache / `DISAGGREGATED=ON` `INPROC=ON` `PIPE_PUSH=ON`
> `BUILD_INTEGRATION_TEST=ON`。**本树的 TCP 端口是 `40917`**(`MOBILEGL_ITEST_TCP_ENDPOINT`):
> 包 B 用 40913、pipe 用默认的 40613,兄弟树同时在跑时端口会撞,`TcpServer.Start` 整条车道红。
>
> 基线门读数(本树实测,非引自包 B):`integration-magma-split` **88**、`-spawn` **67**、
> `-tcp` **69**、`integration-magma-full-split` **521**、`ctest -L unit` **2420**,全绿。
> (包 B 的报告写的是 84 / 63 / 65;差的四条是包 A 在它之后落地的。)
## 0. 每片一条提交,每片至少一次 red-once
| 片 | 名 | 退役 / decline | 提交 |
|---|---|---|---|
| 1 | `multisample-blit-shape@P7` | 缩放/翻转的多重采样颜色 blit **退役**;多重采样目的地 **decline** | `c77c4f29` |
| 2 | `multisample-blit-aspect@P7` + 烘焙 (A) 第三对 | 无 resolve 扩展的深/模板 **退役**(烘焙 pass);改采样数 **decline** | `aba21e26` |
| 3 | `MagmaP7AllocatorDebtScope` ×2 | **删除**(先证明它们在非 monolith 传输下从不武装) | `4bee1313` |
| 4 | (B′):棘轮的 `p7-magma` 桶 | monolith 臂在 disaggregated 构建里改走烘焙模块 | 见 §4 |
**`grep -rn "@P7" MobileGL/ --include=*.cpp --include=*.inc --include=*.h` 在片 2 之后只剩注释**
(19 行,全部是「某某退役了」或 red-once 的散文)。树上没有任何一条拒绝再带这个标记。
---
## 1. 片 1:`multisample-blit-shape@P7`
### 1.1 一个名字下面的两半,问题不是同一个
**有臂的那一半**:多重采样颜色 blit 的目的矩形与源矩形尺寸不同。`vkCmdResolveImage` 每侧只带
**一个 extent 和一个 offset**,既表达不了缩放也表达不了翻转——而这条臂**本来就**为了翻转先
resolve 进一张单采样 scratch、再对 scratch 做 `vkCmdBlitImage`。两个 `width != |dx1-dx0|` 子句
是唯一把缩放挡在同一条路线之外的东西,而 monolith 臂走的正是这条路线
(`VulkanRenderer.cpp` 的 `regionWasTransformed` 分支)。子句去掉;scratch 仍按**源**矩形定尺寸,
所以只有第二段看得见缩放。
**顺带的一件事**:filter 是**从这次缩放起才有意义的**——1:1 时 LINEAR 与 NEAREST 读同一个 texel,
所以在此之前没有人问过这个格式能不能被过滤。对 `optimalTilingFeatures` 缺
`SAMPLED_IMAGE_FILTER_LINEAR` 的格式传 `VK_FILTER_LINEAR` 是未定义行为;GL 本来也说多重采样
resolve 忽略 filter,所以回落是 NEAREST 而不是拒绝。
**没有臂的那一半**:**多重采样目的地**。`vkCmdCopyImage` 是唯一能写多重采样图像的命令,它只带一个
extent,而 GL 4.6 core 18.3.1 说任一侧多重采样时两个矩形尺寸不同就是 `INVALID_OPERATION`。
没有「正确结果」可言,所以按规则 I (a):`MGLOG_E_ONCE` 写出两个矩形与两个采样数、记 GL 错、
目的地原样不动、会话留下。
### 1.2 场景与车道
`F1WireScenario.MultisampleColorBlitScalesAndFlipsThroughTheResolveScratch` 与
`…MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession`,三臂各两条,前缀 `MsBlit.`。
**split-only**,理由和包 B 的 in-place copy 一样、而且更强:monolith 臂对缩放的多重采样 blit 走
`vkCmdResolveImage` 1:1 落在目的偏移上——**缩放被丢掉**——所以没有可对照的 `DirectVulkan.` 读数,
两臂相等的断言等于断言 wire 臂也一样错。
**缩放与翻转在同一条用例里、同一个源上**,因为它们坏的方式不同:丢了缩放会把 8×4 的带子塞进 4×2 的
附件(驱动直接拒命令),丢了翻转会把对的两个颜色按错的顺序返回。第二次 blit 的期望就是第一次的
期望把两行调过来,所以谁也通不过对方的断言。
### 1.3 测出来的一件事:decline 的 GL 错**晚到**
decline 在**执行 blit 的地方**记错,也就是 server 的 apply 线程,而那个错是**跟着后一次 reply**
回到 client 队列的,不是跟着 blit 调用。第一次跑的时候它挂在二十行之下那次 1:1 resolve 上——
一次没有任何毛病的调用。用例里的 `glFinish()` 因此是承重的,不是礼貌。
### 1.4 red-once(R-16,两条,均已执行并还原)
1. 把两个尺寸相等子句放回去 → `…ScalesAndFlipsThroughTheResolveScratch` 死于
`Fatal{UnmigratedVerb, "Magma:multisample-resolve-region"}`;
2. 把 `MagmaWireFatal("multisample-blit-shape@P7")` 放回去 → `…DeclinesAndKeepsTheSession`
死于该名。
还原后三臂 6/6 绿。
### 1.5 门
`integration-magma-split` **90**(+2)、`-spawn` **69**(+2)、`-tcp` **71**(+2)、
`integration-magma-full-split` **523**(+2)、`ctest -L unit` **2420**,全绿;
`fatal_census` 79 abort 站点 / 44 家族词 / 0 无标记;`link_ratchet --assert-monotone` 186 不变;
G1 pull `.text` **`0xa52203`**、`nm --defined-only` 与 `~/w7/p7-before/pull-syms.txt` 逐行相同
(30570 行,0 差异)。
---
## 2. 片 2:`multisample-blit-aspect@P7` 与第三对烘焙模块
### 2.1 `vkCmdResolveImage` 只做颜色
Vulkan 里不用 shader 把深度或模板 aspect resolve 下来的**唯一**办法,是带 depth/stencil resolve
附件的 render pass,它要 `VK_KHR_create_renderpass2` 和 `VK_KHR_depth_stencil_resolve`。两个都没有
的设备,在应用第一次
`glBlitFramebuffer(GL_DEPTH_BUFFER_BIT)`(**这就是 GL 里每一次多重采样深度 resolve**)时会死于
`Magma:multisample-depth-resolve-capability`。§3.2 的 aspect 行点了两条臂的名,这一片补上第二条。
`WireMultisampleResolve.inc` + 三个烘焙模块(一个顶点阶段,每个 aspect 一个片元阶段),取
**样本零**——和扩展臂要的 `VK_RESOLVE_MODE_SAMPLE_ZERO_BIT` 同一个样本,所以两条臂在同一台设备上
互换不会改变像素,车道也才断言得动。GL 4.6 core 18.3.1 对模板本来就要求**选一个**样本,对深度要求
结果落在该像素的 min/max 之间——样本零两边都满足,而平均**不满足**后者的精神(四个深度的均值是一个
哪里都不存在的表面)。
**这条 pass 什么都不带。** scratch 与源同尺寸、同坐标系(两张都是原生图像,彼此不翻转),所以片元
阶段直接用 `gl_FragCoord` 取址,矩形交给 scissor。没有 push constant,没有 varying,没有前端对象。
**模板那一半要扩展才存在**:片元着色器没有 `VK_EXT_shader_stencil_export` 根本写不了模板 aspect。
没有它就 decline 模板(§3.2 原话),而且**模块本身不创建**——把带不支持 capability 的 SPIR-V 交给
`vkCreateShaderModule` 是未定义行为,不是一个能接住的错误。
**两个能力判据不是同一个判据**,差别是结构性的:subpass resolve 的是**格式**拥有的每一个 aspect,
所以每个都要有 mode;而上面这条 pass 只写这次 blit 要的那一个 aspect,scratch 其余部分留成未定义
——安全,因为随后的 copy 读的正是那一个 aspect。
### 2.2 没有臂的那一半
采样数不同、且**目的地**是多重采样的那一侧:一个样本铺成 N 个,或 N 个降成 M 个。
`vkCmdResolveImage` 只写单采样目的地,`vkCmdBlitImage` 两侧都拒绝多重采样,`vkCmdCopyImage` 要求
两个数相等。decline。
### 2.3 场景与车道
**用例是提拔来的,不是新写的。** `DepthStencilReadbackMatrixScenario.AResolvedMultisampleDepth…`
本来就拥有这个可观测(清一张 4× `DEPTH24_STENCIL8`、用 `glBlitFramebuffer` resolve 进单采样、两个
aspect 都读回、并检查只做深度的 resolve 没动目的地的模板、反之亦然),而且**本来就在三条 Magma 臂
的信息档(`…Full.`)里跑**。缺的只是一条 gating 条目。
每臂两条(`MsResolve0.` / `MsResolve1.`),第二条是它那条臂在此唯一可达的方式:lavapipe 两个 resolve
扩展都有,扩展臂会接下本机的每一次 resolve。`MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE` 丢掉扩展臂。
**它在本机不 skip**:lavapipe 也带 `VK_EXT_shader_stencil_export`,所以 shader 臂深度和模板都走通了。
新增的 F1 用例是那条 decline。
> 集成者交代里写的「可能在 lavapipe 上 SKIP(分离的 D24/S8 不支持)」本树**没有发生**:
> 提拔的这条用例用的是**合并**的 `DEPTH24_STENCIL8`,分离 D24/S8 是同一文件里另一条用例
> (`SeparateDepthAndStencilAttachmentsAreBothReadable`)的事,没有提拔。三臂 6/6 绿。
`MOBILEGL_BAKED_INTERNAL_SHADERS` 的表从 8 行涨到 **11 行**。
### 2.4 red-once(R-16,三条,均已执行并还原)
1. **shader 臂**:把 `MagmaWireFatal("multisample-depth-resolve-capability")` 放回非扩展臂 →
`DirectVulkan.Split.MsResolve1.…` 死于该名,而 `MsResolve0`(扩展臂)**仍然绿**——这同时是旋钮
确实改道的证明;
2. **aspect decline**:把 `MagmaWireFatal("multisample-blit-aspect@P7")` 放回去 →
`DirectVulkan.Split.MsBlit.…FromASingleSampleSourceDeclines…` 死于该名;
3. **新鲜度门**:把 `WireMultisampleStencilResolve.frag` 的取样索引从 0 改成 1(**不需要重建**——
门在运行时读源)→ `WireMultisampleStencilResolveFragment` 在**第 158 个字**变红,并指名符号、
头文件与重新生成的命令。
还原后三臂 15/15 绿、新鲜度门 11/11 绿。
### 2.5 门
`integration-magma-split` **93**(+3)、`-spawn` **72**(+3)、`-tcp` **74**(+3)、
`integration-magma-full-split` **524**(+1)、`ctest -L unit` **2423**(+3),全绿;
`bake_internal_shaders.py --check` 五个头全新鲜;`fatal_census` 79 / 44 / 0;
`link_ratchet --assert-monotone` 186 不变;G1 `.text` `0xa52203`、符号表逐行相同。
### 2.6 欠设备核对
shader resolve 臂正是**手机**在没有 `VK_KHR_depth_stencil_resolve` 时会走的那条,而本机只能经旋钮
到达它。它的几何要在 Redmi 上看一眼。
---
## 3. 片 3:两处 `MagmaP7AllocatorDebtScope`
### 3.1 债是真的,但它早就没有对象了
隐藏的 blit 程序、它的两个采样器和隐藏的深度 mip 程序都是**前端对象**。在活动传输下,server 后端在
apply 线程上创建它们、也在 apply 线程上销毁它们,而它们的析构函数会跑 client 死亡助手和那个
lifetime-id 探针——所以 P5c 给这两处 teardown 一个具名豁免,P5e 裁定 12 把它按债重命名。
**退役它的不是一个新豁免,而是对象不存在。** P5f fv 早已给 `InitializeBlitResources` 和
`InitializeDepthMipmapResources` 加了非 monolith 传输的 early return,所以在 monolith 臂之外
`m_blitResources` 从不持有任何东西,`= {}` 什么也不销毁。这两个 scope 自那天起就是罩在一条跑不到的
路径上的标记。
### 3.2 证明,不是论证
两处 teardown 里放一个临时探针:在非 monolith 传输下任一资源非空就按名结束会话。武装着跑:
`integration-magma-split` 93、`-spawn` 72、`-tcp` 74、`integration-magma-full-split` 524、
`-full-spawn` 524、`integration-split` 187、`integration-magma-buffers` 21,**全绿,它一次都没响**。
**负对照才让这成为测量**:把探针的传输判据去掉,**每一条 monolith `DirectVulkan.` 条目都在 teardown
时死了**(8/8 abort,而且用例本身先 PASS 了),split 条目照样绿。
`integration-magma-full-tcp` 在探针下的那一条红是 CONTRACT-P7 §2.5 记过名的 StreamLink × Magma
曝光 texel(`IterationRPProgram203Scenario`),不是探针的死亡——它 Failed,不是 aborted。
### 3.3 对着 P5e 裁定 12 的「四笔债」重数
裁定 12 点了**四**处 Magma 站点(`VulkanRenderer.cpp` ×3、`ResourceTracker.h` ×1)。其中两处在 P7
开始前就没了——`git log -S` 把 `ResourceTracker.h` 那处记在 P5f fv(`4f3d2d6b`)、第三处
`VulkanRenderer.cpp` 记在 P5f fm(`036c3a3d`)——所以 P7 基线 `78b7d6be` 上只剩**两**处,就是本片
删掉的这两处。Magma 的 apply 线程分配器债现在是**零**处。
### 3.4 类型留下,并且写明了为什么
`MG_Test/Wire/RemoteClientTest` 的 `RemoteGuards.BarrieredLegacyScopesCannotExemptAllocator` 与
`.BarrieredFrontendRegistryMembersRefuseBothLegacyScopes` 拿它当**负对照**:在 apply 线程上构造它,
然后断言分配器**照样**拒绝。那是 P5f (fr) 立下的性质,删掉类就删掉了唯一钉住它的用例。
后端已无任何使用点。
---
## 4. 片 4:(B′),棘轮的 `p7-magma` 桶
### 4.1 做了什么
在 **disaggregated 构建**里,monolith 臂也改走烘焙模块:
- `InitializeBlitResources` / `InitializeDepthMipmapResources` → `return true`(无事可做);
- `TryBlitToDefaultFramebufferWithShader` → 把两个 `BlitImageBinding` 转成 `WireImage` 后调
`BlitWireColorToDefault`(`WireColorBlit.inc`);
- `GenerateDepthMipmapWithShader` → 逐级调 `GenerateWireDepthMipLevel`(`WireDepthMipmap.inc`),
收尾一次 `TransitionWireImage` 把图像交回调用方承诺的 `finalLayout`;
- `GetOrCreateBlitPipeline`、三段 GLSL 文本、八个 `kHidden*` id、`BlitResources` /
`DepthMipmapResources` 的前端成员:`#if !MOBILEGL_BUILD_DISAGGREGATED`。
**为什么是 `#if` 而不是运行时分支**,这正是 §4.2 的全部内容:P5f fv 的 early return 让符号**仍然被
引用**,棘轮不管那条分支跑不跑。把构造编译掉才让它们掉下来。而**成员也得走**,不只是构造:
`SharedPtr` 成员的析构本身就是一次对 `~ProgramObject` 的引用。
`#else` 分支逐语句原样,G1 钉住的就是它。
### 4.2 桶表:前 / 后
| bucket | before | after | a6 | 含义 |
|---|---:|---:|---:|---|
| `p6-core` | 11 | 11 | 6 | 没有后端对象引用:传输/applier 自己的账 |
| **`p7-magma`** | **101** | **88** | 101 | 只被 DirectVulkan 引用:P7 的账 |
| `p3b-p4b-espryt` | 13 | 18 | 14 | 只被 DirectGLES 引用 |
| `both-backends` | 61 | 56 | 60 | 两个后端都引用 |
| **合计** | **186** | **173** | 184 | |
`p3b-p4b-espryt` 的 +5 不是变坏:那 5 个符号从 `both-backends` **移**了过去,因为 DirectVulkan 不再
引用它们,只剩 DirectGLES。掉的是 13 个,合计 186 → 173。
**掉下来的 13 个**(`--assert-monotone` 逐条列名,随本片 `--write-baseline` 重基线,7 条
`# P13` 注解原样带过):
- `ProgramObject::{AllocateLifetimeId, AttachShader(...), Link(bool), ~ProgramObject}`(4)
- `SamplerObject::{SetLodRange, SetWrapR, SetWrapS, SetWrapT}`(4)
- `ShaderObject::{Compile, DropCompileNode, JoinPendingCompile, ReleaseCompileNode, SetShaderSource}`(5)
即 §4.2 点名的那三个前端类的全部构造面。
### 4.3 剩下的 88 个:逐个引用对象点名,以及为什么它们不是 (B′) 的
§4.2 写的「降到 0」**按实测不成立**,而且差距不在 (B′) 做没做完,在于桶里绝大多数符号**根本不是
隐藏程序引来的**。逐引用对象:
| 引用对象 | 数量 | 是什么,为什么还在 |
|---|---:|---|
| `Renderer/UniformManager.cpp.o` | 62 | monolith 描述符绑定器对**应用自己的** GL 纹理对象的遍历:`TextureObjectBase` 35、`TextureObjectWithOneMipmap` 20(含其 vtable / typeinfo)、四个 `TextureObject{1D,2D,2DCube,3D}` 各 1、`MGPipeNoteAggregate` 1。split 下 `SetupDraw` 分支到 `SetupWireDraw`,这条路一行都不跑——但那是**运行时**分支(ID-P7-5 明确把它留在优化阶段),所以符号仍被引用。清它要对 `UniformManager` 的 monolith 臂做和 (B′) 同形的 `#if`,是 wave 3 的活,不是本片的 |
| `Renderer/VkSamplerManager.cpp.o` | 9 | `SamplerObject::Get{BorderColor,BorderColorI,BorderColorUI,CompareMode,MinLod,SamplerCompareFunc,WrapR,WrapS,WrapT}`——同上,monolith 采样器缓存读应用的 `SamplerObject` |
| `UniformManager.cpp.o` + `VkSamplerManager.cpp.o` | 5 | `SamplerObject::Get{MagFilter,MaxAnisotropy,MaxLod,MinFilter,MipmapMode}`,同一条路的两个读者 |
| `Renderer/VulkanRenderer.cpp.o` | 3 | `GLImpl::CopyTextureImageToClientOrPBO_State`(monolith 回读打包)、`PipeInputs::HasOpenTransformFeedbackSpan`、`FramebufferAttachmentObject::IsValid` |
| `Renderer/VkTextureManager.cpp.o` | 3 | `PipeInputs::GetTextureObject`、`MGPipeTextureLegacyArmScope::{ctor,dtor}`——legacy 臂的具名 scope,P3b/P4b 的 |
| `BackendObject_DirectVulkan.cpp.o` | 1 | `PipeInputs::InvalidateCompileEnv` |
| `UniformManager.cpp.o` + `VulkanRenderer.cpp.o` | 1 | `BufferObject::EnsureGpuResidentStorage` |
| `VkClearManager` + `VkRenderPassManager` + `VulkanRenderer` | 1 | `FramebufferAttachmentObject::GetSize` |
| `VkRenderPassManager` + `VulkanRenderer` | 1 | `FramebufferAttachmentObject::IsComplete` |
| `Renderer/VkRenderPassManager.cpp.o` | 1 | `RenderbufferObject::IsAllocated` |
| `DirectVulkan.cpp.o` + `VulkanRenderer.cpp.o` | 1 | `VertexArrayObject::GetIndexBufferBindingSlot` |
**没有一个标 `# P13`**,而这是有意的:§4.2 的 `# P13` 是给「不动 pull `.text` 就消不掉」的符号准备
的,上面没有一个证明了这一点——`UniformManager` / `VkSamplerManager` 的 monolith 臂是**pull 构建
自己的代码**,对它们做 `#if MOBILEGL_BUILD_DISAGGREGATED` 与本片对 `VulkanRenderer.cpp` 做的是同一件
事,pull 构建逐字节不动。它们是 **wave 3 的工作量**,不是 P13 的边界问题。把它们提前标成 P13 会把一
件没人试过的事记成一件试过做不到的事。
### 4.4 门
`integration-gpu` **1705**(monolith `DirectVulkan.` 条目都在里面)、`integration-magma-split`
**93**、`-spawn` **72**、`-tcp` **74**、`integration-magma-full-split` **524**、
`ctest -L unit` **2423**,全绿。
`link_ratchet --assert-monotone` 对**重基线后**的文件:173 不变,RC 0。
**retrace(本片的关键读数,因为 monolith 臂在 disaggregated 构建里换了实现)**:
| case | ssim | mismatchPixels |
|---|---:|---:|
| `OpenRA.DirectVulkan`(monolith) | **1.000000** | 0 |
| `OpenRA.DirectVulkan.SPLIT` | **1.000000** | 0 |
整条 DirectVulkan 矩阵:monolith 39/40 绿、SPLIT 38/39 绿,两边红的是**同一条**
`minecraft-1.21.4-fabric-iris-iterationrp-in-world`(ssim 0.027915)。**已核实是既有的**:在包 B 的
树(`~/w7/p7-magma-b/build-retrace`,本分支的父提交)上同一条用例同样红。它要 CONTRACT-P7 §2.4 说的
三个 iterationRP 旋钮,而那三个是 CI 作业导出的、不是 ctest 条目自带的。
G1 pull `.text` **`0xa52203`**、`nm --defined-only` 与 `~/w7/p7-before/pull-syms.txt` 逐行相同
(30570 行,0 差异)——本片改的每一行要么在 `#if MOBILEGL_BUILD_DISAGGREGATED` 里,要么在
`#if !MOBILEGL_BUILD_DISAGGREGATED` 里而那一侧逐语句未动。
### 4.5 欠设备核对
monolith 臂在 **Android 的 disaggregated 包**里现在跑的是烘焙 blit。旋转(`ROTATE_90/180/270`)的
几何只有真机能看:`BlitWireColorImage` 的 `quarterTurn` 归一化与它替换掉的
`TryBlitToDefaultFramebufferWithShader` 的 `dstNormWidth/dstNormHeight` 是同一个算式,但本机每一个
表面都是 `IDENTITY`,这条等式没有被执行过。
---
## 5. 要手机的清单(本包全部的设备债)
1. **片 1**:旋转表面上的缩放多重采样 blit(本机只有 identity)。
2. **片 2**:没有 `VK_KHR_depth_stencil_resolve` 的设备上的 shader 深/模板 resolve——本机只能经
`MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE` 到达。Adreno 830 是否带这两个扩展本身也要看一眼:
带,则这条臂在真机上同样只有旋钮可达;不带,则它是真机的**默认**臂。
3. **片 4**:`ROTATE_90/180/270` 下 monolith 臂的烘焙 blit 几何(§4.5)。
4. 包 B 遗留、本包未碰:非恒等旋转下的 `default-color-blit-shape` scratch 臂,以及
OpenRA 三次 `pm clear` 冷跑的 1.0 验收。
+2 -15
View File
@@ -2,10 +2,10 @@
# Regenerate: python3 scripts/link_ratchet.py --build-dir <dir> \
# --baseline scripts/data/link_ratchet_baseline.txt \
# --write-baseline scripts/data/link_ratchet_baseline.txt
# base commit : 78b7d6beacbebb89a5cf8aa412dd3739be5f1247
# base commit : 4bee13136585c1333823796500ed12a86b991e61
# configure : -DMOBILEGL_BUILD_DISAGGREGATED=ON -DMOBILEGL_BUILD_DISAGGREGATED_INPROC=ON -DMOBILEGL_PIPE_PUSH=ON -DCMAKE_BUILD_TYPE=Release
# build dir : build-split
# count : 186
# count : 173
#
# One demangled symbol per line. A trailing ` # note` is an annotation the parser
# ignores; `# P13` marks a symbol that cannot fall before P13 establishes module
@@ -78,12 +78,8 @@ MobileGL::MG_State::GLState::GetBufferBackendOps()
MobileGL::MG_State::GLState::MGPipeTextureLegacyArmScope::MGPipeTextureLegacyArmScope()
MobileGL::MG_State::GLState::MGPipeTextureLegacyArmScope::~MGPipeTextureLegacyArmScope()
MobileGL::MG_State::GLState::ProgramArtifactsSchemaFingerprint()
MobileGL::MG_State::GLState::ProgramObject::AllocateLifetimeId()
MobileGL::MG_State::GLState::ProgramObject::AttachShader(std::shared_ptr<MobileGL::MG_State::GLState::ShaderObject> const&)
MobileGL::MG_State::GLState::ProgramObject::JoinPendingLink() const
MobileGL::MG_State::GLState::ProgramObject::JoinPendingSpirv() const
MobileGL::MG_State::GLState::ProgramObject::Link(bool)
MobileGL::MG_State::GLState::ProgramObject::~ProgramObject()
MobileGL::MG_State::GLState::RenderbufferObject::GetExternalIndex() const
MobileGL::MG_State::GLState::RenderbufferObject::GetHeight() const
MobileGL::MG_State::GLState::RenderbufferObject::GetInternalFormat() const
@@ -111,22 +107,13 @@ MobileGL::MG_State::GLState::SamplerObject::GetWrapS() const
MobileGL::MG_State::GLState::SamplerObject::GetWrapT() const
MobileGL::MG_State::GLState::SamplerObject::SamplerObject(unsigned int)
MobileGL::MG_State::GLState::SamplerObject::SetCompareMode(MobileGL::SamplerCompareMode)
MobileGL::MG_State::GLState::SamplerObject::SetLodRange(float, float)
MobileGL::MG_State::GLState::SamplerObject::SetMagFilter(MobileGL::SamplerFilterMode)
MobileGL::MG_State::GLState::SamplerObject::SetMinFilter(MobileGL::SamplerFilterMode)
MobileGL::MG_State::GLState::SamplerObject::SetMipmapMode(MobileGL::SamplerMipmapMode)
MobileGL::MG_State::GLState::SamplerObject::SetSamplerCompareFunc(MobileGL::SamplerCompareFunc)
MobileGL::MG_State::GLState::SamplerObject::SetWrapR(MobileGL::SamplerWrapMode)
MobileGL::MG_State::GLState::SamplerObject::SetWrapS(MobileGL::SamplerWrapMode)
MobileGL::MG_State::GLState::SamplerObject::SetWrapT(MobileGL::SamplerWrapMode)
MobileGL::MG_State::GLState::SamplerObject::~SamplerObject()
MobileGL::MG_State::GLState::SamplesAsIncompleteTexture(MobileGL::MG_State::GLState::ITextureObject const*, MobileGL::MG_State::GLState::SamplerObject const*)
MobileGL::MG_State::GLState::SetBufferBackendOps(MobileGL::MG_State::GLState::BufferBackendOps const*)
MobileGL::MG_State::GLState::ShaderObject::Compile()
MobileGL::MG_State::GLState::ShaderObject::DropCompileNode() const
MobileGL::MG_State::GLState::ShaderObject::JoinPendingCompile() const
MobileGL::MG_State::GLState::ShaderObject::ReleaseCompileNode()
MobileGL::MG_State::GLState::ShaderObject::SetShaderSource(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&)
MobileGL::MG_State::GLState::TextureObject1D::TextureObject1D(unsigned int)
MobileGL::MG_State::GLState::TextureObject2D::TextureObject2D(unsigned int)
MobileGL::MG_State::GLState::TextureObject2DCube::TextureObject2DCube(unsigned int)