- 144 commits from785fed0b0onward were rewritten to drop the Co-Authored-By trailer the project forbids; origin/feat/disaggregated moved from 1135c664 to3c80cd62(same trees) - the old->new map is ~/w7/logs/sha-map-20260922.txt; every 7..40-hex citation in the Disaggregated docs and the MG_Remote contracts that matched an old SHA is replaced by the same-length prefix of its rewrite; no other content changes
26 KiB
P7 wave 2-B:Magma blit / copy / mip 簇(分支 p7/magma-b)
计划见
PLAN-PH-P34B-P7.md§1.3 与 §3 wave 2-B;规范见CONTRACT-P7.md§0(规则 I / J)、§3.2(退役 vs decline 表)、§5.1 / §5.2。基线 =feat/disaggregated上~/w7/pipe的头801f8eda(wave 0 已落地:Magma 两进程三臂、MGPipeSessionFailHook→Session::Fail漏斗、scripts/link_ratchet.py)。主机口径:WSL Arch + lavapipe(
/usr/share/vulkan/icd.d/lvp_icd.json,系统唯一 ICD),build-split= Release / clang / ccache /DISAGGREGATED=ONINPROC=ONPIPE_PUSH=ONBUILD_INTEGRATION_TEST=ON,ICD 与~/w7/pipe/build-split逐项相同。基线门读数(本树实测):
integration-magma-split73(71 PASS + 2 SKIP)、-spawn52、-tcp54,三臂 0 红。
0. 每片一条提交,每片一次 red-once
| 片 | 名 | 退役 / decline | 提交 |
|---|---|---|---|
| 1 | mipmap-shader-format-or-shape |
诊断 + 颜色 shader mip 回落(退役一半:可渲染格式的不可附着图像) | 见 §1 |
| 2 | depth-stencil-mipmap@P7 + 烘焙 (A)(D) |
DEPTH-only 退役(烘焙深度 mip 程序);D|S decline | 见 §2 |
| 3 | copy-image-in-place@P7 |
退役(单次 GENERAL 转换);同级同层矩形重叠 decline | 见 §3 |
| 4 | default-color-blit-shape@P7 |
退役(恒等回落 + 旋转 scratch);四旋转之外 decline | 见 §4 |
| 5 | 出口门 3:OpenRA 回读竞态 | 修复(回读自带写可见性 barrier + 等齐所有在飞提交) | 见 §5 |
1. 片 1:mipmap-shader-format-or-shape 的诊断与颜色回落
1.1 设备证据与它为什么主机看不见
minecraft-1.21.4-fabric-iris-iterationt-in-world 与 -iterationt-nodsa-in-world 在 Redmi
(2f7cbe2e,Adreno 830v2,DirectVulkan inproc,--use-pbuffer)上确定性地让 server apply 线程死于
MGPipe: Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape"}
run-ahead 与 lockstep 都复现;monolith 臂通过;WSL/lavapipe 全绿。
那一行就是全部证据,而 WireFramebuffer.inc 里这个名字下挂着五个互斥的析取项(aspect 非颜色、
1D/1D_ARRAY/3D 视图、图像缺 COLOR_ATTACHMENT usage、层范围越界、格式不是采样浮点)。设备能告诉我们的
唯一一件事——是哪一个——恰恰是它没告诉我们的。
主机看不见它,原因是结构性的:lavapipe 对本集成二进制能创建的每一个颜色格式都报
BLIT_SRC|BLIT_DST,于是 GenerateWireMipmap 的 nativeBlit 恒真,两条 shader 臂在门机器上从未
执行过一次。旁边那条看起来覆盖了 shader 臂的 F1WireScenario.GenerateMipmapPackedFloatPixels 也没有:
R11F_G11F_B10F 在这里同样原生 blit。一条没有任何主机车道能到达的拒绝,就是一条会发版的拒绝。
1.2 做了什么
(a) 诊断。 名字仍是第一个词、不加前缀(census、retrace 拒绝普查与所有 log grep 都按首词键控),
其后附上驱动自己的答案:格式枚举、(视图重解释时的)存储格式、extent、levels、aspect、viewType、
usage 位、层范围,以及 optimalTilingFeatures 里缺的特征位名。实测输出形如
Magma:mipmap-shader-format-or-shape format=37 extent=8x8x1 levels=4 aspect=0x1 view=1 \
usage=0x17 layers=0+1/1 missing=DEPTH_STENCIL_ATTACHMENT
下一次设备窗口不必再推导,读一行即可指名。
(b) 颜色回落(退役的那一半)。 原来的 shader 臂直接渲染进下一级,这要求图像创建时带
COLOR_ATTACHMENT usage;而设备拒绝把某格式作为颜色附件时,VkTextureManager 就不会给它这个位——
于是每一个「缺 BLIT 且只采样」的纹理都落进拒绝而不是回落。现在区分两种形状:
- 格式可渲染、图像可附着 → 原臂不变(直接渲染进目标级);
- 格式可渲染、图像不可附着 → 以目标级尺寸创建自有 scratch 颜色附件,shader 渲染进 scratch,再
vkCmdCopyImage原样搬进该级。纯 texel 搬运,无格式转换,texel 仍来自 GPU 内容而非 staged 影子; scratch 按 multisample resolve 臂同样的形状随提交退休(共享 scratch 在此处改尺寸会毁掉更早排队的一次 pass 仍在读的图像)。
仍然拒绝且现在指名的残余:非颜色 aspect(深度归片 2)、1D/1D_ARRAY/3D、层范围越界、非采样浮点格式、 以及格式本身就不能作颜色附件(此时没有任何 shader 臂可走)。
(c) 主机可达性(测试旋钮)。 MGITEST_MAGMA_FORCE_SHADER_MIPMAP,只读一次,只在
#if MOBILEGL_BUILD_DISAGGREGATED 内编译,不进 ConfigLoader 的 accepted-env 表:
1= 丢掉原生 blit(Adreno 对缺BLIT_DST格式走的那条臂);2= 再把目标当作不可附着(Adreno 对只采样纹理走的那条臂,也就是本片之前的拒绝本身)。
1.3 场景与车道
F1WireScenario.GenerateMipmapWithoutNativeBlitPixels:8×8 RGBA8、上下两半不同色(一色铺满时,丢失
GL 行序的实现会平均成同一个数,单色断言会把它判对)、glGenerateMipmap 后读 level 1 的四行,断言下两行
是下半色、上两行是上半色(边界从不落在目标 texel 内部,两色不混)。旋钮未设时 GTEST_SKIP 并写明理由。
注册在 Fm 车道的三臂上,每臂两条(ShaderMip1. / ShaderMip2.),各自一条 ENVIRONMENT——
不是车道级翻转:旋钮改变本进程内每一次 mipmap 的生成方式,放进共享 Fm 环境会把
GenerateMipmapPixels、GenerateMipmapPackedFloatPixels、GenerateMipmapDepthPixels 与两条
base/view 用例一起改道,四条绿条目会不再测量它们被写出来测量的那条臂。
1.4 red-once(R-16,已执行并还原)
把 scratch 臂关掉(viaScratch 恒假)重建,ctest -R ShaderMip:
CTEST_RC=8
MGPipe: Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape format=37 extent=8x8x1
levels=4 aspect=0x1 view=1 usage=0x17 layers=0+1/1 missing=DEPTH_STENCIL_ATTACHMENT"}
75% tests passed, 2 tests failed out of 8
即 tier-2 条目按名死亡,且诊断把格式与形状都说出来了。还原后同一过滤器 8/8 绿。
1.5 门
integration-magma-split 75(+2)、-spawn 54(+2)、-tcp 56(+2),三臂 0 红。
2. 片 2:深度 mip 烘焙 (A) + 新鲜度门 (D)
2.1 为什么 monolith 的回落不能直接用
monolith 臂从拆分之前就带着一条深度 mip 的 shader 回落(GenerateDepthMipmapWithShader),因为
VK_FORMAT_FEATURE_BLIT_DST 对深度格式是可选的,相当多的设备不给。wire 臂没有这条回落:深度纹理
一旦不能原生 blit,第一个析取项 resource->aspect != COLOR 就把它送进
mipmap-shader-format-or-shape(片 1 的诊断现在会把它指名为 aspect=0x2 missing=COLOR_ATTACHMENT)。
monolith 那条回落不可复用,这正是 CONTRACT-P7 §5.1 (A) 的全部内容:它要造一个 GL ProgramObject、
两个 ShaderObject 和一个 GL sampler,经 uniform manager 的 global UBO 绑定,把 uSrcTexelSize 当默认块
uniform 写——四个前端对象加一次 GL 编译器调用,落在 server 的 apply 线程上。
2.2 烘焙 (A)
WireDepthMipmap.vert:几何与WireColorBlit.vert逐字相同(同一个 quad、同一个 push-constant rect、同一个 GL-bottom-left → native-image 映射),只多一个成员ivec2 srcTexelSize。push-constant 块 必须在同一管线的每个 stage 里声明一致,所以顶点阶段也声明它,而不是拆出第二个 range。WireDepthMipmap.frag:kDepthMipmapFragmentShaderSource的移植而非重写——同样的 2×2texelFetch盒、同样的 clamp、同样的平均、同样的gl_FragDepth。§3.2 的「退役」= 与 monolith 臂同 可观测,所以连它那半个 texel 的偏移(ivec2(texCoord * srcTexelSize)落在目标中心偏半 texel)也照搬。WireDepthMipmap.inc:深度附件 render pass(loadOp = DONT_CARE,pass 写满整级)+ 管线 (depthTest/Write = TRUE、compareOp = ALWAYS、无颜色附件)+ 按格式/布局缓存的 target 表,形状与WireColorBlit.inc逐条对齐(同样的懒建模块、同样的每提交描述符池、同 image 时两侧都 GENERAL)。 sampler 用 NEAREST:frag 自己做 2×2 盒,任何采样器过滤都会被算两遍,而且多数设备不给深度格式 linear。GenerateWireMipmap深度分支接入;WireDepthMipmapSpirv.h由下面的门自己烘。
2.3 depth-stencil-mipmap@P7 拆成退役 + decline
DEPTH-only 走上面的烘焙程序(退役)。D|S 合并与 stencil-only 改为 decline(MGLOG_E_ONCE +
return,§3.2),并且在 GrowWireTextureMipChain 之前就 return——否则会先长出一条这个动词不会填的
链,随后又把它记成 defined。
decline 而非 Fatal 的理由:monolith 也做不到。它的 shader 回落自己断言 depth-only
("shader fallback only supports depth-only textures"),而断言在两条臂都发的 release 构建里等于零。GL
也没承诺 stencil mip;这条 pass 写 gl_FragDepth,会把它刚产出的那一级的 stencil aspect 留成未定义——
比整级不动还糟。
2.4 新鲜度门 (D):MOBILEGL_BAKED_INTERNAL_SHADERS
MG_Test/SelfTest/BakedInternalShadersTest,表驱动,8 行(WireColorBlit.{vert,frag}、
WireDepthMipmap.{vert,frag}、DriverPostIterationRPWitness.comp、
PrimitivesGeneratedNoXfbProbe.{vert,tesc,tese}),用树内 glslang 重编每一行并逐字比较。
这个门就是烘焙器。 scripts/bake_internal_shaders.py 自己不编译任何东西:它用
MOBILEGL_BAKE_INTERNAL_SHADERS 跑这个二进制,拿它吐出的字,splice 进头文件。方向是刻意的——门走
glslang 库、烘焙器 shell 出一个 glslangValidator 二进制,就是两个编译器、没有哪个说了算;等它们
的默认值哪天分叉,门会在一个没人能重新生成的头上变红。格式按头保留而不是统一:树里的头对每行几个字
和 u 后缀意见不一,统一会把一个字的改动埋进整文件 diff。
首轮实测(这是事先没人能知道的一件事):
| 头 | 结论 |
|---|---|
WireColorBlitSpirv.h |
已新鲜——手跑 glslangValidator 的字与树内 glslang 逐字相同 |
PrimitivesGeneratedNoXfbProbeSpv.h |
已新鲜(三个数组全部) |
DriverPostIterationRPWitnessSpv.h |
过期,按其自己的散文所说;本片重烘(2142 → 2143 字),witness magic 0x50323033 仍在(它是 .comp 里的常量,不是字的属性) |
三处 provenance 注释改为指向脚本与门。
2.5 场景
F1WireScenario.GenerateMipmapDepthWithoutNativeBlitPixels(旋钮 tier 1,三臂):GL_DEPTH_COMPONENT24(→X8_D24_UNORM_PACK32)与GL_DEPTH_COMPONENT32F(→D32_SFLOAT)各一遍, 16×16、上下两半 0.75 / 0.25,glGenerateMipmap后按采样附件读回 level 1 的 8 行。第 3 行不断言: 移植来的滤波器带半个 texel 偏移,两种读法下第 0..2 行只来自下半、第 4..7 行只来自上半,第 3 行是跨界 带——钉它等于钉那半个 texel 而不是钉 mip。F1WireScenario.GenerateMipmapDepthStencilDeclinesAndKeepsTheSession(无旋钮,三臂):GL_DEPTH24_STENCIL8上glGenerateMipmap不报 GL 错、不带走会话(回读是一次穿到 server 的往返, 一并回答会话还活着与 level 0 还在)。
2.6 red-once(R-16,三条,均已执行并还原)
- 新鲜度门:把
WireDepthMipmap.frag的0.25改成0.2500001(不需要重建——门在运行时读源),WireDepthMipmapFragment行在第 279 个字变红并指名重新生成的办法;还原后 8/8 绿。 - 深度退役:关掉深度分支 →
DirectVulkan.Split.DepthMip.*死于Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape format=125 extent=16x16x1 levels=5 aspect=0x2 view=1 usage=0x27 layers=0+1/1 missing=COLOR_ATTACHMENT"}(格式 125 =X8_D24_UNORM_PACK32,诊断把它指名了)。 - D|S decline:把
MagmaWireFatal放回去 → 该用例死于Fatal{UnmigratedVerb, "Magma:depth-stencil-mipmap@P7"}。
还原后两条 2/2 绿。
2.7 门
integration-magma-split 77(+2)、-spawn 56(+2)、-tcp 58(+2),三臂 0 红;
ctest -L unit 2410(+8 = 新鲜度门的八行)全绿;信息档 integration-magma-full-split 513,
0 红 / 59 skip(基线 510,0 红 / 57 skip——两条新增用例在无旋钮的全量档自 skip)。
G1 实测过了,而且这一条事先不是显然的(片 2):DriverPostIterationRPWitnessSpv.h 是
MG_Util/SelfTest 的头,pull 构建也编它,重烘改了 2142 → 2143 个字。pull .text 仍是 0xa52203,
nm --defined-only 的名字表与 ~/w7/p7-before/pull-syms.txt 逐行相同(30570 行)——字长的变化落在
.rodata,没有改动任何一条指令的编码宽度。fatal_census 绿(79 abort 站点 / 43 家族词 / 0 无标记,
本片退掉一处 Fatal{} 用法但不动 abort 站点数);link_ratchet --assert-monotone 186 不变,
p7-magma 桶 101。
3. 片 3:copy-image-in-place@P7
3.1 那条 Fatal 说的是真话,但只对它下面的代码成立
glCopyImageSubData 在同一个纹理的两个子资源之间拷是普通的、有定义的 GL(4.6 core 18.3.2 还专门
写明纹理与它的 view 是同一 image 上的两个对象、允许互拷)。wire 臂为它带走整个会话,理由写在注释里:
下面那对 TRANSFER_SRC / TRANSFER_DST 描述不了一个 image——两端共用同一个被跟踪的
VkImageLayout,第二次转换会把第一次撤销。
VK_IMAGE_LAYOUT_GENERAL 能描述它(vkCmdCopyImage 两侧都接受 GENERAL)。于是:一次把整个 image
转成 GENERAL(源级与目标级都在里面)、命令两侧都 GENERAL、一次还原;access mask 带上读写两个方向,
因为这一道 barrier 是两边共同的边。
monolith 臂至今仍把所有 in-place 拷贝整体 decline(同函数上方的 !wire && 分支),所以这一片让 wire
臂比 monolith 臂多做一件事——这正是 §3.2 要求的形状,而不是对齐。
inPlace 在 pull 构建里是 constexpr false,它守的每个分支都会折掉:这个函数也编进 monolith 镜像,
而 G1 钉着那个镜像的 .text。
3.2 留下来的那一种 decline
同 image + 同 level + 同 layer + 矩形重叠:GL 4.6 core 18.3.2 说重叠处结果未定义。GENERAL 让这条
命令可记录,不让它有意义——vkCmdCopyImage 对自己在同一 region 内的读写不排序,tiler 产出的是
每个 texel 先到的那一半。所以 MGLOG_E_ONCE + return,把这句话说一次,而不是把它发出去。
判据是三个维度同时成立:同 level、slice 区间相交、矩形相交。level 0→1、层与层不相交、同一级上两个 不相交的矩形,都是有定义的,都照做。
3.3 场景
F1WireScenario 里两条(split-only,因为 monolith 没有可对照的读数),一张 8×8×2、两级的
GL_TEXTURE_2D_ARRAY,每一个被碰到的子资源都填成互不相同的颜色(level 0 layer 0 四象限、
level 0 layer 1 洋红、level 1 layer 0 青、level 1 layer 1 白)——铺平色会让「拷到了错的 level / 错的
layer / 根本没拷」都读成对的。
CopyImageInPlaceAcrossLevelsAndLayers:level 0 → level 1(同层)后 level 1 layer 0 变成源矩形那个 全红象限、layer 1 仍是白;再 level 0 layer 0 → layer 1(同级、层不相交)后两层都是四象限,源不变。CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone:同级同层 (0,0)-(4,4) → (2,2),无 GL 错, level 0 layer 0 逐 texel 不变(若真执行,(4,4) 会从黄变红,断言分得出来)。
三臂各注册两条。
3.4 red-once(R-16,已执行并还原)
把 MagmaWireFatal("copy-image-in-place@P7") 放回去 → 两条都死于
Fatal{UnmigratedVerb, "Magma:copy-image-in-place@P7"};还原后 2/2 绿。
3.5 门
integration-magma-split 79(+2)、-spawn 58(+2)、-tcp 60(+2),三臂 0 红。
本树的 tcp 端口改成了
40913。 跑本片的门时TcpServer.Start整条 tcp 车道红了 59/60, 原因是40613(~/w7/pipe/build-split的默认值)上已经有一个兄弟树的libMobileGLServer在监听。 这是本地 configure 选项,不影响车道语义;magma-two-process-first-run.md里 wave 0 也为同一原因 用了专用端口。
4. 片 4:default-color-blit-shape@P7
4.1 一条 Fatal 盖住了三种互不相干的情况
那条 shader pass 是为旋转存在的:它只能采样自己的源(sampler2D、float 结果、两种 2D view
type 之一)。于是 RGBA8UI 附件、3D 切片、数组层都会让 BlitWireColorToDefault 返回 false——而且在完全
没有旋转的表面上也会,尽管二十行之下那条普通的 vkCmdBlitImage 臂本来就能正确完成。
拆成三块:
- 恒等变换 → 回落到普通
vkCmdBlitImage臂(它已经把 GL 的底行映射到原生图像的末行); - 非恒等旋转 + 采不到的源 → 把源矩形
vkCmdCopyImage进一张自有的 2D、单采样、与源同尺寸兼容类 的采样浮点格式 scratch,再对 scratch 做 shader blit; - 四个旋转之外(镜像 / 镜像旋转)→
MGLOG_E_ONCEdecline 并写明是哪个 transform:两条臂都表达 不了反射,而本包没有一台会报这种 transform 的设备来核对结果。
对整数源的重解释就是重点,GL 也同意:整数与归一化格式之间的 glBlitFramebuffer 是
INVALID_OPERATION(4.6 core 18.3.1),所以没有「正确结果」可言——只有一种以前会结束会话、现在会产出源
比特的形状。对 view type 不对的浮点源,那次 copy 就是一次切片抽取,结果完全正确。
4.2 退役顺带挖出一个真 bug
普通 blit 臂把 3D 源当成层来寻址:region.srcSubresource = {aspect, source.level, source.layer, 1},
srcOffsets[].z = 0..1。3D 图像的切片在 z 上,它唯一的 subresource 是 layer 0——copy 端点
(CopyImageSliceMapping)与上面的 multisample resolve 臂早就是这么寻址的。所以从 3D 纹理第 1 片 blit
会去读第 0 片、并用一个该图像根本没有的层。
在此之前看不见:这种形状先撞上那条 Fatal,根本走不到这条臂。本片第一次跑场景时回落臂的四个象限全错、 scratch 臂全对,bug 就是这么掉出来的。两条臂现在都对。
4.3 场景
F1WireScenario.ColorBlitToDefaultFromANonSampleableSourceDegrades:源是 3D 纹理的第 1 片
(普通合法 GL,正是 shader pass 采不到的形状),第 0 片填一个任何象限都不会出现的颜色(青)——一次忘了 z
原点的 scratch copy 会去读另一片,而那样四个象限断言仍然是在断言某些真 texel,所以必须让它可分辨。
同一条用例、每臂注册两遍:不带旋钮(走恒等回落臂)与 MGITEST_MAGMA_FORCE_DEFAULT_BLIT_SCRATCH=1
(在恒等表面上走旋转臂的 scratch 路径)。两者必须产出同样的像素,这条等式就是断言——也是 scratch 臂
唯一可被主机车道到达的方式:本车道能创建的每一个表面都是 VK_SURFACE_TRANSFORM_IDENTITY,本簇的 Redmi
证据也是 pbuffer。
4.4 red-once(R-16,已执行并还原)
把 MagmaWireFatal("default-color-blit-shape@P7") 放回去 → 两条都死于
Fatal{UnmigratedVerb, "Magma:default-color-blit-shape@P7"};还原后 2/2 绿。
4.5 门与设备债
integration-magma-split 81(+2)、-spawn 60(+2)、-tcp 62(+2),三臂 0 红。
欠设备核对:非恒等旋转下的 scratch 臂只在恒等表面上被旋钮验证过(像素与回落臂相同);真正的 ROTATE_90/180/270 几何要在手机上看一眼。四旋转之外的 decline 无设备可核(没有会报镜像 transform 的 设备),按 decline 收口。
5. 片 5(集成者追加,最高优先):出口门 3 的 OpenRA 回读竞态
5.1 机制(file:line,按本片落地前的树)
两句话:
WireFramebuffer.inc的ReadWirePixels先FlushPendingCommands(),那一步把整帧的 draw 以 自己的 pooled fence 提交上队列后就返回、不等(VulkanRenderer.cpp:13534-13598,成功路径上没有vkWaitForFences)。随后回读的vkCmdCopyImageToBuffer被录进另一个命令缓冲、成为后一次 提交。SubmitReadbackCommandsAndWait(VulkanRenderer.cpp:10707-10748)只等自己那一次提交的 fence, 然后OnSubmitsCompletedUpTo(frame.lastSubmitIndex)把更早那次也记成完成并退休它的 fence。 原注释写着「the wait proved every submission complete」——那句话对 monolith 臂成立(它不先 flush, draw 与 copy 同一次提交、同一个 fence),对 wire 臂不成立。
把两次提交绑在一起的唯一东西,是回读前那道 TransitionWireImage 的 barrier;而它的源作用域来自
图像被跟踪的布局(GetImageTransitionSourceState,VulkanRenderer.cpp:1784-1825)。其中两条臂——
VK_IMAGE_LAYOUT_UNDEFINED 与 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR(:1787-1791)——返回
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT 且 access 为 0,这是 Vulkan 里「什么都不等」的写法。
刚 present 过的默认帧缓冲正处在后一种布局。
于是:pipeline 创建越慢、draw 那次提交拖得越久,copy 越可能在它还没画完时就读走——帧里最后画的那些 对象缺失,正是设备上看到的样子。
5.2 主机实测(决定性,且不依赖竞态碰运气)
在 ReadWirePixels 记录 copy 的那一刻打点(OpenRA / DirectVulkan / inproc):
MBPROBE ReadWirePixels/pre: isDefault=1 layout=1 aspect=0x1 submit=72 completed=71
submit=72 completed=71——承载整帧 draw 的第 72 次提交此刻在飞、且无人等它。这不是概率事件,是
代码结构,主机上确定性可复现。
主机不发散的原因也在这一行里:layout=1 是 VK_IMAGE_LAYOUT_GENERAL,走
GetImageTransitionSourceState 的 default: 臂(ALL_COMMANDS / MEMORY_WRITE),碰巧把 copy 排
在了 draw 之后。设备上的默认帧缓冲不是 GENERAL。
集成者给的复现路径(pipeline 延迟旋钮)在主机上不成立,这里如实记录:新增
MOBILEGL_TEST_PIPELINE_CREATE_DELAY_MS(split-only、未设即惰性,
VulkanRenderer.cpp GetOrCreatePipelineWithInput 的 miss 路径)确实生效——OpenRA DirectVulkan SPLIT
从 1.88 s 变 4.53 s——但 ssim 一直是 1.000000。原因就是上一段:主机那条臂的 barrier 是满的。所以
red-once 用的是 §5.4 的计数,而不是一张发散的图。
5.3 修复(两半,都只在 wire 臂)
RecordWireReadbackWriteBarrier()(WireFramebuffer.inc,新):回读在 copy 之前自己记一道ALL_COMMANDS → TRANSFER、MEMORY_WRITE → TRANSFER_READ|MEMORY_READ的内存 barrier,不再从布局 推断该等谁。barrier 的第一作用域是本队列提交序里更早的每一条命令,所以它跨得过提交边界。SubmitReadbackCommandsAndWait等齐所有在飞提交(VulkanRenderer.cpp,#if MOBILEGL_BUILD_DISAGGREGATED):收集m_inFlightSubmits里submitIndex <= frame.lastSubmitIndex的每一个 fence 一起等。代价是零——回读本来就是一次完整的 CPU 停顿。 pull 构建保持单 fence 原样:它没有 wire 臂、回读前不会 flush,而且是 G1 钉住的那个镜像。
5.4 red-once(R-16,已执行并还原)
打点回读实际等了几个 fence:
fences |
upTo |
completed |
|
|---|---|---|---|
| 修复后 | 2 | 73 | 71 |
| 修复前 | 1(只有自己那一个) | 73 | 71 |
completed=71 两边都一样——即第 72 次提交确实在飞。修复前只等 1 个 fence 然后把 72 记成完成;修复后
两个都等。把 §5.3 的第 2 半还原即回到 fences=1,这是确定性的、主机上的红,不靠时序。
5.5 车道
MOBILEGL_TEST_PIPELINE_CREATE_DELAY_MS=25 下的三条回读用例(VertexIdSamplerAndScalarUniformPixels
draw→回读、ComputeImageStoreFramebufferPixels compute→回读、GenerateMipmapPixels)注册到三臂,
前缀 .PipelineDelay.。它们是回归门而不是复现:钉住「路径上每次 pipeline 创建都多花 25 ms 时,回读
仍然返回那次 draw 的像素」——即答案不随时序移动。
integration-magma-split 84(+3)、-spawn 63(+3)、-tcp 65(+3),三臂 0 红;
OpenRA DirectVulkan SPLIT retrace ssim 1.000000(带与不带 25 ms 延迟各两遍)。
5.6 欠设备核对
集成者要在 Redmi 上重跑三遍冷 cache(pm clear 后)。预期:0.976494 → 1.000000。若仍发散,下一个嫌疑
按序是 WireDraw.inc 里十一处静默丢弃 draw 的 return false(尤其 pipeline == VK_NULL_HANDLE
那处,它与设备日志里那三行 handing vkCreateShaderModule an INVALID module for program 2 stage 4
在同一条路径上),以及 SetImageContentDefined 在 wire 臂是画之前就置位(monolith 是
EndRenderPass 之后),这会让「没有写入记录」那条警告恰好在它该响的时候不响。两条都不在本片修的范围
内,按名记在这里。