[MG_Backend, MG_Test] (P7): resolve multisample depth/stencil with the shader arm first on Qualcomm

This commit is contained in:
2026-09-23 15:13:44 -04:00
parent b1f9ea39f1
commit 53397e6e1e
5 changed files with 81 additions and 3 deletions
@@ -52,6 +52,7 @@
#include <MG_Impl/Pipe/SlotAllocator.h>
// P7 wave 2 package B3: rule I's tally for WireDraw.inc's silent draw drops.
#include "WireDeclineTally.h"
#include "WireDepthResolveArm.h"
#endif
#include <algorithm>
#include <bit>
@@ -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) {
@@ -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);
@@ -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 <Includes.h>
// 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
@@ -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<Uint32>(width), static_cast<Uint32>(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<Uint32>(width), static_cast<Uint32>(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<Uint32>(width), static_cast<Uint32>(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,
@@ -13,6 +13,7 @@
#include <MG_Backend/DirectVulkan/Renderer/ProgramFactory.h>
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Backend/DirectVulkan/Renderer/WireRenderPassCompatibility.h>
#include <MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h>
#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
}