diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclines.def b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclines.def index fc5b8149..bafa87d5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclines.def +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclines.def @@ -104,3 +104,9 @@ MGL_WIRE_DECLINE(ShapeCreateImageFailed) // Appended by the B3 review round: the preserve-copy arm of a reallocation was the one exit in // SyncWireTextureShape still leaving no trace at all. MGL_WIRE_DECLINE(ShapePreserveCopyFailed) + +// ---- VulkanRenderer::GenerateWireMipmap (P8-F) ----------------------------------------- +// `mipmap-shader-format-or-shape` was a Fatal on this arm and a decline on the monolith arm; it +// is the monolith's decline now. Counted once per refused glGenerateMipmap, whose chain is left +// as it was. +MGL_WIRE_DECLINE(MipmapShaderFormatOrShape) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc index fd1cf1ec..7bb8bcb4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc @@ -294,7 +294,14 @@ const Uint32 location = input.bindingAttributeLocations[binding]; const auto& attr = vao.Attributes[location]; const auto& resource = state.VertexBuffers[location]; - if (MG_Pipe::MGPipeHandleIsNull(resource.Res)) MagmaWireFatal("client-vertex-array@P8"); + // P8-F: A RECORD INVARIANT, NOT AN UNMIGRATED VERB (it carried a P8 phase tag). + // The client turns every enabled client-memory array into an owned buffer before the + // draw record is built, or refuses the draw itself (MG_Impl/Pipe/OwnedDrawInputs.h), + // so an attribute the program reads with no buffer handle is a foreign peer's record. + if (MG_Pipe::MGPipeHandleIsNull(resource.Res)) + MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"Magma:client-vertex-array\"} location=%u: an " + "attribute the program reads carries no buffer handle", location); BufferSlice slice{}; if (!m_bufferManager.AcquireWireSlice(BufferKind::Vertex, resource.Res, slice)) MagmaWireFatal("vertex-buffer-resource"); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index 6c525cf7..9a772c02 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -20,6 +20,8 @@ // 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. + // P8-F: the refusal is a named decline now (WireDeclines.def MipmapShaderFormatOrShape); + // the line it logs keeps this word and this detail. static String MagmaWireShapeDetail(const char* name, VkFormat format, VkFormat storageFormat, const VkFormatProperties& props, VkImageAspectFlags aspect, VkImageViewType viewType, VkImageUsageFlags usage, @@ -1400,8 +1402,10 @@ base + 1, base + count - 1); return; } - if (!m_textureManager->GrowWireTextureMipChain(handle,base+count,frame.commandBuffer)) - MagmaWireFatal("mipmap-growth"); + // P8-F: WHICH ARM TAKES THIS CHAIN IS DECIDED BEFORE THE CHAIN IS GROWN, for the + // depth-stencil decline's reason above. Nothing here depends on the growth: the format, + // its features, the knob tier, the aspect, view type, usage and layer window are the + // same before and after it (GrowWireTextureMipChain carries every one of them over). VkFormatProperties props{}; const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat; vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,format,&props); @@ -1411,6 +1415,50 @@ (props.optimalTilingFeatures & required) == required; const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + const Bool layersInRange = baseLayer < resource->arrayLayers && + layerCount <= resource->arrayLayers - baseLayer; + // The depth arm's shape: the layer window, and what WireDepthMipmap.inc's pass refuses + // for an image this function hands it - a format that does not both sample and attach + // as depth (its other refusals, 3D / multisample / a default image, are never built here). + const auto depthPassFeatures = + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; + const Bool depthArmTakesShape = + layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures; + // The colour arm's shape (the two shader shapes are explained at the colour arm below). + 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; + // 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 decline 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; + const Bool colourArmTakesShape = + colour && planar && layersInRange && sampledFloat && (attachable || viaScratch); + // P8-F: `mipmap-shader-format-or-shape` IS A NAMED DECLINE, NOT A FATAL (rule I (a)). + // The monolith arm has always answered this chain with a decline (VulkanRenderer.cpp's + // GenerateMipmap: a format without blit support is skipped and the chain left unwritten), + // and a shape no shader arm takes is a coverage gap in this backend, not a broken record. + // The leading word stays, so every device-log grep keyed on it still finds the line. + const Bool shaderArmTakesShape = + resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? depthArmTakesShape : colourArmTakesShape; + if (!nativeBlit && !shaderArmTakesShape) { + const String detail = 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); + MGL_WIRE_DECLINE_AT(MipmapShaderFormatOrShape, "%s; no mip arm takes this shape, levels %u..%u left as they are", + detail.c_str(), base + 1, base + count - 1); + return; + } + if (!m_textureManager->GrowWireTextureMipChain(handle,base+count,frame.commandBuffer)) + MagmaWireFatal("mipmap-growth"); if (!nativeBlit && resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT) { // P7 wave 2-B, CONTRACT-P7 §5.1 (A): THE DEPTH ARM, and it is first because a // depth format without BLIT_DST is the ordinary case, not the exotic one - @@ -1422,12 +1470,7 @@ // The per-level pass is WireDepthMipmap.inc's, which is the monolith's fragment // shader with the monolith's arithmetic on a baked pipeline that owns no frontend // object - §3.2's "observably the monolith's" without the GL program the monolith - // needs to get there. - if (baseLayer >= resource->arrayLayers || layerCount > resource->arrayLayers - baseLayer) - 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()); + // needs to get there. Its layer window and format were checked before the growth. for (Uint32 level = base + 1; level < base + count; ++level) { for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) { WireImage source{}; @@ -1445,15 +1488,19 @@ destination.level = level; destination.extent = {std::max(resource->extent.width >> level, 1u), std::max(resource->extent.height >> level, 1u)}; - // The pass reports the SHAPE it cannot take (1D, 3D, multisample, a - // format without sampled or depth-attachment support), so the refusal - // keeps its name and gains the reason. - if (!GenerateWireDepthMipLevel(source, destination)) - MagmaWireFatal(MagmaWireShapeDetail("mipmap-shader-format-or-shape", format, + // The pass reports the SHAPE it cannot take. depthArmTakesShape above asks + // its questions first, so this is reached only if the two lists drift apart + // - and then it is the same named decline (P8-F), not a session's end. It + // can only refuse the first level: the shape does not change down the chain. + if (!GenerateWireDepthMipLevel(source, destination)) { + const String detail = 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()); + resource->mipLevels, baseLayer, layerCount, resource->arrayLayers); + MGL_WIRE_DECLINE_AT(MipmapShaderFormatOrShape, "%s; the depth pass refused level %u", + detail.c_str(), level); + return; + } } } } else if (!nativeBlit) { @@ -1470,27 +1517,9 @@ // 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()); + // + // Which of the two (`attachable` or `viaScratch`), and whether either takes the + // shape at all, was decided before the growth (colourArmTakesShape, P8-F). for (Uint32 level = base + 1; level < base + count; ++level) { for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) { WireImage source{}; diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index 9d057b6a..0bf7aba6 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -186,9 +186,9 @@ namespace MobileGL::MG_Backend { // ZERO IS THE EXPLICIT ANSWER FOR P5, not an omission (ServerSession.h's block): // every optional capability bit belongs to the package that owns its question, and // withholding one leaves the legacy path running, which is the safe direction. - // kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes must be 0 for the whole of P5 - // by ruling - they are the only two things that ask for an MGHostSpan, and 0 is - // what keeps every one of them out of the first IPC frame (contract table 0). + // kCapNeedsHostIndexBytes, kCapNeedsHostUboBytes and kCapViewportArray are RESERVED + // (P8-F) and never set here: the first two are the only things that ask for an + // MGHostSpan, and 0 keeps every one of them off the wire (contract table 0). // // P5b t2 (CONTRACT-P5B.md §6.5) PUBLISHES THE ONE BIT P5b ADDS, and this is the // only place that can: the question kCapBackendOwnsXfbCapture answers is "does the diff --git a/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h b/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h index 530fc6c4..5344a367 100644 --- a/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h +++ b/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h @@ -247,10 +247,10 @@ namespace MobileGL::MG_Pipe { for (Uint32 w = 0; w < kMGPipeShaderBufferWritableMaskWords; ++w) { header.WritableMask[w] = writableMask[w]; } - // HostSpanCount IS 0 ALWAYS on Espryt, and that is a ruling rather than an omission: - // the second var-tail exists for kCapNeedsHostUboBytes (Magma's named-UBO ring) and - // that bit is 0 for the whole of P5 (CONTRACT-P5.md table 0). The codec's host-span - // honesty pass is the guard that says so out loud if a backend ever publishes it. + // HostSpanCount IS 0 ALWAYS, and that is a ruling rather than an omission: the + // second var-tail exists for kCapNeedsHostUboBytes (a Magma named-UBO ring that was + // never built) and that bit is RESERVED (P8-F): no server publishes it. The codec's + // host-span honesty pass is the guard that says so out loud if a peer sends one. header.HostSpanCount = 0; header.ContentHash = hash; MGPipeRouteSetShaderBuffers(header, m_entries.data()); diff --git a/MobileGL/MG_Impl/Pipe/VertexInputEmit.h b/MobileGL/MG_Impl/Pipe/VertexInputEmit.h index f7af6b75..dd07b633 100644 --- a/MobileGL/MG_Impl/Pipe/VertexInputEmit.h +++ b/MobileGL/MG_Impl/Pipe/VertexInputEmit.h @@ -263,8 +263,8 @@ namespace MobileGL::MG_Pipe { if (vao) { if (const auto& bound = vao->GetIndexBufferBindingSlot().GetBoundObject()) { m_lastIndex.Res = MGPipeSlots().Acquire(MGPipeKind::Buffer, bound->GetLifetimeId()); - // The ELEMENT_ARRAY bit, and it is the one the split path keys on - // (kCapNeedsHostIndexBytes -> restart rewriting, multi-draw flattening). + // The INDEX|ELEMENT_ARRAY bind bits. ELEMENT_ARRAY's D-B7 reader + // (kCapNeedsHostIndexBytes) is RESERVED (P8-F) and was never built. // Noted at every draw for RefreshBindMask's reason: an EBO defined through // DSA and unbound before its last respecify would otherwise never publish // it, and getting that bit wrong is invisible in monolith. diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 9929f305..387da33c 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -3354,6 +3354,26 @@ if (MOBILEGL_BUILD_DISAGGREGATED) "${CMAKE_CURRENT_SOURCE_DIR}/Harness/split_log_paths.py" check "${CMAKE_CTEST_COMMAND}" "${CMAKE_BINARY_DIR}") set_tests_properties(SplitLogPaths.PrivateAndDistinct PROPERTIES LABELS "integration-split") + + # P8-F: `Magma:mipmap-shader-format-or-shape` is a named DECLINE on the wire arm now + # (WireDeclines.def MipmapShaderFormatOrShape), as the monolith arm's GenerateMipmap already + # declines. The shape is a 3D chain with the native blit forced off - neither shader mip arm + # takes it - and the knob is read BY THE SERVER, so split + spawn only (the ShaderMip entries' + # tcp note below). Its own `.ShaderMipDecline.` tail is in MAGMA_SERVER_ENV_KNOB_NO_TCP + # (scripts/ci/spawn_lane_parity.py: one tail per knob case). The Fm lane's flavour plus the arm. + foreach(mglItestArm IN ITEMS Split Spawn) + mgl_itest_magma_arm_shape(${mglItestArm}) + mgl_itest_join_environment(mglItestP8fMipEnvironment + "MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1" + ${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_VULKAN_ENV} ${mglItestArmTransportEnv}) + gtest_discover_tests(MobileGLIntegrationTest + TEST_PREFIX "DirectVulkan.${mglItestArm}.ShaderMipDecline." + TEST_FILTER "F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession" + DISCOVERY_TIMEOUT 30 + PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT} + ${mglItestArmProperties} + ENVIRONMENT "${mglItestP8fMipEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p8f-shadermip-decline${mglItestArmLogTag}.log") + endforeach() endif() # f1: split-only source keeps the monolith registry unchanged. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index 5c9a6c3b..160f8288 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -675,6 +675,65 @@ TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) { glClearDepth(1.0); } +// P8-F: `Magma:mipmap-shader-format-or-shape` IS A NAMED DECLINE ON THE WIRE ARM NOW, which is +// the monolith arm's answer for a chain it cannot build (VulkanRenderer.cpp's GenerateMipmap +// declines a format without blit support and leaves the chain unwritten). It used to end the +// session. A 3D texture is a shape neither wire shader arm takes (the colour pass samples a +// sampler2D), so with the native blit forced off (MGITEST_MAGMA_FORCE_SHADER_MIPMAP) it is what +// reaches the refusal on this host - lavapipe blits a 3D RGBA8 chain natively otherwise. +// +// What this case pins: no GL error, the session still answering (two readback round trips), level +// 0 untouched, and level 1 still holding the application's own texels - the decline wrote nothing. +// Magma only, and knob-gated, like GenerateMipmapWithoutNativeBlitPixels above. +// Red once (executed, reverted): restore the MagmaWireFatal and this case dies with +// Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape ..."}. +TEST_F(F1WireScenario, GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession) { + if (!Ready()) return; + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (P8-F, mipmap-shader-format-or-shape): the knob and " + "the shader mip arms it forces are Magma's; Espryt (" + << Gl().BackendName() << ") generates this chain itself"; + 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 blits a 3D RGBA8 " + "chain natively, so the decline under test is not entered"; + GLuint volume = 0; + glGenTextures(1, &volume); + glBindTexture(GL_TEXTURE_3D, volume); + glTexStorage3D(GL_TEXTURE_3D, 3, GL_RGBA8, 4, 4, 4); + const std::array base{200, 40, 80, 255}; + const std::array sentinel{10, 220, 30, 255}; + const auto fill = [](int side, const std::array& texel) { + std::vector pixels(static_cast(side * side * side * 4)); + for (size_t at = 0; at < pixels.size(); at += 4) std::copy(texel.begin(), texel.end(), pixels.begin() + at); + return pixels; + }; + const auto level0 = fill(4, base); + const auto level1 = fill(2, sentinel); + glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, level0.data()); + glTexSubImage3D(GL_TEXTURE_3D, 1, 0, 0, 0, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, level1.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.setup"; + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_3D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ShaderMipDecline.wire"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.error"; + for (const int level : {0, 1}) { + const int side = 4 >> level; + std::vector pixels(static_cast(side * side * side * 4), 0x5a); + glGetTexImage(GL_TEXTURE_3D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.readback level " << level; + const std::array& expected = level == 0 ? base : sentinel; + size_t wrong = 0; + for (size_t at = 0; at < pixels.size(); at += 4) + if (!std::equal(expected.begin(), expected.end(), pixels.begin() + at)) ++wrong; + EXPECT_EQ(wrong, 0u) << "F1.ShaderMipDecline.level" << level << " tier=" << tier + << ": a declined mip must leave every level as the application wrote it"; + } + glBindTexture(GL_TEXTURE_3D, 0); + glDeleteTextures(1, &volume); + glBindTexture(GL_TEXTURE_2D, texture); +} + TEST_F(F1WireScenario, GenerateMipmapHonorsMutableBaseAndMaxWithoutChangingOtherLevels) { if (!Ready()) return; const std::array colors[5] = { diff --git a/MobileGL/MG_Pipe/MGPipeHostSpan.h b/MobileGL/MG_Pipe/MGPipeHostSpan.h index edec2fa4..8085a194 100644 --- a/MobileGL/MG_Pipe/MGPipeHostSpan.h +++ b/MobileGL/MG_Pipe/MGPipeHostSpan.h @@ -13,16 +13,16 @@ // // Monolith: Ptr addresses the frontend shadow or the application's own memory and the // accessor is one predictable branch. Split: Ptr is null and the bytes live in a staging -// segment named by Seg/Offset, or - for the index bytes a server-side primitive-restart -// rewrite or multi-draw flattening consumes - in the server's own index host mirror, which -// costs no wire traffic at all (D-B7). +// segment named by Seg/Offset. D-B7's third shape - index bytes a server-side restart +// rewrite reads out of its own index host mirror, at no wire cost - was never built; its +// sentinel below is RESERVED (P8-F). namespace MobileGL::MG_Pipe { // Seg sentinels. Anything else is a real SEG_STAGE id assigned by the transport. inline constexpr Uint32 kMGHostSpanSegNone = 0; - // "The bytes are already on your side": the server reads them out of the index host - // mirror it maintains for every resource created with the ELEMENT_ARRAY bind bit while - // kCapNeedsHostIndexBytes is set. When the mirror is over budget the tracker degrades - // to per-draw staging and counts the bytes in index-bytes-shipped. + // RESERVED (P8-F). It was to mean "the bytes are already on your side", read out of an + // index host mirror armed by kCapNeedsHostIndexBytes. Neither was built (client indices + // travel as owned element buffers; the cap is reserved), so no encoder writes this value + // and SegmentTable::Resolve answers it "unknown". Kept so the value is never reused. inline constexpr Uint32 kMGHostSpanSegFromServerIndexMirror = 0xFFFFFFFFu; struct MGHostSpan { diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index d1b40ad3..9806b6d4 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -107,19 +107,19 @@ namespace MobileGL::MG_Pipe { // flattening - is not expressible as a capability (D-B7). enum MGPCapBit : Uint64 { kCapNone = 0, - kCapViewportArray = 1ull << 0, + kCapViewportArray = 1ull << 0, // RESERVED (P8-F): no producer, no consumer; never renumber kCapFloat64VertexAttrib = 1ull << 1, kCapResidentSubData = 1ull << 2, kCapCpuXfbPrimitiveAccounting = 1ull << 3, kCapTimerQuery = 1ull << 4, kCapOcclusionQuery = 1ull << 5, kCapXfbPrimitivesQuery = 1ull << 6, - // The server rewrites restart indices / flattens multi-draws itself and therefore - // needs the index bytes on its side: under split this arms the index host mirror - // (D-B7). + // RESERVED (P8-F): never published, never read. The index host mirror it would arm + // (D-B7) was not built: client indices travel as owned element buffers and the server + // reads its own staged bytes. The bit position is wire format: never reuse/renumber it. kCapNeedsHostIndexBytes = 1ull << 7, - // The server packs named uniform blocks into its own ring and therefore needs the - // host bytes of a set_shader_buffers(Uniform) range (D-B8). + // RESERVED (P8-F): never published, never read - the named-UBO host span (D-B8) has + // no producer. The bit position is wire format: never reuse or renumber it. kCapNeedsHostUboBytes = 1ull << 8, // P5b t2 (CONTRACT-P5B.md §6.5), the one cap bit P5b adds. The SERVER's backend owns // the transform-feedback capture, i.e. its own table registers EndTransformFeedback. @@ -312,11 +312,11 @@ namespace MobileGL::MG_Pipe { kMGPipeBindDepthStencil = 1u << 8, kMGPipeBindStreamOutput = 1u << 9, kMGPipeBindAtomic = 1u << 10, - // THE D-B7 SWITCH. With kCapNeedsHostIndexBytes set the server mirrors this - // resource's bytes so it can rewrite restart indices and flatten multi-draws - // (ARCHITECTURE.md 10.3). Getting it wrong is invisible in monolith and silently - // disables both under split, which is why it is set from a table rather than from a - // special case at the emission site. + // Set beside INDEX on every element-array buffer, from a table (ResourceTracker.h). + // It was THE D-B7 SWITCH: with kCapNeedsHostIndexBytes the server would mirror this + // resource's bytes for restart rewriting and multi-draw flattening (ARCHITECTURE.md + // 10.3). That cap is RESERVED (P8-F) and the mirror was never built, so no reader + // consumes this bit today; it stays on the wire, at this position. kMGPipeBindElementArray = 1u << 11, }; @@ -331,8 +331,8 @@ namespace MobileGL::MG_Pipe { Uint8 StorageKind; // == TextureStorageType (Mipmap | Buffer) // MGPipeBindBit, above: VERTEX|INDEX|CONSTANT|SHADER_BUFFER|INDIRECT|SAMPLER| // SHADER_IMAGE|RENDER_TARGET|DEPTH_STENCIL|STREAM_OUTPUT|ATOMIC|ELEMENT_ARRAY. The - // ELEMENT_ARRAY bit is the D-B7 switch: with kCapNeedsHostIndexBytes set the server - // mirrors this resource. + // ELEMENT_ARRAY bit was the D-B7 switch; kCapNeedsHostIndexBytes is RESERVED (P8-F) + // and no server mirrors this resource. Uint16 BindMask; Uint32 InternalFormat; // already resolved to an uncompressed fallback by the client Uint32 Width, Height, Depth; @@ -992,11 +992,11 @@ namespace MobileGL::MG_Pipe { MGP_ASSERT_POD(MGPShaderImages, 16); // One bound buffer range: 24 bytes, no inline host span. The named-UBO host bytes a - // backend needs under kCapNeedsHostUboBytes (D-B8) travel as an OPTIONAL second var-tail, - // MGHostSpan[HostSpanCount] behind the ranges, announced by MGPShaderBuffers below. An - // inline span would have cost every SSBO, atomic-counter and XFB range 32 dead bytes, and - // D-B8 says not to freeze that payload's shape before the stage-ubo-named counter has - // produced numbers. + // backend would need under kCapNeedsHostUboBytes (D-B8; that cap is RESERVED since P8-F + // and nothing sends the tail) travel as an OPTIONAL second var-tail, MGHostSpan[HostSpanCount] + // behind the ranges, announced by MGPShaderBuffers below. An inline span would have cost + // every SSBO, atomic-counter and XFB range 32 dead bytes, and D-B8 says not to freeze that + // payload's shape before the stage-ubo-named counter has produced numbers. struct MGPBufferRange { MGPipeHandle Res; Uint64 Offset; @@ -1005,8 +1005,8 @@ namespace MobileGL::MG_Pipe { MGP_ASSERT_POD(MGPBufferRange, 24); // Var-tail header: MGPBufferRange[Count], then MGHostSpan[HostSpanCount]. HostSpanCount is - // 0, or Count for the Uniform class under kCapNeedsHostUboBytes (a range with nothing to - // ship carries an empty span, so the two arrays stay index-aligned). + // 0, or Count for the Uniform class under kCapNeedsHostUboBytes (RESERVED, P8-F: 0 today; + // a range with nothing to ship carries an empty span, so the two arrays stay aligned). // MGPShaderBuffers::Class's three values, and the ONLY spelling of them (P5e, // MG_Remote/CONTRACT-P5E.md §1). The payload's comment has named them since P4a and // nothing numbered them, so the emitter and the applier were one literal each away from diff --git a/MobileGL/MG_Remote/CapsCodec.h b/MobileGL/MG_Remote/CapsCodec.h index 7f326c7d..f30641d9 100644 --- a/MobileGL/MG_Remote/CapsCodec.h +++ b/MobileGL/MG_Remote/CapsCodec.h @@ -44,9 +44,9 @@ namespace MobileGL::MG_Remote { // (MGPipeTypes.h:108-124) and has no per-family bit at all, so "read the CallMask" was // not yet an implementable instruction. It is now: // - // bits 0..8 MGPCapBit, unchanged. kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes - // are BOTH ZERO in P5 by ruling (table 0), which is what keeps every - // MGHostSpan out of the first IPC frame. + // bits 0..8 MGPCapBit, unchanged. kCapViewportArray (0), kCapNeedsHostIndexBytes (7) + // and kCapNeedsHostUboBytes (8) are RESERVED (P8-F): never published, and + // their positions are never reused, which keeps every MGHostSpan off the wire. // bits 9..31 reserved for further MGPCapBits. // bits 32..47 THE CONSUMER MASK: bit (32 + n) means "the server has a consumer for // MGPipe subsystem bit n" - i.e. the server's own subsystem mask, shifted. diff --git a/MobileGL/MG_Remote/Client/WireTables.cpp b/MobileGL/MG_Remote/Client/WireTables.cpp index bbe1a887..d0541a44 100644 --- a/MobileGL/MG_Remote/Client/WireTables.cpp +++ b/MobileGL/MG_Remote/Client/WireTables.cpp @@ -251,7 +251,7 @@ namespace MobileGL::MG_Remote::Client { // P5e (sb, CONTRACT-P5E.md §5.6): the indexed buffer binding points, one record per // class. The generic tail wrapper carries the FIRST tail only, which is the whole of // the record on Espryt - the optional MGHostSpan tail exists for kCapNeedsHostUboBytes - // and that bit is 0 for the whole of P5. + // and that bit is RESERVED (P8-F): no server publishes it. MGP_WIRE_TAIL(SetShaderBuffers, MGPShaderBuffers, MGPBufferRange) MGP_WIRE_TAIL(SetVertexAttribDefaults, MGPVertexAttribDefaults, MGPAttribValue) diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 978f76d8..a2e2ff5a 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -766,17 +766,20 @@ namespace MobileGL::MG_Remote::Server { return LandReadbackInBuffer(record, bytes.data(), bpp); } - // P5b's server-side stub shape (CONTRACT-P5B.md): the same line the client's class-C table - // raises (EmitTables.cpp UnmigratedVerbFatal) and the same family the census greps, so a - // slot flipped on the client ahead of its server half aborts BY NAME on the apply thread - // rather than rendering nothing. Named "(server sink)" in the message so a log reader can - // tell which half is missing. + // P8-F: A PROTOCOL GUARD, NOT AN UNMIGRATED VERB. This was P5b's server-side stub shape + // (`Fatal{UnmigratedVerb, ...}`, "ServerVerbSink has no body for it yet"), but no package is + // going to give these two multi-draw shapes a body, because our client can no longer send + // either: client index arrays become one owned element buffer before the record is built + // (EmitTables.cpp's multi-draw arm, OwnedDrawInputs.h clears kDrawHasUserIndices), and no GL + // entry point produces an instanced multi-draw. A record that carries one came from a + // foreign peer, so the family is the one every other malformed-record check uses. // // PH-1 (3): the shape is the PEER's (a multi-draw record it chose to send), so in an armed // session child the refusal latches and the verb returns false; unarmed it still dies. - static Bool ServerUnmigratedVerbLatch(const char* slot) { - return SessionLatch(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"%s\"} (server sink: the record crossed and " - "ServerVerbSink has no body for it yet - CONTRACT-P5B.md names the package)", + static Bool MultiDrawShapeLatch(const char* slot) { + return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} a multi-draw " + "record with a shape this protocol does not carry: client indices travel as an owned " + "element buffer and no GL entry point produces an instanced multi-draw", slot); } @@ -802,10 +805,10 @@ namespace MobileGL::MG_Remote::Server { // of 1 and no base instance is the plain draw it is equivalent to, and a count of 0 must // NOT collapse onto the plain draw (it draws nothing, the plain draw would draw once). // - // What is still refused by name (the census's own grep family): a multi-draw that arrived - // with a span (the client refuses "MultiDrawElements+CLIENT_INDICES" first; P8's - // HostResolve.cpp flattens it) and a multi-draw that claims instancing (no GL entry point - // produces one; the client never sends it). + // What is still refused by name, as protocol corruption (P8-F): a multi-draw that arrived + // with a span (our client stages client indices into an owned element buffer and never sets + // kDrawHasUserIndices) and a multi-draw that claims instancing (no GL entry point produces + // one; the client never sends it). Only a foreign peer reaches either. #if MOBILEGL_BUILD_DISAGGREGATED namespace { // P5e (tx2), CONTRACT-P5E §5.3 / ruling 19 (ID-95, A8 closed). THE TWO UNIT WINDOWS MUST @@ -970,12 +973,12 @@ namespace MobileGL::MG_Remote::Server { // ---- the multi-draws: the two arrays rebuilt from the ranges (rule C) -------------- if (info.NumDraws != 1) { if (userIndices != nullptr) { - return ServerUnmigratedVerbLatch(info.IndexSize == 0 ? "MultiDrawArrays+CLIENT_INDICES" - : "MultiDrawElements+CLIENT_INDICES"); + return MultiDrawShapeLatch(info.IndexSize == 0 ? "MultiDrawArrays+CLIENT_INDICES" + : "MultiDrawElements+CLIENT_INDICES"); } if (instanced) { - return ServerUnmigratedVerbLatch(info.IndexSize == 0 ? "MultiDrawArrays+INSTANCED" - : "MultiDrawElements+INSTANCED"); + return MultiDrawShapeLatch(info.IndexSize == 0 ? "MultiDrawArrays+INSTANCED" + : "MultiDrawElements+INSTANCED"); } const auto n = static_cast(info.NumDraws); m_multiCounts.resize(n); diff --git a/MobileGL/MG_Remote/Server/ServerSession.h b/MobileGL/MG_Remote/Server/ServerSession.h index 77554a88..b11963af 100644 --- a/MobileGL/MG_Remote/Server/ServerSession.h +++ b/MobileGL/MG_Remote/Server/ServerSession.h @@ -162,9 +162,9 @@ namespace MobileGL::MG_Remote::Server { // that owns the question (kCapTimerQuery to the query family, kCapResidentSubData to // b1, and so on). `SetCapabilityBits(0)` is a legitimate and explicit answer - "this // server offers no optional capability" - and is the right call while those packages - // land. kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes must stay 0 for the whole of - // P5 by ruling (CONTRACT-P5 table 0): they are the only two things that ask for an - // MGHostSpan, and 0 is what keeps every one of them out of the first IPC frame. + // land. kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes / kCapViewportArray are RESERVED + // (P8-F; CONTRACT-P5 table 0 first held the two host-byte bits at 0): the first two are + // the only things that ask for an MGHostSpan, and 0 keeps every one of them off the wire. void SetCapabilityBits(Uint64 capBits); // BITS 32..47, THE CONSUMER MASK (R-8 / C-4): which MGPipe subsystems this server has diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp index 650bde6c..b9b2ad97 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp @@ -456,8 +456,9 @@ namespace MobileGL::MG_Remote::Wire { } const void* SegmentTable::Resolve(Uint32 seg, Uint64 offset, Uint64 size) const { - // 0 is "no segment", ALWAYS (table 0), and 0xFFFFFFFF is P8's index-mirror sentinel - // which this phase does not resolve. Both land here as "unknown", and the CALLER + // 0 is "no segment", ALWAYS (table 0), and 0xFFFFFFFF is the RESERVED index-mirror + // sentinel (P8-F: the mirror was never built, so nothing resolves it and no client of + // ours sends it). Both land here as "unknown", and the CALLER // escalates to Fatal - a unit case has to be able to exercise this arithmetic without // dying. if (seg == kSegNone || seg > static_cast(kSegAdopt)) { @@ -2121,7 +2122,7 @@ namespace MobileGL::MG_Remote::Wire { // entire point is that the server stops walking the client's binding-point table. // // THE HOST-SPAN PASS STILL RUNS FIRST, and it runs whether or not the record is - // applied: kCapNeedsHostUboBytes is 0 for the whole of P5 (table 0), so the second + // applied: kCapNeedsHostUboBytes is RESERVED (P8-F, never published), so the second // tail is always absent here, and this pass is what says so out loud if it ever is // not. A span that was going to be refused must be refused BEFORE the applier has // stored the window it rides with. diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.h b/MobileGL/MG_Remote/Wire/PipeWireCodec.h index 5b8fb18f..6cef4e4f 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.h +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.h @@ -148,9 +148,9 @@ namespace MobileGL::MG_Remote::Wire { // declared. Fatal on an absent blob, then CheckBlobIsHonest on a present one. Bool RequireDeclaredBlob(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob, const SegmentTable& segments); - // R-2.3 arms 1 and 3 for MGHostSpan. P5's reduced path should produce ZERO host spans - // (kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes are both 0 in P5, table 0), so this - // firing at all is a finding, not just a corruption check. + // R-2.3 arms 1 and 3 for MGHostSpan. Our client produces ZERO host spans + // (kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes are RESERVED since P8-F, never set), so + // this firing at all is a finding, not just a corruption check. // // IT CANNOT DO ARM 4 - it has no segment table - so a span that names a real segment and a // run PAST THE END OF IT passes this function. Use the overload below on any path that has diff --git a/MobileGL/MG_Test/Wire/PeerLatchTest.cpp b/MobileGL/MG_Test/Wire/PeerLatchTest.cpp index bd26eb01..ae41acea 100644 --- a/MobileGL/MG_Test/Wire/PeerLatchTest.cpp +++ b/MobileGL/MG_Test/Wire/PeerLatchTest.cpp @@ -1159,8 +1159,10 @@ namespace { p.FrameSerial = 0; return Forge(c, r, P::MGPWireOp::Present, Bytes(p)); }}, + // P8-F: ProtocolCorruption, no longer UnmigratedVerb - our client cannot send this shape + // (nor +CLIENT_INDICES, the same site), so only a foreign peer like this one reaches it. {"MultiDrawInstanced", "PipeApplier.cpp", "MultiDraw*+INSTANCED", Outcome::Latched, - "Fatal{UnmigratedVerb, \"MultiDrawArrays+INSTANCED\"}", false, + "Fatal{ProtocolCorruption, \"MultiDrawArrays+INSTANCED\"}", false, [](Client::ClientSession& c, PeerReport& r) { P::MGPDrawInfo info{}; info.Mode = GL_TRIANGLES; diff --git a/docs/Disaggregated/notes/p8/F.md b/docs/Disaggregated/notes/p8/F.md new file mode 100644 index 00000000..12c34e1e --- /dev/null +++ b/docs/Disaggregated/notes/p8/F.md @@ -0,0 +1,58 @@ +# P8-F 重分类与死闩清理 + +> 包说明:`~/w7/notes/p8/BRIEF-F.md`(树外)。基线 `8d3e2317`,分支 `p8/f`。我们自己的 client 行为不变:四项都是它发不出的形状,或只改注释。 + +## 结论 + +| 项 | 改动 | 位置 | 之前 | 之后 | +|---|---|---|---|---| +| 1 | multi-draw 的两个 server 闩改族 | `PipeApplier.cpp:779-784`、`:976-981` | `Fatal{UnmigratedVerb, "MultiDraw{Arrays,Elements}+{CLIENT_INDICES,INSTANCED}"}` | `Fatal{ProtocolCorruption, <同名>}`,仍按名闩住外来 peer | +| 2 | `Magma:client-vertex-array` 去掉 P8 阶段标签,改为协议不变量 | `WireDraw.inc:297-304` | `MagmaWireFatal`(UnmigratedVerb 族,名字带 `@P8`) | `MGPipeSessionFail(ProtocolCorruption)`,名字 `Magma:client-vertex-array` 加 location | +| 3 | Magma 生成 mip 的"无着色器臂形状"改为具名拒绝 | `WireFramebuffer.inc:1405-1459`(判定)、`:1456`、`:1500`(两个拒绝点) | wire 臂三处 Fatal(旧 `:1427`、`:1452`、`:1489-1493`) | `WireDeclines.def:112` 的 `MipmapShaderFormatOrShape`;判定在 `GrowWireTextureMipChain`(`:1460`)之前,拒绝时不增长 mip 链 | +| 4 | `kCapViewportArray`、`kCapNeedsHostIndexBytes`、`kCapNeedsHostUboBytes` 与 index-mirror 哨兵标为保留位 | `MGPipeTypes.h:110`、`:117-123`、`:315-320`、`:332-335`、`:994-999`、`:1007-1009`;`MGPipeHostSpan.h:16-26`;`VertexInputEmit.h:266-267`;`ShaderBufferEmit.h:250-253`;`MG_Backend/Init.cpp:189-191` | 注释说"P5 期间为 0"或"arms the index host mirror" | 注释说 RESERVED、从不发布、位不复用不重编号;只改注释 | + +## 各项依据 + +| 项 | 事实 | 出处 | +|---|---|---| +| 1 | client 把 multi-draw 的 client 索引拼成一个自有 EBO | `EmitTables.cpp:688-709` | +| 1 | 自有 EBO 生效时清掉 `kDrawHasUserIndices` | `OwnedDrawInputs.h:45-57` | +| 1 | multi-draw 计划的实例数恒为 1、base instance 恒为 0 | `EmitTables.cpp:686` | +| 1 | 四个名共用一个 `SessionLatch` 格式串,`PeerLatchTest` 的一行覆盖整组 | `PipeApplier.cpp:779-784`、`PeerLatchTest.cpp:1162-1165` | +| 1 | 族选 `ProtocolCorruption`:它的桶就是"peer 发了协议不承载的东西" | `FatalFamilies.def:26-29` | +| 2 | client 把每个启用的 client 内存数组转成自有 buffer,指针为空就拒绝整个 draw | `OwnedDrawInputs.h:102-131` | +| 2 | `fatal_census.py` 只数 `Fatal{Word` 族词与 abort 点,不按 `@P` 字面计数 | `fatal_census.py:165-167` | +| 2 | 普查前后:79 个 abort 点、20 个文件、45 个族词,均不变;不重写基线 | 本包门禁读数 | +| 2 | 树内 `@P8` 字面:1(`WireDraw.inc:297`,基线)→ 0 | `git grep '@P8' -- MobileGL scripts tools` | +| 3 | monolith 臂对无 BLIT 的格式一直是拒绝(跳过、链不写) | `VulkanRenderer.cpp:12225-12229` | +| 3 | 增长前后格式、特性、knob 档位、aspect、view 类型、usage、层窗口都不变 | `VkTextureManager.cpp:2884-2995` | +| 3 | 深度 pass 对本函数交给它的图像只会因"格式不能同时采样和作深度附件"拒绝 | `WireDepthMipmap.inc:58-81` | +| 3 | 深度 pass 若仍拒绝(两份清单漂移),走同一行拒绝,不结束会话 | `WireFramebuffer.inc:1491-1502` | +| 4 | 生产者 0:唯一的 `SetCapabilityBits` 只置 XFB、query、resident、run-ahead 位 | `MG_Backend/Init.cpp:202-255` | +| 4 | 消费者 0:`kCapViewportArray` 在树内只有枚举定义;`kMGPipeBindElementArray` 只在 bind mask 表里被置 | `MGPipeTypes.h:110`、`ResourceTracker.h:95` | +| 4 | `0xFFFFFFFF` 哨兵无编码方;`SegmentTable::Resolve` 把它当未知段 | `MGPipeHostSpan.h:26`、`PipeWireCodec.cpp:458-465` | +| 4 | pull 构建只改注释,逐文件行数不变;G1 `added=0 removed=0`,`.text` 与基线同为 `a5ba13` | 本包门禁读数 | + +## 新门与 red-once + +| 项 | 门 | 变异 | 载体 | 观测到的红 | +|---|---|---|---|---| +| 1 | `PeerLatchTest` 行 `MultiDrawInstanced` 期望 `Fatal{ProtocolCorruption, "MultiDrawArrays+INSTANCED"}` | 闩的族改回 `UnmigratedVerb` | `FuzzArm2/PeerLatchSite.*/MultiDrawInstanced`、`PeerLatch.SiteMap` | 两例红:首行 `Fatal{UnmigratedVerb, ...}` 不含期望标记;站点图报该站点未映射 | +| 3 | `F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession`(3D RGBA8,`MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1`) | 拒绝改回 `MagmaWireFatal` | `DirectVulkan.{Split,Spawn}.ShaderMipDecline.` 两例(当时前缀为 `.ShaderMip1.`,后改名,见下) | 两例 `Subprocess aborted`,server 日志 `Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape format=37 extent=4x4x4 ..."}` | +| 3 | 同上(前提) | 无(新用例落在未改的基线代码上) | 同上 | 同样两例 abort:前提成立 | +| 3 | `spawn_lane_parity.py` 的 `.ShaderMipDecline.` 例外行 | 删掉该行 | `spawn_lane_parity.py build-split` | rc 1:`the tcp arm does not match the split arm: missing=['DirectVulkan.ShaderMipDecline.F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession']` | +| 2 | 普查读数 | — | `fatal_census.py` | 无变化:该脚本不计阶段标签(见上) | + +- 新用例只登记 split + spawn:knob 由 server 读,tcp fixture 带不过去。登记在 `MobileGL/MG_IntegrationTest/CMakeLists.txt:3358-3376`,前缀 `.ShaderMipDecline.`。 +- 该尾自成一行加进 `MAGMA_SERVER_ENV_KNOB_NO_TCP`(`spawn_lane_parity.py:141-143`):脚本要求每个尾恰好命中一个用例,借用 `.ShaderMip1.` 会命中两个,门禁时 parity 报 1 个错,已改。 +- Espryt 不涉及第 2、3 项(Magma 专属);新用例在 DirectGLES 上按后端名跳过。第 1 项在后端无关的 applier 里。 + +## 发现但未修 + +| 发现 | 证据 | 影响 | +|---|---|---| +| Magma wire 原生 blit 生成 3D mip 时把 depth 当层数传给 `vkCmdBlitImage`(layerCount=4,图像 arrayLayers=1) | `WireFramebuffer.inc:1612`、`VkTextureManager.cpp:2738-2741`;验证层报 `VUID-vkCmdBlitImage-srcImage-00240`、`-srcSubresource-01707`、`-dstSubresource-01708`(临时探针,未提交) | 只在 split;lavapipe 容忍;其他驱动上 3D 生成 mip 行为未定义 | +| 深度/模板生成 mip 的拒绝只记日志,不计入 `WireDeclineTally` | `WireFramebuffer.inc:1397-1404` | `MGWIRE-DECLINES` 看不到这类拒绝 | +| `GenerateWireDepthMipLevel` 的注释说会拒绝 1D,代码只拒绝 `is3D`,而调用方从不设置它 | `WireDepthMipmap.inc:66`、`WireFramebuffer.inc:1495` | 无原生 blit 的 1D 深度链会进入 pass(推断,未测) | + +- monolith 缺陷:无。 diff --git a/scripts/ci/spawn_lane_parity.py b/scripts/ci/spawn_lane_parity.py index cecd9c7d..f442a506 100644 --- a/scripts/ci/spawn_lane_parity.py +++ b/scripts/ci/spawn_lane_parity.py @@ -138,7 +138,9 @@ MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.", ".Reclaim.MagmaWireReclaimScenario.ADrawAfterTheEarlyReclaimFollowsTheNewStoreNotTheMemoizedHandle", ".Reclaim.MagmaWireReclaimScenario.DescriptorSetsRewindInsideOneLongFrameAfterTheirSubmitRetires") + tuple( f".PrimGenReroute.PrimitivesGeneratedNoXfbScenario.{case}" for case in PRIMGEN_REROUTE_CASES) + tuple( - f".SplitRecording64.OversizedRecordingSplitScenario.{case}" for case in SPLIT_RECORDING_CASES) + f".SplitRecording64.OversizedRecordingSplitScenario.{case}" for case in SPLIT_RECORDING_CASES) + ( + # P8-F: the mip shader-shape decline, forced by MGITEST_MAGMA_FORCE_SHADER_MIPMAP (server-read). + ".ShaderMipDecline.",) # THE MIRROR SHAPE - entries that exist on tcp ALONE, by construction. Each tail must match at least