From c130d74efccdb8a5e71f62ac42240ea8256fa9b0 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 22 Aug 2026 01:47:46 +0200 Subject: [PATCH] graphics: synthesize always-visible occlusion query results --- .../command_processor/commandProcessor.h | 7 +-- .../command_processor/pm4Handlers.cpp | 15 ++++-- src/graphics/guest_gpu/graphicsRun.cpp | 35 ++++++++++++-- tests/ShaderRecompilerComputeTests.cpp | 47 +++++++++++++++++++ 4 files changed, 93 insertions(+), 11 deletions(-) diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index 84092b2b1..bb17aa4be 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -103,7 +103,7 @@ public: uint32_t mode); void DispatchIndirect(uint32_t data_offset, uint32_t mode); void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index); - void TriggerEvent(uint32_t event_type, uint32_t event_index); + void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0); void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; } [[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; } @@ -178,8 +178,9 @@ private: uint32_t m_const_ram[0x3000] = {0}; FlipInfo m_flip; - uint64_t m_submit_id = 0; - bool m_predicate_skip = false; + uint64_t m_submit_id = 0; + uint64_t m_synthetic_occlusion_counter = 0; + bool m_predicate_skip = false; }; } // namespace Libs::Graphics diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index bd79fdb22..b22bb4f45 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -2147,12 +2147,19 @@ KYTY_CP_OP_PARSER(CpOpEventWrite) { EXIT_NOT_IMPLEMENTED(((cmd_id >> 8u) & 0xffu) != Pm4::IT_EVENT_WRITE); - uint32_t event_index = (buffer[0] >> 8u) & 0x7u; - uint32_t event_type = (buffer[0]) & 0x3fu; + const auto packet_size_dw = KYTY_PM4_LEN(cmd_id); + uint32_t event_index = (buffer[0] >> 8u) & 0x7u; + uint32_t event_type = (buffer[0]) & 0x3fu; + uint64_t event_address = 0; - cp.TriggerEvent(event_type, event_index); + if (event_type == 0x39u) { + EXIT_NOT_IMPLEMENTED(packet_size_dw != 4u); + event_address = buffer[1] | (static_cast(buffer[2]) << 32u); + } - return KYTY_PM4_LEN(cmd_id) - 1u; + cp.TriggerEvent(event_type, event_index, event_address); + + return packet_size_dw - 1u; } KYTY_CP_OP_PARSER(CpOpEventWriteEop) { diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index bcb0a4681..05d5f91de 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1467,12 +1468,14 @@ void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), interrupt_context_id); } -void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) { +void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index, + uint64_t event_address) { if (GraphicsRunDebugDumpEnabled()) { LOGF("CommandProcessor::TriggerEvent()\n" "\t event_type = 0x%08" PRIx32 "\n" - "\t event_index = 0x%08" PRIx32 "\n", - event_type, event_index); + "\t event_index = 0x%08" PRIx32 "\n" + "\t address = 0x%016" PRIx64 "\n", + event_type, event_index, event_address); } const auto valid_cache_event_index = event_index == 0x00000000 || event_index == 0x00000007; @@ -1509,12 +1512,36 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) { case 0x0000001a: case 0x0000001b: case 0x00000038: - case 0x00000039: case 0x0000003a: LOGF("\t temporary: ignoring unsupported event_write type 0x%08" PRIx32 ", index 0x%08" PRIx32 "\n", event_type, event_index); break; + case 0x00000039: { + if (event_index != 0x00000001 || event_address == 0 || (event_address & 0x7u) != 0) { + EXIT("invalid occlusion-counter dump: index=0x%08" PRIx32 ", address=0x%016" PRIx64 + "\n", + event_index, event_address); + } + static std::once_flag warning_once; + std::call_once(warning_once, [] { + std::printf("Warning: game uses occlusion queries, which are currently treated as " + "always visible; GPU usage may be higher and FPS may be lower.\n"); + }); + + // Until host occlusion queries are implemented, publish an always-visible result. The + // PS5 layout contains one interleaved begin/end pair per DB, and bit 63 marks a result + // ready. + constexpr uint64_t ready_bit = 1ull << 63u; + constexpr uint64_t counter_mask = ready_bit - 1u; + auto* results = reinterpret_cast(event_address); + const auto value = ready_bit | m_synthetic_occlusion_counter; + for (uint32_t db = 0; db < 16u; db++) { + results[db * 2u] = value; + } + m_synthetic_occlusion_counter = (m_synthetic_occlusion_counter + 1u) & counter_mask; + break; + } default: EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type, event_index); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 54255c626..08a2b03b7 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -22706,6 +22706,47 @@ void CheckPm4AcquireMemNoOp(RenderContext &renderer) { std::printf("[host] %-32s ok\n", "Pm4AcquireMemNoOp"); } +void CheckPm4SyntheticOcclusionCounterDump(RenderContext &renderer) { + GraphicsInitJmpTables(); + CommandProcessor processor(renderer); + constexpr uint64_t untouched = 0x1122334455667788ull; + constexpr uint64_t ready_bit = 1ull << 63u; + alignas(16) std::array, 16> results; + for (auto &pair : results) { + pair.fill(untouched); + } + + const auto dump = [&](uint64_t *address) { + const auto raw_address = reinterpret_cast(address); + std::array packet{ + KYTY_PM4(4, Pm4::IT_EVENT_WRITE, Pm4::R_ZERO), 0x00000139u, + static_cast(raw_address), + static_cast(raw_address >> 32u)}; + Pm4Execution execution; + return processor.Process(execution, packet.data(), packet.size()); + }; + + const auto begin_result = dump(&results[0][0]); + bool begin_written = begin_result == Pm4ProcessResult::Complete; + for (const auto &pair : results) { + begin_written &= pair[0] == ready_bit && pair[1] == untouched; + } + + const auto end_result = dump(&results[0][1]); + bool end_written = end_result == Pm4ProcessResult::Complete; + uint64_t visible_samples = 0; + for (const auto &pair : results) { + end_written &= pair[0] == ready_bit && pair[1] == (ready_bit | 1u); + visible_samples += pair[1] - pair[0]; + } + + Require("Pm4SyntheticOcclusionCounterDump", "always-visible result", + begin_written && end_written && visible_samples == results.size(), + "EVENT_WRITE did not publish valid nonzero begin/end counters to " + "every PS5 DB"); + std::printf("[host] %-32s ok\n", "Pm4SyntheticOcclusionCounterDump"); +} + void CheckPm4StencilInfoValueLane(RenderContext &renderer) { CommandProcessor processor(renderer); constexpr std::array payload{0x00100801u, 0x28000000u}; @@ -23409,6 +23450,11 @@ int main(int argc, char **argv) { vulkan.CheckSchedulerTimeline(); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--occlusion-dump-only") == 0) { + VulkanHarness vulkan; + CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--descriptor-heap-only") == 0) { VulkanHarness vulkan; vulkan.CheckDescriptorHeapLargeSet(); @@ -23651,6 +23697,7 @@ int main(int argc, char **argv) { CheckReferenceClockScale(); CheckVulkan13FeatureRequirements(); CheckPm4AcquireMemNoOp(vulkan.RuntimeRenderer()); + CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer()); CheckPm4StencilInfoValueLane(vulkan.RuntimeRenderer()); CheckPm4DirectShaderRegisterFallback(vulkan.RuntimeRenderer()); CheckPm4GuardBandRegisterRanges(vulkan.RuntimeRenderer());