From 1d5f3c8f66a1394084d853a7d5053dc32ab8cf75 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 8 Aug 2026 01:21:03 +0200 Subject: [PATCH] gpu emulation: remove unnecessary Vulkan synchronization --- .../command_processor/pm4Handlers.cpp | 33 ++-- src/graphics/guest_gpu/graphicsRun.cpp | 19 +- tests/ShaderRecompilerComputeTests.cpp | 174 +++++++++++++++++- 3 files changed, 183 insertions(+), 43 deletions(-) diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index 034a72222..e0b359b37 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -2682,14 +2682,21 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) { const bool gl2_writeback = ((gcr_cntl & GcrGl2Writeback) != 0); auto trigger_interrupt = [&]() { + bool queued = false; switch (interrupt_selector) { case 0x00: case 0x03: break; case 0x01: case 0x02: - case 0x04: cp.TriggerEopEventAtEndOfPipe(interrupt_context_id); break; + case 0x04: + cp.TriggerEopEventAtEndOfPipe(interrupt_context_id); + queued = true; + break; default: EXIT("unknown release_mem interrupt selector\n"); } + if (queued) { + cp.BufferFlush(); + } }; if (data_sel == 0 || interrupt_selector == 4) { @@ -2697,10 +2704,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) { cp.EmitGlobalBarrier(); } - if (gl2_writeback) { - cp.SynchronizeGpu(); - } - trigger_interrupt(); return 7; @@ -2711,10 +2714,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) { cp.EmitGlobalBarrier(); } - if (gl2_writeback) { - cp.SynchronizeGpu(); - } - trigger_interrupt(); return 7; @@ -2728,17 +2727,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) { cache_policy = 0; if (data_sel == 1) { - static std::atomic log_count {0}; - const auto dst_addr = reinterpret_cast(dst_gpu_addr); - if ((dst_addr & 0xffffffffff000000ull) == 0x0000001021000000ull || log_count.load() < 64) { - if (log_count.fetch_add(1) < 256) { - LOGF( - "\t runtime release_mem32: dst=0x%016" PRIx64 ", value=0x%08" PRIx32 - ", action=0x%02" PRIx32 ", gcr=0x%04" PRIx32 ", cache_action=0x%08" PRIx32 "\n", - dst_addr, static_cast(value), eop_event_type, gcr_cntl, cache_action); - } - } - eop_event_type = 0x2f; event_index = 6; auto event_source = 2u; @@ -2753,7 +2741,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) { if (data_sel == 5) { if (gl2_writeback) { - cp.SynchronizeGpu(); cache_action = 0; } @@ -2764,7 +2751,9 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) { cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action, event_index, event_source, dst_gpu_addr, static_cast(value), interrupt_selector, interrupt_context_id); - cp.BufferFlush(); + if (interrupt_selector == 0x01) { + cp.BufferFlush(); + } return 7; } diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 92c010e1c..8545e10f7 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -124,7 +124,6 @@ private: uint64_t flip_request_id = 0; }; - void WaitLocked(); void Enqueue(Submission submission); void WaitForIdle(); void ProcessCommands(); @@ -276,10 +275,8 @@ void GpuState::SubmitFlipPreparation(uint64_t request_id) { void GpuState::Done() { GpuMutexLock lock(m_submission_mutex); - if (IsGpuThread()) { - m_gfx_cp->BufferWait(); - } else { - WaitLocked(); + if (!IsGpuThread()) { + WaitForIdle(); } m_graphics_done = true; m_done_num++; @@ -289,11 +286,6 @@ int GpuState::GetFrameNum() { return m_done_num; } -void GpuState::WaitLocked() { - WaitForIdle(); - SendCommandSync([this] { m_gfx_cp->BufferWait(); }); -} - CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) { EXIT_IF(queue_id >= QueueCount); if (queue_id == 0) { @@ -307,8 +299,6 @@ CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) { } void CommandProcessor::Reset() { - BufferWait(); - m_sh_ctx.Reset(); m_ucfg.Reset(); m_ctx.Reset(); @@ -385,7 +375,6 @@ void CommandProcessor::WaitDeDiff(uint32_t diff) { } void CommandProcessor::IncrementDe() { - BufferWait(); m_de_count++; } @@ -1352,6 +1341,9 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr value >> 16u); Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu, value >> 16u); + if (with_interrupt) { + m_renderer.TriggerEopEvent(interrupt_context_id); + } return; } } else if (eop_event_type == 0x04 && cache_action == 0x00 && event_index == 0x05) { @@ -1554,7 +1546,6 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) { event_index); } EmitGlobalBarrier(); - SynchronizeGpu(); break; // DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate. case 0x0000002a: diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 944fc684d..985985519 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1607,6 +1607,145 @@ public: order == 2 && gpu_thread != caller_thread && nested_thread == gpu_thread, "host command did not run on the GPU thread or nested sync deadlocked"); + { + const auto make_release_mem = [](uint32_t data_sel, uint32_t interrupt_selector, + void* destination, uint64_t value) { + std::array packet {}; + const auto address = reinterpret_cast(destination); + constexpr uint32_t eop_none = 0x28u; + constexpr uint32_t gcr_gl2_writeback = 1u << 9u; + packet[0] = KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_RELEASE_MEM); + packet[1] = eop_none | (gcr_gl2_writeback << 12u); + packet[2] = (interrupt_selector << 24u) | (data_sel << 29u); + packet[3] = static_cast(address); + packet[4] = static_cast(address >> 32u); + packet[5] = static_cast(value); + packet[6] = static_cast(value >> 32u); + packet[7] = 0x123u; + return packet; + }; + + vk::SemaphoreTypeCreateInfo timeline_type {}; + timeline_type.sType = vk::StructureType::eSemaphoreTypeCreateInfo; + timeline_type.semaphoreType = vk::SemaphoreType::eTimeline; + vk::SemaphoreCreateInfo timeline_create {}; + timeline_create.sType = vk::StructureType::eSemaphoreCreateInfo; + timeline_create.pNext = &timeline_type; + vk::Semaphore blocked_timeline = nullptr; + Require("GpuCommandLane", "blocked timeline create", + m_runtime_context.device.createSemaphore( + &timeline_create, nullptr, &blocked_timeline) == vk::Result::eSuccess && + blocked_timeline != nullptr, + "failed to create the blocked GPU timeline"); + + constexpr uint64_t release_tick = 1; + auto& gpu_scheduler = context.GetCommandScheduler(); + auto processor = std::make_unique(context); + gpu.SendCommandSync([&] { + SubmitInfo blocked_submit; + blocked_submit.AddWait(blocked_timeline, release_tick); + gpu_scheduler.Flush(blocked_submit); + }); + + std::binary_semaphore parser_complete {0}; + alignas(uint64_t) uint64_t interrupt_only_gds_label = UINT64_MAX; + bool parser_kept_nonblocking_boundaries = false; + std::jthread no_gpu_wait([&] { + gpu.SendCommandSync([&] { + processor->BufferInit(); + const auto tick_before = gpu_scheduler.CurrentTick(); + processor->TriggerEvent(0x16u, 0u); + auto packet = make_release_mem(0, 0, nullptr, 0); + Pm4Execution execution; + const auto result = processor->Process(execution, packet.data(), packet.size()); + processor->IncrementDe(); + const bool cache_and_counter_did_not_submit = + result == Pm4ProcessResult::Complete && + gpu_scheduler.CurrentTick() == tick_before; + + auto gds_interrupt_only = + make_release_mem(5, 1, &interrupt_only_gds_label, 1ull << 16u); + Pm4Execution gds_interrupt_execution; + const auto interrupt_tick = gpu_scheduler.CurrentTick(); + const auto interrupt_result = processor->Process( + gds_interrupt_execution, gds_interrupt_only.data(), gds_interrupt_only.size()); + parser_kept_nonblocking_boundaries = + cache_and_counter_did_not_submit && + interrupt_result == Pm4ProcessResult::Complete && + gpu_scheduler.CurrentTick() == interrupt_tick + 1 && + interrupt_only_gds_label == UINT64_MAX; + }); + parser_complete.release(); + }); + const bool parser_did_not_wait_gpu = + parser_complete.try_acquire_for(std::chrono::seconds(2)); + + vk::SemaphoreSignalInfo signal_info {}; + signal_info.sType = vk::StructureType::eSemaphoreSignalInfo; + signal_info.semaphore = blocked_timeline; + signal_info.value = release_tick; + Require("GpuCommandLane", "blocked timeline signal", + m_runtime_context.device.signalSemaphore(&signal_info) == vk::Result::eSuccess, + "failed to release the blocked GPU timeline"); + no_gpu_wait.join(); + gpu.SendCommandSync([&] { gpu_scheduler.Finish(); }); + m_runtime_context.device.destroySemaphore(blocked_timeline, nullptr); + Require("GpuCommandLane", "nonblocking GPU packets", + parser_did_not_wait_gpu && parser_kept_nonblocking_boundaries, + "a cache event, GL2 writeback, DE counter, or interrupt boundary used the wrong " + "submission behavior"); + + alignas(uint64_t) uint64_t release_label = 0; + alignas(uint64_t) uint64_t gds_label = UINT64_MAX; + bool release_mem_submission_counts = false; + gpu.SendCommandSync([&] { + processor->BufferInit(); + + auto immediate = make_release_mem(1, 0, &release_label, 0x11223344u); + Pm4Execution immediate_execution; + const auto immediate_tick = gpu_scheduler.CurrentTick(); + const auto immediate_result = + processor->Process(immediate_execution, immediate.data(), immediate.size()); + const bool immediate_split_once = + gpu_scheduler.CurrentTick() == immediate_tick + 1; + + auto gds = make_release_mem(5, 0, &gds_label, 1ull << 16u); + Pm4Execution gds_execution; + const auto gds_tick = gpu_scheduler.CurrentTick(); + const auto gds_result = processor->Process(gds_execution, gds.data(), gds.size()); + const bool gds_waited_once = gpu_scheduler.CurrentTick() == gds_tick + 1; + + auto interrupt_only = make_release_mem(0, 4, nullptr, 0); + Pm4Execution interrupt_execution; + const auto interrupt_tick = gpu_scheduler.CurrentTick(); + const auto interrupt_result = processor->Process( + interrupt_execution, interrupt_only.data(), interrupt_only.size()); + const bool interrupt_split_once = + gpu_scheduler.CurrentTick() == interrupt_tick + 1; + + auto gds_interrupt = make_release_mem(5, 2, &gds_label, 1ull << 16u); + Pm4Execution gds_interrupt_execution; + const auto gds_interrupt_tick = gpu_scheduler.CurrentTick(); + const auto gds_interrupt_result = processor->Process( + gds_interrupt_execution, gds_interrupt.data(), gds_interrupt.size()); + const bool gds_interrupt_waited_once = + gpu_scheduler.CurrentTick() == gds_interrupt_tick + 1; + + release_mem_submission_counts = + immediate_result == Pm4ProcessResult::Complete && immediate_split_once && + gds_result == Pm4ProcessResult::Complete && gds_waited_once && + interrupt_result == Pm4ProcessResult::Complete && interrupt_split_once && + gds_interrupt_result == Pm4ProcessResult::Complete && + gds_interrupt_waited_once; + }); + gpu.SendCommandSync([&] { gpu_scheduler.Finish(); }); + Require("GpuCommandLane", "RELEASE_MEM submission counts", + release_mem_submission_counts && + static_cast(release_label) == 0x11223344u && + static_cast(gds_label) == 0, + "RELEASE_MEM lost its required split/readback or retained a redundant GPU wait"); + } + alignas(uint32_t) uint32_t packet_marker_a = 0; alignas(uint32_t) uint32_t packet_marker_b = 0; const auto write_packet = [](uint32_t* packet, uint32_t* destination, uint32_t value) { @@ -18420,14 +18559,16 @@ void CheckPm4CeCompletion(RenderContext& renderer) { uint32_t suffix = 0; const auto address = reinterpret_cast(&suffix); - std::array commands {}; + std::array commands {}; commands[0] = 0xc0008600u; commands[1] = 1; - commands[2] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0); + commands[2] = 0xc0008500u; commands[3] = 0; - commands[4] = static_cast(address); - commands[5] = static_cast(address >> 32u); - commands[6] = 33; + commands[4] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0); + commands[5] = 0; + commands[6] = static_cast(address); + commands[7] = static_cast(address >> 32u); + commands[8] = 33; processor.ResetDeCe(); Pm4Execution execution; @@ -18437,11 +18578,30 @@ void CheckPm4CeCompletion(RenderContext& renderer) { suffix == 0, "DE did not wait for an active CE stream"); - processor.SetCeComplete(true); - Require("Pm4CeCompletion", "complete", + std::array ce_increment {0xc0008400u, 1}; + Pm4Execution ce_execution; + Require("Pm4CeCompletion", "CE increment", + processor.Process(ce_execution, ce_increment.data(), ce_increment.size()) == + Pm4ProcessResult::Complete, + "CE counter increment did not complete"); + Require("Pm4CeCompletion", "counter gate", processor.Process(execution, commands.data(), commands.size()) == Pm4ProcessResult::Complete && suffix == 33, + "DE did not resume after CE advanced or failed to increment its counter"); + + suffix = 0; + Pm4Execution balanced_execution; + Require("Pm4CeCompletion", "balanced wait", + processor.Process(balanced_execution, commands.data(), commands.size()) == + Pm4ProcessResult::Blocked && + suffix == 0, + "balanced CE/DE counters did not gate the next DE packet"); + processor.SetCeComplete(true); + Require("Pm4CeCompletion", "stream complete", + processor.Process(balanced_execution, commands.data(), commands.size()) == + Pm4ProcessResult::Complete && + suffix == 33, "DE remained blocked after the CE stream completed"); std::printf("[host] %-32s ok\n", "Pm4CeCompletion"); }