diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index bd3519e3..218bbdaf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -13,6 +13,80 @@ "MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail); } + // P7 wave 2-B. THE NAME STAYS FIRST AND THE DRIVER'S ANSWER FOLLOWS IT. Two Iris cases + // died on the Redmi (Adreno 830v2) with `Fatal{UnmigratedVerb, "Magma:mipmap-shader- + // format-or-shape"}` and that line was the whole evidence: five disjunctions share the + // name, so the one thing the device could have told us - WHICH format, at what shape, + // missing WHICH feature bit - was the one thing it did not. The census, the retrace + // refusal tally and every log grep key on the leading word, so it is unchanged and + // unprefixed; everything after the first space is for the reader of the next device run. + static String MagmaWireShapeDetail(const char* name, VkFormat format, VkFormat storageFormat, + const VkFormatProperties& props, VkImageAspectFlags aspect, + VkImageViewType viewType, VkImageUsageFlags usage, + const VkExtent2D& extent, Uint32 depth, Uint32 levels, + Uint32 baseLayer, Uint32 layerCount, Uint32 arrayLayers) { + const auto hex = [](Uint32 value) { + static const char* digits = "0123456789abcdef"; + String out = "0x"; + Bool leading = true; + for (Int shift = 28; shift >= 0; shift -= 4) { + const Uint32 nibble = (value >> shift) & 0xfu; + if (nibble == 0 && leading && shift) continue; + leading = false; + out.push_back(digits[nibble]); + } + return out; + }; + const VkFormatFeatureFlags absent = ~props.optimalTilingFeatures; + String missing; + const auto note = [&](VkFormatFeatureFlags bit, const char* word) { + if (!(absent & bit)) return; + if (!missing.empty()) missing += "|"; + missing += word; + }; + note(VK_FORMAT_FEATURE_BLIT_SRC_BIT, "BLIT_SRC"); + note(VK_FORMAT_FEATURE_BLIT_DST_BIT, "BLIT_DST"); + note(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT, "SAMPLED"); + note(VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT, "SAMPLED_LINEAR"); + note(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT, "COLOR_ATTACHMENT"); + note(VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, "DEPTH_STENCIL_ATTACHMENT"); + if (missing.empty()) missing = "none"; + String out = name; + out += " format=" + std::to_string(static_cast(format)); + // A view that reinterprets its owner's format is the OTHER reason the native blit + // arm is skipped, and it is invisible in the format number alone. + if (storageFormat != format) out += " storage=" + std::to_string(static_cast(storageFormat)); + out += " extent=" + std::to_string(extent.width) + "x" + std::to_string(extent.height) + + "x" + std::to_string(depth); + out += " levels=" + std::to_string(levels); + out += " aspect=" + hex(static_cast(aspect)); + out += " view=" + std::to_string(static_cast(viewType)); + out += " usage=" + hex(static_cast(usage)); + out += " layers=" + std::to_string(baseLayer) + "+" + std::to_string(layerCount) + + "/" + std::to_string(arrayLayers); + out += " missing=" + missing; + return out; + } + + // THE HOST GATE CANNOT REACH THE SHADER MIP ARM ON ITS OWN. lavapipe reports + // BLIT_SRC|BLIT_DST for every colour format this lane can create, so `nativeBlit` is + // always true there and both shader shapes below would only ever run on the phone - + // which is how the Adreno refusal reached a release in the first place. The knob makes + // them reachable from a host lane and is read exactly once: + // 1 - drop the native blit (the arm an Adreno takes for a format without BLIT_DST); + // 2 - also hide the destination's COLOR_ATTACHMENT usage (the arm it takes for a + // sampled-only texture, which renders through owned scratch and copies back). + // MGITEST_, not MOBILEGL_: it is a test knob, it is not mirrored into the accepted-env + // table, and this file is compiled only into the disaggregated build. + static Uint32 MagmaWireForcedShaderMipmapTier() { + static const Uint32 tier = [] { + const char* value = std::getenv("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); + if (!value || !*value) return 0u; + return static_cast(std::strtoul(value, nullptr, 10)); + }(); + return tier; + } + VulkanRenderer::WireImage VulkanRenderer::ResolveWireImage( const MG_Pipe::MGPFramebufferState& fbo, const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect, Bool isWriteTarget) { @@ -762,19 +836,46 @@ const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat; vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,format,&props); const auto required = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; - const Bool nativeBlit = format == resource->format && (props.optimalTilingFeatures & required) == required; + const Uint32 forcedShaderTier = MagmaWireForcedShaderMipmapTier(); + const Bool nativeBlit = forcedShaderTier == 0 && format == resource->format && + (props.optimalTilingFeatures & required) == required; const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; if (!nativeBlit) { // Packed floating-point color formats may support sampling and // rendering without BLIT_DST. Generate from actual GPU mip contents // through the native shader pass; no client or staged texels enter. - if (resource->aspect != VK_IMAGE_ASPECT_COLOR_BIT || - resource->viewType == VK_IMAGE_VIEW_TYPE_1D || resource->viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY || - resource->viewType == VK_IMAGE_VIEW_TYPE_3D || - !(resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) || - baseLayer >= resource->arrayLayers || layerCount > resource->arrayLayers - baseLayer) - MagmaWireFatal("mipmap-shader-format-or-shape"); + // + // TWO SHADER SHAPES, not one. The arm that was here rendered STRAIGHT INTO the + // next level, which needs the image to have been created with + // COLOR_ATTACHMENT usage - and a texture whose format the device refuses as a + // colour attachment never gets that bit (VkTextureManager.cpp:2156-2160), so + // every sampled-only texture without BLIT support fell into the refusal rather + // than into a fallback. When the FORMAT can be rendered and only the IMAGE + // cannot, an owned scratch attachment of the destination level's size renders + // the mip and vkCmdCopyImage moves it in: a raw texel move, no conversion, and + // the texels still come from GPU contents rather than from a staged shadow. + const Bool colour = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT; + const Bool planar = resource->viewType != VK_IMAGE_VIEW_TYPE_1D && + resource->viewType != VK_IMAGE_VIEW_TYPE_1D_ARRAY && + resource->viewType != VK_IMAGE_VIEW_TYPE_3D; + const Bool layersInRange = baseLayer < resource->arrayLayers && + layerCount <= resource->arrayLayers - baseLayer; + // The shader arm samples through a sampler2D and writes a vec4, which is what + // BlitWireColorImage's own float check enforces; naming it here rather than + // letting the blit return false is what puts the reason in the fatal line. + const Bool sampledFloat = vkuFormatIsSampledFloat(format); + const Bool renderableFormat = + (props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0; + const Bool attachable = forcedShaderTier < 2 && + (resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0; + const Bool viaScratch = !attachable && renderableFormat && + (resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0; + if (!colour || !planar || !layersInRange || !sampledFloat || (!attachable && !viaScratch)) + MagmaWireFatal(MagmaWireShapeDetail("mipmap-shader-format-or-shape", format, + resource->format, props, resource->aspect, resource->viewType, + resource->usageFlags, resource->extent, resource->depth, resource->mipLevels, + baseLayer, layerCount, resource->arrayLayers).c_str()); for (Uint32 level = base + 1; level < base + count; ++level) { for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) { WireImage source{}; @@ -792,10 +893,63 @@ destination.level = level; destination.extent = {std::max(resource->extent.width >> level, 1u), std::max(resource->extent.height >> level, 1u)}; - if (!BlitWireColorImage(source, destination, 0, 0, source.extent.width, source.extent.height, - 0, 0, destination.extent.width, destination.extent.height, - filter == VK_FILTER_LINEAR ? GL_LINEAR : GL_NEAREST, true)) + const GLenum blitFilter = filter == VK_FILTER_LINEAR ? GL_LINEAR : GL_NEAREST; + if (attachable) { + if (!BlitWireColorImage(source, destination, 0, 0, source.extent.width, + source.extent.height, 0, 0, destination.extent.width, + destination.extent.height, blitFilter, true)) + MagmaWireFatal("mipmap-shader-blit"); + continue; + } + // Owned, per level, and retired with the submit that consumes it - the + // shape the multisample resolve above already uses. A shared scratch + // resized here would destroy an image an earlier queued pass still reads. + auto scratch = MakeUnique(); + VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = format; + info.extent = {destination.extent.width, destination.extent.height, 1}; + info.mipLevels = info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VmaAllocationCreateInfo allocation{}; + allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocation.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + if (vmaCreateImage(m_allocator, &info, &allocation, &scratch->image, + &scratch->allocation, nullptr) != VK_SUCCESS) + MagmaWireFatal("mipmap-shader-scratch-allocation"); + scratch->format = format; + scratch->extent = destination.extent; + scratch->mipLevels = scratch->arrayLayers = scratch->depth = 1; + scratch->aspect = VK_IMAGE_ASPECT_COLOR_BIT; + WireImage target{}; + target.image = scratch->image; + target.format = format; + target.aspect = VK_IMAGE_ASPECT_COLOR_BIT; + target.extent = destination.extent; + target.trackedLayout = &scratch->layout; + if (!BlitWireColorImage(source, target, 0, 0, source.extent.width, + source.extent.height, 0, 0, target.extent.width, target.extent.height, + blitFilter, true)) MagmaWireFatal("mipmap-shader-blit"); + target.layout = scratch->layout; + TransitionWireImage(target, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + WireImage writeBack = destination; + writeBack.layout = resource->layout; + TransitionWireImage(writeBack, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + VkImageCopy copy{}; + copy.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + copy.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1}; + copy.extent = {destination.extent.width, destination.extent.height, 1}; + vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, scratch->image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, resource->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + WireRetiredObjects retired{}; + retired.textures.push_back(Move(scratch)); + RetireWireObjects(Move(retired)); } } } else { diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 3cdfcb54..2718a803 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2684,6 +2684,32 @@ if (MOBILEGL_BUILD_DISAGGREGATED) ${mglItestArmProperties} ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5f-fm${mglItestArmLogTag}-${fmCase}.log") endforeach() + # ---- P7 wave 2-B: THE TWO SHADER MIP ARMS, FORCED ------------------------------------ + # + # WHY A KNOB AND NOT A FORMAT. The refusal these two entries cover + # (`Magma:mipmap-shader-format-or-shape`) killed two Iris cases on an Adreno 830v2 and + # has never once fired on this host, because lavapipe reports BLIT_SRC|BLIT_DST for + # every colour format the integration binary can create - so GenerateWireMipmap takes + # its NATIVE arm for all of them and neither shader arm is reachable by choosing a + # format. `GenerateMipmapPackedFloatPixels` next door looks like it covers the shader + # arm and does not: R11F_G11F_B10F blits natively here too. + # MGITEST_MAGMA_FORCE_SHADER_MIPMAP drops that arm instead, one tier per shape, so the + # host lane exercises the code the device actually runs. + # + # ITS OWN ENTRIES RATHER THAN A LANE-WIDE FLIP, for the reason the strict-arming entry + # gives: the knob changes how EVERY mipmap in the process is generated, so putting it in + # the shared Fm environment would silently retarget GenerateMipmapPixels, + # GenerateMipmapPackedFloatPixels, GenerateMipmapDepthPixels and the two base/view cases + # as well - four green entries that would stop measuring the arm they were written for. + foreach(shaderMipTier 1 2) + gtest_discover_tests(MobileGLIntegrationTest + TEST_PREFIX "DirectVulkan.${mglItestArm}.ShaderMip${shaderMipTier}." + TEST_FILTER "F1WireScenario.GenerateMipmapWithoutNativeBlitPixels" + DISCOVERY_TIMEOUT 30 + PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT} + ${mglItestArmProperties} + ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_MIPMAP=${shaderMipTier}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-shadermip${mglItestArmLogTag}-tier${shaderMipTier}.log") + endforeach() endforeach() endif() diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index cfd573a6..ba3d2b6e 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -481,6 +481,66 @@ TEST_F(F1WireScenario, GenerateMipmapPackedFloatPixels) { for (int i = 0; i < 8; ++i) EXPECT_NEAR(rows[i], generated[i], 0.01f); } +// P7 wave 2-B: THE ARM THE HOST CANNOT REACH ON ITS OWN. +// +// Two Iris cases (minecraft-1.21.4-fabric-iris-iterationt-in-world and its -nodsa twin) killed +// the server apply thread on a Redmi (Adreno 830v2) with +// MGPipe: Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape"} +// deterministically, under both run-ahead and lockstep, while the monolith arm passed and the +// host lane stayed green. It stayed green because lavapipe reports BLIT_SRC|BLIT_DST for every +// colour format this scenario can create: `nativeBlit` is always true here, so NEITHER shader +// arm of GenerateWireMipmap has ever executed on the gate machine. A refusal no host lane can +// reach is a refusal that ships. +// +// MGITEST_MAGMA_FORCE_SHADER_MIPMAP is what makes them reachable, and it selects which one: +// 1 - drop the native blit only. The destination level is still a colour attachment, so the +// pass renders straight into it (the arm an Adreno takes for a format without BLIT_DST). +// 2 - also treat the destination as not attachable. The pass renders into owned scratch and +// copies the mip in (the arm it takes for a texture whose image never got +// COLOR_ATTACHMENT usage - which, before this package, was the refusal itself). +// +// The assertion is the ORDINARY mip result, per half: a shader mip that renders somewhere else +// is still wrong unless the level holds what a blit would have put there, in GL's row order. +// Red once (executed, reverted): drop the scratch arm (force `viaScratch` false) and the tier-2 +// entry dies with Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape ..."} by name. +TEST_F(F1WireScenario, GenerateMipmapWithoutNativeBlitPixels) { + if (!Ready()) return; + const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); + if (tier.empty()) + GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver reports " + "BLIT_SRC|BLIT_DST for GL_RGBA8, so the shader mip arms under test are " + "not entered and this case would assert the native blit twice over"; + Attach(GL_RGBA8, GL_COLOR_ATTACHMENT0, 4); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.75f, 0.25f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + // Two different halves, not one flat colour: a pass that loses GL's row order averages to + // the same number everywhere and a single-colour assertion would call that correct. + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, 8, 4); + glClearColor(0.25f, 0.75f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ShaderMip.wire"; + // Level one is 4x4, and each of its rows averages two source rows from ONE half - the + // boundary never falls inside a destination texel, so the two colours survive unmixed. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 1); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + GLubyte rows[4][4]{}; + glReadPixels(1, 0, 1, 4, GL_RGBA, GL_UNSIGNED_BYTE, rows); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMip.error"; + const int bottom[4] = {64, 191, 128, 255}; + const int top[4] = {191, 64, 128, 255}; + for (int row = 0; row < 4; ++row) { + const int* expected = row < 2 ? bottom : top; + for (int channel = 0; channel < 4; ++channel) + EXPECT_NEAR(rows[row][channel], expected[channel], 2) + << "F1.ShaderMip.pixels tier=" << tier << " row=" << row << " channel=" << channel; + } +} + TEST_F(F1WireScenario, GenerateMipmapDepthPixels) { if (!Ready()) return; std::array source{}; diff --git a/docs/Disaggregated/notes/p7/magma-b.md b/docs/Disaggregated/notes/p7/magma-b.md new file mode 100644 index 00000000..e9dd401a --- /dev/null +++ b/docs/Disaggregated/notes/p7/magma-b.md @@ -0,0 +1,104 @@ +# P7 wave 2-B:Magma blit / copy / mip 簇(分支 `p7/magma-b`) + +> 计划见 [`PLAN-PH-P34B-P7.md`](PLAN-PH-P34B-P7.md) §1.3 与 §3 wave 2-B;规范见 +> [`CONTRACT-P7.md`](../../../../MobileGL/MG_Remote/CONTRACT-P7.md) §0(规则 I / J)、§3.2(退役 vs +> decline 表)、§5.1 / §5.2。基线 = `feat/disaggregated` 上 `~/w7/pipe` 的头 `8652bac0` +> (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=ON` `INPROC=ON` `PIPE_PUSH=ON` +> `BUILD_INTEGRATION_TEST=ON`,ICD 与 `~/w7/pipe/build-split` 逐项相同。 +> +> 基线门读数(本树实测):`integration-magma-split` **73**(71 PASS + 2 SKIP)、`-spawn` **52**、 +> `-tcp` **54**,三臂 0 红。 + +## 0. 每片一条提交,每片一次 red-once + +| 片 | 名 | 退役 / decline | 提交 | +|---|---|---|---| +| 1 | `mipmap-shader-format-or-shape` | 诊断 + 颜色 shader mip 回落(**退役**一半:可渲染格式的不可附着图像) | 见 §1 | + +--- + +## 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 红。