[MG_Util, MG_Test] (P7): list the no-draw depth/stencil render-pass resolve in the Vulkan POST's Known Driver Bugs as FIXED

This commit is contained in:
2026-09-23 15:13:45 -04:00
parent e83581c3c4
commit 564602b71d
4 changed files with 174 additions and 1 deletions
@@ -1171,3 +1171,55 @@ TEST(DriverBugProbes, Packed16MirrorArithmeticMatchesTheDeviceEvidence) {
EXPECT_EQ(MirrorPacked5551(0x0047), 0x8C20);
EXPECT_EQ(MirrorPacked5551(0x3800), 0x0007);
}
// ===================== DEPTH/STENCIL RENDER-PASS RESOLVE (Vulkan) =====================
//
// The Vulkan table's row for MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h's probe: a
// pure function of its measurement. FIXED where the shader substitute can write every aspect,
// UNFIXABLE where a stencil resolve still has to use the render pass that writes nothing, and no
// row at all for a clean or inconclusive measurement.
#if MOBILEGL_BUILD_DISAGGREGATED
namespace {
MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement DepthResolveMeasurement(Uint32 renderPassMatches,
Uint32 shaderMatches) {
MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement measurement;
measurement.ran = true;
MG_Backend::DirectVulkan::WireDepthResolveFormatReading reading;
reading.name = "D24_UNORM_S8_UINT";
reading.samples = 4;
reading.ran = true;
for (auto* aspect : {&reading.depth, &reading.stencil}) {
aspect->measured = true;
aspect->texels = 16;
aspect->renderPassMatches = renderPassMatches;
aspect->shaderRan = true;
aspect->shaderMatches = shaderMatches;
}
measurement.formats.push_back(reading);
return measurement;
}
} // namespace
#endif
TEST(DriverBugProbes, DepthStencilResolvePassRowIsFixedWhereTheShaderSubstituteWritesEveryAspect) {
#if MOBILEGL_BUILD_DISAGGREGATED
using MobileGL::MG_Util::SelfTest::DescribeDepthStencilResolvePassBug;
const auto broken = DepthResolveMeasurement(0, 16);
const auto fixed = DescribeDepthStencilResolvePassBug(broken, /*shaderStencilExport=*/true);
ASSERT_TRUE(fixed.has_value()) << "the render pass wrote nothing while the control resolved: a bug row";
EXPECT_EQ(fixed->verdict, DriverBugVerdict::Fixed);
EXPECT_FALSE(fixed->name.empty());
EXPECT_NE(fixed->detail.find("render pass 0/16"), std::string::npos) << fixed->detail;
const auto stencilLeft = DescribeDepthStencilResolvePassBug(broken, /*shaderStencilExport=*/false);
ASSERT_TRUE(stencilLeft.has_value());
EXPECT_EQ(stencilLeft->verdict, DriverBugVerdict::Unfixable)
<< "without VK_EXT_shader_stencil_export a stencil resolve still lands on the broken pass";
EXPECT_FALSE(DescribeDepthStencilResolvePassBug(DepthResolveMeasurement(16, 16), true).has_value())
<< "a clean render pass is no row";
EXPECT_FALSE(DescribeDepthStencilResolvePassBug(DepthResolveMeasurement(0, 0), true).has_value())
<< "an inconclusive probe (the control failed too) must never be reported as the bug";
EXPECT_FALSE(DescribeDepthStencilResolvePassBug({}, true).has_value()) << "a probe that did not run is no row";
#else
GTEST_SKIP() << "the Vulkan table exists only in the disaggregated build";
#endif
}
@@ -11,6 +11,9 @@
#include <Config.h>
#include <MG_Util/Debug/Log.h>
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h>
#endif
#include <algorithm>
#include <cstring>
@@ -2475,4 +2478,73 @@ namespace MobileGL::MG_Util::SelfTest {
}
return findings;
}
#if MOBILEGL_BUILD_DISAGGREGATED
Optional<DriverBugFinding> DescribeDepthStencilResolvePassBug(
const MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement& measurement, Bool shaderStencilExport) {
using namespace MG_Backend::DirectVulkan;
if (EvaluateWireDepthResolveProbe(measurement) != WireDepthResolveProbeVerdict::RenderPassResolveBroken)
return std::nullopt;
String detail =
"a render pass whose subpass carries a VK_KHR_depth_stencil_resolve attachment (SAMPLE_ZERO) and "
"records no draw leaves its single-sample resolve target unwritten, while MobileGL's shader "
"resolve pass fed the same multisample inputs resolves them correctly - measured here on 4x4 "
"4-sample images cleared to depth 0.25 / stencil 0x5A over a depth 0.75 / stencil 0xA5 "
"sentinel: ";
detail += DescribeWireDepthResolveProbe(measurement);
detail +=
". On the split, inproc and tcp transports a multisample depth/stencil resolve "
"(glBlitFramebuffer out of a multisample framebuffer) therefore takes the shader pass "
"first - sample 0 fetched and written through gl_FragDepth / gl_FragStencilRefARB - and "
"keeps the render pass only as the fallback for what the shader cannot write; the "
"monolith transport never records that render pass";
if (!shaderStencilExport) {
detail +=
". THIS DEVICE HAS NO VK_EXT_shader_stencil_export, so a fragment shader cannot write "
"the stencil aspect: a STENCIL resolve still falls back to the render pass and reads "
"back unwritten. Depth is repaired, stencil is not";
}
return DriverBugFinding{"Depth/stencil render-pass resolve writes nothing",
shaderStencilExport ? DriverBugVerdict::Fixed : DriverBugVerdict::Unfixable,
Move(detail)};
}
namespace {
// The same probe the Magma renderer runs at bring-up (VulkanRenderer::
// ArmWireDepthResolveOrder), on a throwaway device carrying the renderer's extensions.
Optional<DriverBugFinding> ProbeDepthStencilResolvePassBug(const VulkanDriverBugProbeContext& context) {
using namespace MG_Backend::DirectVulkan;
if (context.getInstanceProcAddr == nullptr || context.deviceExtensions == nullptr) return std::nullopt;
Bool renderPassArmAvailable = false, shaderStencilExport = false;
const WireDepthResolveProbeMeasurement measurement = RunWireDepthResolveProbeOnThrowawayDevice(
context.getInstanceProcAddr, context.instance, context.physicalDevice,
context.graphicsQueueFamilyIndex, *context.deviceExtensions, renderPassArmAvailable,
shaderStencilExport);
// Without the extension there is no render-pass arm and nothing to have a bug in.
if (!renderPassArmAvailable) return std::nullopt;
const WireDepthResolveProbeVerdict verdict = EvaluateWireDepthResolveProbe(measurement);
MGLOG_I("Driver POST: depth/stencil resolve probe verdict=%s%s%s",
WireDepthResolveProbeVerdictName(verdict), measurement.failureReason.empty() ? "" : " - ",
measurement.failureReason.c_str());
for (const WireDepthResolveFormatReading& reading : measurement.formats)
MGLOG_I("Driver POST: depth/stencil resolve probe %s", DescribeWireDepthResolveFormat(reading).c_str());
return DescribeDepthStencilResolvePassBug(measurement, shaderStencilExport);
}
using VulkanDriverBugProbeFn = Optional<DriverBugFinding> (*)(const VulkanDriverBugProbeContext&);
constexpr VulkanDriverBugProbeFn kVulkanDriverBugProbes[] = {
&ProbeDepthStencilResolvePassBug,
};
} // namespace
Vector<DriverBugFinding> CollectVulkanKnownDriverBugs(const VulkanDriverBugProbeContext& context) {
Vector<DriverBugFinding> findings;
for (const VulkanDriverBugProbeFn probe : kVulkanDriverBugProbes) {
if (Optional<DriverBugFinding> finding = probe(context)) {
findings.push_back(Move(*finding));
}
}
return findings;
}
#endif
} // namespace MobileGL::MG_Util::SelfTest
+36 -1
View File
@@ -9,6 +9,9 @@
#pragma once
#include <Includes.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h>
#endif
namespace MobileGL::MG_Util::SelfTest {
// ===================== KNOWN DRIVER BUGS =====================
@@ -33,7 +36,8 @@ namespace MobileGL::MG_Util::SelfTest {
//
// ADDING A SIBLING IS ONE FUNCTION: write an `Optional<DriverBugFinding> ProbeXxx(gl)`
// that returns nullopt when the driver is not affected, and add it to the table in
// CollectGlesKnownDriverBugs().
// CollectGlesKnownDriverBugs(). A Vulkan probe takes a VulkanDriverBugProbeContext instead
// and goes in CollectVulkanKnownDriverBugs()'s table (bottom of this file).
// What MobileGL can do about a bug this device HAS. There is deliberately no "not
// affected" member: a driver that passes the probe produces no finding at all, so the
@@ -342,4 +346,35 @@ namespace MobileGL::MG_Util::SelfTest {
// Every known driver bug this GLES driver actually has. Bugs it does not have are absent,
// so an unaffected device renders an empty section rather than a wall of "not affected".
Vector<DriverBugFinding> CollectGlesKnownDriverBugs(const MG_External::GLESFunctionsTable& gl);
#if MOBILEGL_BUILD_DISAGGREGATED
// ===================== KNOWN VULKAN DRIVER BUGS =====================
//
// The DirectVulkan (Magma) backend's table, under the rules above: a control per probe, an
// inconclusive probe is never a finding, a clean one contributes nothing. A probe gets the
// POST's throwaway instance and the physical device it picked, and creates whatever device it
// needs itself.
//
// DISAGGREGATED BUILDS ONLY, for now: its one row is a defect of the Magma WIRE arm, which the
// monolith never records, and the pull build's image must not move (P7 gate G1). A row about a
// path the monolith takes moves this guard into the table.
struct VulkanDriverBugProbeContext {
PFN_vkGetInstanceProcAddr getInstanceProcAddr = nullptr;
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
Uint32 graphicsQueueFamilyIndex = 0;
const Vector<VkExtensionProperties>* deviceExtensions = nullptr;
};
// The row for the multisample depth/stencil resolve probe
// (MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h), a pure function of its
// measurement: nullopt unless the verdict is the defect. FIXED where the shader substitute
// can write every aspect (VK_EXT_shader_stencil_export present), UNFIXABLE otherwise - a
// stencil resolve then still lands on the render pass that writes nothing.
Optional<DriverBugFinding> DescribeDepthStencilResolvePassBug(
const MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement& measurement, Bool shaderStencilExport);
// Every known driver bug this Vulkan driver actually has.
Vector<DriverBugFinding> CollectVulkanKnownDriverBugs(const VulkanDriverBugProbeContext& context);
#endif
} // namespace MobileGL::MG_Util::SelfTest
+14
View File
@@ -3028,6 +3028,20 @@ namespace MobileGL::MG_Util::SelfTest {
graphicsQueueFamilyIndex, deviceExtensions, features,
vkGetPhysicalDeviceFeatures2Fn, vkGetPhysicalDeviceProperties2Fn);
#if MOBILEGL_BUILD_DISAGGREGATED
// The "Known Driver Bugs" section's Vulkan table (DriverBugProbes.h): each probe makes its
// own throwaway device on the physical device picked above.
{
VulkanDriverBugProbeContext bugContext;
bugContext.getInstanceProcAddr = getInstanceProcAddr;
bugContext.instance = instance;
bugContext.physicalDevice = physicalDevice;
bugContext.graphicsQueueFamilyIndex = graphicsQueueFamilyIndex;
bugContext.deviceExtensions = &deviceExtensions;
builder.report.knownDriverBugs = CollectVulkanKnownDriverBugs(bugContext);
}
#endif
if (HasVkExtension(deviceExtensions, VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME)) {
builder.Pass("VK_KHR_draw_indirect_count",
"supported (count-buffer indirect draws run as single native "