diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 8721ee30..cdc368f8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -52,6 +52,7 @@ #include // P7 wave 2 package B3: rule I's tally for WireDraw.inc's silent draw drops. #include "WireDeclineTally.h" +#include "WireDepthResolveArm.h" #endif #include #include @@ -15147,6 +15148,14 @@ void main() { if (m_wireShaderStencilExport) EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); + // P7 gate 5 (g5-msrbo): the Adreno's no-draw depth/stencil resolve pass writes nothing, + // so the shader resolve goes first there (WireDepthResolveArm.h says why and where). + m_wirePreferShaderDepthResolve = MG_Config::Transport != MG_Config::TransportMode::Monolith && + WirePrefersShaderDepthResolve(m_physicalDevice.properties.vendorID); + if (m_wirePreferShaderDepthResolve) + MGLOG_I("DirectVulkan: vendor 0x%x - the wire arm resolves multisample depth/stencil with the " + "shader pass first (the no-draw resolve render pass is the fallback)", + m_physicalDevice.properties.vendorID); #endif if ((descriptorIndexingCore || descriptorIndexingExtension) && getPhysicalDeviceFeatures2 != nullptr && getPhysicalDeviceProperties2 != nullptr) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index e6a8c601..66e56f08 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -556,6 +556,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { WireMultisampleResolveResources* m_wireMultisampleResolveResources = nullptr; void DestroyWireMultisampleResolveResources(); Bool m_wireShaderStencilExport = false; + // WirePrefersShaderDepthResolve(vendorID) for this device (WireDepthResolveArm.h): the + // shader arm resolves first, the render-pass arm is the fallback (P7 gate 5). + Bool m_wirePreferShaderDepthResolve = false; Bool BlitWireColorToDefault(WireImage source, WireImage destination, GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h new file mode 100644 index 00000000..eec0604c --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h @@ -0,0 +1,34 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header +#pragma once + +#include + +// P7 gate 5 (g5-msrbo): WHICH ARM RESOLVES A MULTISAMPLE DEPTH/STENCIL ASPECT FIRST. +// +// The wire arm has two (WireFramebuffer.inc, ResolveWireDepthStencil): a render pass that carries +// a VK_KHR_depth_stencil_resolve attachment and no draw, and the baked shader pass of +// WireMultisampleResolve.inc, which draws. P7 wave 2-B2 took the render pass wherever the +// extension exists. On the Redmi (Adreno 830, Vulkan 1.3.284, driver 512.800.71) that pass left +// its resolve target unwritten - KHR-GL46.direct_state_access.renderbuffers_storage_multisample +// read every resolved depth and stencil value back as 0 on the inproc arm while the monolith arm +// passed, and the same case passed on the device with the shader arm forced +// (MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=1). So on a Qualcomm device the shader arm goes first; +// the render pass stays the fallback for what the shader cannot write (a stencil aspect without +// VK_EXT_shader_stencil_export). Every other vendor keeps B2's order: nothing measured says +// otherwise, and a vendor list that grew on a guess would be the same mistake the other way round. +// +// A pure function of the vendor id so the policy is a unit-testable fact rather than a branch +// buried in the apply thread. +namespace MobileGL::MG_Backend::DirectVulkan { + inline constexpr Uint32 kWireVendorIdQualcomm = 0x5143u; + + inline constexpr Bool WirePrefersShaderDepthResolve(Uint32 vendorId) { + return vendorId == kWireVendorIdQualcomm; + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index 0d141a32..f1c3df46 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -723,7 +723,22 @@ VkImageView views[2]{}; VkRenderPass pass = VK_NULL_HANDLE; VkFramebuffer framebuffer = VK_NULL_HANDLE; - if (extensionResolve) { + // P7 gate 5 (g5-msrbo): WHERE THE EXTENSION ARM IS KNOWN NOT TO RESOLVE, THE SHADER ARM + // GOES FIRST. The extension arm is a render pass with no draw in it, and on the Redmi + // (Adreno 830, driver 512.800.71) that pass left the resolve target unwritten: + // KHR-GL46.direct_state_access.renderbuffers_storage_multisample read every resolved depth + // and stencil value back as 0 on the inproc arm - exactly its samples 2/4/4 iterations, + // every format - and the same case PASSED on that device with + // MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=1. The shader arm draws, so nothing about it + // can be elided. It still declines what it cannot express (a stencil aspect without + // VK_EXT_shader_stencil_export), and then the extension arm is the fallback rather than + // a decline. m_wirePreferShaderDepthResolve is WirePrefersShaderDepthResolve's answer for + // this device (WireDepthResolveArm.h); everywhere else the order is the one B2 chose. + const Bool shaderFirst = m_wirePreferShaderDepthResolve || !extensionResolve; + const Bool resolvedByShader = + shaderFirst && ResolveWireDepthStencilWithShader(source, resolved, source.aspect, sxMin, syMin, + static_cast(width), static_cast(height)); + if (!resolvedByShader && extensionResolve) { TransitionWireImage(source, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); TransitionWireImage(resolved, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); @@ -780,8 +795,7 @@ begin.renderArea = {{sxMin, syMin}, {static_cast(width), static_cast(height)}}; vkCmdBeginRenderPass(m_frameContext.GetCurrent().commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdEndRenderPass(m_frameContext.GetCurrent().commandBuffer); - } else if (!ResolveWireDepthStencilWithShader(source, resolved, source.aspect, sxMin, syMin, - static_cast(width), static_cast(height))) { + } else if (!resolvedByShader) { // NEITHER ARM, and rule I (a) says what that is: this device has no // VK_KHR_depth_stencil_resolve and the pass cannot stand in for it either - the // commonest reason being a stencil-only resolve without VK_EXT_shader_stencil_export, diff --git a/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp b/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp index 6b5ad74a..f479f6dd 100644 --- a/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp +++ b/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp @@ -13,6 +13,7 @@ #include #if MOBILEGL_BUILD_DISAGGREGATED #include +#include #endif using namespace MobileGL; @@ -515,3 +516,20 @@ TEST(WirePipelineCompatibility, HashUsesCompatibilityIdentityAndSeparatesNativeD GTEST_SKIP() << "wire pipeline compatibility requires disaggregated build"; #endif } + +// P7 gate 5 (g5-msrbo): the multisample depth/stencil resolve's arm order. On the Redmi (Adreno +// 830) the no-draw VK_KHR_depth_stencil_resolve render pass left its target unwritten and the +// shader arm resolved correctly, so a Qualcomm device takes the shader arm first; every other +// vendor keeps P7 wave 2-B2's order (render pass first) until a device says otherwise. +TEST(PipelineQuirkTest, WireDepthResolvePrefersTheShaderArmOnQualcommOnly) { +#if MOBILEGL_BUILD_DISAGGREGATED + using MobileGL::MG_Backend::DirectVulkan::WirePrefersShaderDepthResolve; + EXPECT_TRUE(WirePrefersShaderDepthResolve(kVendorIdQualcomm)) + << "the Adreno's no-draw resolve pass wrote nothing; the shader arm must go first there"; + EXPECT_FALSE(WirePrefersShaderDepthResolve(kVendorIdArm)); + EXPECT_FALSE(WirePrefersShaderDepthResolve(0x10005u)) << "Mesa (lavapipe) keeps the render-pass arm first"; + EXPECT_FALSE(WirePrefersShaderDepthResolve(0x10DEu)); +#else + GTEST_SKIP() << "the wire resolve arms exist only in the disaggregated build"; +#endif +}