From cff1493a49b3a56d0f89e7da3d3d58c815ea92e9 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:01:52 -0400 Subject: [PATCH 01/13] p5f fm: pin VBO refusal and sRGB and 1D framebuffer behavior --- .../Scenarios/F1WireScenario.cpp | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index cc77fadc..9a0001e2 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -2,9 +2,15 @@ #include "../Harness/ScenarioFixture.h" #include "../Harness/SplitRuntimePeek.h" #include +#include +#include +#include #include +#include #include -#include +#if !defined(_WIN32) && GTEST_HAS_DEATH_TEST +#include +#endif #ifdef GLAPI #undef GLAPI #endif @@ -397,6 +403,131 @@ void main() { glDeleteProgram(program); } +TEST_F(F1WireScenario, EnabledVertexBufferDrawKeepsNamedP7Fatal) { + if (!Ready()) return; +#if !defined(_WIN32) && GTEST_HAS_DEATH_TEST + Attach(GL_RGBA8); + const char* vertex = R"(#version 430 core +layout(location=0) in vec2 position; +void main() { gl_Position = vec4(position, 0.0, 1.0); } +)"; + const char* fragment = R"(#version 430 core +layout(location=0) out vec4 color; +void main() { color = vec4(0.0, 1.0, 0.0, 1.0); } +)"; + const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLuint vao = 0, vbo = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + const GLfloat positions[] = {-1, -1, 3, -1, -1, 3}; + glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(0); + glUseProgram(program); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + + // A fresh exec reconstructs the fixture and apply thread; no child executes GL + // against a forked mutex/thread state. Capture the named log because shipped + // builds may disable console logging, which makes a stderr regex insufficient. + const char* oldLogEnv = std::getenv("MOBILEGL_LOG_FILE_PATH"); + const bool hadLogEnv = oldLogEnv != nullptr; + const std::string oldLogPath = hadLogEnv ? oldLogEnv : ""; + const std::string childLog = oldLogPath.empty() + ? "/tmp/mobilegl-vbo-death-" + std::to_string(static_cast(::getpid())) + ".log" + : oldLogPath + ".vbo-death"; + std::remove(childLog.c_str()); + ASSERT_EQ(::setenv("MOBILEGL_LOG_FILE_PATH", childLog.c_str(), 1), 0); + const std::string oldStyle = ::testing::FLAGS_gtest_death_test_style; + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + EXPECT_DEATH({ + glDrawArrays(GL_TRIANGLES, 0, 3); + glFinish(); + }, ""); + ::testing::FLAGS_gtest_death_test_style = oldStyle; + if (hadLogEnv) ::setenv("MOBILEGL_LOG_FILE_PATH", oldLogPath.c_str(), 1); + else ::unsetenv("MOBILEGL_LOG_FILE_PATH"); + std::ifstream log(childLog, std::ios::binary); + std::ostringstream contents; + contents << log.rdbuf(); + log.close(); + EXPECT_NE(contents.str().find("buffer-legacy-arm"), std::string::npos) + << "draw must keep the existing named P7 refusal, not die at a new object/field access\n" + << contents.str(); + std::remove(childLog.c_str()); + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &vbo); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); +#else + GTEST_SKIP() << "requires gtest thread-safe exec death tests and POSIX environment handling"; +#endif +} + +TEST_F(F1WireScenario, SrgbDrawTracksFramebufferConversion) { + if (!Ready()) return; + Attach(GL_SRGB8_ALPHA8); + const char* fragment = R"(#version 430 core +layout(location=0) out vec4 color; +void main() { color = vec4(0.5, 0.5, 0.5, 1.0); } +)"; + const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, + {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(program); + glViewport(0, 0, 8, 8); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_RASTERIZER_DISCARD); + for (const bool conversion : {false, true}) { + if (conversion) glEnable(GL_FRAMEBUFFER_SRGB); + else glDisable(GL_FRAMEBUFFER_SRGB); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(3, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + for (int channel = 0; channel < 3; ++channel) + EXPECT_NEAR(pixel[channel], conversion ? 188 : 128, 1) << "FRAMEBUFFER_SRGB=" << conversion; + EXPECT_EQ(pixel[3], 255); + } + glDisable(GL_FRAMEBUFFER_SRGB); + glUseProgram(0); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); +} + +TEST_F(F1WireScenario, Texture1DFramebufferClearPixels) { + if (!Ready()) return; + GLuint line = 0; + glGenTextures(1, &line); + glBindTexture(GL_TEXTURE_1D, line); + glTexStorage1D(GL_TEXTURE_1D, 1, GL_RGBA8, 8); + glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_1D, line, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + std::array pixels{}; + glReadPixels(0, 0, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const int expected[] = {64, 128, 191, 255}; + for (int x = 0; x < 8; ++x) + for (int channel = 0; channel < 4; ++channel) + EXPECT_NEAR(pixels[x * 4 + channel], expected[channel], 1) << "1D texel " << x; + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glBindTexture(GL_TEXTURE_1D, 0); + glDeleteTextures(1, &line); +} + TEST_F(F1WireScenario, PartialTextureUploadPreservesGpuClearPixels) { if (!Ready()) return; Attach(GL_RGBA8); From 76b8a983d8f56568e2551b310dfb194a34195a7f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:02:35 -0400 Subject: [PATCH 02/13] [MG_IntegrationTest] (Disaggregated): P5f fm - pin format behavior and the buffer refusal boundary --- MobileGL/MG_IntegrationTest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 763087d9..ec09c1e2 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2383,7 +2383,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) "MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_TRANSPORT=inproc" "MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1" ${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV}) - foreach(fmSlot VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels) + foreach(fmSlot EnabledVertexBufferDrawKeepsNamedP7Fatal SrgbDrawTracksFramebufferConversion Texture1DFramebufferClearPixels VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels) set(MGL_ITEST_FM_LABEL "integration-magma-split") if (fmSlot STREQUAL "GenerateMipmapPackedFloatPixels") # R11F_G11F_B10F mip blits already fail on the baseline Magma lane. From e49414250c98495970b3369f8371adb3a02f8d14 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:03:18 -0400 Subject: [PATCH 03/13] [MG_Backend] (Disaggregated): P5f fm - respect unrestricted sampler view windows --- .../DirectVulkan/Renderer/UniformManager.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index a9014fa1..d2f26041 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -122,9 +122,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto viewHandle = state.BoundSamplerViews[unit].View; if (!MG_Pipe::MGPipeHandleIsNull(viewHandle) && viewHandle.Slot < state.SamplerViewCsos.size()) { const auto& view = state.SamplerViewCsos[viewHandle.Slot]; - if (!view.Live || view.Gen != viewHandle.Gen || view.View.NumLevels <= localLevel) + if (!view.Live || view.Gen != viewHandle.Gen || view.View.Texture != handle) WireDescriptorFatal("sampler-view-record"); - levels = std::min(levels, view.View.NumLevels - localLevel); + // Ordinary texture views can encode an unrestricted window with zero + // counts; only an actual restriction narrows the resource/parameter range. + if (view.View.NumLevels != 0) { + if (view.View.NumLevels <= localLevel) WireDescriptorFatal("sampler-view-level"); + levels = std::min(levels, view.View.NumLevels - localLevel); + } + } else if (!MG_Pipe::MGPipeHandleIsNull(viewHandle)) { + WireDescriptorFatal("sampler-view-record"); } levels = std::min(levels, resource->mipLevels - level); } From 6c3662be9b1c7d2a797f3e1842e6e8b85bc14baf Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:03:24 -0400 Subject: [PATCH 04/13] [Docs] (Disaggregated): P5f fm - document implementation and verified gate checkpoint --- docs/Disaggregated/notes/p5f/fm-report.md | 146 ++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 docs/Disaggregated/notes/p5f/fm-report.md diff --git a/docs/Disaggregated/notes/p5f/fm-report.md b/docs/Disaggregated/notes/p5f/fm-report.md new file mode 100644 index 00000000..111a8e0e --- /dev/null +++ b/docs/Disaggregated/notes/p5f/fm-report.md @@ -0,0 +1,146 @@ +# P5f fm — Magma 跨角色读写退役 + +> 分支 `p5f/fm`;Windows worktree `MobileGL-p5f-fm`;WSL gate tree `~/w7/p5f-fm`。 +> 代码 checkpoint:`b394a09f`。实现与范围结论已核对;§5 的最后一轮数字由门禁执行者补齐。 +> 本报告只裁定 fm;不宣布 P5f 总出口完成。 + +## 1. 真实可达性修正 + +f0 的“带 buffer 的 draw 最终到 buffer-legacy-arm”不能推出整个 draw 子树不可达。 +`ClipDistanceScenario.AnEnabledClipDistanceRemovesTheNegativeHalf` 使用 `gl_VertexID`,没有 VBO;在 f1 基线 +`~/w7/p5f-f1` 的真实 Magma/inproc、role-split=0、strict=0 下像素测试通过(`/tmp/p5f-fm-baseline-clip.log`)。 +因此本包保留无 buffer draw/dispatch,新增 server program source、descriptor 和 render-pass 路径;没有把所有 draw 拒绝。 +迁移后同一 case 在 role-split=1、strict=1 下通过(`fm-clip.log`;集成者独立复核 `int-wire-clip.log`)。 + +记录能被消费也必须能被发射。client 的 P4a family gate 和 server 的 `NoP4aConsumer` +原来都绑在 buffer resource-ops 上,而 Magma 没有该 ops。本包改为 client 查询该 family +的已发布 consumer bit、server 接受其 object records;残余 fill memo 加 caps generation。 +Magma 仍不发布 buffer resources bit 7;发布 indexed-binding bit 13,用记录辨别未绑定与 P7 buffer 债。 +texture staged bytes 原本也只由 GLES 的 resource-ops hook 采纳,本包补无 hook 时的 server-owned +adoption、respecify/defined-ness、destroy/release,保留有 hook 的 Espryt 路径避免双采纳。 + +## 2. 九字段与邻接字段 + +| 字段 | Magma transport 的现在来源 / 边界 | +|---|---| +| GetBoundVertexArray | `SetupWireDraw` 读 `BoundVertexElements` / `VertexElementsCsos`;无数组与 current generic attribute 正常画;实际读取 enabled array 时在 client 读前具名 `buffer-legacy-arm`(P7)。| +| GetBufferBindingPoint | UniformManager 读 `BoundShaderBuffers` 判断绑定;已存在的无绑定 native buffer placeholder 保留,有真实 SSBO/UBO buffer 时具名 `buffer-legacy-arm`。XFB capture 同样在读取前端绑定前拒绝。| +| GetFramebufferBindingSlot | `WireFramebuffer.inc` 的 clear/blit/read/copy 与 `WireDraw.inc` / pipeline builder 全读 framebuffer record / `MGPSurface`。| +| GetImageTextureBinding | UniformManager 读 `BoundShaderImages`;native image view 与 sampler 使用 server resource record。| +| GetTextureUnitObject | sampler 读 `BoundSamplerViews` / `BoundSamplerStates`;mipmap 与 framebuffer-copy 读各 verb handle。| +| GetProgramForDraw | `MagmaProgramSource(handle, ShaderCsoRecord)`;SPIR-V / reflection 来自 `Archive`,可变绑定和 global UBO 来自记录尾部。| +| GetProgramForDispatch | 同一 source,以 `DispatchProgram` 句柄选择 compute program。| +| GetTransformFeedbackProgram | 无 XFB draw 不进该读点;active XFB capture 在 wire setup 的 P7 buffer 边界具名拒绝。本包没有宣称 Magma 的 XFB capture 已可用。| +| GetTextureObject | handle-keyed VkTextureManager 完成 sync,无 GL-name / client weak_ptr 查询。| + +邻接项:`GetBufferBindingPointCount` 不再被 wire uniform 路径读取;`HasOpenTransformFeedbackSpan` +只留在 class-C query / P7 capture 子树。`GetBufferBindingSlot` 的 indirect wrappers 在第一处前端读取前 +保持具名 P7 拒绝;server 生成的 LINE_LOOP indices 用 transient server bytes,普通无 buffer draw 不受影响。 +`ShaderStorageBlockBinding` 用 `VerbStorageBlockProgram` 更新 server `StorageOverrides`,不再经 +`TryGetDirectVulkanProgram` / `ValidateProgramName` / `GetProgramObject` 探 client 名表。 + +独立 fm 的全局 FieldOwnership 分类仍保留 15 个 BARRIER_PULLED 字段;fe 集成后两个 scalar sticky +改为 APPLIER_DERIVED,仍余 **13 个字段**。其余对象行需在收口时与 residual-fill / +generator 的静态不变量一起归零,不是本包门绿后自动翻成 RECORD_SUPPLIED。 +这里没有把“没有调用旧对象 getter”冒充“旧 SharedPtr 字段已由记录填充”。`FieldOwnership.def` 已加该说明。 +`RecordError` 的反向通道归 fv;`PipeInputs::IsLive()` 仍 forward 到 `LiveContext()`,也是 +未退役的 client liveness 读取,留给 fv / 阶段收口核对;均未在本包领取完成。 + +### Program source、descriptor 与发射约束 + +`MagmaProgramSource` 是仅在 disaggregated 构建存在的只读视图:monolith/internal-program +构造接 `const ProgramObject&`,wire 构造接 handle 和 server `MGPipeShaderCsoRecord`。 +非 disaggregated 构建是原 `ProgramObject` 的类型别名,保持 pull 代码形态。 +SPIR-V、linked stages、uniform/block/XFB reflection 来自 ProgramArchive;`BlockBindings`、 +`SamplerUnits`、`StorageOverrides`、`GlobalConstants` 来自当前记录,后者覆盖 archive 的旧值。 +全局 UBO 字节与版本不再问 client,SSBO 索引沿用旧 SPIR-V reflection 的排序规则。 + +server 视图不使用或写回 `ProgramObject` 的 hash memo:每次按当前 archive/绑定尾部算 hash, +编译产物仍由 ProgramFactory 的 server cache 复用。按内容进一步缓存 hash 属性能工作, +本包没有以共享 client memo 换取“快”。draw 和 dispatch 各自从记录选 program;普通 +无 buffer draw 支持 current generic attributes,`gl_InstanceID` 的既有 SPIR-V rebase 保留。 + +sampler/image descriptor 消费 `BoundSamplerViews` / `BoundSamplerStates` / `BoundShaderImages`, +以 server texture record 创建原生 image view、按 record sampler 参数创建 sampler;view 的 +mip/layer window、format alias、swizzle 与隐式 alpha 按记录恢复。真实 buffer/texel-buffer +消费者仍在前端解引用前具名拒绝。无绑定 image 所需的 frontend placeholder 则明确报 +`Fatal{UnmigratedVerb, "Magma:unbound-image-placeholder@P7"}`,不是构造 client 对象来兜底。 + +vertex-input 的发射不受 Magma 缺 bit 7 的牵连:`NewVertexElements` 属 bit 8,P4a 的 +consumer/dependency gate 只管该阶段的四个 object families。server 因而能区分 enabled array +与 current attributes;wire 缺少 vertex-elements record 会具名拒绝,不能静默当空 VAO。 + +`CapsMirrorTest.ObjectFamilyEmissionTracksIndependentConsumerCapsWithoutBufferOps` 在同一 +子进程中采用 Texture-only → Framebuffer-only → Texture-only 三个 +snapshot;buffer bit 始终为 0,逐次断言真实 `MGPipeP4aFamilyEmits` 的正反变化。 +当前对象行仍被 BARRIER_PULLED 分类支配,测试没有假造 `SuppliedFieldMask` 的值变化; +caps generation 是该环境缓存的失效键,也是后续归属表收口必须保留的条件。 + +## 3. T5 与三类结构耦合 + +- T5:mipmap 使用 `VerbMipRes/BaseLevel/LevelCount`,只改 server image / staged shadow;upload-target 集合由 resource descriptor 推导,view window 从 `ViewOf` / sampler-view record 解析。无 client texture unit、upload-target vector 或 mip-level storage 写入。 +- texture twin / memo:独立 `std::unordered_map` 以 `{slot, gen}` 为键;dead-generation prune、shutdown 清理;shape 重建保留旧 GPU 内容。pending-upload 只上传指定 regions,避免 CPU 旧影子覆盖 GPU clear/blit/mipmap 后未改区域。失败上传不推进 synced serial。 +- program twin:`MagmaProgramSource` 在非 disaggregated 编译下是 `ProgramObject` 别名,在 split 下按值引用 server archive/record;`GetBackendHashMemo/SetBackendHashMemo` 不再写 client 对象。 +- server 构造 client 类型:wire clear/blit/mipmap/draw 使用 native image/view/framebuffer/render-pass,sampler 从 `SamplerParameters` 构造;不走隐藏 ShaderObject/ProgramObject/SamplerObject 或前端 texture placeholder 构造。原 frontend placeholder 的 allocator 边界显式改写为上节的具名 P7 拒绝,未装成新可用功能。 +- buffer twin / 直写:真实前端 BufferObject 的 backing resource、mapped bytes、EnsureGpuResident 等仅留 monolith 或既有 P7 拒绝之后;`ResourceTracker.h` 的 GPU-written fallback 归 fv,没有改。 + +`CopyImageEndpoint.TextureHandle` / `VerbStorageBlockProgram` 与 fe 采用相同 seam。 +为独立 fm 门可运行,同时带入 fe 的 GLES CopyImage endpoint 接线和 shared readback helper 的 server reply-scratch +分支;集成保留同一份,不重复计算为两份迁移。 + +新 draw render-pass 仍沿用既有正确性规则:sRGB attachment 随 `GL_FRAMEBUFFER_SRGB` +选择 sRGB/UNORM 写 view;1D/1D-array storage 保留相应 view 类型;各 attachment 使用 +GENERAL layout 与 descriptor 一致。旧 pass/framebuffer/view 退休前等待已提交 GPU 工作完成。 +每次 command buffer 重开均经 FrameContext observer 重置 dynamic-state shadow,viewport、 +scissor、blend/stencil 等不会因连续 draw 的相同值而漏发。此处选择正确性优先的提交/等待, +不声称完成 P7 的批处理性能设计。 + +staged store 的 hookless 路径与 GLES hook 二选一,绝不双 Adopt。named respecify 定义对应 +mip(包含 null-data),whole immutable respecify 重定义完整链,destroy 删除 handle key, +object-record release 清空;make-current reset 保留仍活对象的数据。 + +## 4. 出口门 4 的回答 + +**Magma 不发布 `kCapRunAheadApply`。** 无 buffer draw / compute、framebuffer、texture 诸实际可达路径 +以与 Espryt 相同的 role-split + strict 机制验证;分块时旧 client pointers 根本不在 server 块里,lockstep 不能 +掩盖这些读取。真实 buffer consumers、host-texel placeholders 和少量 native format/function shapes 仍是 P7 功能债。 +新 wire render-pass 为保证对象退休正确,提交后等待 queue 完成才销毁,尚未建立 run-ahead 的资源版本和性能契约。 +此外 `IsLive` forward、fv 剩余反向写入与完整字段归属收口仍未完成,不能把当前已测工作集 +扩大成“全 server apply 路径已满足 run-ahead”的能力承诺。保留 lockstep 是能力声明; +不把它当跨角色读写的豁免。`MagmaPipeIdentityTest.AMagmaServerNeverPublishesTheRunAheadCapBit` +分别对 ready=false/true 固定 Magma 的回答为不发布,避免随 Espryt readiness 翻转而误开启。 + +R11F_G11F_B10F 的 mip blit 是已实测的原有功能债:f1 基线真实 inproc 下 +`F1WireScenario.GenerateMipmapPackedFloatPixels` 像素失败,RGB 都为 0(`fm-base-packed.log`)。 +本包对应失败为 `Fatal{UnmigratedVerb, "Magma:mipmap-native-format@P7"}`;新增 Magma 同名测试留在 +`integration-magma-p7-known-red`,CI 强制恰一条实际执行、failure 非 skip、私有日志精确含该 marker。 +它不加入双块 field-fatal 棘轮;原 Espryt case 与所有既有测试名保留。 + +## 5. 门与 red-once + +验证数字将在最后一轮运行后补齐。已完成的独立证据: + +- 无 buffer ClipDistance:baseline 共享块绿 / fm 双块 strict 绿。 +- Program source 单测 3/3;临时退回 archive 旧 binding 值,`MagmaProgramSourceTest.ServerBindingTailsReplaceLinkTimeDefaults` 真红(旧值 9 分别不等于记录值 3/6);健康源码 3/3 绿(`fm-program-source-tests.log` / `fm-program-source-red-once.log`)。这份证据为独立临时 object/executable 执行;最终集成 unit 门仍单列核对。 +- `StagedTextureProductionTest.HooklessTextureConsumerOwnsBytesAndScopedStorageLifetime`:旧 metadata-only 条件吞 named null-data mip,level-1 extent 为 0 导致真红;修复后 1/1 绿(集成者 `int-fm-staging-red-once.log` / `int-fm-staging-green.log`)。 +- G1 pull 已测 checkpoint:`.text` 10806051 → 10806051;defined symbols 27815 → 27815;0 added / 0 removed / 0 resized / 0 renamed(`fm-g1-final.log`)。最后新增代码后的确认由下表更新。 +- flavours 已测 checkpoint:pull / push 生效宏与构建通过;独立 unit 首轮 2266/2266、0 failed(10 个宏/平台基线 skip;该次尚不含后来加入的 caps case)。 +- generator check/self-test、include closure 已绿;doc citations 只有与 f1 相同的两处既有 basename 歧义。 + +最终门记录(门禁执行者填入最终头与对应日志,不从上述 checkpoint 推算): + +| 门 | 最终结果 | +|---|---| +| 最终代码头 / build | 待最后一轮结果 | +| unit / integration-split | 待最后一轮结果 | +| Magma 双块 + strict | 待最后一轮结果;新 sampler/uniform、compute image-store 场景也必须实际执行 | +| 双块 Fatal 棘轮 | 待集成复测:fm 要退役 `GetFramebufferBindingSlot@Clear`;独立 fm 的其它 Espryt 首阻塞仍归 fe,不追加新预期对 | +| integration-gpu | 待集成门结果 | +| pull / push / G1 / G2 / G14 | 待最后一轮确认 | +| packed-format known-red | 待最后一轮精确 marker 核对;不是 skip,也不是双块字段 Fatal | + +strict、双块与 gpu 会覆盖同一测试的私有日志,串行运行并保存各自摘要;后跑车道的日志 +不能拿来替前一车道补证。新增 scenario 名集合保留既有测试,known-red 专门车道也必须实际 +执行并验证自己的失败原因,避免通过移标签隐藏回归。 + +设备门未执行:当前没有 Redmi `2f7cbe2e`;该项由阶段收口记录,不能由 host lavapipe 结果代替。 From 20f48d6e40a80eb801438837ffe003f554aa331e Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:03:30 -0400 Subject: [PATCH 05/13] [MG_Pipe, MG_IntegrationTest] (Disaggregated): P5f fm - ratchet retired Magma clear and shared copy pulls --- .../Harness/dualblock-expected-fatals.txt | 10 ++++------ MobileGL/MG_Pipe/FieldOwnership.def | 11 +++++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt b/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt index ad2e54fb..e50407d6 100644 --- a/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt +++ b/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt @@ -42,12 +42,10 @@ GetFramebufferBindingSlot@ReadPixels # The XFB program check at draw setup (FieldOwnership.def: "P3b/P4b (Espryt), P7 (Magma)"). # 11 entries (the TessellationXfbCapture / XfbCaptureBufferReuse family) GetTransformFeedbackProgram@DrawArrays -# CopyImageSubData resolves both textures through the sticky forward (def: "P7"). -# 6 entries -GetTextureObject@CopyImageSubData -# Magma's Clear path (the two DirectVulkan.Split.NamedBlit entries) - fm's first rows. -# 2 entries -GetFramebufferBindingSlot@Clear +# P5f fm measured retirement: GetTextureObject@CopyImageSubData disappeared via +# the shared fe/fm endpoint-handle seam; those six Espryt cases now reach ReadPixels. +# P5f fm measured retirement: the two Magma NamedBlit entries are green; Clear, +# readback and blit resolve server framebuffer/resource records. # CopyTexImage2D's texture-unit read (def: "P5e (Espryt unbarriered), P7 (Magma)"). # 2 entries GetTextureUnitObject@CopyTexImage2D diff --git a/MobileGL/MG_Pipe/FieldOwnership.def b/MobileGL/MG_Pipe/FieldOwnership.def index 4c3bc75f..c2cea6e2 100644 --- a/MobileGL/MG_Pipe/FieldOwnership.def +++ b/MobileGL/MG_Pipe/FieldOwnership.def @@ -50,6 +50,17 @@ // RetiringPhase is `-` for every class but BARRIER_PULLED, where it is the ROADMAP phase // whose row stops the pull. It is a string, not an enum, because two of them name two // phases for the two backends and flattening that would lose the half that matters. +// +// P5f fm: the Magma transport consumers of the nine object rows below have retired. +// WireDraw/WireFramebuffer and MagmaProgramSource/UniformManager read server records; +// enabled vertex arrays, EBO/indirect/named UBO/SSBO/texel-buffer uses meet the existing +// named P7 buffer refusal before any frontend read. Texture sync uses handle-keyed images +// and staged bytes, never GetTextureObject. The old getters remain reachable only through +// the monolith implementation (or behind the named P7/class-C boundary). Keep these global +// classifications until fe + fm are integrated and the residual-fill/generator invariants +// are retired together; a missing wire branch must still poison, never read a default. +// RecordError belongs to fv. Details and the no-buffer draw counterexample are in +// docs/Disaggregated/notes/p5f/fm-report.md. #define MGP_FIELD_OWNERSHIP_LIST(X) \ /* ---- BARRIER_PULLED: the 9 non-sticky rows the reduced path actually reads ---- */ \ /* scout-unmigrated-census section 3 intersects each verb class's may-read mask with "no */ \ From 4fd3a6b0e6639263bd9ce0eb2a4bb6126c4e9c6d Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:05:05 -0400 Subject: [PATCH 06/13] [MG_IntegrationTest] (Disaggregated): P5f fm - reconcile measured strict markers without widening --- .../Harness/strict-expected-markers.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt b/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt index 09db9ad0..538c5e5e 100644 --- a/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt +++ b/MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt @@ -36,10 +36,8 @@ GetBufferBindingSlot@ReadPixels # BoundDrawIndirectBufferId's GetBufferBindingSlot(DrawIndirect) a latent row that "would surface # as GetBufferBindingSlot@DrawArrays if an indirect tier were ever forced under strict", and then # gated the five multi-draw tiers, which forced it. Retires in P8 (FieldOwnership.def:78). -GetBufferBindingSlot@DrawArrays GetFramebufferBindingSlot@ReadPixels GetProgramObject@ShaderStorageBlockBinding -GetTextureObject@CopyImageSubData GetTextureUnitObject@CopyTexImage2D GetTransformFeedbackProgram@DrawArrays ValidateProgramName@ShaderStorageBlockBinding @@ -48,5 +46,9 @@ ValidateProgramName@ShaderStorageBlockBinding # said so - an open transform-feedback span, or a draw carrying client vertex arrays - which no # table indexed by (field, verb) can see. All three are pulls on the draw verb by entries that # are NOT the ordinary draw path. -GetBoundVertexArray@DrawArrays GetBufferBindingPoint@DrawArrays +# P5f fm remeasurement: 179/179 lane entries, Fatal=0, six ordinary pairs above plus +# GetBufferBindingPoint@DrawArrays escalated. GetTextureObject@CopyImageSubData retired +# through the shared fe/fm handle endpoint seam. The absent GetBoundVertexArray@DrawArrays +# and GetBufferBindingSlot@DrawArrays were already baseline-stale (f1-report §8.1); +# remove those two stale rows without widening either side of the ratchet. From 5855101d45ac89d03126236fa35cdde8c60e56dd Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:05:18 -0400 Subject: [PATCH 07/13] p5f fm: verify non-layered texture ignores image binding layer --- MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index 9a0001e2..0af65c33 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -381,7 +381,7 @@ void main() { )"; const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); ASSERT_NE(program, 0u); - glBindImageTexture(2, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + glBindImageTexture(2, texture, 0, GL_FALSE, 7, GL_WRITE_ONLY, GL_RGBA8); // 2D ignores the layer. glUseProgram(program); glDispatchCompute(8, 8, 1); glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); From e3919cee57143aa5188eecc20dc3e9c1d3dd2801 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:06:17 -0400 Subject: [PATCH 08/13] [MG_Backend] (Disaggregated): P5f fm - normalize storage image layer semantics by target --- .../DirectVulkan/Renderer/UniformManager.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index d2f26041..dcf2828e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -99,17 +99,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!resource) WireDescriptorFatal("image-resource"); m_textureManager->FlushPendingUploads(); + const auto target = static_cast(record.Desc.Target); + using Target = MG_Pipe::MGPipeResourceTarget; + const Bool layerable = target == Target::Tex1DArray || target == Target::Tex2DArray || + target == Target::Tex2DMSArray || target == Target::Tex3D || + target == Target::TexCube || target == Target::TexCubeArray; Uint32 level = storage ? state.BoundShaderImages[unit].Level : record.Params.BaseLevel; - Uint32 layer = storage && !state.BoundShaderImages[unit].Layered ? state.BoundShaderImages[unit].Layer : 0; + // GL ignores Layer/Layered for non-layerable targets. In particular image1D + // stays a 1D view, and an ignored nonzero Layer must not address another slice. + Uint32 layer = storage && layerable && !state.BoundShaderImages[unit].Layered + ? state.BoundShaderImages[unit].Layer : 0; const Uint32 localLevel = level; const Uint32 localLayer = layer; Uint32 layers = 0; VkFormat aliasFormat = VK_FORMAT_UNDEFINED; const auto root = m_textureManager->ResolveWireTextureStorage(handle, level, layer, &aliasFormat, &layers); if (MG_Pipe::MGPipeHandleIsNull(root)) WireDescriptorFatal("image-view-window"); - const auto target = static_cast(record.Desc.Target); VkImageViewType type = WireViewType(target); - if (storage && !state.BoundShaderImages[unit].Layered) { + if (storage && layerable && !state.BoundShaderImages[unit].Layered) { layers = 1; type = target == MG_Pipe::MGPipeResourceTarget::Tex1DArray ? VK_IMAGE_VIEW_TYPE_1D : VK_IMAGE_VIEW_TYPE_2D; } From 60bc3ca113ce6b4e9ee961a5bc4ece8685bc4a6e Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:06:44 -0400 Subject: [PATCH 09/13] [MG_Backend] (Disaggregated): P5f fm - request native storage usage through texture views --- MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index dcf2828e..1fc14a04 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -95,7 +95,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("image-record"); const auto& record = state.TextureResources[handle.Slot]; if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("image-record-generation"); - auto* resource = m_textureManager->SyncTextureResourceByHandle(handle); + auto* resource = m_textureManager->SyncTextureResourceByHandle(handle, false, storage); if (!resource) WireDescriptorFatal("image-resource"); m_textureManager->FlushPendingUploads(); From 1091846651e406d6422d38c3a6e69331a3c62db6 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:06:55 -0400 Subject: [PATCH 10/13] [MG_Backend] (Disaggregated): P5f fm - retain native storage usage for texture views --- .../DirectVulkan/Renderer/VkTextureManager.cpp | 14 ++++++++------ .../DirectVulkan/Renderer/VkTextureManager.h | 6 ++++-- .../MG_Backend/DirectVulkan/Renderer/WireDraw.inc | 10 +++++++--- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 39f972ac..89a75724 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -2008,7 +2008,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool VkTextureManager::SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, - TextureResource& resource) { + TextureResource& resource, Bool requireStorage) { const MG_Pipe::MGPResourceDesc& desc = record.Desc; if (desc.StorageKind != static_cast(TextureStorageType::Mipmap)) { MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: buffer-backed textures are not migrated; declined", @@ -2072,7 +2072,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); // The descriptor's sticky bind mask carries the image-unit hint, so the image is born // with STORAGE usage and no re-mint ever pulls texels back (TextureEmit.h D-A3). - const Bool markedAsStorageImage = (desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0; + const Bool markedAsStorageImage = requireStorage || resource.storageUsageResolved || + (desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0; const Bool storageImageCapable = !isMultisample && (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; @@ -2570,7 +2571,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkTextureManager::TextureResource* VkTextureManager::SyncTextureResourceByHandle( - MG_Pipe::MGPipeHandle handle, Bool renderbuffer) { + MG_Pipe::MGPipeHandle handle, Bool renderbuffer, Bool requireStorage) { MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureResourceByHandle: m_device == VK_NULL_HANDLE"); if (MG_Pipe::MGPipeHandleIsNull(handle)) { return nullptr; @@ -2582,7 +2583,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto storage = ResolveWireTextureStorage(handle, level, layer, &viewFormat); if (MG_Pipe::MGPipeHandleIsNull(storage)) return nullptr; if (storage != handle) { - auto* resource = SyncTextureResourceByHandle(storage); + auto* resource = SyncTextureResourceByHandle(storage, false, requireStorage); if (!resource) return nullptr; if (viewFormat != resource->format && ((resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0 || @@ -2615,15 +2616,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { (void)inserted; TextureResource& resource = it->second; if (resource.image != VK_NULL_HANDLE && resource.syncedWireSerial == record.Serial && - record.PendingUploads.empty()) { + record.PendingUploads.empty() && (!requireStorage || resource.storageUsageResolved)) { return &resource; } - if (!SyncWireTextureShape(record, resource)) { + if (!SyncWireTextureShape(record, resource, requireStorage)) { return nullptr; } if (!renderbuffer && !UploadPendingWireLevels(handle, record, resource)) { return nullptr; } + if (requireStorage && (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) == 0) return nullptr; resource.syncedWireSerial = record.Serial; return &resource; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 6b5e4543..0621b10c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -553,7 +553,8 @@ public: // a NAMESPACE THE FRONTEND-KEYED m_textureResources NEVER MEETS, so a recycled {slot, gen} // can never inherit its predecessor's image. Texture views resolve the storage owner from // Desc.ViewOf and the window from ViewCso; buffer-backed textures remain P7's buffer arm. - TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false); + TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false, + Bool requireStorage = false); // Map a texture/view-relative subresource to its live storage owner. layerCount, when // supplied, receives the number of accessible layers starting at the input layer. // viewFormat is UNDEFINED for an ordinary texture, or the outer view's format. @@ -733,7 +734,8 @@ private: // descriptor (create / recreate / compatibility), then the pending-upload walk against the // server's staged store. UploadPendingWireLevels mutates the applier's record (consumed // entries are dropped), which is why the record is a non-const reference. - Bool SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource); + Bool SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource, + Bool requireStorage); Bool UploadPendingWireLevels(MG_Pipe::MGPipeHandle handle, MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource); // The handle-keyed twin of m_textureResources, keyed by StagedTextureStore::KeyForHandle. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc index 846a1b8f..b05e9252 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc @@ -57,6 +57,13 @@ WireBufferLegacyFatal(); } + if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording(); + // Descriptor resolution can upgrade a view's root image to STORAGE usage. + // Resolve it before constructing framebuffer views so both roles of that image + // name the same allocation even on its first storage-image use. + if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, + m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) return false; + Vector attachments; Vector views; Vector colors; @@ -146,9 +153,6 @@ VertexInputStateFactory::BackendVertexInputState input{}; const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass, false); if (pipeline == VK_NULL_HANDLE) return false; - if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording(); - if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, - m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) return false; Uint32 binding = 0; for (Uint32 location = 0; location < kMaxVertexAttribs; ++location) { if (!(programObj.activeVertexInputLocationMask & (1u << location))) continue; From 0dfbc89a60495e10ec2c374f75225ed8ff5e05bf Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:07:45 -0400 Subject: [PATCH 11/13] [MG_Backend] (Disaggregated): P5f fm - keep failed storage upgrades out of the sync fast path --- MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 89a75724..9f6e2ebb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -2616,7 +2616,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { (void)inserted; TextureResource& resource = it->second; if (resource.image != VK_NULL_HANDLE && resource.syncedWireSerial == record.Serial && - record.PendingUploads.empty() && (!requireStorage || resource.storageUsageResolved)) { + record.PendingUploads.empty() && (!requireStorage || (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) != 0)) { return &resource; } if (!SyncWireTextureShape(record, resource, requireStorage)) { From 66c79322bcd787554de47f611583c6d92f460537 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:08:28 -0400 Subject: [PATCH 12/13] p5f fm: verify image stores through a view upgrade root storage --- .../Scenarios/F1WireScenario.cpp | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index 0af65c33..f2d32cfd 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -403,6 +403,61 @@ void main() { glDeleteProgram(program); } +TEST_F(F1WireScenario, ComputeImageStoreThroughViewPreservesOtherRootLayer) { + if (!Ready()) return; + GLuint root = 0, view = 0; + glGenTextures(1, &root); + glBindTexture(GL_TEXTURE_2D_ARRAY, root); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 8, 8, 2); + std::array red{}; + for (size_t i = 0; i < red.size(); i += 4) { + red[i] = 255; + red[i + 3] = 255; + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, + GL_RGBA, GL_UNSIGNED_BYTE, red.data()); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + // Materialize the root's Vulkan image before any storage-image binding. The + // subsequent view-only bind must upgrade the root and preserve its other layer. + std::array initial{}; + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, initial.data()); + ASSERT_EQ(initial, (std::array{255, 0, 0, 255})); + glGenTextures(1, &view); + glTextureView(view, GL_TEXTURE_2D, root, GL_RGBA8, 0, 1, 1, 1); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const char* compute = R"(#version 430 core +layout(local_size_x=1, local_size_y=1, local_size_z=1) in; +layout(rgba8, binding=2) writeonly uniform image2D destination; +void main() { imageStore(destination, ivec2(gl_GlobalInvocationID.xy), vec4(0.0, 1.0, 0.0, 1.0)); } +)"; + const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); + ASSERT_NE(program, 0u); + glBindImageTexture(2, view, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + glUseProgram(program); + glDispatchCompute(8, 8, 1); + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); + for (const int layer : {0, 1}) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, layer); + std::array pixels{}; + glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const std::array expected = layer == 1 + ? std::array{0, 255, 0, 255} : std::array{255, 0, 0, 255}; + for (size_t at = 0; at < pixels.size(); at += 4) { + const std::array actual{pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]}; + EXPECT_EQ(actual, expected) << "root layer " << layer << ", texel " << at / 4; + } + } + glBindImageTexture(2, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + glUseProgram(0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + glDeleteProgram(program); + glDeleteTextures(1, &view); + glDeleteTextures(1, &root); +} + TEST_F(F1WireScenario, EnabledVertexBufferDrawKeepsNamedP7Fatal) { if (!Ready()) return; #if !defined(_WIN32) && GTEST_HAS_DEATH_TEST From 552ade5b377c25ad6542732f45c683820f9905c9 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:09:05 -0400 Subject: [PATCH 13/13] [MG_IntegrationTest] (Disaggregated): P5f fm - gate storage upgrades through texture views --- MobileGL/MG_IntegrationTest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index ec09c1e2..97ec3ea9 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2383,7 +2383,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) "MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_TRANSPORT=inproc" "MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1" ${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV}) - foreach(fmSlot EnabledVertexBufferDrawKeepsNamedP7Fatal SrgbDrawTracksFramebufferConversion Texture1DFramebufferClearPixels VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels) + foreach(fmSlot ComputeImageStoreThroughViewPreservesOtherRootLayer EnabledVertexBufferDrawKeepsNamedP7Fatal SrgbDrawTracksFramebufferConversion Texture1DFramebufferClearPixels VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels) set(MGL_ITEST_FM_LABEL "integration-magma-split") if (fmSlot STREQUAL "GenerateMipmapPackedFloatPixels") # R11F_G11F_B10F mip blits already fail on the baseline Magma lane.