diff --git a/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp b/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp index 330953bf..9c09c2ab 100644 --- a/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp +++ b/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp @@ -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 +} diff --git a/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp b/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp index 8b4b01c5..ca8baf27 100644 --- a/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp @@ -11,6 +11,9 @@ #include #include +#if MOBILEGL_BUILD_DISAGGREGATED +#include +#endif #include #include @@ -2475,4 +2478,73 @@ namespace MobileGL::MG_Util::SelfTest { } return findings; } + +#if MOBILEGL_BUILD_DISAGGREGATED + Optional 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 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 (*)(const VulkanDriverBugProbeContext&); + constexpr VulkanDriverBugProbeFn kVulkanDriverBugProbes[] = { + &ProbeDepthStencilResolvePassBug, + }; + } // namespace + + Vector CollectVulkanKnownDriverBugs(const VulkanDriverBugProbeContext& context) { + Vector findings; + for (const VulkanDriverBugProbeFn probe : kVulkanDriverBugProbes) { + if (Optional finding = probe(context)) { + findings.push_back(Move(*finding)); + } + } + return findings; + } +#endif } // namespace MobileGL::MG_Util::SelfTest diff --git a/MobileGL/MG_Util/SelfTest/DriverBugProbes.h b/MobileGL/MG_Util/SelfTest/DriverBugProbes.h index 1ad151a4..c90ffd02 100644 --- a/MobileGL/MG_Util/SelfTest/DriverBugProbes.h +++ b/MobileGL/MG_Util/SelfTest/DriverBugProbes.h @@ -9,6 +9,9 @@ #pragma once #include #include +#if MOBILEGL_BUILD_DISAGGREGATED +#include +#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 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 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* 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 DescribeDepthStencilResolvePassBug( + const MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement& measurement, Bool shaderStencilExport); + + // Every known driver bug this Vulkan driver actually has. + Vector CollectVulkanKnownDriverBugs(const VulkanDriverBugProbeContext& context); +#endif } // namespace MobileGL::MG_Util::SelfTest diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index 6ca30fd6..4b071b3a 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -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 "