From 629ae1ac0583c0a1d781e573e601d99591fbc773 Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 15 Sep 2026 20:15:39 +0400 Subject: [PATCH] Handle Z-pass predication and query waits --- src/graphics/guest_gpu/graphicsRun.cpp | 61 +++++++++----- tests/ShaderRecompilerComputeTests.cpp | 106 +++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 21 deletions(-) diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index b291efeb0..dcdc8a8c7 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -818,36 +818,55 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) { void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op, const volatile void* address, uint32_t count_in_dwords) { - if (wait_op != 0) { - BufferFlushAndWait(); - } - (void)count_in_dwords; + uint64_t value = 0; switch (op) { - case 0x00: { + case 0x00: m_predicate_skip = false; - } break; - case 0x03: { + return; + case 0x01: { EXIT_NOT_IMPLEMENTED(address == nullptr); - - auto value = *reinterpret_cast(address); - - switch (condition) { - case 0x00: m_predicate_skip = (value != 0); break; - case 0x01: m_predicate_skip = (value == 0); break; - default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition); - } - static std::atomic log_count {0}; - if (log_count.fetch_add(1) < 128) { - LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64 - ", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n", - reinterpret_cast(address), value, condition, - m_predicate_skip ? 1u : 0u, wait_op); + // One begin/end pair per DB; bit 63 marks each counter ready. + constexpr uint64_t ready_bit = 1ull << 63u; + const auto* results = reinterpret_cast(address); + for (uint32_t db = 0; db < 16u; db++) { + const auto begin = results[db * 2u]; + const auto end = results[db * 2u + 1u]; + if ((begin & end & ready_bit) == 0) { + if (wait_op == 0) { + SuspendPm4(); + } else { + m_predicate_skip = false; + } + return; + } + value += end - begin; } } break; + case 0x03: + if (wait_op != 0) { + BufferFlushAndWait(); + } + EXIT_NOT_IMPLEMENTED(address == nullptr); + value = *reinterpret_cast(address); + break; default: EXIT("unknown predication op: 0x%08" PRIx32 "\n", op); } + switch (condition) { + case 0x00: m_predicate_skip = (value != 0); break; + case 0x01: m_predicate_skip = (value == 0); break; + default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition); + } + if (op == 0x03) { + static std::atomic log_count {0}; + if (log_count.fetch_add(1) < 128) { + LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64 + ", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n", + reinterpret_cast(address), value, condition, + m_predicate_skip ? 1u : 0u, wait_op); + } + } } void CommandProcessor::DrawIndex(DrawIndexArgs args) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 83fddebe9..dc2eb7fde 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -29450,6 +29450,106 @@ void CheckPm4SyntheticOcclusionCounterDump(RenderContext &renderer) { std::printf("[host] %-32s ok\n", "Pm4SyntheticOcclusionCounterDump"); } +void CheckPm4Predication(RenderContext &renderer) { + constexpr const char *name = "Pm4Predication"; + constexpr uint64_t ready = 1ull << 63u; + GraphicsInitJmpTables(); + CommandProcessor processor(renderer, 0); + processor.BufferInit(); + alignas(16) std::array query{}; + alignas(16) uint64_t boolean = 0; + uint32_t predicated = 0; + uint32_t unconditional = 0; + const auto commands = [&](uint32_t op, uint32_t condition, uint32_t wait, + const void *source) { + const auto address = reinterpret_cast(source); + const auto predicated_address = reinterpret_cast(&predicated); + const auto unconditional_address = reinterpret_cast(&unconditional); + return std::array{ + // Captured AGC form: c0022000 00010100 . + 0xc0022000u, (op << 16u) | (wait << 12u) | (condition << 8u), + static_cast(address), static_cast(address >> 32u), + KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0) | 1u, 0, + static_cast(predicated_address), + static_cast(predicated_address >> 32u), 11, + KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0), 0, + static_cast(unconditional_address), + static_cast(unconditional_address >> 32u), 22}; + }; + const auto check = [&](const char *stage, uint32_t op, uint32_t condition, + uint32_t wait, const void *source, bool skip) { + predicated = unconditional = 0; + const auto packet = commands(op, condition, wait, source); + Pm4Execution execution; + Require(name, stage, + processor.Process(execution, packet) == Pm4ProcessResult::Complete && + processor.ShouldSkipPredicatedPackets() == skip && + predicated == (skip ? 0u : 11u) && unconditional == 22, + "predicate polarity, tagged packet execution, or packet consumption is wrong"); + }; + struct Case { + uint64_t delta; + uint32_t condition; + bool skip; + }; + constexpr Case cases[]{{1, 1, false}, {1, 0, true}, + {0, 1, true}, {0, 0, false}}; + for (const auto &test : cases) { + query.fill(ready | 0x344u); + for (size_t db = 0; db < 16; db++) { + query[db * 2 + 1] += test.delta; + } + boolean = test.delta; + for (uint32_t wait : {0u, 1u}) { + check("ready Z-pass", 1, test.condition, wait, query.data(), test.skip); + check("bool preservation", 3, test.condition, wait, &boolean, test.skip); + } + } + query.fill(ready | 0x344u); + query[31]++; + check("only last DB visible", 1, 1, 0, query.data(), false); + check("inverse last DB visibility", 1, 0, 0, query.data(), true); + check("clear", 0, 0, 0, nullptr, false); + + // Every begin AND end counter must be ready; no-wait must discard old skip state. + for (size_t missing = 0; missing < query.size(); missing++) { + query.fill(ready | 0x344u); + query[missing] &= ~ready; + for (uint32_t condition : {0u, 1u}) { + boolean = 0; + processor.SetPredication(1, 3, 0, &boolean, 0); + check("unavailable no-wait", 1, condition, 1, query.data(), false); + } + } + + // Retry the same packet when its last DB's begin or end arrives later. + for (size_t missing : {30u, 31u}) { + query.fill(ready | 0x344u); + query[31]++; + query[missing] &= ~ready; + boolean = 0; + processor.SetPredication(1, 3, 0, &boolean, 0); + predicated = unconditional = 0; + const auto packet = commands(1, 1, 0, query.data()); + Pm4Execution execution; + for (int retry = 0; retry < 2; retry++) { + Require(name, "pending wait suspends", + processor.Process(execution, packet) == Pm4ProcessResult::Blocked && + !execution.MadeProgress() && predicated == 0 && unconditional == 0 && + processor.ShouldSkipPredicatedPackets(), + "pending query advanced its packet or changed the prior predicate"); + } + query[missing] |= ready; + Require(name, "ready wait resumes", + processor.Process(execution, packet) == Pm4ProcessResult::Complete && + execution.MadeProgress() && predicated == 11 && unconditional == 22 && + !processor.ShouldSkipPredicatedPackets(), + "ready query did not reevaluate and execute the suspended packet's suffix"); + } + processor.BufferWait(); + std::printf("[host] %-32s ok\n", name); +} + void CheckPm4StencilInfoValueLane(RenderContext &renderer) { CommandProcessor processor(renderer, 0); constexpr std::array payload{0x00100801u, 0x28000000u}; @@ -30729,6 +30829,11 @@ int main(int argc, char **argv) { CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--predication-only") == 0) { + VulkanHarness vulkan; + CheckPm4Predication(vulkan.RuntimeRenderer()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--descriptor-heap-only") == 0) { VulkanHarness vulkan; vulkan.CheckDescriptorHeapLargeSet(); @@ -31041,6 +31146,7 @@ int main(int argc, char **argv) { CheckVulkan13FeatureRequirements(); CheckPm4AcquireMemNoOp(vulkan.RuntimeRenderer()); CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer()); + CheckPm4Predication(vulkan.RuntimeRenderer()); CheckPm4StencilInfoValueLane(vulkan.RuntimeRenderer()); CheckPm4NativeTargetGeometryRegisters(vulkan.RuntimeRenderer()); CheckPm4PrivateAgcShaderRegisters(vulkan.RuntimeRenderer());