mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend, MG_Test, MG_IntegrationTest, scripts] (P7): choose the multisample depth/stencil resolve arm by a device probe with a shader-arm control instead of the Qualcomm vendor id
This commit is contained in:
@@ -655,6 +655,11 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
# and its codec against protocol.fbs's SurfaceOp/SurfaceReply (the spawn arm's seam).
|
||||
MobileGL/MG_Remote/Server/SurfaceControlFrame.cpp
|
||||
MobileGL/MG_Remote/Protocol/SurfaceOpCodec.cpp
|
||||
# P7 gate 5 (g5-msprobe): the Magma wire arm's two multisample depth/stencil resolve
|
||||
# arms and the probe that measures the first against the second, shared by the
|
||||
# renderer's bring-up and the Vulkan POST. Wire-only - the monolith records neither arm -
|
||||
# so it stays out of the pull build (gate G1).
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
// P7 wave 2 package B3: rule I's tally for WireDraw.inc's silent draw drops.
|
||||
#include "WireDeclineTally.h"
|
||||
#include "WireDepthResolveArm.h"
|
||||
#include "WireDepthResolveProbe.h"
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
@@ -15148,28 +15149,9 @@ 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).
|
||||
//
|
||||
// THE VENDOR THE POLICY READS CAN BE SUBSTITUTED, AND ONLY HERE (review round). No host
|
||||
// lane runs on a Qualcomm device, so the order this line picks for the Redmi ran nowhere
|
||||
// a lane could see it: dropping this assignment, or the `m_wirePreferShaderDepthResolve ||`
|
||||
// in ResolveWireDepthStencil, left every lane green and put
|
||||
// KHR-GL46.direct_state_access.renderbuffers_storage_multisample back at 54 Fail lines on
|
||||
// the device. MGITEST_MAGMA_DEPTH_RESOLVE_VENDOR_ID (strtoul base 0, so `0x5143`) replaces
|
||||
// the id handed to WirePrefersShaderDepthResolve and nothing else - no other vendor
|
||||
// check reads it - and the DirectVulkan.{Split,Spawn}.MsResolveQ./MsFlipQ. entries set it
|
||||
// to Qualcomm's id and read ResolveWireDepthStencil's arm line back from the server log.
|
||||
Uint32 depthResolvePolicyVendor = m_physicalDevice.properties.vendorID;
|
||||
if (const char* substitute = std::getenv("MGITEST_MAGMA_DEPTH_RESOLVE_VENDOR_ID");
|
||||
substitute != nullptr && *substitute != '\0')
|
||||
depthResolvePolicyVendor = static_cast<Uint32>(std::strtoul(substitute, nullptr, 0));
|
||||
m_wirePreferShaderDepthResolve = MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
WirePrefersShaderDepthResolve(depthResolvePolicyVendor);
|
||||
if (m_wirePreferShaderDepthResolve)
|
||||
MGLOG_I("DirectVulkan: vendor 0x%x (policy 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, depthResolvePolicyVendor);
|
||||
// P7 gate 5 (g5-msprobe): which multisample depth/stencil resolve arm goes first is measured
|
||||
// once the device exists (ArmWireDepthResolveOrder, below), not keyed on the vendor.
|
||||
m_wirePreferShaderDepthResolve = false;
|
||||
#endif
|
||||
if ((descriptorIndexingCore || descriptorIndexingExtension) && getPhysicalDeviceFeatures2 != nullptr &&
|
||||
getPhysicalDeviceProperties2 != nullptr) {
|
||||
@@ -15826,8 +15808,81 @@ void main() {
|
||||
// Last, because it records on m_graphicsQueue: decide the PRIMITIVES_GENERATED
|
||||
// reroute for XFB-inactive draws. Nothing else has touched the queue yet.
|
||||
ArmPrimGenReroute();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Same terms: it records on m_graphicsQueue, which nothing but the probe above has used.
|
||||
ArmWireDepthResolveOrder();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P7 gate 5 (g5-msprobe): THE ARM ORDER OF THE MULTISAMPLE DEPTH/STENCIL RESOLVE IS A MEASURED
|
||||
// PROPERTY OF THIS DEVICE. WireDepthResolveArm.h says why (the Adreno 830's no-draw
|
||||
// VK_KHR_depth_stencil_resolve pass writes nothing), WireDepthResolveProbe.h what is run. The
|
||||
// answer is a device property, so it is memoized per process like the PRIMITIVES_GENERATED
|
||||
// probe's, and logged once with every reading - the line the device evidence quotes.
|
||||
//
|
||||
// THE MEASUREMENT CAN BE REPLACED, AND ONLY HERE: MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=bug|clean
|
||||
// hands ChooseWireDepthResolveArm a canned measurement instead of running the probe - no host
|
||||
// lane runs on a device with the defect - and the DirectVulkan.{Split,Spawn}.MsResolveBug./
|
||||
// MsFlipBug. entries force `bug` and read ResolveWireDepthStencil's arm line back from the
|
||||
// server log. The canned measurement is evaluated like a real one, so those entries go red
|
||||
// when the evaluation stops detecting the defect.
|
||||
void VulkanRenderer::ArmWireDepthResolveOrder() {
|
||||
m_wirePreferShaderDepthResolve = false;
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
// ResolveWireDepthStencil's own test for the render-pass arm, less its per-format half
|
||||
// (the probe asks that per format): without it the shader pass is the only arm.
|
||||
const Bool renderPassArmAvailable = !MagmaWireForcedShaderDepthResolve() && m_wireCreateRenderPass2 &&
|
||||
(m_wireDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT) != 0;
|
||||
static const WireDepthResolveArmChoice s_choice = [&]() {
|
||||
const char* knobValue = std::getenv("MGITEST_MAGMA_DEPTH_RESOLVE_PROBE");
|
||||
const WireDepthResolveProbeKnob knob = ParseWireDepthResolveProbeKnob(knobValue);
|
||||
if (knob == WireDepthResolveProbeKnob::Unrecognised)
|
||||
MGLOG_W("DirectVulkan: MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=%s is neither `bug` nor `clean`; "
|
||||
"the resolve probe runs as if it were unset", knobValue);
|
||||
WireDepthResolveArmChoice choice = ChooseWireDepthResolveArm(knob, renderPassArmAvailable, [&]() {
|
||||
WireDepthResolveProbeContext context;
|
||||
context.physicalDevice = m_physicalDevice.handle;
|
||||
context.device = m_device;
|
||||
context.queue = m_graphicsQueue;
|
||||
context.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
|
||||
context.createRenderPass2 = m_wireCreateRenderPass2;
|
||||
context.depthResolveModes = m_wireDepthResolveModes;
|
||||
context.stencilResolveModes = m_wireStencilResolveModes;
|
||||
context.shaderStencilExport = m_wireShaderStencilExport;
|
||||
return RunWireDepthResolveProbe(context);
|
||||
});
|
||||
if (choice.measurement.fenceWaitTimedOut) {
|
||||
// Its objects are leaked on purpose (see the PRIMITIVES_GENERATED probe above for
|
||||
// why this device must not be idle-waited on their account).
|
||||
MGLOG_W("DirectVulkan: the depth/stencil resolve probe timed out waiting on its own "
|
||||
"submission; its Vulkan objects are deliberately leaked and the render-pass arm "
|
||||
"keeps its place");
|
||||
} else if (choice.verdict == WireDepthResolveProbeVerdict::Inconclusive) {
|
||||
MGLOG_W("DirectVulkan: depth/stencil resolve probe verdict=inconclusive - the shader "
|
||||
"control did not resolve the probe's inputs either, so nothing is concluded about "
|
||||
"the render pass and the default arm order stands");
|
||||
}
|
||||
MGLOG_I("DirectVulkan: depth/stencil resolve probe (%s) verdict=%s: %s%s%s",
|
||||
choice.measurement.fromKnob ? "forced by MGITEST_MAGMA_DEPTH_RESOLVE_PROBE"
|
||||
: renderPassArmAvailable ? "measured on this device"
|
||||
: "not run: no render-pass arm",
|
||||
WireDepthResolveProbeVerdictName(choice.verdict),
|
||||
choice.preferShader ? "the shader pass resolves first, the no-draw render pass is the fallback"
|
||||
: (renderPassArmAvailable || choice.measurement.fromKnob)
|
||||
? "the VK_KHR_depth_stencil_resolve render pass resolves first"
|
||||
: "the shader pass is the only arm",
|
||||
choice.measurement.failureReason.empty() ? "" : "; ",
|
||||
choice.measurement.failureReason.c_str());
|
||||
for (const WireDepthResolveFormatReading& reading : choice.measurement.formats)
|
||||
MGLOG_I("DirectVulkan: depth/stencil resolve probe %s",
|
||||
DescribeWireDepthResolveFormat(reading).c_str());
|
||||
return choice;
|
||||
}();
|
||||
m_wirePreferShaderDepthResolve = s_choice.preferShader;
|
||||
}
|
||||
#endif
|
||||
|
||||
void VulkanRenderer::ArmPrimGenReroute() {
|
||||
using namespace MG_Util::SelfTest;
|
||||
m_primGenRerouteKind = PrimGenRerouteKind::None;
|
||||
|
||||
@@ -556,9 +556,14 @@ 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).
|
||||
// The resolve probe's verdict for this device (WireDepthResolveArm.h,
|
||||
// WireDepthResolveProbe.h): true when the no-draw render pass was measured leaving its
|
||||
// target unwritten while the shader control resolved - the shader arm then resolves
|
||||
// first and the render-pass arm is the fallback (P7 gate 5, g5-msprobe).
|
||||
Bool m_wirePreferShaderDepthResolve = false;
|
||||
// Runs the probe (memoized per process) and sets the member above. Called at the end of
|
||||
// device creation, after ArmPrimGenReroute: it records on m_graphicsQueue.
|
||||
void ArmWireDepthResolveOrder();
|
||||
Bool BlitWireColorToDefault(WireImage source, WireImage destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
// P7 gate 5 (g5-msrbo): WHICH ARM RESOLVES A MULTISAMPLE DEPTH/STENCIL ASPECT FIRST.
|
||||
// P7 gate 5 (g5-msrbo, g5-msprobe): 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
|
||||
@@ -18,17 +18,222 @@
|
||||
// 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.
|
||||
// (MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=1).
|
||||
//
|
||||
// A pure function of the vendor id so the policy is a unit-testable fact rather than a branch
|
||||
// buried in the apply thread.
|
||||
// A DRIVER THAT CLAIMS WHAT IT CANNOT DO IS A POST PROBE, NOT A VENDOR QUIRK (standing rule,
|
||||
// 2026-08-22). The first fix keyed the order on Qualcomm's vendor id; it is replaced by a probe
|
||||
// that reproduces the production resolve on the actual VkDevice (WireDepthResolveProbe.cpp) and
|
||||
// asks the one question the order depends on: does the no-draw render pass write the resolve
|
||||
// target? A driver that fixes the defect gets the fast arm back, and a driver of any vendor that
|
||||
// has it gets the shader arm - neither needs a table edit.
|
||||
//
|
||||
// EVERY BUG PROBE CARRIES A CONTROL (MG_Util/SelfTest/DriverBugProbes.h). The control is the
|
||||
// production SHADER arm fed the same inputs: when it does not resolve them either, the probe has
|
||||
// measured its own setup (or a device that cannot resolve depth at all) rather than the render
|
||||
// pass, the verdict is INCONCLUSIVE, and the default order stands.
|
||||
//
|
||||
// This header is the pure half - the measurement, the verdict, the test knob and the choice - so
|
||||
// a unit test pins every mapping with a fake result source (MG_Test/Pipeline/PipelineQuirkTest.cpp).
|
||||
// The Vulkan half that fills the measurement is WireDepthResolveProbe.{h,cpp}.
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
inline constexpr Uint32 kWireVendorIdQualcomm = 0x5143u;
|
||||
// One aspect of one probed format, both arms. Values are the RAW aspect words the
|
||||
// buffer copy returns (D16: 16 bits; D24: the low 24 bits of the 32-bit word; D32F: the float's
|
||||
// bits; stencil: 8 bits), compared within `tolerance` (1 LSB for a UNORM depth, 0 otherwise).
|
||||
struct WireDepthResolveAspectReading {
|
||||
// The format has this aspect and the render-pass arm's copy of it was read back.
|
||||
Bool measured = false;
|
||||
Uint32 texels = 0;
|
||||
Uint32 expected = 0; // the multisample source's cleared value
|
||||
Uint32 sentinel = 0; // what the resolve target was pre-filled with
|
||||
Uint32 tolerance = 0;
|
||||
// THE SUBJECT: VK_KHR_depth_stencil_resolve render pass, no draw.
|
||||
Uint32 renderPassMatches = 0; // texels within tolerance of `expected`
|
||||
Uint32 renderPassSentinels = 0; // texels still holding the sentinel
|
||||
Uint32 renderPassFirst = 0; // texel (0,0)
|
||||
// THE CONTROL: the production shader pass, same inputs. False where it cannot run for this
|
||||
// aspect (a stencil aspect without VK_EXT_shader_stencil_export, a failed pipeline).
|
||||
Bool shaderRan = false;
|
||||
Uint32 shaderMatches = 0;
|
||||
Uint32 shaderFirst = 0;
|
||||
};
|
||||
|
||||
inline constexpr Bool WirePrefersShaderDepthResolve(Uint32 vendorId) {
|
||||
return vendorId == kWireVendorIdQualcomm;
|
||||
struct WireDepthResolveFormatReading {
|
||||
String name; // e.g. "D24_UNORM_S8_UINT"
|
||||
Int32 format = 0; // the VkFormat
|
||||
Uint32 samples = 0;
|
||||
// False when the format was not probed; `skipReason` says why (unsupported, the render-pass
|
||||
// arm would not be taken for it, an object the probe needed could not be created).
|
||||
Bool ran = false;
|
||||
String skipReason;
|
||||
WireDepthResolveAspectReading depth, stencil;
|
||||
};
|
||||
|
||||
struct WireDepthResolveProbeMeasurement {
|
||||
// At least one format was recorded, submitted and read back.
|
||||
Bool ran = false;
|
||||
// The bounded fence wait expired: every object the probe created was deliberately leaked
|
||||
// (the queue may still be executing it), and nothing was read back.
|
||||
Bool fenceWaitTimedOut = false;
|
||||
String failureReason;
|
||||
// Came from MGITEST_MAGMA_DEPTH_RESOLVE_PROBE, not from the device.
|
||||
Bool fromKnob = false;
|
||||
Vector<WireDepthResolveFormatReading> formats;
|
||||
};
|
||||
|
||||
enum class WireDepthResolveProbeVerdict : Uint8 {
|
||||
// Nothing was measured: the render-pass arm is not available (nothing to choose between),
|
||||
// or the probe could not set itself up. The default order stands.
|
||||
NotRun,
|
||||
// Measured, but no format's CONTROL resolved: the probe cannot tell the render pass's
|
||||
// defect from its own. The default order stands, and the report must never call it a bug.
|
||||
Inconclusive,
|
||||
// Every format whose control resolved was resolved by the render pass too.
|
||||
Clean,
|
||||
// The defect: on at least one format the shader control resolved the inputs and the
|
||||
// render pass did not (wrote nothing - the sentinel survived - or wrote wrong values).
|
||||
RenderPassResolveBroken,
|
||||
};
|
||||
|
||||
inline const char* WireDepthResolveProbeVerdictName(WireDepthResolveProbeVerdict verdict) {
|
||||
switch (verdict) {
|
||||
case WireDepthResolveProbeVerdict::NotRun: return "not-run";
|
||||
case WireDepthResolveProbeVerdict::Inconclusive: return "inconclusive";
|
||||
case WireDepthResolveProbeVerdict::Clean: return "clean";
|
||||
case WireDepthResolveProbeVerdict::RenderPassResolveBroken: return "render-pass-resolve-broken";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
inline Bool WireDepthResolveControlHolds(const WireDepthResolveAspectReading& aspect) {
|
||||
return aspect.measured && aspect.shaderRan && aspect.texels > 0 && aspect.shaderMatches == aspect.texels;
|
||||
}
|
||||
|
||||
inline Bool WireDepthResolveSubjectHolds(const WireDepthResolveAspectReading& aspect) {
|
||||
return aspect.texels > 0 && aspect.renderPassMatches == aspect.texels;
|
||||
}
|
||||
|
||||
// An aspect only counts where its control holds; a format is conclusive when any of its
|
||||
// aspects is. The render pass resolves every aspect of the format in one pass, so one aspect
|
||||
// it fails is the defect for the whole arm.
|
||||
inline WireDepthResolveProbeVerdict EvaluateWireDepthResolveProbe(const WireDepthResolveProbeMeasurement& measurement) {
|
||||
if (!measurement.ran || measurement.fenceWaitTimedOut) return WireDepthResolveProbeVerdict::NotRun;
|
||||
Bool conclusive = false, broken = false;
|
||||
for (const WireDepthResolveFormatReading& reading : measurement.formats) {
|
||||
if (!reading.ran) continue;
|
||||
for (const WireDepthResolveAspectReading* aspect : {&reading.depth, &reading.stencil}) {
|
||||
if (!WireDepthResolveControlHolds(*aspect)) continue;
|
||||
conclusive = true;
|
||||
if (!WireDepthResolveSubjectHolds(*aspect)) broken = true;
|
||||
}
|
||||
}
|
||||
if (broken) return WireDepthResolveProbeVerdict::RenderPassResolveBroken;
|
||||
return conclusive ? WireDepthResolveProbeVerdict::Clean : WireDepthResolveProbeVerdict::Inconclusive;
|
||||
}
|
||||
|
||||
// THE TEST KNOB, read by the SERVER (split and spawn arms only; the tcp server is a lane fixture
|
||||
// whose environment no entry reaches - spawn_lane_parity.py's MAGMA_SERVER_ENV_KNOB_NO_TCP).
|
||||
// Lavapipe resolves correctly through both arms, so no host lane can reach the verdict that
|
||||
// matters on the Redmi. MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=bug|clean replaces the MEASUREMENT
|
||||
// with a canned one that says exactly that, and nothing else: the canned measurement still goes
|
||||
// through EvaluateWireDepthResolveProbe and the same member, so an entry that forces `bug` is red
|
||||
// when the evaluation stops detecting the defect.
|
||||
enum class WireDepthResolveProbeKnob : Uint8 { Measure, ForceBug, ForceClean, Unrecognised };
|
||||
|
||||
inline WireDepthResolveProbeKnob ParseWireDepthResolveProbeKnob(const char* value) {
|
||||
if (value == nullptr || *value == '\0') return WireDepthResolveProbeKnob::Measure;
|
||||
if (std::strcmp(value, "bug") == 0) return WireDepthResolveProbeKnob::ForceBug;
|
||||
if (std::strcmp(value, "clean") == 0) return WireDepthResolveProbeKnob::ForceClean;
|
||||
return WireDepthResolveProbeKnob::Unrecognised;
|
||||
}
|
||||
|
||||
// One packed format's worth of readings: the control resolved both aspects; the render pass
|
||||
// either did too (`renderPassWrites`) or left the sentinel everywhere.
|
||||
inline WireDepthResolveProbeMeasurement CannedWireDepthResolveProbeMeasurement(Bool renderPassWrites) {
|
||||
WireDepthResolveProbeMeasurement measurement;
|
||||
measurement.ran = true;
|
||||
measurement.fromKnob = true;
|
||||
WireDepthResolveFormatReading reading;
|
||||
reading.name = "MGITEST_MAGMA_DEPTH_RESOLVE_PROBE";
|
||||
reading.samples = 4;
|
||||
reading.ran = true;
|
||||
const auto fill = [&](WireDepthResolveAspectReading& aspect, Uint32 expected, Uint32 sentinel) {
|
||||
aspect.measured = true;
|
||||
aspect.texels = 16;
|
||||
aspect.expected = expected;
|
||||
aspect.sentinel = sentinel;
|
||||
aspect.renderPassMatches = renderPassWrites ? 16 : 0;
|
||||
aspect.renderPassSentinels = renderPassWrites ? 0 : 16;
|
||||
aspect.renderPassFirst = renderPassWrites ? expected : sentinel;
|
||||
aspect.shaderRan = true;
|
||||
aspect.shaderMatches = 16;
|
||||
aspect.shaderFirst = expected;
|
||||
};
|
||||
fill(reading.depth, 0x400000u, 0xBFFFFFu);
|
||||
fill(reading.stencil, 0x5Au, 0xA5u);
|
||||
measurement.formats.push_back(Move(reading));
|
||||
return measurement;
|
||||
}
|
||||
|
||||
struct WireDepthResolveArmChoice {
|
||||
WireDepthResolveProbeMeasurement measurement;
|
||||
WireDepthResolveProbeVerdict verdict = WireDepthResolveProbeVerdict::NotRun;
|
||||
// m_wirePreferShaderDepthResolve: the shader pass goes first, the render pass is the
|
||||
// fallback for what the shader cannot write.
|
||||
Bool preferShader = false;
|
||||
};
|
||||
|
||||
// The whole decision, with the Vulkan half behind `measure` (called at most once, and only when
|
||||
// the knob does not decide and the render-pass arm exists at all: without it the shader pass
|
||||
// is the only arm and there is no order to choose).
|
||||
template <typename MeasureFn>
|
||||
WireDepthResolveArmChoice ChooseWireDepthResolveArm(WireDepthResolveProbeKnob knob, Bool renderPassArmAvailable,
|
||||
MeasureFn&& measure) {
|
||||
WireDepthResolveArmChoice choice;
|
||||
if (knob == WireDepthResolveProbeKnob::ForceBug || knob == WireDepthResolveProbeKnob::ForceClean) {
|
||||
choice.measurement = CannedWireDepthResolveProbeMeasurement(knob == WireDepthResolveProbeKnob::ForceClean);
|
||||
} else if (renderPassArmAvailable) {
|
||||
choice.measurement = measure();
|
||||
}
|
||||
choice.verdict = EvaluateWireDepthResolveProbe(choice.measurement);
|
||||
choice.preferShader = choice.verdict == WireDepthResolveProbeVerdict::RenderPassResolveBroken;
|
||||
return choice;
|
||||
}
|
||||
|
||||
// One format's readings, both arms: "D24_UNORM_S8_UINT x4: depth expected 0x400000 sentinel
|
||||
// 0xbfffff: render pass 0/16 (first 0xbfffff, sentinel 16), shader control 16/16 (first
|
||||
// 0x400000) / stencil ...".
|
||||
inline String DescribeWireDepthResolveFormat(const WireDepthResolveFormatReading& reading) {
|
||||
const auto aspectText = [](const char* name, const WireDepthResolveAspectReading& aspect) {
|
||||
String text = format("{} expected 0x{:x} sentinel 0x{:x}: render pass {}/{} (first 0x{:x}, sentinel {}), ",
|
||||
name, aspect.expected, aspect.sentinel, aspect.renderPassMatches, aspect.texels,
|
||||
aspect.renderPassFirst, aspect.renderPassSentinels);
|
||||
if (aspect.shaderRan) {
|
||||
text += format("shader control {}/{} (first 0x{:x})", aspect.shaderMatches, aspect.texels,
|
||||
aspect.shaderFirst);
|
||||
} else {
|
||||
text += "no shader control";
|
||||
}
|
||||
return text;
|
||||
};
|
||||
String out = reading.name;
|
||||
if (!reading.ran) return out + " skipped (" + reading.skipReason + ")";
|
||||
out += format(" x{}: ", reading.samples);
|
||||
if (reading.depth.measured) out += aspectText("depth", reading.depth);
|
||||
if (reading.depth.measured && reading.stencil.measured) out += " / ";
|
||||
if (reading.stencil.measured) out += aspectText("stencil", reading.stencil);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Every format, "; "-separated, for the POST row.
|
||||
inline String DescribeWireDepthResolveProbe(const WireDepthResolveProbeMeasurement& measurement) {
|
||||
if (!measurement.ran) {
|
||||
return measurement.failureReason.empty() ? String("not run") : "not run (" + measurement.failureReason + ")";
|
||||
}
|
||||
String out;
|
||||
for (const WireDepthResolveFormatReading& reading : measurement.formats) {
|
||||
if (!out.empty()) out += "; ";
|
||||
out += DescribeWireDepthResolveFormat(reading);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -0,0 +1,987 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp
|
||||
// Copyright (c) 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
|
||||
//
|
||||
// See WireDepthResolveProbe.h for the arms and the probe's shape, WireDepthResolveArm.h for the
|
||||
// verdict. The plumbing is the PRIMITIVES_GENERATED probe's (MG_Util/SelfTest/
|
||||
// PrimitivesGeneratedNoXfbProbe.cpp): one throwaway command buffer, a bounded fence wait that
|
||||
// leaks rather than idle-waits a hung GPU, teardown on every other path.
|
||||
|
||||
#include "WireDepthResolveProbe.h"
|
||||
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
#include "WireMultisampleResolveSpirv.h"
|
||||
|
||||
VkImageAspectFlags WireDepthStencilFormatAspects(VkFormat format) {
|
||||
if (format == VK_FORMAT_S8_UINT) return VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
if (format == VK_FORMAT_D16_UNORM_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
format == VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||
return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
return VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo WireDepthStencilViewInfo(VkImage image, VkFormat format, VkImageAspectFlags aspects,
|
||||
Uint32 level, Uint32 layer) {
|
||||
VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
info.image = image;
|
||||
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
info.format = format;
|
||||
info.subresourceRange = {aspects, level, 1, layer, 1};
|
||||
return info;
|
||||
}
|
||||
|
||||
VkResult CreateWireDepthStencilResolvePass(PFN_vkCreateRenderPass2 createRenderPass2, VkDevice device,
|
||||
VkFormat format, VkSampleCountFlagBits samples, VkRenderPass* pass) {
|
||||
const VkImageAspectFlags formatAspects = WireDepthStencilFormatAspects(format);
|
||||
VkAttachmentDescription2 attachments[2]{};
|
||||
for (Uint32 i = 0; i < 2; ++i) {
|
||||
auto& attachment = attachments[i];
|
||||
attachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
|
||||
attachment.format = format;
|
||||
attachment.samples = i == 0 ? samples : VK_SAMPLE_COUNT_1_BIT;
|
||||
attachment.loadOp = attachment.stencilLoadOp =
|
||||
i == 0 ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment.storeOp = attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
VkAttachmentReference2 sourceReference{VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2};
|
||||
sourceReference.attachment = 0;
|
||||
sourceReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
sourceReference.aspectMask = formatAspects;
|
||||
VkAttachmentReference2 resolveReference = sourceReference;
|
||||
resolveReference.attachment = 1;
|
||||
VkSubpassDescriptionDepthStencilResolve resolve{VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE};
|
||||
resolve.depthResolveMode =
|
||||
(formatAspects & VK_IMAGE_ASPECT_DEPTH_BIT) ? VK_RESOLVE_MODE_SAMPLE_ZERO_BIT : VK_RESOLVE_MODE_NONE;
|
||||
resolve.stencilResolveMode =
|
||||
(formatAspects & VK_IMAGE_ASPECT_STENCIL_BIT) ? VK_RESOLVE_MODE_SAMPLE_ZERO_BIT : VK_RESOLVE_MODE_NONE;
|
||||
resolve.pDepthStencilResolveAttachment = &resolveReference;
|
||||
VkSubpassDescription2 subpass{VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2};
|
||||
subpass.pNext = &resolve;
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.pDepthStencilAttachment = &sourceReference;
|
||||
VkRenderPassCreateInfo2 passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2};
|
||||
passInfo.attachmentCount = 2;
|
||||
passInfo.pAttachments = attachments;
|
||||
passInfo.subpassCount = 1;
|
||||
passInfo.pSubpasses = &subpass;
|
||||
return createRenderPass2(device, &passInfo, nullptr, pass);
|
||||
}
|
||||
|
||||
VkResult CreateWireShaderResolveKit(VkDevice device, Bool shaderStencilExport, WireShaderResolveKit& kit) {
|
||||
const auto shader = [&](const Uint32* code, SizeT bytes, VkShaderModule& module) {
|
||||
VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
info.codeSize = bytes;
|
||||
info.pCode = code;
|
||||
return vkCreateShaderModule(device, &info, nullptr, &module);
|
||||
};
|
||||
VkResult result =
|
||||
shader(kWireMultisampleResolveVertexSpirv, sizeof(kWireMultisampleResolveVertexSpirv), kit.vertex);
|
||||
if (result != VK_SUCCESS) return result;
|
||||
result = shader(kWireMultisampleDepthResolveFragmentSpirv, sizeof(kWireMultisampleDepthResolveFragmentSpirv),
|
||||
kit.depthFragment);
|
||||
if (result != VK_SUCCESS) return result;
|
||||
// THE STENCIL MODULE IS ONLY CREATED WHERE IT CAN RUN: its SPIR-V declares the
|
||||
// StencilExportEXT capability, and handing a module with an unsupported capability to
|
||||
// vkCreateShaderModule is undefined behaviour - not an error this could catch.
|
||||
if (shaderStencilExport) {
|
||||
result = shader(kWireMultisampleStencilResolveFragmentSpirv,
|
||||
sizeof(kWireMultisampleStencilResolveFragmentSpirv), kit.stencilFragment);
|
||||
if (result != VK_SUCCESS) return result;
|
||||
}
|
||||
const VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
|
||||
VkDescriptorSetLayoutCreateInfo layout{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
layout.bindingCount = 1;
|
||||
layout.pBindings = &binding;
|
||||
result = vkCreateDescriptorSetLayout(device, &layout, nullptr, &kit.descriptorLayout);
|
||||
if (result != VK_SUCCESS) return result;
|
||||
// NO PUSH CONSTANT RANGE AT ALL: the pass reads its coordinate from gl_FragCoord and its
|
||||
// rectangle from the scissor, so neither stage takes an input.
|
||||
VkPipelineLayoutCreateInfo pipelineLayout{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||
pipelineLayout.setLayoutCount = 1;
|
||||
pipelineLayout.pSetLayouts = &kit.descriptorLayout;
|
||||
result = vkCreatePipelineLayout(device, &pipelineLayout, nullptr, &kit.pipelineLayout);
|
||||
if (result != VK_SUCCESS) return result;
|
||||
// NEAREST: texelFetch ignores the sampler's filter, and most devices do not advertise
|
||||
// linear filtering for a depth format anyway. The sampler exists only because GLSL's
|
||||
// sampler2DMS is a COMBINED image sampler.
|
||||
VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||||
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
sampler.addressModeU = sampler.addressModeV = sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler.magFilter = sampler.minFilter = VK_FILTER_NEAREST;
|
||||
sampler.maxLod = 0.0f;
|
||||
return vkCreateSampler(device, &sampler, nullptr, &kit.nearest);
|
||||
}
|
||||
|
||||
void DestroyWireShaderResolveKit(VkDevice device, WireShaderResolveKit& kit) {
|
||||
vkDestroySampler(device, kit.nearest, nullptr);
|
||||
vkDestroyPipelineLayout(device, kit.pipelineLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device, kit.descriptorLayout, nullptr);
|
||||
vkDestroyShaderModule(device, kit.vertex, nullptr);
|
||||
vkDestroyShaderModule(device, kit.depthFragment, nullptr);
|
||||
vkDestroyShaderModule(device, kit.stencilFragment, nullptr);
|
||||
kit = {};
|
||||
}
|
||||
|
||||
VkResult CreateWireShaderResolveRenderPass(VkDevice device, VkFormat format, VkRenderPass* pass) {
|
||||
VkAttachmentDescription attachment{};
|
||||
attachment.format = format;
|
||||
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
// DONT_CARE on BOTH aspects: the pass writes every texel of the scissored rect for the
|
||||
// aspect it owns, and the caller copies only that aspect out. The other aspect of a packed
|
||||
// format is left undefined on purpose - it is never read, and LOADing it would order this
|
||||
// pass behind a write nobody is waiting for.
|
||||
attachment.loadOp = attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment.storeOp = attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
const VkAttachmentReference depthStencil{0, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.pDepthStencilAttachment = &depthStencil;
|
||||
VkRenderPassCreateInfo info{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
info.attachmentCount = 1;
|
||||
info.pAttachments = &attachment;
|
||||
info.subpassCount = 1;
|
||||
info.pSubpasses = &subpass;
|
||||
return vkCreateRenderPass(device, &info, nullptr, pass);
|
||||
}
|
||||
|
||||
VkResult CreateWireShaderResolvePipeline(VkDevice device, const WireShaderResolveKit& kit, VkRenderPass pass,
|
||||
VkImageAspectFlags aspect, VkPipeline* pipeline) {
|
||||
const VkShaderModule fragment =
|
||||
aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? kit.stencilFragment : kit.depthFragment;
|
||||
if (fragment == VK_NULL_HANDLE || kit.vertex == VK_NULL_HANDLE) return VK_ERROR_FEATURE_NOT_PRESENT;
|
||||
VkPipelineShaderStageCreateInfo stages[2]{};
|
||||
for (auto& stage : stages) {
|
||||
stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stage.pName = "main";
|
||||
}
|
||||
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
stages[0].module = kit.vertex;
|
||||
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
stages[1].module = fragment;
|
||||
const VkPipelineVertexInputStateCreateInfo vertex{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
||||
VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||
assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
VkPipelineViewportStateCreateInfo viewport{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||
viewport.viewportCount = viewport.scissorCount = 1;
|
||||
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
raster.cullMode = VK_CULL_MODE_NONE;
|
||||
raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
raster.lineWidth = 1.0f;
|
||||
VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencilState{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
depthStencilState.minDepthBounds = 0.0f;
|
||||
depthStencilState.maxDepthBounds = 1.0f;
|
||||
if (aspect == VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||
// ALWAYS + write: gl_FragDepth is the answer, and a comparison against whatever
|
||||
// DONT_CARE left behind would discard some of it.
|
||||
depthStencilState.depthTestEnable = VK_TRUE;
|
||||
depthStencilState.depthWriteEnable = VK_TRUE;
|
||||
depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
} else {
|
||||
// REPLACE on every outcome, compare ALWAYS, full write mask: that is what makes the
|
||||
// shader-exported reference the value that lands. The static `reference` is overridden
|
||||
// by gl_FragStencilRefARB, which is the whole point of the extension.
|
||||
depthStencilState.stencilTestEnable = VK_TRUE;
|
||||
VkStencilOpState op{};
|
||||
op.failOp = op.passOp = op.depthFailOp = VK_STENCIL_OP_REPLACE;
|
||||
op.compareOp = VK_COMPARE_OP_ALWAYS;
|
||||
op.compareMask = op.writeMask = 0xFFu;
|
||||
op.reference = 0;
|
||||
depthStencilState.front = depthStencilState.back = op;
|
||||
}
|
||||
// No colour attachment in this render pass, so no attachment states either.
|
||||
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
||||
const VkDynamicState dynamicStates[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
|
||||
dynamic.dynamicStateCount = 2;
|
||||
dynamic.pDynamicStates = dynamicStates;
|
||||
VkGraphicsPipelineCreateInfo info{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
info.stageCount = 2;
|
||||
info.pStages = stages;
|
||||
info.pVertexInputState = &vertex;
|
||||
info.pInputAssemblyState = &assembly;
|
||||
info.pViewportState = &viewport;
|
||||
info.pRasterizationState = &raster;
|
||||
info.pMultisampleState = &samples;
|
||||
info.pDepthStencilState = &depthStencilState;
|
||||
info.pColorBlendState = &blend;
|
||||
info.pDynamicState = &dynamic;
|
||||
info.layout = kit.pipelineLayout;
|
||||
info.renderPass = pass;
|
||||
return vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &info, nullptr, pipeline);
|
||||
}
|
||||
|
||||
void RecordWireShaderResolveDraw(VkCommandBuffer commandBuffer, const WireShaderResolveKit& kit,
|
||||
VkRenderPass pass, VkPipeline pipeline, VkFramebuffer framebuffer,
|
||||
VkDescriptorSet descriptorSet, VkExtent2D attachmentExtent, VkRect2D rect) {
|
||||
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
begin.renderPass = pass;
|
||||
begin.framebuffer = framebuffer;
|
||||
// The RECTANGLE is the render area, exactly as the render-pass arm's renderArea is - the
|
||||
// scratch outside it is never read.
|
||||
begin.renderArea = rect;
|
||||
vkCmdBeginRenderPass(commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
// THE VIEWPORT IS THE WHOLE ATTACHMENT AND THE SCISSOR IS THE RECT, and the two are not
|
||||
// interchangeable here: gl_FragCoord is what the fragment shader fetches by, so the
|
||||
// clip-space-to-attachment mapping has to be the identity for every rectangle. A viewport
|
||||
// shrunk onto the rect would slide the whole image instead of moving a window over it.
|
||||
const VkViewport viewport{0.0f, 0.0f, static_cast<float>(attachmentExtent.width),
|
||||
static_cast<float>(attachmentExtent.height), 0.0f, 1.0f};
|
||||
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
|
||||
vkCmdSetScissor(commandBuffer, 0, 1, &rect);
|
||||
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, kit.pipelineLayout, 0, 1,
|
||||
&descriptorSet, 0, nullptr);
|
||||
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
|
||||
vkCmdEndRenderPass(commandBuffer);
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr Uint32 kProbeExtent = 4;
|
||||
constexpr Uint32 kProbeTexels = kProbeExtent * kProbeExtent;
|
||||
// Every Vulkan device must support 4 samples on a depth/stencil attachment
|
||||
// (framebufferDepthSampleCounts), and the CTS case the probe stands for fails at 2 and 4.
|
||||
constexpr VkSampleCountFlagBits kProbeSamples = VK_SAMPLE_COUNT_4_BIT;
|
||||
constexpr Float kClearDepth = 0.25f;
|
||||
constexpr Float kSentinelDepth = 0.75f;
|
||||
constexpr Uint32 kClearStencil = 0x5Au;
|
||||
constexpr Uint32 kSentinelStencil = 0xA5u;
|
||||
constexpr Uint64 kFenceTimeoutNs = 5ull * 1000ull * 1000ull * 1000ull;
|
||||
|
||||
// The formats the wire maps GL's depth/stencil internal formats onto (VkTextureManager),
|
||||
// packed first: the case that set all this off failed on every one of them.
|
||||
struct ProbeFormat {
|
||||
VkFormat format;
|
||||
const char* name;
|
||||
};
|
||||
constexpr ProbeFormat kProbeFormats[] = {
|
||||
{VK_FORMAT_D24_UNORM_S8_UINT, "D24_UNORM_S8_UINT"},
|
||||
{VK_FORMAT_D32_SFLOAT_S8_UINT, "D32_SFLOAT_S8_UINT"},
|
||||
{VK_FORMAT_D16_UNORM, "D16_UNORM"},
|
||||
{VK_FORMAT_X8_D24_UNORM_PACK32, "X8_D24_UNORM_PACK32"},
|
||||
{VK_FORMAT_D32_SFLOAT, "D32_SFLOAT"},
|
||||
{VK_FORMAT_S8_UINT, "S8_UINT"},
|
||||
};
|
||||
|
||||
// The usages production gives the two images: a wire multisample depth/stencil attachment
|
||||
// (VkTextureManager: SAMPLED | DEPTH_STENCIL_ATTACHMENT | TRANSFER_SRC | TRANSFER_DST) and
|
||||
// ResolveWireDepthStencil's resolve scratch (DEPTH_STENCIL_ATTACHMENT | TRANSFER_SRC).
|
||||
constexpr VkImageUsageFlags kSourceUsage = VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
constexpr VkImageUsageFlags kScratchUsage =
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
Bool IsFloatDepth(VkFormat format) {
|
||||
return format == VK_FORMAT_D32_SFLOAT || format == VK_FORMAT_D32_SFLOAT_S8_UINT;
|
||||
}
|
||||
|
||||
Uint32 DepthCopyBytes(VkFormat format) { return format == VK_FORMAT_D16_UNORM ? 2u : 4u; }
|
||||
|
||||
Uint32 EncodeDepth(VkFormat format, Float depth) {
|
||||
if (IsFloatDepth(format)) return std::bit_cast<Uint32>(depth);
|
||||
const double scale = format == VK_FORMAT_D16_UNORM ? 65535.0 : 16777215.0;
|
||||
return static_cast<Uint32>(std::lround(static_cast<double>(depth) * scale));
|
||||
}
|
||||
|
||||
Uint32 ReadDepthWord(VkFormat format, const Uint8* texel) {
|
||||
if (format == VK_FORMAT_D16_UNORM) {
|
||||
Uint16 word = 0;
|
||||
std::memcpy(&word, texel, sizeof(word));
|
||||
return word;
|
||||
}
|
||||
Uint32 word = 0;
|
||||
std::memcpy(&word, texel, sizeof(word));
|
||||
// A D24 aspect copies out as a 32-bit word whose top byte is undefined.
|
||||
return IsFloatDepth(format) ? word : (word & 0x00FFFFFFu);
|
||||
}
|
||||
|
||||
Uint32 WordDistance(Uint32 a, Uint32 b) { return a > b ? a - b : b - a; }
|
||||
|
||||
struct ProbeImage {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
struct ControlPass {
|
||||
Bool ready = false;
|
||||
ProbeImage target;
|
||||
VkRenderPass pass = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkImageView sampledView = VK_NULL_HANDLE;
|
||||
VkImageView targetView = VK_NULL_HANDLE;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||
VkDeviceSize offset = 0;
|
||||
};
|
||||
|
||||
struct FormatWork {
|
||||
SizeT reading = 0;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImageAspectFlags aspects = 0;
|
||||
ProbeImage subjectSource, controlSource, subjectTarget;
|
||||
VkRenderPass resolvePass = VK_NULL_HANDLE;
|
||||
VkImageView resolveViews[2]{};
|
||||
VkFramebuffer resolveFramebuffer = VK_NULL_HANDLE;
|
||||
VkDeviceSize subjectDepthOffset = 0, subjectStencilOffset = 0;
|
||||
ControlPass depthControl, stencilControl;
|
||||
};
|
||||
|
||||
// Everything the probe creates, destroyed together (or leaked together on a timeout).
|
||||
struct ProbeObjects {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
Vector<VkImage> images;
|
||||
Vector<VkDeviceMemory> memories;
|
||||
Vector<VkImageView> views;
|
||||
Vector<VkRenderPass> passes;
|
||||
Vector<VkFramebuffer> framebuffers;
|
||||
Vector<VkPipeline> pipelines;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
|
||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
WireShaderResolveKit kit;
|
||||
|
||||
void Destroy() {
|
||||
for (VkPipeline pipeline : pipelines) vkDestroyPipeline(device, pipeline, nullptr);
|
||||
for (VkFramebuffer framebuffer : framebuffers) vkDestroyFramebuffer(device, framebuffer, nullptr);
|
||||
for (VkRenderPass pass : passes) vkDestroyRenderPass(device, pass, nullptr);
|
||||
for (VkImageView view : views) vkDestroyImageView(device, view, nullptr);
|
||||
for (VkImage image : images) vkDestroyImage(device, image, nullptr);
|
||||
vkDestroyBuffer(device, buffer, nullptr);
|
||||
for (VkDeviceMemory memory : memories) vkFreeMemory(device, memory, nullptr);
|
||||
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
|
||||
vkDestroyCommandPool(device, commandPool, nullptr);
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
DestroyWireShaderResolveKit(device, kit);
|
||||
}
|
||||
};
|
||||
|
||||
Optional<Uint32> PickMemoryType(const VkPhysicalDeviceMemoryProperties& properties, Uint32 allowed,
|
||||
VkMemoryPropertyFlags wanted, VkMemoryPropertyFlags required) {
|
||||
Optional<Uint32> fallback;
|
||||
for (Uint32 i = 0; i < properties.memoryTypeCount; ++i) {
|
||||
if (!(allowed & (1u << i))) continue;
|
||||
const VkMemoryPropertyFlags flags = properties.memoryTypes[i].propertyFlags;
|
||||
if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) continue;
|
||||
if ((flags & required) != required) continue;
|
||||
if ((flags & wanted) == wanted) return i;
|
||||
if (!fallback) fallback = i;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void ImageBarrier(VkCommandBuffer commandBuffer, VkImage image, VkImageAspectFlags aspects,
|
||||
VkImageLayout oldLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStage,
|
||||
VkAccessFlags srcAccess, VkPipelineStageFlags dstStage, VkAccessFlags dstAccess) {
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = srcAccess;
|
||||
barrier.dstAccessMask = dstAccess;
|
||||
barrier.oldLayout = oldLayout;
|
||||
barrier.newLayout = newLayout;
|
||||
barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange = {aspects, 0, 1, 0, VK_REMAINING_ARRAY_LAYERS};
|
||||
vkCmdPipelineBarrier(commandBuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WireDepthResolveProbeMeasurement RunWireDepthResolveProbe(const WireDepthResolveProbeContext& context) {
|
||||
WireDepthResolveProbeMeasurement measurement;
|
||||
const VkDevice device = context.device;
|
||||
if (device == VK_NULL_HANDLE || context.queue == VK_NULL_HANDLE || context.createRenderPass2 == nullptr) {
|
||||
measurement.failureReason = "no device, queue or vkCreateRenderPass2";
|
||||
return measurement;
|
||||
}
|
||||
ProbeObjects objects;
|
||||
objects.device = device;
|
||||
Bool leak = false;
|
||||
struct Teardown {
|
||||
ProbeObjects& objects;
|
||||
Bool& leak;
|
||||
~Teardown() {
|
||||
if (!leak) objects.Destroy();
|
||||
}
|
||||
} teardown{objects, leak};
|
||||
const auto fail = [&](String reason) {
|
||||
measurement.ran = false;
|
||||
measurement.failureReason = Move(reason);
|
||||
return measurement;
|
||||
};
|
||||
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(context.physicalDevice, &memoryProperties);
|
||||
const auto makeImage = [&](VkFormat format, VkSampleCountFlagBits samples, VkImageUsageFlags usage,
|
||||
ProbeImage& out) -> Bool {
|
||||
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
info.imageType = VK_IMAGE_TYPE_2D;
|
||||
info.format = format;
|
||||
info.extent = {kProbeExtent, kProbeExtent, 1};
|
||||
info.mipLevels = info.arrayLayers = 1;
|
||||
info.samples = samples;
|
||||
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
info.usage = usage;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
if (vkCreateImage(device, &info, nullptr, &out.image) != VK_SUCCESS) return false;
|
||||
objects.images.push_back(out.image);
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetImageMemoryRequirements(device, out.image, &requirements);
|
||||
const Optional<Uint32> type = PickMemoryType(memoryProperties, requirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0);
|
||||
if (!type) return false;
|
||||
VkMemoryAllocateInfo allocate{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocate.allocationSize = requirements.size;
|
||||
allocate.memoryTypeIndex = *type;
|
||||
if (vkAllocateMemory(device, &allocate, nullptr, &out.memory) != VK_SUCCESS) return false;
|
||||
objects.memories.push_back(out.memory);
|
||||
return vkBindImageMemory(device, out.image, out.memory, 0) == VK_SUCCESS;
|
||||
};
|
||||
const auto makeView = [&](const VkImageViewCreateInfo& info, VkImageView& out) -> Bool {
|
||||
if (vkCreateImageView(device, &info, nullptr, &out) != VK_SUCCESS) return false;
|
||||
objects.views.push_back(out);
|
||||
return true;
|
||||
};
|
||||
const auto makeFramebuffer = [&](VkRenderPass pass, const VkImageView* views, Uint32 count,
|
||||
VkFramebuffer& out) -> Bool {
|
||||
VkFramebufferCreateInfo info{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
info.renderPass = pass;
|
||||
info.attachmentCount = count;
|
||||
info.pAttachments = views;
|
||||
info.width = info.height = kProbeExtent;
|
||||
info.layers = 1;
|
||||
if (vkCreateFramebuffer(device, &info, nullptr, &out) != VK_SUCCESS) return false;
|
||||
objects.framebuffers.push_back(out);
|
||||
return true;
|
||||
};
|
||||
// The wire's clear (WireFramebuffer.inc, ClearWireFramebuffer): a LOAD/STORE pass over every
|
||||
// aspect of the format with vkCmdClearAttachments inside it. Recorded later; built now.
|
||||
struct ClearPass {
|
||||
VkRenderPass pass = VK_NULL_HANDLE;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
VkImageAspectFlags aspects = 0;
|
||||
Float depth = 0.0f;
|
||||
Uint32 stencil = 0;
|
||||
};
|
||||
Vector<ClearPass> clears;
|
||||
const auto makeClear = [&](const ProbeImage& image, VkFormat format, VkSampleCountFlagBits samples,
|
||||
Float depth, Uint32 stencil) -> Bool {
|
||||
const VkImageAspectFlags aspects = WireDepthStencilFormatAspects(format);
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
if (!makeView(WireDepthStencilViewInfo(image.image, format, aspects, 0, 0), view)) return false;
|
||||
VkAttachmentDescription attachment{};
|
||||
attachment.format = format;
|
||||
attachment.samples = samples;
|
||||
attachment.loadOp = attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachment.storeOp = attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
const VkAttachmentReference reference{0, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.pDepthStencilAttachment = &reference;
|
||||
VkRenderPassCreateInfo info{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
info.attachmentCount = 1;
|
||||
info.pAttachments = &attachment;
|
||||
info.subpassCount = 1;
|
||||
info.pSubpasses = &subpass;
|
||||
ClearPass clear;
|
||||
if (vkCreateRenderPass(device, &info, nullptr, &clear.pass) != VK_SUCCESS) return false;
|
||||
objects.passes.push_back(clear.pass);
|
||||
if (!makeFramebuffer(clear.pass, &view, 1, clear.framebuffer)) return false;
|
||||
clear.aspects = aspects;
|
||||
clear.depth = depth;
|
||||
clear.stencil = stencil;
|
||||
clears.push_back(clear);
|
||||
return true;
|
||||
};
|
||||
|
||||
// THE CONTROL'S FORMAT-INDEPENDENT HALF. A kit that cannot be made leaves every control
|
||||
// un-run, which the verdict reads as INCONCLUSIVE - never as the bug.
|
||||
String kitFailure;
|
||||
if (const VkResult result = CreateWireShaderResolveKit(device, context.shaderStencilExport, objects.kit);
|
||||
result != VK_SUCCESS)
|
||||
kitFailure = format("shader resolve kit creation failed (VkResult {})", static_cast<Int>(result));
|
||||
|
||||
// Readback layout: every region on a 16-byte boundary (a depth/stencil buffer copy wants a
|
||||
// multiple of 4).
|
||||
VkDeviceSize bufferBytes = 0;
|
||||
const auto reserve = [&](VkDeviceSize bytes) {
|
||||
const VkDeviceSize offset = bufferBytes;
|
||||
bufferBytes += (bytes + 15u) & ~VkDeviceSize{15u};
|
||||
return offset;
|
||||
};
|
||||
|
||||
Vector<FormatWork> work;
|
||||
for (const ProbeFormat& probeFormat : kProbeFormats) {
|
||||
WireDepthResolveFormatReading reading;
|
||||
reading.name = probeFormat.name;
|
||||
reading.format = static_cast<Int32>(probeFormat.format);
|
||||
reading.samples = static_cast<Uint32>(kProbeSamples);
|
||||
const SizeT readingIndex = measurement.formats.size();
|
||||
measurement.formats.push_back(reading);
|
||||
auto& slot = measurement.formats.back();
|
||||
// A skipped format records nothing: the clears it had already built are dropped with it
|
||||
// (their images stay in `objects` and are only destroyed).
|
||||
const SizeT clearsBefore = clears.size();
|
||||
const auto skip = [&](String reason) {
|
||||
slot.skipReason = Move(reason);
|
||||
clears.resize(clearsBefore);
|
||||
};
|
||||
|
||||
const VkFormat format = probeFormat.format;
|
||||
const VkImageAspectFlags aspects = WireDepthStencilFormatAspects(format);
|
||||
VkFormatProperties formatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(context.physicalDevice, format, &formatProperties);
|
||||
constexpr VkFormatFeatureFlags kRequired =
|
||||
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||
if ((formatProperties.optimalTilingFeatures & kRequired) != kRequired) {
|
||||
skip("not a sampled depth/stencil attachment format here");
|
||||
continue;
|
||||
}
|
||||
VkImageFormatProperties imageProperties{};
|
||||
if (vkGetPhysicalDeviceImageFormatProperties(context.physicalDevice, format, VK_IMAGE_TYPE_2D,
|
||||
VK_IMAGE_TILING_OPTIMAL, kSourceUsage, 0,
|
||||
&imageProperties) != VK_SUCCESS ||
|
||||
!(imageProperties.sampleCounts & kProbeSamples)) {
|
||||
skip("no 4x multisample image with the wire's attachment usage");
|
||||
continue;
|
||||
}
|
||||
if (vkGetPhysicalDeviceImageFormatProperties(context.physicalDevice, format, VK_IMAGE_TYPE_2D,
|
||||
VK_IMAGE_TILING_OPTIMAL, kScratchUsage, 0,
|
||||
&imageProperties) != VK_SUCCESS) {
|
||||
skip("no single-sample image with the resolve scratch's usage");
|
||||
continue;
|
||||
}
|
||||
// ResolveWireDepthStencil's own test for the render-pass arm, per format.
|
||||
if (((aspects & VK_IMAGE_ASPECT_DEPTH_BIT) &&
|
||||
!(context.depthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT)) ||
|
||||
((aspects & VK_IMAGE_ASPECT_STENCIL_BIT) &&
|
||||
!(context.stencilResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT))) {
|
||||
skip("the render-pass arm is not taken for this format (no SAMPLE_ZERO resolve mode)");
|
||||
continue;
|
||||
}
|
||||
|
||||
FormatWork item;
|
||||
item.reading = readingIndex;
|
||||
item.format = format;
|
||||
item.aspects = aspects;
|
||||
if (!makeImage(format, kProbeSamples, kSourceUsage, item.subjectSource) ||
|
||||
!makeImage(format, kProbeSamples, kSourceUsage, item.controlSource) ||
|
||||
!makeImage(format, VK_SAMPLE_COUNT_1_BIT, kScratchUsage, item.subjectTarget)) {
|
||||
skip("image creation failed");
|
||||
continue;
|
||||
}
|
||||
if (CreateWireDepthStencilResolvePass(context.createRenderPass2, device, format, kProbeSamples,
|
||||
&item.resolvePass) != VK_SUCCESS) {
|
||||
skip("the render-pass arm's vkCreateRenderPass2 failed");
|
||||
continue;
|
||||
}
|
||||
objects.passes.push_back(item.resolvePass);
|
||||
if (!makeView(WireDepthStencilViewInfo(item.subjectSource.image, format, aspects, 0, 0),
|
||||
item.resolveViews[0]) ||
|
||||
!makeView(WireDepthStencilViewInfo(item.subjectTarget.image, format, aspects, 0, 0),
|
||||
item.resolveViews[1]) ||
|
||||
!makeFramebuffer(item.resolvePass, item.resolveViews, 2, item.resolveFramebuffer)) {
|
||||
skip("the render-pass arm's views or framebuffer could not be created");
|
||||
continue;
|
||||
}
|
||||
if (!makeClear(item.subjectSource, format, kProbeSamples, kClearDepth, kClearStencil) ||
|
||||
!makeClear(item.controlSource, format, kProbeSamples, kClearDepth, kClearStencil) ||
|
||||
!makeClear(item.subjectTarget, format, VK_SAMPLE_COUNT_1_BIT, kSentinelDepth, kSentinelStencil)) {
|
||||
skip("the clear passes could not be created");
|
||||
continue;
|
||||
}
|
||||
// The control, one target per aspect: the shader pass's attachment is DONT_CARE on both
|
||||
// aspects, so a second aspect's pass into the same target would discard the first.
|
||||
const auto makeControl = [&](VkImageAspectFlags aspect, ControlPass& control) {
|
||||
if (!kitFailure.empty()) return;
|
||||
if (aspect == VK_IMAGE_ASPECT_STENCIL_BIT && objects.kit.stencilFragment == VK_NULL_HANDLE) return;
|
||||
if (!makeImage(format, VK_SAMPLE_COUNT_1_BIT, kScratchUsage, control.target)) return;
|
||||
if (CreateWireShaderResolveRenderPass(device, format, &control.pass) != VK_SUCCESS) return;
|
||||
objects.passes.push_back(control.pass);
|
||||
if (CreateWireShaderResolvePipeline(device, objects.kit, control.pass, aspect, &control.pipeline) !=
|
||||
VK_SUCCESS)
|
||||
return;
|
||||
objects.pipelines.push_back(control.pipeline);
|
||||
if (!makeView(WireDepthStencilViewInfo(item.controlSource.image, format, aspect, 0, 0),
|
||||
control.sampledView) ||
|
||||
!makeView(WireDepthStencilViewInfo(control.target.image, format, aspects, 0, 0),
|
||||
control.targetView) ||
|
||||
!makeFramebuffer(control.pass, &control.targetView, 1, control.framebuffer))
|
||||
return;
|
||||
if (!makeClear(control.target, format, VK_SAMPLE_COUNT_1_BIT, kSentinelDepth, kSentinelStencil))
|
||||
return;
|
||||
control.ready = true;
|
||||
};
|
||||
if (aspects & VK_IMAGE_ASPECT_DEPTH_BIT) makeControl(VK_IMAGE_ASPECT_DEPTH_BIT, item.depthControl);
|
||||
if (aspects & VK_IMAGE_ASPECT_STENCIL_BIT) makeControl(VK_IMAGE_ASPECT_STENCIL_BIT, item.stencilControl);
|
||||
|
||||
if (aspects & VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||
item.subjectDepthOffset = reserve(kProbeTexels * DepthCopyBytes(format));
|
||||
if (item.depthControl.ready) item.depthControl.offset = reserve(kProbeTexels * DepthCopyBytes(format));
|
||||
}
|
||||
if (aspects & VK_IMAGE_ASPECT_STENCIL_BIT) {
|
||||
item.subjectStencilOffset = reserve(kProbeTexels);
|
||||
if (item.stencilControl.ready) item.stencilControl.offset = reserve(kProbeTexels);
|
||||
}
|
||||
slot.ran = true;
|
||||
work.push_back(item);
|
||||
}
|
||||
if (work.empty()) return fail("no format could be probed");
|
||||
|
||||
// Descriptor sets for the controls, one per draw (the production shader arm's own rule).
|
||||
Uint32 controlDraws = 0;
|
||||
for (const FormatWork& item : work)
|
||||
controlDraws += (item.depthControl.ready ? 1u : 0u) + (item.stencilControl.ready ? 1u : 0u);
|
||||
if (controlDraws > 0) {
|
||||
const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, controlDraws};
|
||||
VkDescriptorPoolCreateInfo pool{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||
pool.maxSets = controlDraws;
|
||||
pool.poolSizeCount = 1;
|
||||
pool.pPoolSizes = &poolSize;
|
||||
if (vkCreateDescriptorPool(device, &pool, nullptr, &objects.descriptorPool) != VK_SUCCESS)
|
||||
return fail("vkCreateDescriptorPool failed");
|
||||
for (FormatWork& item : work) {
|
||||
for (ControlPass* control : {&item.depthControl, &item.stencilControl}) {
|
||||
if (!control->ready) continue;
|
||||
VkDescriptorSetAllocateInfo alloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
alloc.descriptorPool = objects.descriptorPool;
|
||||
alloc.descriptorSetCount = 1;
|
||||
alloc.pSetLayouts = &objects.kit.descriptorLayout;
|
||||
if (vkAllocateDescriptorSets(device, &alloc, &control->set) != VK_SUCCESS)
|
||||
return fail("vkAllocateDescriptorSets failed");
|
||||
const VkDescriptorImageInfo sampled{objects.kit.nearest, control->sampledView,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = control->set;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
write.pImageInfo = &sampled;
|
||||
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The host-visible readback buffer.
|
||||
VkDeviceMemory bufferMemory = VK_NULL_HANDLE;
|
||||
Bool bufferCoherent = false;
|
||||
{
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
info.size = bufferBytes;
|
||||
info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
if (vkCreateBuffer(device, &info, nullptr, &objects.buffer) != VK_SUCCESS)
|
||||
return fail("vkCreateBuffer failed");
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetBufferMemoryRequirements(device, objects.buffer, &requirements);
|
||||
const Optional<Uint32> type =
|
||||
PickMemoryType(memoryProperties, requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||||
if (!type) return fail("no host-visible memory type for the readback buffer");
|
||||
bufferCoherent = (memoryProperties.memoryTypes[*type].propertyFlags &
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
VkMemoryAllocateInfo allocate{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocate.allocationSize = requirements.size;
|
||||
allocate.memoryTypeIndex = *type;
|
||||
if (vkAllocateMemory(device, &allocate, nullptr, &bufferMemory) != VK_SUCCESS)
|
||||
return fail("vkAllocateMemory (readback) failed");
|
||||
objects.memories.push_back(bufferMemory);
|
||||
if (vkBindBufferMemory(device, objects.buffer, bufferMemory, 0) != VK_SUCCESS)
|
||||
return fail("vkBindBufferMemory failed");
|
||||
}
|
||||
|
||||
VkCommandPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
|
||||
poolInfo.queueFamilyIndex = context.queueFamilyIndex;
|
||||
if (vkCreateCommandPool(device, &poolInfo, nullptr, &objects.commandPool) != VK_SUCCESS)
|
||||
return fail("vkCreateCommandPool failed");
|
||||
VkCommandBufferAllocateInfo commandInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||||
commandInfo.commandPool = objects.commandPool;
|
||||
commandInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
commandInfo.commandBufferCount = 1;
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
if (vkAllocateCommandBuffers(device, &commandInfo, &cmd) != VK_SUCCESS)
|
||||
return fail("vkAllocateCommandBuffers failed");
|
||||
VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
if (vkBeginCommandBuffer(cmd, &beginInfo) != VK_SUCCESS) return fail("vkBeginCommandBuffer failed");
|
||||
|
||||
const VkRect2D fullRect{{0, 0}, {kProbeExtent, kProbeExtent}};
|
||||
// (1) Every image UNDEFINED -> DEPTH_STENCIL_ATTACHMENT_OPTIMAL, the way TransitionWireImage
|
||||
// moves a fresh image (source state from UNDEFINED, destination ALL_COMMANDS).
|
||||
for (const FormatWork& item : work) {
|
||||
const VkImage images[] = {item.subjectSource.image, item.controlSource.image, item.subjectTarget.image,
|
||||
item.depthControl.ready ? item.depthControl.target.image : VK_NULL_HANDLE,
|
||||
item.stencilControl.ready ? item.stencilControl.target.image : VK_NULL_HANDLE};
|
||||
for (const VkImage image : images) {
|
||||
if (image == VK_NULL_HANDLE) continue;
|
||||
ImageBarrier(cmd, image, item.aspects, VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
|
||||
}
|
||||
}
|
||||
// (2) The inputs and the sentinels, cleared the wire's way.
|
||||
for (const ClearPass& clear : clears) {
|
||||
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
begin.renderPass = clear.pass;
|
||||
begin.framebuffer = clear.framebuffer;
|
||||
begin.renderArea = fullRect;
|
||||
vkCmdBeginRenderPass(cmd, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
VkClearAttachment attachment{};
|
||||
attachment.aspectMask = clear.aspects;
|
||||
attachment.clearValue.depthStencil = {clear.depth, clear.stencil};
|
||||
const VkClearRect rect{fullRect, 0, 1};
|
||||
vkCmdClearAttachments(cmd, 1, &attachment, 1, &rect);
|
||||
vkCmdEndRenderPass(cmd);
|
||||
}
|
||||
// (3) What TransitionWireImage records when the layout already matches - the case of a
|
||||
// source the application cleared and then resolves.
|
||||
{
|
||||
VkMemoryBarrier memory{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
memory.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
memory.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
|
||||
&memory, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
// (4) THE SUBJECT: the render-pass arm, begun and ended with no draw.
|
||||
for (const FormatWork& item : work) {
|
||||
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
begin.renderPass = item.resolvePass;
|
||||
begin.framebuffer = item.resolveFramebuffer;
|
||||
begin.renderArea = fullRect;
|
||||
vkCmdBeginRenderPass(cmd, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdEndRenderPass(cmd);
|
||||
}
|
||||
// (5) THE CONTROL: the shader arm on the twin source, which TransitionWireImage moves to
|
||||
// SHADER_READ_ONLY_OPTIMAL first (both aspects of a packed format).
|
||||
for (const FormatWork& item : work) {
|
||||
if (!item.depthControl.ready && !item.stencilControl.ready) continue;
|
||||
ImageBarrier(cmd, item.controlSource.image, item.aspects, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
|
||||
for (const ControlPass* control : {&item.depthControl, &item.stencilControl}) {
|
||||
if (!control->ready) continue;
|
||||
RecordWireShaderResolveDraw(cmd, objects.kit, control->pass, control->pipeline, control->framebuffer,
|
||||
control->set, {kProbeExtent, kProbeExtent}, fullRect);
|
||||
}
|
||||
}
|
||||
// (6) Every target to TRANSFER_SRC with the barrier ResolveWireDepthStencil puts after its
|
||||
// resolve (a depth/stencil resolve writes in COLOR_ATTACHMENT_OUTPUT), then one copy per aspect.
|
||||
const auto copyOut = [&](VkImage image, VkImageAspectFlags aspect, VkDeviceSize offset) {
|
||||
VkBufferImageCopy copy{};
|
||||
copy.bufferOffset = offset;
|
||||
copy.imageSubresource = {aspect, 0, 0, 1};
|
||||
copy.imageExtent = {kProbeExtent, kProbeExtent, 1};
|
||||
vkCmdCopyImageToBuffer(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, objects.buffer, 1, ©);
|
||||
};
|
||||
const auto toTransfer = [&](VkImage image, VkImageAspectFlags aspects) {
|
||||
ImageBarrier(cmd, image, aspects, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
};
|
||||
for (const FormatWork& item : work) {
|
||||
toTransfer(item.subjectTarget.image, item.aspects);
|
||||
if (item.aspects & VK_IMAGE_ASPECT_DEPTH_BIT)
|
||||
copyOut(item.subjectTarget.image, VK_IMAGE_ASPECT_DEPTH_BIT, item.subjectDepthOffset);
|
||||
if (item.aspects & VK_IMAGE_ASPECT_STENCIL_BIT)
|
||||
copyOut(item.subjectTarget.image, VK_IMAGE_ASPECT_STENCIL_BIT, item.subjectStencilOffset);
|
||||
if (item.depthControl.ready) {
|
||||
toTransfer(item.depthControl.target.image, item.aspects);
|
||||
copyOut(item.depthControl.target.image, VK_IMAGE_ASPECT_DEPTH_BIT, item.depthControl.offset);
|
||||
}
|
||||
if (item.stencilControl.ready) {
|
||||
toTransfer(item.stencilControl.target.image, item.aspects);
|
||||
copyOut(item.stencilControl.target.image, VK_IMAGE_ASPECT_STENCIL_BIT, item.stencilControl.offset);
|
||||
}
|
||||
}
|
||||
{
|
||||
VkBufferMemoryBarrier barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = objects.buffer;
|
||||
barrier.size = VK_WHOLE_SIZE;
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 1,
|
||||
&barrier, 0, nullptr);
|
||||
}
|
||||
if (vkEndCommandBuffer(cmd) != VK_SUCCESS) return fail("vkEndCommandBuffer failed");
|
||||
|
||||
VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
if (vkCreateFence(device, &fenceInfo, nullptr, &objects.fence) != VK_SUCCESS)
|
||||
return fail("vkCreateFence failed");
|
||||
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||
submit.commandBufferCount = 1;
|
||||
submit.pCommandBuffers = &cmd;
|
||||
if (const VkResult result = vkQueueSubmit(context.queue, 1, &submit, objects.fence); result != VK_SUCCESS)
|
||||
return fail(format("vkQueueSubmit failed (VkResult {})", static_cast<Int>(result)));
|
||||
const VkResult waited = vkWaitForFences(device, 1, &objects.fence, VK_TRUE, kFenceTimeoutNs);
|
||||
if (waited == VK_TIMEOUT) {
|
||||
// The submission may still be executing: destroying anything it names, or idle-waiting
|
||||
// the device, is the hang the bound exists to prevent. Everything leaks.
|
||||
leak = true;
|
||||
measurement.fenceWaitTimedOut = true;
|
||||
return fail("the probe's fence did not signal within 5 s");
|
||||
}
|
||||
if (waited != VK_SUCCESS) return fail(format("vkWaitForFences failed (VkResult {})", static_cast<Int>(waited)));
|
||||
|
||||
void* mapped = nullptr;
|
||||
if (vkMapMemory(device, bufferMemory, 0, VK_WHOLE_SIZE, 0, &mapped) != VK_SUCCESS || mapped == nullptr)
|
||||
return fail("vkMapMemory failed");
|
||||
if (!bufferCoherent) {
|
||||
VkMappedMemoryRange range{VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE};
|
||||
range.memory = bufferMemory;
|
||||
range.size = VK_WHOLE_SIZE;
|
||||
vkInvalidateMappedMemoryRanges(device, 1, &range);
|
||||
}
|
||||
const Uint8* bytes = static_cast<const Uint8*>(mapped);
|
||||
const auto tally = [&](WireDepthResolveAspectReading& aspect, VkDeviceSize offset, Uint32 texelBytes,
|
||||
Bool depth, VkFormat format, Bool subject) {
|
||||
Uint32 matches = 0, sentinels = 0, first = 0;
|
||||
for (Uint32 i = 0; i < kProbeTexels; ++i) {
|
||||
const Uint8* texel = bytes + offset + static_cast<VkDeviceSize>(i) * texelBytes;
|
||||
const Uint32 word = depth ? ReadDepthWord(format, texel) : static_cast<Uint32>(*texel);
|
||||
if (i == 0) first = word;
|
||||
if (WordDistance(word, aspect.expected) <= aspect.tolerance) ++matches;
|
||||
if (WordDistance(word, aspect.sentinel) <= aspect.tolerance) ++sentinels;
|
||||
}
|
||||
if (subject) {
|
||||
aspect.renderPassMatches = matches;
|
||||
aspect.renderPassSentinels = sentinels;
|
||||
aspect.renderPassFirst = first;
|
||||
} else {
|
||||
aspect.shaderRan = true;
|
||||
aspect.shaderMatches = matches;
|
||||
aspect.shaderFirst = first;
|
||||
}
|
||||
};
|
||||
for (const FormatWork& item : work) {
|
||||
WireDepthResolveFormatReading& reading = measurement.formats[item.reading];
|
||||
if (item.aspects & VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||
auto& aspect = reading.depth;
|
||||
aspect.measured = true;
|
||||
aspect.texels = kProbeTexels;
|
||||
aspect.expected = EncodeDepth(item.format, kClearDepth);
|
||||
aspect.sentinel = EncodeDepth(item.format, kSentinelDepth);
|
||||
aspect.tolerance = IsFloatDepth(item.format) ? 0u : 1u;
|
||||
tally(aspect, item.subjectDepthOffset, DepthCopyBytes(item.format), true, item.format, true);
|
||||
if (item.depthControl.ready)
|
||||
tally(aspect, item.depthControl.offset, DepthCopyBytes(item.format), true, item.format, false);
|
||||
}
|
||||
if (item.aspects & VK_IMAGE_ASPECT_STENCIL_BIT) {
|
||||
auto& aspect = reading.stencil;
|
||||
aspect.measured = true;
|
||||
aspect.texels = kProbeTexels;
|
||||
aspect.expected = kClearStencil;
|
||||
aspect.sentinel = kSentinelStencil;
|
||||
aspect.tolerance = 0;
|
||||
tally(aspect, item.subjectStencilOffset, 1, false, item.format, true);
|
||||
if (item.stencilControl.ready)
|
||||
tally(aspect, item.stencilControl.offset, 1, false, item.format, false);
|
||||
}
|
||||
}
|
||||
vkUnmapMemory(device, bufferMemory);
|
||||
measurement.ran = true;
|
||||
if (!kitFailure.empty()) measurement.failureReason = kitFailure;
|
||||
return measurement;
|
||||
}
|
||||
|
||||
WireDepthResolveProbeMeasurement RunWireDepthResolveProbeOnThrowawayDevice(
|
||||
PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice,
|
||||
Uint32 graphicsQueueFamilyIndex, const Vector<VkExtensionProperties>& deviceExtensions,
|
||||
Bool& renderPassArmAvailable, Bool& shaderStencilExport) {
|
||||
WireDepthResolveProbeMeasurement measurement;
|
||||
const auto has = [&](const char* name) {
|
||||
for (const VkExtensionProperties& extension : deviceExtensions)
|
||||
if (std::strcmp(extension.extensionName, name) == 0) return true;
|
||||
return false;
|
||||
};
|
||||
renderPassArmAvailable = has(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME) &&
|
||||
has(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
|
||||
shaderStencilExport = has(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
|
||||
if (!renderPassArmAvailable) {
|
||||
measurement.failureReason = "no VK_KHR_create_renderpass2 + VK_KHR_depth_stencil_resolve";
|
||||
return measurement;
|
||||
}
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
|
||||
auto getProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
||||
getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2"));
|
||||
if (getProperties2 == nullptr)
|
||||
getProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
||||
getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR"));
|
||||
const auto createDevice =
|
||||
reinterpret_cast<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
|
||||
if (properties.apiVersion < VK_API_VERSION_1_1 || getProperties2 == nullptr || createDevice == nullptr) {
|
||||
renderPassArmAvailable = false;
|
||||
measurement.failureReason = "a pre-1.1 device, or no vkGetPhysicalDeviceProperties2 / vkCreateDevice";
|
||||
return measurement;
|
||||
}
|
||||
VkPhysicalDeviceDepthStencilResolveProperties resolveProperties{
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 properties2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
|
||||
properties2.pNext = &resolveProperties;
|
||||
getProperties2(physicalDevice, &properties2);
|
||||
|
||||
// The renderer's own extension set for the two arms (VulkanRenderer.cpp,
|
||||
// CreateLogicalDeviceAndQueues), nothing more.
|
||||
const Float priority = 1.0f;
|
||||
VkDeviceQueueCreateInfo queueInfo{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
||||
queueInfo.queueFamilyIndex = graphicsQueueFamilyIndex;
|
||||
queueInfo.queueCount = 1;
|
||||
queueInfo.pQueuePriorities = &priority;
|
||||
Vector<const char*> extensions{VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME,
|
||||
VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME};
|
||||
if (shaderStencilExport) extensions.push_back(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
|
||||
VkDeviceCreateInfo deviceInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
||||
deviceInfo.queueCreateInfoCount = 1;
|
||||
deviceInfo.pQueueCreateInfos = &queueInfo;
|
||||
deviceInfo.enabledExtensionCount = static_cast<Uint32>(extensions.size());
|
||||
deviceInfo.ppEnabledExtensionNames = extensions.data();
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
if (const VkResult result = createDevice(physicalDevice, &deviceInfo, nullptr, &device);
|
||||
result != VK_SUCCESS || device == VK_NULL_HANDLE) {
|
||||
measurement.failureReason = format("vkCreateDevice failed (VkResult {})", static_cast<Int>(result));
|
||||
return measurement;
|
||||
}
|
||||
WireDepthResolveProbeContext context;
|
||||
context.physicalDevice = physicalDevice;
|
||||
context.device = device;
|
||||
vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &context.queue);
|
||||
context.queueFamilyIndex = graphicsQueueFamilyIndex;
|
||||
context.createRenderPass2 =
|
||||
reinterpret_cast<PFN_vkCreateRenderPass2>(vkGetDeviceProcAddr(device, "vkCreateRenderPass2KHR"));
|
||||
context.depthResolveModes = resolveProperties.supportedDepthResolveModes;
|
||||
context.stencilResolveModes = resolveProperties.supportedStencilResolveModes;
|
||||
context.shaderStencilExport = shaderStencilExport;
|
||||
measurement = RunWireDepthResolveProbe(context);
|
||||
// A timed-out probe leaked its children on purpose; destroying their device would block
|
||||
// exactly where the bound says not to.
|
||||
if (!measurement.fenceWaitTimedOut) vkDestroyDevice(device, nullptr);
|
||||
return measurement;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
#endif
|
||||
@@ -0,0 +1,117 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h
|
||||
// Copyright (c) 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 "WireDepthResolveArm.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <Includes.h>
|
||||
|
||||
// P7 gate 5 (g5-msprobe): the Vulkan half of the multisample depth/stencil resolve probe, and the
|
||||
// two resolve arms it measures.
|
||||
//
|
||||
// THE ARMS LIVE HERE, NOT ONLY IN THE RENDERER. A probe that rebuilt "something like" the wire's
|
||||
// render pass would measure its own copy; the render-pass description, the shader pass's modules,
|
||||
// pipeline and draw, and the view shapes both arms attach are defined once below and called by
|
||||
// ResolveWireDepthStencil (WireFramebuffer.inc), ResolveWireDepthStencilWithShader
|
||||
// (WireMultisampleResolve.inc) and the probe alike.
|
||||
//
|
||||
// Two callers: the Magma renderer at device bring-up (server side, once per process, only where
|
||||
// the render-pass arm exists at all - VulkanRenderer::ArmWireDepthResolveOrder) and the POST's
|
||||
// "Known Driver Bugs" section (MG_Util/SelfTest/DriverBugProbes.cpp) on a throwaway device.
|
||||
//
|
||||
// Disaggregated builds only: the monolith never records either arm, and the pull build's image
|
||||
// must not change (P7 gate G1).
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Every aspect a wire depth/stencil format has.
|
||||
VkImageAspectFlags WireDepthStencilFormatAspects(VkFormat format);
|
||||
|
||||
// A 2D view of one level/layer of a depth/stencil image naming `aspects`. The render-pass arm
|
||||
// attaches both images with every aspect of the format; the shader arm SAMPLES its source
|
||||
// through a view of exactly one aspect (a depth/stencil image can only be sampled that way) and
|
||||
// attaches its target with every aspect.
|
||||
VkImageViewCreateInfo WireDepthStencilViewInfo(VkImage image, VkFormat format, VkImageAspectFlags aspects,
|
||||
Uint32 level, Uint32 layer);
|
||||
|
||||
// THE RENDER-PASS ARM: attachment 0 the multisample source (LOAD/STORE), attachment 1 the
|
||||
// single-sample resolve target (DONT_CARE/STORE), both in DEPTH_STENCIL_ATTACHMENT_OPTIMAL, one
|
||||
// subpass whose VkSubpassDescriptionDepthStencilResolve resolves every aspect of the format
|
||||
// with VK_RESOLVE_MODE_SAMPLE_ZERO_BIT. The caller begins and ends it with NO draw.
|
||||
VkResult CreateWireDepthStencilResolvePass(PFN_vkCreateRenderPass2 createRenderPass2, VkDevice device,
|
||||
VkFormat format, VkSampleCountFlagBits samples, VkRenderPass* pass);
|
||||
|
||||
// THE SHADER ARM's device objects that do not depend on the format.
|
||||
struct WireShaderResolveKit {
|
||||
VkShaderModule vertex = VK_NULL_HANDLE;
|
||||
VkShaderModule depthFragment = VK_NULL_HANDLE;
|
||||
// Only where VK_EXT_shader_stencil_export is enabled: its SPIR-V declares StencilExportEXT.
|
||||
VkShaderModule stencilFragment = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkSampler nearest = VK_NULL_HANDLE;
|
||||
};
|
||||
// Creates every member; the first failure is returned and the members made so far stay set,
|
||||
// for DestroyWireShaderResolveKit.
|
||||
VkResult CreateWireShaderResolveKit(VkDevice device, Bool shaderStencilExport, WireShaderResolveKit& kit);
|
||||
void DestroyWireShaderResolveKit(VkDevice device, WireShaderResolveKit& kit);
|
||||
// One (format, aspect)'s single-attachment render pass: DONT_CARE/STORE on both aspects,
|
||||
// DEPTH_STENCIL_ATTACHMENT_OPTIMAL throughout.
|
||||
VkResult CreateWireShaderResolveRenderPass(VkDevice device, VkFormat format, VkRenderPass* pass);
|
||||
// Its pipeline: full-screen triangle, gl_FragDepth (ALWAYS + write) or gl_FragStencilRefARB
|
||||
// (REPLACE everywhere), viewport and scissor dynamic. VK_ERROR_FEATURE_NOT_PRESENT when the kit
|
||||
// has no fragment module for `aspect`.
|
||||
VkResult CreateWireShaderResolvePipeline(VkDevice device, const WireShaderResolveKit& kit, VkRenderPass pass,
|
||||
VkImageAspectFlags aspect, VkPipeline* pipeline);
|
||||
// Records the draw: the render area and the scissor are `rect`, the viewport is the WHOLE
|
||||
// attachment (gl_FragCoord is what the fragment stage fetches by), one triangle.
|
||||
void RecordWireShaderResolveDraw(VkCommandBuffer commandBuffer, const WireShaderResolveKit& kit,
|
||||
VkRenderPass pass, VkPipeline pipeline, VkFramebuffer framebuffer,
|
||||
VkDescriptorSet descriptorSet, VkExtent2D attachmentExtent, VkRect2D rect);
|
||||
|
||||
// What the probe runs on. The device must have been created with VK_KHR_create_renderpass2 and
|
||||
// VK_KHR_depth_stencil_resolve (or Vulkan 1.2 core), and with VK_EXT_shader_stencil_export where
|
||||
// `shaderStencilExport` says so. The queue must be otherwise idle for the probe's duration.
|
||||
struct WireDepthResolveProbeContext {
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkQueue queue = VK_NULL_HANDLE;
|
||||
Uint32 queueFamilyIndex = 0;
|
||||
PFN_vkCreateRenderPass2 createRenderPass2 = nullptr;
|
||||
VkResolveModeFlags depthResolveModes = 0;
|
||||
VkResolveModeFlags stencilResolveModes = 0;
|
||||
Bool shaderStencilExport = false;
|
||||
};
|
||||
|
||||
// Per format the device can host as a 4x multisample depth/stencil attachment AND for which
|
||||
// ResolveWireDepthStencil would take the render-pass arm (D16, X8_D24, D32F, D24S8, D32FS8, S8):
|
||||
// - two identical 4x4 4x multisample sources (production usage), each cleared to depth 0.25 /
|
||||
// stencil 0x5A the way the wire clears (a LOAD pass with vkCmdClearAttachments);
|
||||
// - single-sample resolve targets (the production scratch usage) pre-filled with the sentinel
|
||||
// depth 0.75 / stencil 0xA5;
|
||||
// - SUBJECT: the render-pass arm's exact pass, begun and ended with no draw;
|
||||
// - CONTROL: the shader arm's exact pass on the other source, one target per aspect (stencil
|
||||
// only with VK_EXT_shader_stencil_export);
|
||||
// - every target copied aspect by aspect into a host-visible buffer and compared texel by texel.
|
||||
// One submission, a bounded fence wait (on timeout every object is leaked and the measurement
|
||||
// says so - the caller must then not destroy or idle-wait the device), teardown on every other
|
||||
// path. Never touches renderer state.
|
||||
WireDepthResolveProbeMeasurement RunWireDepthResolveProbe(const WireDepthResolveProbeContext& context);
|
||||
|
||||
// The POST's entry: creates a throwaway device on `physicalDevice` with exactly the extensions
|
||||
// the renderer enables for the two arms, runs the probe on its graphics queue and destroys the
|
||||
// device (leaks it on a fence timeout). `renderPassArmAvailable` is false - and nothing is run -
|
||||
// when the device lacks VK_KHR_create_renderpass2 / VK_KHR_depth_stencil_resolve; the shader
|
||||
// pass is then the only arm and the probe has nothing to choose between.
|
||||
// `shaderStencilExport` reports whether VK_EXT_shader_stencil_export was available.
|
||||
WireDepthResolveProbeMeasurement RunWireDepthResolveProbeOnThrowawayDevice(
|
||||
PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice,
|
||||
Uint32 graphicsQueueFamilyIndex, const Vector<VkExtensionProperties>& deviceExtensions,
|
||||
Bool& renderPassArmAvailable, Bool& shaderStencilExport);
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
#endif
|
||||
@@ -669,11 +669,7 @@
|
||||
static_cast<Uint64>(dxMin + width) > destination.extent.width ||
|
||||
static_cast<Uint64>(dyMin + height) > destination.extent.height)
|
||||
MagmaWireFatal("multisample-depth-resolve-region");
|
||||
VkImageAspectFlags formatAspects = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (source.format == VK_FORMAT_S8_UINT) formatAspects = VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
else if (source.format == VK_FORMAT_D16_UNORM_S8_UINT || source.format == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
source.format == VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||
formatAspects |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
const VkImageAspectFlags formatAspects = WireDepthStencilFormatAspects(source.format);
|
||||
// P7 wave 2-B2, CONTRACT-P7 §3.2 (`multisample-blit-aspect`): TWO ARMS, and which one
|
||||
// runs is a fact about the DEVICE, not about the shape. VK_KHR_depth_stencil_resolve is
|
||||
// the direct route and is taken wherever it exists; where it does not - which is where
|
||||
@@ -726,30 +722,32 @@
|
||||
VkImageView views[2]{};
|
||||
VkRenderPass pass = VK_NULL_HANDLE;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
// 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:
|
||||
// P7 gate 5 (g5-msrbo, g5-msprobe): WHERE THE EXTENSION ARM IS MEASURED 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.
|
||||
// a decline. m_wirePreferShaderDepthResolve is the verdict of the resolve probe that ran
|
||||
// THIS pass and the shader pass on this device at bring-up (ArmWireDepthResolveOrder,
|
||||
// WireDepthResolveProbe.cpp); where the probe finds the pass clean - or cannot tell - 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));
|
||||
// WHICH ARM RESOLVED IS SAID, ONCE PER ARM (review round). Lavapipe resolves correctly
|
||||
// through both, so the pixels cannot tell a lane which one ran; these two lines can, and
|
||||
// the MsResolveQ./MsFlipQ. entries (Qualcomm's arm order substituted on the host) and the
|
||||
// the MsResolveBug./MsFlipBug. entries (the probe's defect verdict forced on the host) and the
|
||||
// MsResolve1./MsFlip1. ones (the extension arm dropped) read them back from the server
|
||||
// log - DepthStencilReadbackMatrixScenario::ExpectTheShaderResolveArmWhereTheLaneAsks.
|
||||
// log - DepthStencilReadbackMatrixScenario::ExpectTheResolveArmWhereTheLaneAsks.
|
||||
// Keep the two phrases in step with that reader.
|
||||
//
|
||||
// AND A PREFERENCE THAT FELL BACK IS NOT SILENT: where the shader arm goes first it goes
|
||||
// first because the render pass is known to leave its target unwritten on that device,
|
||||
// first because the probe measured the render pass leaving its target unwritten here,
|
||||
// so a shader decline (a stencil aspect without VK_EXT_shader_stencil_export, a format
|
||||
// that is not SAMPLED_IMAGE + DEPTH_STENCIL_ATTACHMENT, a failed pipeline create -
|
||||
// WireMultisampleResolve.inc) that lands on the render pass is the resolve most likely
|
||||
@@ -763,7 +761,7 @@
|
||||
MGLOG_W_ONCE("DirectVulkan::ResolveWireDepthStencil: this device prefers the shader pass, "
|
||||
"which did not resolve aspect 0x%x of a %u-sample format %d (stencil export=%d, "
|
||||
"source usage 0x%x); falling back to the no-draw VK_KHR_depth_stencil_resolve "
|
||||
"render pass, which on the device that set the preference left its target "
|
||||
"render pass, which this device's resolve probe measured leaving its target "
|
||||
"unwritten",
|
||||
static_cast<Uint32>(source.aspect), static_cast<Uint32>(source.samples),
|
||||
static_cast<Int>(source.format), m_wireShaderStencilExport ? 1 : 0,
|
||||
@@ -776,45 +774,16 @@
|
||||
TransitionWireImage(source, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
TransitionWireImage(resolved, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.image = source.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = source.format;
|
||||
viewInfo.subresourceRange = {formatAspects, source.level, 1, source.layer, 1};
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &views[0]));
|
||||
viewInfo.image = resolved.image;
|
||||
viewInfo.subresourceRange = {formatAspects, 0, 1, 0, 1};
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &views[1]));
|
||||
VkAttachmentDescription2 attachments[2]{};
|
||||
for (Uint32 i = 0; i < 2; ++i) {
|
||||
auto& attachment = attachments[i];
|
||||
attachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
|
||||
attachment.format = source.format;
|
||||
attachment.samples = i == 0 ? source.samples : VK_SAMPLE_COUNT_1_BIT;
|
||||
attachment.loadOp = attachment.stencilLoadOp = i == 0 ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment.storeOp = attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
VkAttachmentReference2 sourceReference{VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2};
|
||||
sourceReference.attachment = 0;
|
||||
sourceReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
sourceReference.aspectMask = formatAspects;
|
||||
VkAttachmentReference2 resolveReference = sourceReference;
|
||||
resolveReference.attachment = 1;
|
||||
VkSubpassDescriptionDepthStencilResolve resolve{VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE};
|
||||
resolve.depthResolveMode = (formatAspects & VK_IMAGE_ASPECT_DEPTH_BIT) ? VK_RESOLVE_MODE_SAMPLE_ZERO_BIT : VK_RESOLVE_MODE_NONE;
|
||||
resolve.stencilResolveMode = (formatAspects & VK_IMAGE_ASPECT_STENCIL_BIT) ? VK_RESOLVE_MODE_SAMPLE_ZERO_BIT : VK_RESOLVE_MODE_NONE;
|
||||
resolve.pDepthStencilResolveAttachment = &resolveReference;
|
||||
VkSubpassDescription2 subpass{VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2};
|
||||
subpass.pNext = &resolve;
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.pDepthStencilAttachment = &sourceReference;
|
||||
VkRenderPassCreateInfo2 passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2};
|
||||
passInfo.attachmentCount = 2;
|
||||
passInfo.pAttachments = attachments;
|
||||
passInfo.subpassCount = 1;
|
||||
passInfo.pSubpasses = &subpass;
|
||||
VK_VERIFY(m_wireCreateRenderPass2(m_device, &passInfo, nullptr, &pass));
|
||||
// The view shapes and the pass are WireDepthResolveProbe.cpp's: the resolve probe runs this
|
||||
// exact pass on the device at bring-up, so there is one definition of it (g5-msprobe).
|
||||
const VkImageViewCreateInfo sourceViewInfo =
|
||||
WireDepthStencilViewInfo(source.image, source.format, formatAspects, source.level, source.layer);
|
||||
VK_VERIFY(vkCreateImageView(m_device, &sourceViewInfo, nullptr, &views[0]));
|
||||
const VkImageViewCreateInfo resolvedViewInfo =
|
||||
WireDepthStencilViewInfo(resolved.image, source.format, formatAspects, 0, 0);
|
||||
VK_VERIFY(vkCreateImageView(m_device, &resolvedViewInfo, nullptr, &views[1]));
|
||||
VK_VERIFY(CreateWireDepthStencilResolvePass(m_wireCreateRenderPass2, m_device, source.format, source.samples,
|
||||
&pass));
|
||||
VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebufferInfo.renderPass = pass;
|
||||
framebufferInfo.attachmentCount = 2;
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
// would make "the wire arm is observably the monolith's" untestable, and GL 4.6 core 18.3.1
|
||||
// requires a SELECTED sample for stencil anyway.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include "WireMultisampleResolveSpirv.h"
|
||||
|
||||
// The pass's modules, layouts, render passes and pipeline, and the draw it records, are
|
||||
// WireDepthResolveProbe.cpp's (g5-msprobe): the multisample depth/stencil resolve probe runs
|
||||
// this exact arm as its CONTROL, so it is defined once for both.
|
||||
struct VulkanRenderer::WireMultisampleResolveResources {
|
||||
struct Target {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
@@ -25,11 +26,7 @@
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
};
|
||||
VkShaderModule vertex = VK_NULL_HANDLE;
|
||||
VkShaderModule depthFragment = VK_NULL_HANDLE, stencilFragment = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkSampler nearest = VK_NULL_HANDLE;
|
||||
WireShaderResolveKit kit;
|
||||
Vector<Target> targets;
|
||||
};
|
||||
|
||||
@@ -42,12 +39,7 @@
|
||||
vkDestroyPipeline(m_device, target.pipeline, nullptr);
|
||||
vkDestroyRenderPass(m_device, target.renderPass, nullptr);
|
||||
}
|
||||
vkDestroySampler(m_device, cache.nearest, nullptr);
|
||||
vkDestroyPipelineLayout(m_device, cache.pipelineLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(m_device, cache.descriptorLayout, nullptr);
|
||||
vkDestroyShaderModule(m_device, cache.vertex, nullptr);
|
||||
vkDestroyShaderModule(m_device, cache.depthFragment, nullptr);
|
||||
vkDestroyShaderModule(m_device, cache.stencilFragment, nullptr);
|
||||
DestroyWireShaderResolveKit(m_device, cache.kit);
|
||||
delete m_wireMultisampleResolveResources;
|
||||
m_wireMultisampleResolveResources = nullptr;
|
||||
}
|
||||
@@ -74,57 +66,17 @@
|
||||
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
if ((properties.optimalTilingFeatures & required) != required) return false;
|
||||
|
||||
VkImageAspectFlags formatAspects = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (source.format == VK_FORMAT_S8_UINT) formatAspects = VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
else if (source.format == VK_FORMAT_D16_UNORM_S8_UINT || source.format == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
source.format == VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||
formatAspects |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
const VkImageAspectFlags formatAspects = WireDepthStencilFormatAspects(source.format);
|
||||
if (!(formatAspects & aspect)) return false;
|
||||
|
||||
if (!m_wireMultisampleResolveResources) {
|
||||
m_wireMultisampleResolveResources = new WireMultisampleResolveResources;
|
||||
auto& cache = *m_wireMultisampleResolveResources;
|
||||
const auto shader = [&](const Uint32* code, SizeT bytes, VkShaderModule& module) {
|
||||
VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
info.codeSize = bytes;
|
||||
info.pCode = code;
|
||||
VK_VERIFY(vkCreateShaderModule(m_device, &info, nullptr, &module));
|
||||
};
|
||||
shader(kWireMultisampleResolveVertexSpirv, sizeof(kWireMultisampleResolveVertexSpirv), cache.vertex);
|
||||
shader(kWireMultisampleDepthResolveFragmentSpirv,
|
||||
sizeof(kWireMultisampleDepthResolveFragmentSpirv), cache.depthFragment);
|
||||
// THE STENCIL MODULE IS ONLY CREATED WHERE IT CAN RUN: its SPIR-V declares the
|
||||
// StencilExportEXT capability, and handing a module with an unsupported capability
|
||||
// to vkCreateShaderModule is undefined behaviour - not an error this could catch.
|
||||
if (m_wireShaderStencilExport)
|
||||
shader(kWireMultisampleStencilResolveFragmentSpirv,
|
||||
sizeof(kWireMultisampleStencilResolveFragmentSpirv), cache.stencilFragment);
|
||||
const VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
|
||||
VkDescriptorSetLayoutCreateInfo layout{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
layout.bindingCount = 1;
|
||||
layout.pBindings = &binding;
|
||||
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &layout, nullptr, &cache.descriptorLayout));
|
||||
// NO PUSH CONSTANT RANGE AT ALL: the pass reads its coordinate from gl_FragCoord and
|
||||
// its rectangle from the scissor, so neither stage takes an input.
|
||||
VkPipelineLayoutCreateInfo pipelineLayout{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||
pipelineLayout.setLayoutCount = 1;
|
||||
pipelineLayout.pSetLayouts = &cache.descriptorLayout;
|
||||
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayout, nullptr, &cache.pipelineLayout));
|
||||
// NEAREST: texelFetch ignores the sampler's filter, and most devices do not
|
||||
// advertise linear filtering for a depth format anyway. The sampler exists only
|
||||
// because GLSL's sampler2DMS is a COMBINED image sampler.
|
||||
VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||||
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
sampler.addressModeU = sampler.addressModeV = sampler.addressModeW =
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler.magFilter = sampler.minFilter = VK_FILTER_NEAREST;
|
||||
sampler.maxLod = 0.0f;
|
||||
VK_VERIFY(vkCreateSampler(m_device, &sampler, nullptr, &cache.nearest));
|
||||
VK_VERIFY(CreateWireShaderResolveKit(m_device, m_wireShaderStencilExport,
|
||||
m_wireMultisampleResolveResources->kit));
|
||||
}
|
||||
auto& cache = *m_wireMultisampleResolveResources;
|
||||
const VkShaderModule fragment = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? cache.stencilFragment
|
||||
: cache.depthFragment;
|
||||
const VkShaderModule fragment = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? cache.kit.stencilFragment
|
||||
: cache.kit.depthFragment;
|
||||
if (fragment == VK_NULL_HANDLE) return false;
|
||||
|
||||
WireMultisampleResolveResources::Target* target = nullptr;
|
||||
@@ -135,90 +87,9 @@
|
||||
target = &cache.targets.back();
|
||||
target->format = resolved.format;
|
||||
target->aspect = aspect;
|
||||
VkAttachmentDescription attachment{};
|
||||
attachment.format = resolved.format;
|
||||
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
// DONT_CARE on BOTH aspects: the pass writes every texel of the scissored rect for
|
||||
// the aspect it owns, and the caller copies only that aspect out. The other aspect
|
||||
// of a packed format is left undefined on purpose - it is never read, and LOADing
|
||||
// it would order this pass behind a write nobody is waiting for.
|
||||
attachment.loadOp = attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment.storeOp = attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
const VkAttachmentReference depthStencil{0, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.pDepthStencilAttachment = &depthStencil;
|
||||
VkRenderPassCreateInfo pass{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
pass.attachmentCount = 1;
|
||||
pass.pAttachments = &attachment;
|
||||
pass.subpassCount = 1;
|
||||
pass.pSubpasses = &subpass;
|
||||
VK_VERIFY(vkCreateRenderPass(m_device, &pass, nullptr, &target->renderPass));
|
||||
VkPipelineShaderStageCreateInfo stages[2]{};
|
||||
for (auto& stage : stages) {
|
||||
stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stage.pName = "main";
|
||||
}
|
||||
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
stages[0].module = cache.vertex;
|
||||
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
stages[1].module = fragment;
|
||||
const VkPipelineVertexInputStateCreateInfo vertex{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
||||
VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||
assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
VkPipelineViewportStateCreateInfo viewport{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||
viewport.viewportCount = viewport.scissorCount = 1;
|
||||
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
raster.cullMode = VK_CULL_MODE_NONE;
|
||||
raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
raster.lineWidth = 1.0f;
|
||||
VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencilState{
|
||||
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
depthStencilState.minDepthBounds = 0.0f;
|
||||
depthStencilState.maxDepthBounds = 1.0f;
|
||||
if (aspect == VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||
// ALWAYS + write: gl_FragDepth is the answer, and a comparison against whatever
|
||||
// DONT_CARE left behind would discard some of it.
|
||||
depthStencilState.depthTestEnable = VK_TRUE;
|
||||
depthStencilState.depthWriteEnable = VK_TRUE;
|
||||
depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
} else {
|
||||
// REPLACE on every outcome, compare ALWAYS, full write mask: that is what makes
|
||||
// the shader-exported reference the value that lands. The static `reference` is
|
||||
// overridden by gl_FragStencilRefARB, which is the whole point of the extension.
|
||||
depthStencilState.stencilTestEnable = VK_TRUE;
|
||||
VkStencilOpState op{};
|
||||
op.failOp = op.passOp = op.depthFailOp = VK_STENCIL_OP_REPLACE;
|
||||
op.compareOp = VK_COMPARE_OP_ALWAYS;
|
||||
op.compareMask = op.writeMask = 0xFFu;
|
||||
op.reference = 0;
|
||||
depthStencilState.front = depthStencilState.back = op;
|
||||
}
|
||||
// No colour attachment in this render pass, so no attachment states either.
|
||||
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
||||
const VkDynamicState dynamicStates[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
|
||||
dynamic.dynamicStateCount = 2;
|
||||
dynamic.pDynamicStates = dynamicStates;
|
||||
VkGraphicsPipelineCreateInfo pipeline{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
pipeline.stageCount = 2;
|
||||
pipeline.pStages = stages;
|
||||
pipeline.pVertexInputState = &vertex;
|
||||
pipeline.pInputAssemblyState = &assembly;
|
||||
pipeline.pViewportState = &viewport;
|
||||
pipeline.pRasterizationState = &raster;
|
||||
pipeline.pMultisampleState = &samples;
|
||||
pipeline.pDepthStencilState = &depthStencilState;
|
||||
pipeline.pColorBlendState = &blend;
|
||||
pipeline.pDynamicState = &dynamic;
|
||||
pipeline.layout = cache.pipelineLayout;
|
||||
pipeline.renderPass = target->renderPass;
|
||||
if (vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipeline, nullptr,
|
||||
&target->pipeline) != VK_SUCCESS) {
|
||||
VK_VERIFY(CreateWireShaderResolveRenderPass(m_device, resolved.format, &target->renderPass));
|
||||
if (CreateWireShaderResolvePipeline(m_device, cache.kit, target->renderPass, aspect,
|
||||
&target->pipeline) != VK_SUCCESS) {
|
||||
vkDestroyRenderPass(m_device, target->renderPass, nullptr);
|
||||
cache.targets.pop_back();
|
||||
return false;
|
||||
@@ -229,22 +100,19 @@
|
||||
TransitionWireImage(source, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
TransitionWireImage(resolved, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.image = source.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = source.format;
|
||||
// ONE ASPECT on the sampled view: a depth/stencil image can only be SAMPLED through a
|
||||
// view that names exactly one of its aspects, which is also what makes the stencil half
|
||||
// a usampler2DMS and the depth half a sampler2DMS.
|
||||
viewInfo.subresourceRange = {aspect, source.level, 1, source.layer, 1};
|
||||
const VkImageViewCreateInfo sourceViewInfo =
|
||||
WireDepthStencilViewInfo(source.image, source.format, aspect, source.level, source.layer);
|
||||
VkImageView sourceView = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &sourceView));
|
||||
viewInfo.image = resolved.image;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &sourceViewInfo, nullptr, &sourceView));
|
||||
// BOTH aspects on the attachment view: a depth/stencil ATTACHMENT names every aspect
|
||||
// its format has, whichever one the pipeline writes.
|
||||
viewInfo.subresourceRange = {formatAspects, 0, 1, 0, 1};
|
||||
const VkImageViewCreateInfo targetViewInfo =
|
||||
WireDepthStencilViewInfo(resolved.image, resolved.format, formatAspects, 0, 0);
|
||||
VkImageView targetView = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &targetView));
|
||||
VK_VERIFY(vkCreateImageView(m_device, &targetViewInfo, nullptr, &targetView));
|
||||
|
||||
VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebufferInfo.renderPass = target->renderPass;
|
||||
@@ -269,9 +137,9 @@
|
||||
VkDescriptorSetAllocateInfo alloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
alloc.descriptorPool = descriptorPool;
|
||||
alloc.descriptorSetCount = 1;
|
||||
alloc.pSetLayouts = &cache.descriptorLayout;
|
||||
alloc.pSetLayouts = &cache.kit.descriptorLayout;
|
||||
VK_VERIFY(vkAllocateDescriptorSets(m_device, &alloc, &descriptorSet));
|
||||
const VkDescriptorImageInfo sampled{cache.nearest, sourceView,
|
||||
const VkDescriptorImageInfo sampled{cache.kit.nearest, sourceView,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = descriptorSet;
|
||||
@@ -282,28 +150,8 @@
|
||||
vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr);
|
||||
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
begin.renderPass = target->renderPass;
|
||||
begin.framebuffer = framebuffer;
|
||||
// The RECTANGLE is the render area, exactly as the extension arm's renderArea is - the
|
||||
// scratch outside it is never read.
|
||||
begin.renderArea = {{sx0, sy0}, {width, height}};
|
||||
vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, target->pipeline);
|
||||
// THE VIEWPORT IS THE WHOLE ATTACHMENT AND THE SCISSOR IS THE RECT, and the two are not
|
||||
// interchangeable here: gl_FragCoord is what the fragment shader fetches by, so the
|
||||
// clip-space-to-attachment mapping has to be the identity for every rectangle. A
|
||||
// viewport shrunk onto the rect would slide the whole image instead of moving a window
|
||||
// over it.
|
||||
const VkViewport viewport{0.0f, 0.0f, static_cast<float>(resolved.extent.width),
|
||||
static_cast<float>(resolved.extent.height), 0.0f, 1.0f};
|
||||
const VkRect2D scissor{{sx0, sy0}, {width, height}};
|
||||
vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport);
|
||||
vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor);
|
||||
vkCmdBindDescriptorSets(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
cache.pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
|
||||
vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0);
|
||||
vkCmdEndRenderPass(frame.commandBuffer);
|
||||
RecordWireShaderResolveDraw(frame.commandBuffer, cache.kit, target->renderPass, target->pipeline, framebuffer,
|
||||
descriptorSet, resolved.extent, {{sx0, sy0}, {width, height}});
|
||||
ResetDynamicStateShadow();
|
||||
InvalidatePipelineMemo();
|
||||
// This native pass binds its own set/layout without going through the application's
|
||||
|
||||
@@ -3356,13 +3356,23 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
if(shaderDepthResolve AND NOT mglItestServerEnvKnobArm)
|
||||
continue()
|
||||
endif()
|
||||
# P7 gate 5 (g5-msprobe): MsResolve0 on split and spawn also pins the REAL resolve probe
|
||||
# on this host - it must run at the server's bring-up, find lavapipe's no-draw resolve
|
||||
# render pass clean, and leave that pass first. MGITEST_EXPECT_DEPTH_RESOLVE_PROBE is a
|
||||
# marker only the scenario reads (DepthStencilReadbackMatrixScenario::
|
||||
# ExpectTheResolveArmWhereTheLaneAsks), and only where the server's private log belongs
|
||||
# to the entry - the tcp server is the lane fixture, so its MsResolve0 asserts pixels only.
|
||||
set(msResolveProbeMarker "")
|
||||
if(NOT shaderDepthResolve AND mglItestServerEnvKnobArm)
|
||||
set(msResolveProbeMarker "\;MGITEST_EXPECT_DEPTH_RESOLVE_PROBE=clean")
|
||||
endif()
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.MsResolve${shaderDepthResolve}."
|
||||
TEST_FILTER "DepthStencilReadbackMatrixScenario.AResolvedMultisampleDepthReadsBackFromTheDestination"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=${shaderDepthResolve}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-msresolve${mglItestArmLogTag}-shader${shaderDepthResolve}.log")
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=${shaderDepthResolve}${msResolveProbeMarker}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-msresolve${mglItestArmLogTag}-shader${shaderDepthResolve}.log")
|
||||
endforeach()
|
||||
# ---- P7 gate 5 (g5-msrbo): the SINGLE-SAMPLED depth/stencil blit ----------------------
|
||||
#
|
||||
@@ -3405,40 +3415,41 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-msflip${mglItestArmLogTag}-shader1.log")
|
||||
endif()
|
||||
# ---- P7 gate 5 (g5-msrbo review round): THE QUALCOMM ARM ORDER, ON A HOST -------------
|
||||
# ---- P7 gate 5 (g5-msrbo review round, g5-msprobe): THE DEFECT'S ARM ORDER, ON A HOST --
|
||||
#
|
||||
# What made KHR-GL46.direct_state_access.renderbuffers_storage_multisample pass on the
|
||||
# Redmi is an arm ORDER: on a Qualcomm device ResolveWireDepthStencil takes the shader
|
||||
# pass first, because the Adreno's no-draw VK_KHR_depth_stencil_resolve render pass left
|
||||
# its target unwritten. Lavapipe resolves correctly through both arms, so no pixel on this
|
||||
# host can see the order, and without these entries dropping it went unnoticed by every
|
||||
# lane. MGITEST_MAGMA_DEPTH_RESOLVE_VENDOR_ID substitutes the vendor id the server's arm
|
||||
# policy reads (VulkanRenderer.cpp, next to m_wirePreferShaderDepthResolve) - nothing
|
||||
# else reads it - and the two resolve cases then assert their pixels AND that the
|
||||
# server's private log says the shader pass resolved and the render pass never did
|
||||
# (DepthStencilReadbackMatrixScenario::ExpectTheShaderResolveArmWhereTheLaneAsks, which
|
||||
# asserts the same of the MsResolve1./MsFlip1. entries above).
|
||||
# Redmi is an arm ORDER: where the server's resolve probe measures the no-draw
|
||||
# VK_KHR_depth_stencil_resolve render pass leaving its target unwritten (the Adreno 830's
|
||||
# does) ResolveWireDepthStencil takes the shader pass first. Lavapipe resolves correctly
|
||||
# through both arms - its probe verdict is clean, which MsResolve0 above pins - so no
|
||||
# pixel on this host can see the order. MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=bug replaces the
|
||||
# probe's MEASUREMENT with a canned "the render pass wrote nothing, the shader control
|
||||
# resolved" one (VulkanRenderer::ArmWireDepthResolveOrder) - nothing else reads it - and the
|
||||
# two resolve cases then assert their pixels AND that the server's private log says the
|
||||
# verdict was the defect, the shader pass resolved and the render pass never did
|
||||
# (DepthStencilReadbackMatrixScenario::ExpectTheResolveArmWhereTheLaneAsks, which asserts
|
||||
# the same of the MsResolve1./MsFlip1. entries above).
|
||||
#
|
||||
# Split+spawn only, the server-side-knob reason above, named in spawn_lane_parity.py's
|
||||
# MAGMA_SERVER_ENV_KNOB_NO_TCP.
|
||||
#
|
||||
# Red once (executed, reverted): drop `m_wirePreferShaderDepthResolve ||` from
|
||||
# ResolveWireDepthStencil's `shaderFirst` (WireFramebuffer.inc) and all four entries fail
|
||||
# on the arm assertion with their pixels green.
|
||||
# Red once (executed, reverted): EvaluateWireDepthResolveProbe (WireDepthResolveArm.h)
|
||||
# answering Clean for every measurement - the probe's detection switched off - fails all
|
||||
# four entries on the arm assertions with their pixels green.
|
||||
if(mglItestServerEnvKnobArm)
|
||||
foreach(qualcommOrderCase MsResolveQ:AResolvedMultisampleDepthReadsBackFromTheDestination
|
||||
MsFlipQ:AFlippedMultisampleResolveMirrorsTheBandsAndAScaleDeclines)
|
||||
string(REPLACE ":" ";" qualcommOrderPair "${qualcommOrderCase}")
|
||||
list(GET qualcommOrderPair 0 qualcommOrderTail)
|
||||
list(GET qualcommOrderPair 1 qualcommOrderTest)
|
||||
string(TOLOWER "${qualcommOrderTail}" qualcommOrderLog)
|
||||
foreach(probeBugCase MsResolveBug:AResolvedMultisampleDepthReadsBackFromTheDestination
|
||||
MsFlipBug:AFlippedMultisampleResolveMirrorsTheBandsAndAScaleDeclines)
|
||||
string(REPLACE ":" ";" probeBugPair "${probeBugCase}")
|
||||
list(GET probeBugPair 0 probeBugTail)
|
||||
list(GET probeBugPair 1 probeBugTest)
|
||||
string(TOLOWER "${probeBugTail}" probeBugLog)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.${qualcommOrderTail}."
|
||||
TEST_FILTER "DepthStencilReadbackMatrixScenario.${qualcommOrderTest}"
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.${probeBugTail}."
|
||||
TEST_FILTER "DepthStencilReadbackMatrixScenario.${probeBugTest}"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_DEPTH_RESOLVE_VENDOR_ID=0x5143\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-${qualcommOrderLog}${mglItestArmLogTag}.log")
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=bug\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-${probeBugLog}${mglItestArmLogTag}.log")
|
||||
endforeach()
|
||||
endif()
|
||||
foreach(inPlaceCase CopyImageInPlaceAcrossLevelsAndLayers
|
||||
|
||||
@@ -231,49 +231,72 @@ namespace MGITest {
|
||||
: "");
|
||||
}
|
||||
|
||||
// P7 gate 5 (g5-msrbo review round): WHICH WIRE ARM RESOLVED, WHERE THE ENTRY SAYS
|
||||
// WHICH ONE IT HAS TO BE. Lavapipe resolves a multisample depth/stencil aspect
|
||||
// P7 gate 5 (g5-msrbo review round, g5-msprobe): WHICH WIRE ARM RESOLVED, WHERE THE ENTRY
|
||||
// SAYS WHICH ONE IT HAS TO BE. Lavapipe resolves a multisample depth/stencil aspect
|
||||
// correctly through both of ResolveWireDepthStencil's arms, so the pixel assertions
|
||||
// above cannot tell them apart - and the fix that made
|
||||
// KHR-GL46.direct_state_access.renderbuffers_storage_multisample pass on the Redmi is
|
||||
// exactly an arm ORDER (the shader pass first on Qualcomm, because the Adreno's no-draw
|
||||
// resolve render pass leaves its target unwritten). Two kinds of entry name the arm:
|
||||
// - MsResolveQ. / MsFlipQ. set MGITEST_MAGMA_DEPTH_RESOLVE_VENDOR_ID to Qualcomm's id,
|
||||
// the one input the server's arm policy reads in place of the device's vendor;
|
||||
// - MsResolve1. / MsFlip1. set MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=1, which
|
||||
// drops the render-pass arm altogether.
|
||||
// Either way the server must have resolved with the shader pass and never with the
|
||||
// render pass. The claim is read off the SERVER's private log (the backend runs on the
|
||||
// apply thread, the server role, on every arm), where ResolveWireDepthStencil says
|
||||
// once per arm which one ran; keep the two phrases in step with WireFramebuffer.inc.
|
||||
// Every other entry - the device's own vendor, whatever order that picks - asserts
|
||||
// nothing here.
|
||||
// exactly an arm ORDER, chosen by a resolve probe at the server's device bring-up (the
|
||||
// shader pass first where the no-draw resolve render pass is measured leaving its target
|
||||
// unwritten, as the Adreno 830's does). Three kinds of entry name the arm:
|
||||
// - MsResolveBug. / MsFlipBug. set MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=bug, which hands
|
||||
// the server's arm choice a canned "the render pass wrote nothing" measurement in
|
||||
// place of running the probe: the shader pass must resolve, the render pass never;
|
||||
// - MsResolve1. / MsFlip1. set MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=1, which drops
|
||||
// the render-pass arm altogether: same assertion;
|
||||
// - MsResolve0. on split and spawn sets MGITEST_EXPECT_DEPTH_RESOLVE_PROBE=clean - a
|
||||
// marker only this function reads - and asserts the REAL probe ran on the lane's
|
||||
// device (lavapipe), found the render pass clean, and that the render pass resolved.
|
||||
// Without it a probe that stopped working (or started reporting lavapipe as broken)
|
||||
// would leave every pixel green.
|
||||
// The claim is read off the SERVER's private log (the backend runs on the apply thread,
|
||||
// the server role, on every arm), where ResolveWireDepthStencil says once per arm which
|
||||
// one ran and ArmWireDepthResolveOrder states the verdict; keep the phrases in step with
|
||||
// WireFramebuffer.inc and VulkanRenderer.cpp. Every other entry asserts nothing here.
|
||||
//
|
||||
// Red once (executed, reverted): drop `m_wirePreferShaderDepthResolve ||` from
|
||||
// ResolveWireDepthStencil's `shaderFirst` and both Q entries fail here on split and
|
||||
// spawn with the render-pass line in the log, while their pixels stay green.
|
||||
static void ExpectTheShaderResolveArmWhereTheLaneAsks(const char* what) {
|
||||
const std::string vendor = SplitLane::MarkerValue("MGITEST_MAGMA_DEPTH_RESOLVE_VENDOR_ID");
|
||||
const bool qualcommOrder = !vendor.empty() && std::strtoul(vendor.c_str(), nullptr, 0) == 0x5143ul;
|
||||
// Red once (executed, reverted): EvaluateWireDepthResolveProbe answering Clean for every
|
||||
// measurement fails the four Bug entries here (the render-pass line is in the log and
|
||||
// the verdict line says clean) while their pixels stay green.
|
||||
static void ExpectTheResolveArmWhereTheLaneAsks(const char* what) {
|
||||
const std::string probe = SplitLane::MarkerValue("MGITEST_MAGMA_DEPTH_RESOLVE_PROBE");
|
||||
const bool forcedBug = probe == "bug";
|
||||
const bool renderPassDropped = SplitLane::MarkerIsOne("MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE");
|
||||
if (!qualcommOrder && !renderPassDropped) return;
|
||||
const bool measuredClean = SplitLane::MarkerValue("MGITEST_EXPECT_DEPTH_RESOLVE_PROBE") == "clean";
|
||||
if (!forcedBug && !renderPassDropped && !measuredClean) return;
|
||||
if (PipeStatsWindow::ServerLibraryLogPath().empty()) {
|
||||
ADD_FAILURE() << what << ": the entry names the resolve arm (vendor=" << vendor
|
||||
<< ", force-shader=" << renderPassDropped
|
||||
ADD_FAILURE() << what << ": the entry names the resolve arm (probe=" << probe
|
||||
<< ", force-shader=" << renderPassDropped << ", expect-clean=" << measuredClean
|
||||
<< ") but configured no MOBILEGL_LOG_FILE_PATH, and the server's log "
|
||||
"is the only place the arm is visible";
|
||||
return;
|
||||
}
|
||||
glFinish();
|
||||
const std::string server = PipeStatsWindow::ReadServerLogSince(PipeStatsWindow::LogMark{});
|
||||
EXPECT_NE(server.find("ResolveWireDepthStencil: resolved by the shader pass"), std::string::npos)
|
||||
<< what << ": the server never resolved with the shader pass (vendor=" << vendor
|
||||
<< ", force-shader=" << renderPassDropped << ")";
|
||||
EXPECT_EQ(server.find("ResolveWireDepthStencil: resolved by the VK_KHR_depth_stencil_resolve render pass"),
|
||||
const bool shaderResolved =
|
||||
server.find("ResolveWireDepthStencil: resolved by the shader pass") != std::string::npos;
|
||||
const bool renderPassResolved =
|
||||
server.find("ResolveWireDepthStencil: resolved by the VK_KHR_depth_stencil_resolve render pass") !=
|
||||
std::string::npos;
|
||||
if (forcedBug || renderPassDropped) {
|
||||
EXPECT_TRUE(shaderResolved) << what << ": the server never resolved with the shader pass (probe="
|
||||
<< probe << ", force-shader=" << renderPassDropped << ")";
|
||||
EXPECT_FALSE(renderPassResolved)
|
||||
<< what << ": the server resolved with the no-draw render pass, the arm the probe verdict "
|
||||
"(or the knob) rules out (probe=" << probe
|
||||
<< ", force-shader=" << renderPassDropped << ")";
|
||||
if (forcedBug) {
|
||||
EXPECT_NE(server.find("verdict=render-pass-resolve-broken"), std::string::npos)
|
||||
<< what << ": the forced `bug` measurement did not evaluate to the defect verdict";
|
||||
}
|
||||
return;
|
||||
}
|
||||
EXPECT_NE(server.find("depth/stencil resolve probe (measured on this device) verdict=clean"),
|
||||
std::string::npos)
|
||||
<< what << ": the server resolved with the no-draw render pass, the arm that leaves its "
|
||||
"target unwritten on the Adreno (vendor=" << vendor
|
||||
<< ", force-shader=" << renderPassDropped << ")";
|
||||
<< what << ": the server's resolve probe did not measure this device's render pass clean";
|
||||
EXPECT_TRUE(renderPassResolved)
|
||||
<< what << ": a clean probe verdict must leave the render pass first, and it never resolved";
|
||||
EXPECT_FALSE(shaderResolved) << what << ": the shader pass resolved although the probe found the "
|
||||
"render pass clean";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -446,7 +469,7 @@ namespace MGITest {
|
||||
ExpectAllDepth(ReadDepthFloat(0, 0, kWidth, kHeight), 0.375f,
|
||||
"stencil-only resolve preserves destination depth");
|
||||
EXPECT_EQ(FirstGLError(), 0u);
|
||||
ExpectTheShaderResolveArmWhereTheLaneAsks("depth-only and stencil-only resolves");
|
||||
ExpectTheResolveArmWhereTheLaneAsks("depth-only and stencil-only resolves");
|
||||
|
||||
DestroySource(resolved);
|
||||
DestroySource(multisampled);
|
||||
@@ -613,7 +636,7 @@ namespace MGITest {
|
||||
}
|
||||
// The two flipped resolves above are the only ones in this case that reach an arm: every
|
||||
// leg below declines on its shape first.
|
||||
ExpectTheShaderResolveArmWhereTheLaneAsks("flipped depth and narrow stencil resolves");
|
||||
ExpectTheResolveArmWhereTheLaneAsks("flipped depth and narrow stencil resolves");
|
||||
|
||||
// A SCALE, which is INVALID_OPERATION and must leave the destination as it is.
|
||||
ClearDepthStencil(GL_DEPTH24_STENCIL8, 0.5f, 3);
|
||||
|
||||
@@ -517,18 +517,195 @@ TEST(WirePipelineCompatibility, HashUsesCompatibilityIdentityAndSeparatesNativeD
|
||||
#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) {
|
||||
// P7 gate 5 (g5-msrbo, g5-msprobe): the multisample depth/stencil resolve's arm order is the
|
||||
// resolve probe's VERDICT (WireDepthResolveArm.h), not the device's vendor. On the Redmi (Adreno
|
||||
// 830) the no-draw VK_KHR_depth_stencil_resolve render pass left its target unwritten while the
|
||||
// shader arm resolved correctly; the probe reproduces that on the actual device with the shader
|
||||
// arm as its control. These tests pin every mapping from a measurement to the order through a
|
||||
// FAKE RESULT SOURCE - a canned measurement handed to ChooseWireDepthResolveArm in place of the
|
||||
// Vulkan probe.
|
||||
#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));
|
||||
namespace {
|
||||
using MobileGL::MG_Backend::DirectVulkan::ChooseWireDepthResolveArm;
|
||||
using MobileGL::MG_Backend::DirectVulkan::DescribeWireDepthResolveProbe;
|
||||
using MobileGL::MG_Backend::DirectVulkan::EvaluateWireDepthResolveProbe;
|
||||
using MobileGL::MG_Backend::DirectVulkan::ParseWireDepthResolveProbeKnob;
|
||||
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveAspectReading;
|
||||
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveFormatReading;
|
||||
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveProbeKnob;
|
||||
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement;
|
||||
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveProbeVerdict;
|
||||
|
||||
// How one aspect's two arms read back, out of 16 texels.
|
||||
struct FakeArms {
|
||||
Uint32 renderPassMatches = 16;
|
||||
Bool shaderRan = true;
|
||||
Uint32 shaderMatches = 16;
|
||||
};
|
||||
|
||||
WireDepthResolveAspectReading FakeAspect(Uint32 expected, Uint32 sentinel, FakeArms arms) {
|
||||
WireDepthResolveAspectReading aspect;
|
||||
aspect.measured = true;
|
||||
aspect.texels = 16;
|
||||
aspect.expected = expected;
|
||||
aspect.sentinel = sentinel;
|
||||
aspect.renderPassMatches = arms.renderPassMatches;
|
||||
aspect.renderPassSentinels = 16 - arms.renderPassMatches;
|
||||
aspect.renderPassFirst = arms.renderPassMatches == 16 ? expected : sentinel;
|
||||
aspect.shaderRan = arms.shaderRan;
|
||||
aspect.shaderMatches = arms.shaderRan ? arms.shaderMatches : 0;
|
||||
aspect.shaderFirst = arms.shaderMatches == 16 ? expected : 0;
|
||||
return aspect;
|
||||
}
|
||||
|
||||
// A packed format (both aspects) or a depth-only one (stencil = nullptr-like: not measured).
|
||||
WireDepthResolveFormatReading FakeFormat(const char* name, FakeArms depth, Optional<FakeArms> stencil) {
|
||||
WireDepthResolveFormatReading reading;
|
||||
reading.name = name;
|
||||
reading.samples = 4;
|
||||
reading.ran = true;
|
||||
reading.depth = FakeAspect(0x400000u, 0xBFFFFFu, depth);
|
||||
if (stencil) reading.stencil = FakeAspect(0x5Au, 0xA5u, *stencil);
|
||||
return reading;
|
||||
}
|
||||
|
||||
WireDepthResolveProbeMeasurement FakeMeasurement(Vector<WireDepthResolveFormatReading> formats) {
|
||||
WireDepthResolveProbeMeasurement measurement;
|
||||
measurement.ran = true;
|
||||
measurement.formats = Move(formats);
|
||||
return measurement;
|
||||
}
|
||||
|
||||
// The Adreno 830's reading: the render pass left the sentinel in every texel of both aspects,
|
||||
// the shader control resolved all of them.
|
||||
constexpr FakeArms kRenderPassWroteNothing{0, true, 16};
|
||||
constexpr FakeArms kBothArmsResolve{16, true, 16};
|
||||
// Neither arm resolved: the probe measured its own setup, or a device that cannot resolve.
|
||||
constexpr FakeArms kControlFailsToo{0, true, 0};
|
||||
constexpr FakeArms kNoStencilExport{0, false, 0};
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST(PipelineQuirkTest, WireDepthResolveProbeDetectsARenderPassThatWritesNothing) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Int measured = 0;
|
||||
const auto choice = ChooseWireDepthResolveArm(WireDepthResolveProbeKnob::Measure, true, [&] {
|
||||
++measured;
|
||||
return FakeMeasurement({FakeFormat("D24_UNORM_S8_UINT", kRenderPassWroteNothing, kRenderPassWroteNothing),
|
||||
FakeFormat("D32_SFLOAT", kRenderPassWroteNothing, std::nullopt)});
|
||||
});
|
||||
EXPECT_EQ(measured, 1);
|
||||
EXPECT_EQ(choice.verdict, WireDepthResolveProbeVerdict::RenderPassResolveBroken);
|
||||
EXPECT_TRUE(choice.preferShader) << "the render pass writes nothing and the shader control resolves: the "
|
||||
"shader arm must go first";
|
||||
// Writing WRONG values is the same defect as writing none.
|
||||
auto wrong = FakeMeasurement({FakeFormat("D16_UNORM", kBothArmsResolve, std::nullopt)});
|
||||
wrong.formats[0].depth.renderPassMatches = 15;
|
||||
wrong.formats[0].depth.renderPassSentinels = 0;
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(wrong), WireDepthResolveProbeVerdict::RenderPassResolveBroken);
|
||||
// One aspect of one format is enough: the render pass resolves every aspect in one pass.
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(FakeMeasurement(
|
||||
{FakeFormat("D24_UNORM_S8_UINT", kBothArmsResolve, kRenderPassWroteNothing),
|
||||
FakeFormat("D32_SFLOAT", kBothArmsResolve, std::nullopt)})),
|
||||
WireDepthResolveProbeVerdict::RenderPassResolveBroken);
|
||||
EXPECT_NE(DescribeWireDepthResolveProbe(choice.measurement).find("render pass 0/16"), String::npos);
|
||||
#else
|
||||
GTEST_SKIP() << "the wire resolve arms exist only in the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(PipelineQuirkTest, WireDepthResolveProbeKeepsTheRenderPassFirstWhereItResolves) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const auto choice = ChooseWireDepthResolveArm(WireDepthResolveProbeKnob::Measure, true, [] {
|
||||
return FakeMeasurement({FakeFormat("D24_UNORM_S8_UINT", kBothArmsResolve, kBothArmsResolve),
|
||||
FakeFormat("D32_SFLOAT", kBothArmsResolve, std::nullopt)});
|
||||
});
|
||||
EXPECT_EQ(choice.verdict, WireDepthResolveProbeVerdict::Clean);
|
||||
EXPECT_FALSE(choice.preferShader) << "lavapipe's reading: the fast arm keeps its place";
|
||||
#else
|
||||
GTEST_SKIP() << "the wire resolve arms exist only in the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
// THE CONTROL (DriverBugProbes.h's iron rule): a render pass that failed where the shader pass
|
||||
// failed too is not evidence about the render pass.
|
||||
TEST(PipelineQuirkTest, WireDepthResolveProbeIsInconclusiveWhenTheShaderControlFailsToo) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const auto choice = ChooseWireDepthResolveArm(WireDepthResolveProbeKnob::Measure, true, [] {
|
||||
return FakeMeasurement({FakeFormat("D24_UNORM_S8_UINT", kControlFailsToo, kControlFailsToo),
|
||||
FakeFormat("D32_SFLOAT", kControlFailsToo, std::nullopt)});
|
||||
});
|
||||
EXPECT_EQ(choice.verdict, WireDepthResolveProbeVerdict::Inconclusive);
|
||||
EXPECT_FALSE(choice.preferShader) << "an inconclusive probe must never move the arm order";
|
||||
// No control at all (no shader kit) is the same answer.
|
||||
auto uncontrolled = FakeMeasurement({FakeFormat("D32_SFLOAT", kNoStencilExport, std::nullopt)});
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(uncontrolled), WireDepthResolveProbeVerdict::Inconclusive);
|
||||
// A stencil aspect with no control (no VK_EXT_shader_stencil_export) is not counted, either
|
||||
// way: its render-pass failure proves nothing, and the controlled depth aspect decides.
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(
|
||||
FakeMeasurement({FakeFormat("D24_UNORM_S8_UINT", kBothArmsResolve, kNoStencilExport)})),
|
||||
WireDepthResolveProbeVerdict::Clean);
|
||||
// A format whose control failed does not veto another format's conclusive defect.
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(
|
||||
FakeMeasurement({FakeFormat("D16_UNORM", kControlFailsToo, std::nullopt),
|
||||
FakeFormat("D32_SFLOAT", kRenderPassWroteNothing, std::nullopt)})),
|
||||
WireDepthResolveProbeVerdict::RenderPassResolveBroken);
|
||||
// Skipped formats and a probe that did not run (or timed out) conclude nothing.
|
||||
auto skipped = FakeMeasurement({FakeFormat("X8_D24_UNORM_PACK32", kRenderPassWroteNothing, std::nullopt)});
|
||||
skipped.formats[0].ran = false;
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(skipped), WireDepthResolveProbeVerdict::Inconclusive);
|
||||
auto timedOut = FakeMeasurement({FakeFormat("D32_SFLOAT", kRenderPassWroteNothing, std::nullopt)});
|
||||
timedOut.fenceWaitTimedOut = true;
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(timedOut), WireDepthResolveProbeVerdict::NotRun);
|
||||
EXPECT_EQ(EvaluateWireDepthResolveProbe(WireDepthResolveProbeMeasurement{}), WireDepthResolveProbeVerdict::NotRun);
|
||||
#else
|
||||
GTEST_SKIP() << "the wire resolve arms exist only in the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(PipelineQuirkTest, WireDepthResolveProbeRunsOnlyWhereThereIsARenderPassArmToJudge) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Int measured = 0;
|
||||
const auto choice = ChooseWireDepthResolveArm(WireDepthResolveProbeKnob::Measure, false, [&] {
|
||||
++measured;
|
||||
return FakeMeasurement({FakeFormat("D32_SFLOAT", kRenderPassWroteNothing, std::nullopt)});
|
||||
});
|
||||
EXPECT_EQ(measured, 0) << "without VK_KHR_depth_stencil_resolve the shader pass is the only arm";
|
||||
EXPECT_EQ(choice.verdict, WireDepthResolveProbeVerdict::NotRun);
|
||||
EXPECT_FALSE(choice.preferShader);
|
||||
#else
|
||||
GTEST_SKIP() << "the wire resolve arms exist only in the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
// MGITEST_MAGMA_DEPTH_RESOLVE_PROBE replaces the MEASUREMENT, not the verdict: the canned one goes
|
||||
// through the same evaluation, which is what lets the MsResolveBug./MsFlipBug. entries red when
|
||||
// the evaluation stops detecting the defect.
|
||||
TEST(PipelineQuirkTest, WireDepthResolveProbeKnobReplacesTheMeasurementNotTheVerdict) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
EXPECT_EQ(ParseWireDepthResolveProbeKnob(nullptr), WireDepthResolveProbeKnob::Measure);
|
||||
EXPECT_EQ(ParseWireDepthResolveProbeKnob(""), WireDepthResolveProbeKnob::Measure);
|
||||
EXPECT_EQ(ParseWireDepthResolveProbeKnob("bug"), WireDepthResolveProbeKnob::ForceBug);
|
||||
EXPECT_EQ(ParseWireDepthResolveProbeKnob("clean"), WireDepthResolveProbeKnob::ForceClean);
|
||||
EXPECT_EQ(ParseWireDepthResolveProbeKnob("0x5143"), WireDepthResolveProbeKnob::Unrecognised);
|
||||
Int measured = 0;
|
||||
const auto measure = [&] {
|
||||
++measured;
|
||||
return FakeMeasurement({FakeFormat("D32_SFLOAT", kBothArmsResolve, std::nullopt)});
|
||||
};
|
||||
const auto bug = ChooseWireDepthResolveArm(WireDepthResolveProbeKnob::ForceBug, true, measure);
|
||||
EXPECT_TRUE(bug.measurement.fromKnob);
|
||||
EXPECT_EQ(bug.verdict, WireDepthResolveProbeVerdict::RenderPassResolveBroken);
|
||||
EXPECT_TRUE(bug.preferShader);
|
||||
const auto clean = ChooseWireDepthResolveArm(WireDepthResolveProbeKnob::ForceClean, true, measure);
|
||||
EXPECT_TRUE(clean.measurement.fromKnob);
|
||||
EXPECT_EQ(clean.verdict, WireDepthResolveProbeVerdict::Clean);
|
||||
EXPECT_FALSE(clean.preferShader);
|
||||
EXPECT_EQ(measured, 0) << "a forced verdict must not also run the device probe";
|
||||
const auto other = ChooseWireDepthResolveArm(WireDepthResolveProbeKnob::Unrecognised, true, measure);
|
||||
EXPECT_EQ(measured, 1) << "an unrecognised value runs the probe as if the knob were unset";
|
||||
EXPECT_FALSE(other.measurement.fromKnob);
|
||||
EXPECT_EQ(other.verdict, WireDepthResolveProbeVerdict::Clean);
|
||||
#else
|
||||
GTEST_SKIP() << "the wire resolve arms exist only in the disaggregated build";
|
||||
#endif
|
||||
|
||||
@@ -85,13 +85,14 @@ MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.")
|
||||
# private log with the stats channel and MOBILEGL_IPC_WIRE_DEFERRED_MB=8 in the entry's environment -
|
||||
# server-process environment again, so the same two arms and the same tcp absence. Its three cases
|
||||
# are three tails (one key each) under the `.Reclaim.` prefix.
|
||||
# P7 gate 5 (g5-msrbo review round): `.MsResolveQ.` / `.MsFlipQ.` set
|
||||
# MGITEST_MAGMA_DEPTH_RESOLVE_VENDOR_ID, which the SERVER's depth/stencil resolve arm policy reads
|
||||
# in place of the device's vendor id - the knob-free `.MsResolve0.` / `.MsFlip.` keep their tcp
|
||||
# entries.
|
||||
# P7 gate 5 (g5-msrbo review round, g5-msprobe): `.MsResolveBug.` / `.MsFlipBug.` set
|
||||
# MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=bug, which the SERVER's depth/stencil resolve arm choice reads
|
||||
# in place of running its resolve probe - the knob-free `.MsResolve0.` / `.MsFlip.` keep their tcp
|
||||
# entries (MsResolve0's split/spawn copies also carry a client-read expectation marker for the
|
||||
# real probe's verdict, which the tcp copy cannot: same name on all three arms, no exception).
|
||||
MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.",
|
||||
".DefaultBlitShape1.", ".MsResolve1.", ".StaleSerial.", ".MsFlip1.",
|
||||
".MsResolveQ.", ".MsFlipQ.",
|
||||
".MsResolveBug.", ".MsFlipBug.",
|
||||
".Reclaim.MagmaWireReclaimScenario.RespecifiesWithNoDrawBetweenKeepTheLiveStoreCountBounded",
|
||||
".Reclaim.MagmaWireReclaimScenario.RespecifyAndDrawEachStoreInOneFrameStaysWithinTheDeferredBudget",
|
||||
".Reclaim.MagmaWireReclaimScenario.ManySmallRespecifyAndDrawRoundsStayUnderTheStoreCountCeiling",
|
||||
|
||||
Reference in New Issue
Block a user