diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index ca432602d..eee971ed6 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -62,7 +62,6 @@ public: void BufferInit(); void BufferFlush(); - void BufferFlushAndWait(); void BufferWait(); HW::Context& GetCtx() { return m_ctx; } HW::UserConfig& GetUcfg() { return m_ucfg; } diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 94510e0bd..411095395 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -35,7 +35,6 @@ namespace Libs::Graphics { static thread_local CommandProcessor* g_current_processor = nullptr; static thread_local Pm4Execution* g_current_execution = nullptr; -static thread_local bool g_gpu_mutex_owned = false; static thread_local bool g_gpu_thread = false; static thread_local GuestGpu* g_gpu_state = nullptr; @@ -54,27 +53,6 @@ struct DrawIndexedIndirectArgs { uint32_t start_instance_location; }; -class GpuMutexLock final { -public: - explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) { - if (g_gpu_mutex_owned) { - EXIT("recursive GPU mutex acquisition\n"); - } - g_gpu_mutex_owned = true; - m_mutex.Lock(); - } - ~GpuMutexLock() { - if (!g_gpu_mutex_owned) { - EXIT("invalid GPU mutex release\n"); - } - m_mutex.Unlock(); - g_gpu_mutex_owned = false; - } - -private: - Common::Mutex& m_mutex; -}; - static bool GraphicsRunDebugDumpEnabled() { return Config::GraphicsDebugDumpEnabled() && Config::GetPrintfDirection() != Config::LogDirection::Silent; @@ -84,6 +62,7 @@ GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); GraphicsInitJmpTables(); m_gfx_cp = std::make_unique(renderer, 0); + m_gfx_cp->Reset(); m_thread = std::jthread(ThreadRun, this); } @@ -160,20 +139,16 @@ void GuestGpu::Submit(std::span draw_commands, if (draw_commands.empty()) { return; } - GpuMutexLock lock(m_submission_mutex); - Submission submission; + Submission submission; submission.type = SubmissionType::Graphics; submission.queue_id = 0; submission.commands = draw_commands; submission.constant_commands = constant_commands; - submission.reset_processor = m_graphics_done; - m_graphics_done = false; Enqueue(std::move(submission)); } void GuestGpu::SubmitCompute(uint32_t queue, std::span commands) { EXIT_IF(commands.empty()); - GpuMutexLock lock(m_submission_mutex); EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount); @@ -186,22 +161,21 @@ void GuestGpu::SubmitCompute(uint32_t queue, std::span commands) } void GuestGpu::SubmitFlipPreparation(uint64_t request_id) { - GpuMutexLock lock(m_submission_mutex); - Submission submission; + Submission submission; submission.type = SubmissionType::FlipPreparation; submission.queue_id = 0; - submission.reset_processor = m_graphics_done; submission.flip_request_id = request_id; - m_graphics_done = false; Enqueue(std::move(submission)); } -void GuestGpu::Done() { - GpuMutexLock lock(m_submission_mutex); - if (!IsGpuThread()) { - WaitForIdle(); - } - m_graphics_done = true; +void GuestGpu::SuspendPoint() { + EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation()); + // Do not hold a queue lock while waiting: asynchronous work may be needed to + // finish the preceding graphics frame. The first point returns immediately. + m_suspend_point_ready->acquire(); + Submission submission; + submission.type = SubmissionType::SuspendPoint; + Enqueue(std::move(submission)); m_done_num++; } @@ -229,6 +203,9 @@ void CommandProcessor::Reset() { m_context_state_pushed = false; m_index_type_and_size = 0; m_index_buffer_size = 0; + m_index_base_addr = 0; + m_num_instances = 1; + m_predicate_skip = false; m_user_data_marker = HW::UserSgprType::Unknown; m_draw_indirect_args_base_addr = 0; m_dispatch_indirect_args_base_addr = 0; @@ -268,10 +245,6 @@ void CommandProcessor::BufferFlush() { GetScheduler().Flush(); } -void CommandProcessor::BufferFlushAndWait() { - GetScheduler().FlushAndWait(); -} - void CommandProcessor::BufferWait() { BufferInit(); GetScheduler().Finish(); @@ -459,6 +432,7 @@ void GuestGpu::Enqueue(Submission submission) { } void GuestGpu::WaitForIdle() { + EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation()); Common::LockGuard lock(m_queue_mutex); while (m_processing || !m_commands.empty() || m_submission_count != 0) { m_idle.Wait(&m_queue_mutex); @@ -564,11 +538,7 @@ void GuestGpu::ThreadRun(void* data) { bool GuestGpu::Process(Submission& submission) { const bool first_slice = !submission.started; - auto& cp = GetProcessor(submission.queue_id); - - if (first_slice && submission.reset_processor) { - cp.Reset(); - } + auto& cp = GetProcessor(submission.queue_id); if (first_slice) { submission.started = true; @@ -645,6 +615,13 @@ bool GuestGpu::Process(Submission& submission) { m_renderer.RunGarbageCollector(); cp.PrepareCpuFlip(submission.flip_request_id); break; + case SubmissionType::SuspendPoint: + cp.EmitGlobalBarrier(); + m_renderer.GetCommandScheduler().DeferPriorityOperation( + [ready = m_suspend_point_ready] { ready->release(); }); + cp.BufferFlush(); + cp.Reset(); + break; } return complete; @@ -849,9 +826,7 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t } } break; case 0x03: - if (wait_op != 0) { - BufferFlushAndWait(); - } + // The wait selector applies only to Z-pass query readiness. EXIT_NOT_IMPLEMENTED(address == nullptr); value = *reinterpret_cast(address); break; @@ -1034,9 +1009,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode)); uint32_t frame_num = 0; - // uint32_t local_x = 1; - // uint32_t local_y = 1; - // uint32_t local_z = 1; { frame_num = m_renderer.GetGpu().GetFrameNum(); @@ -1058,35 +1030,9 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g } } - const auto& cs = m_sh_ctx.GetCs().cs_regs; - // local_x = std::max(cs.num_thread_x, 1u); - // local_y = std::max(cs.num_thread_y, 1u); - // local_z = std::max(cs.num_thread_z, 1u); m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x, thread_group_y, thread_group_z, mode); } - - /*constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u; - auto group_count = [](uint32_t threads, uint32_t group_size) { - return (threads == 0 - ? 0u - : (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u)); - }; - - auto groups_x = thread_group_x; - auto groups_y = thread_group_y; - auto groups_z = thread_group_z; - if ((mode & DispatchInitiatorUseThreadDimensions) != 0) { - groups_x = group_count(thread_group_x, local_x); - groups_y = group_count(thread_group_y, local_y); - groups_z = group_count(thread_group_z, local_z); - } - - const uint64_t invocations = - static_cast(groups_x) * groups_y * groups_z * local_x * local_y * local_z; - if (invocations != 0) { - BufferFlushAndWait(); - }*/ } void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) { diff --git a/src/graphics/guest_gpu/graphicsRun.h b/src/graphics/guest_gpu/graphicsRun.h index 2cdf624df..781bda2d3 100644 --- a/src/graphics/guest_gpu/graphicsRun.h +++ b/src/graphics/guest_gpu/graphicsRun.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -32,11 +33,14 @@ public: void SendCommandSync(Common::UniqueFunction&& command); // Submitted command memory is borrowed and must remain valid until GPU execution completes. - void Submit(std::span draw_commands, - std::span constant_commands); - void SubmitCompute(uint32_t queue, std::span commands); - void SubmitFlipPreparation(uint64_t request_id); - void Done(); + void Submit(std::span draw_commands, + std::span constant_commands); + void SubmitCompute(uint32_t queue, std::span commands); + void SubmitFlipPreparation(uint64_t request_id); + // Insert an ordered graphics drain; only a previous suspend point can block the caller. + void SuspendPoint(); + // Wait for guest command processing, including all compute queues (not native GPU completion). + void WaitForIdle(); [[nodiscard]] int GetFrameNum() const; [[nodiscard]] static bool IsGpuThread() noexcept; @@ -48,7 +52,7 @@ private: static constexpr uint32_t ComputeQueueBase = 0x20; static constexpr uint32_t QueueCount = 1 + ComputeQueueCount; - enum class SubmissionType { Graphics, Compute, FlipPreparation }; + enum class SubmissionType { Graphics, Compute, FlipPreparation, SuspendPoint }; struct Submission { SubmissionType type = SubmissionType::Graphics; @@ -57,7 +61,6 @@ private: std::span constant_commands; Pm4Execution command_execution; Pm4Execution constant_execution; - bool reset_processor = false; bool started = false; bool command_complete = false; bool constant_complete = false; @@ -66,14 +69,12 @@ private: }; void Enqueue(Submission submission); - void WaitForIdle(); void ProcessCommands(); bool Process(Submission& submission); static void ThreadRun(void* data); CommandProcessor& GetProcessor(uint32_t queue_id); RenderContext& m_renderer; - Common::Mutex m_submission_mutex; Common::Mutex m_queue_mutex; std::mutex m_shutdown_mutex; Common::CondVar m_work_available; @@ -84,10 +85,12 @@ private: uint32_t m_next_queue = 0; uint32_t m_submission_count = 0; bool m_processing = false; - bool m_graphics_done = true; bool m_accepting = true; bool m_stopping = false; bool m_shutdown_complete = false; + // Completion callbacks can outlive GuestGpu during renderer shutdown. + std::shared_ptr m_suspend_point_ready = + std::make_shared(1); std::unique_ptr m_gfx_cp; std::array, ComputeQueueCount> m_compute_cp; diff --git a/src/libs/agc.cpp b/src/libs/agc.cpp index 8c4ba62dd..48a10a589 100644 --- a/src/libs/agc.cpp +++ b/src/libs/agc.cpp @@ -1552,7 +1552,7 @@ int KYTY_SYSV_ABI AgcSuspendPoint() { PRINT_NAME(); EXIT_IF(g_renderer == nullptr); - g_renderer->GetGpu().Done(); + g_renderer->GetGpu().SuspendPoint(); return OK; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 8cbdc7c04..5c183abe2 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -2402,6 +2402,232 @@ public: std::printf("[host] %-32s ok\n", "StreamBufferRing"); } + void CheckGpuSuspendPoint() { + constexpr const char *name = "GpuSuspendPoint"; + EnsureRuntimeContext(); + auto &context = Renderer(); + context.InitializeGpu(nullptr); + auto &gpu = context.GetGpu(); + auto &scheduler = context.GetCommandScheduler(); + CommandProcessor setup(context, 0); + + 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 gate = nullptr; + Require(name, "timeline create", + m_runtime_context.device.createSemaphore( + &timeline_create, nullptr, &gate) == vk::Result::eSuccess, + "failed to create the native completion gate"); + const auto signal = [&](uint64_t value) { + vk::SemaphoreSignalInfo info{}; + info.sType = vk::StructureType::eSemaphoreSignalInfo; + info.semaphore = gate; + info.value = value; + Require(name, "timeline signal", + m_runtime_context.device.signalSemaphore(&info) == + vk::Result::eSuccess, + "failed to release native GPU work"); + }; + const auto finish = [&] { + gpu.WaitForIdle(); + gpu.SendCommandSync([&] { + scheduler.Finish(); + scheduler.WaitPriorityOperations(scheduler.CurrentTick() - 1); + }); + }; + const auto write = [](uint32_t *packet, uint32_t *destination, + uint32_t value, bool predicated = false) { + const auto address = reinterpret_cast(destination); + packet[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0) | uint32_t{predicated}; + packet[1] = 0; + packet[2] = static_cast(address); + packet[3] = static_cast(address >> 32u); + packet[4] = value; + }; + + gpu.SendCommandSync([&] { + setup.BufferInit(); + SubmitInfo blocked; + blocked.AddWait(gate, 1); + scheduler.Flush(blocked); + }); + std::binary_semaphore first_returned{0}; + std::jthread first([&] { + gpu.SuspendPoint(); + first_returned.release(); + }); + const bool first_nonblocking = + first_returned.try_acquire_for(std::chrono::seconds(2)); + if (!first_nonblocking) { + signal(1); + } + first.join(); + Require(name, "first call", first_nonblocking, + "the first suspend point waited for native GPU completion"); + + // Let the marker reach Vulkan while its completion remains gated. + std::binary_semaphore marker_parsed{0}; + std::jthread parser([&] { + gpu.WaitForIdle(); + marker_parsed.release(); + }); + const bool parser_nonblocking = + marker_parsed.try_acquire_for(std::chrono::seconds(2)); + if (!parser_nonblocking) { + signal(1); + } + parser.join(); + Require(name, "marker insertion", parser_nonblocking, + "inserting a suspend point blocked the GPU command thread"); + + std::binary_semaphore second_entered{0}; + std::binary_semaphore second_returned{0}; + std::jthread second([&] { + second_entered.release(); + gpu.SuspendPoint(); + second_returned.release(); + }); + second_entered.acquire(); + const bool second_overtook_completion = + second_returned.try_acquire_for(std::chrono::milliseconds(100)); + + uint32_t compute_value = 0; + std::array compute_commands{}; + write(compute_commands.data(), &compute_value, 33); + bool host_lane_responded = false; + std::binary_semaphore compute_progress{0}; + std::jthread compute([&] { + gpu.SubmitCompute(0x20, compute_commands); + gpu.SendCommandSync([&] { host_lane_responded = true; }); + gpu.WaitForIdle(); + compute_progress.release(); + }); + const bool compute_did_not_wait = + compute_progress.try_acquire_for(std::chrono::seconds(2)); + signal(1); + second.join(); + compute.join(); + finish(); + Require(name, "one outstanding point", !second_overtook_completion, + "a second suspend point returned before the first native completion"); + Require(name, "independent queue progress", + compute_did_not_wait && host_lane_responded && compute_value == 33, + "waiting for a previous suspend point blocked compute submissions " + "or the host command lane"); + Require(name, "frame count", gpu.GetFrameNum() == 2, + "suspend-point insertion did not advance the frame counter once"); + + // A marker must stay behind a stalled graphics stream, then clear its + // predicate before the next stream. Async compute releases that stream. + alignas(16) uint64_t predicate = 0; + uint32_t label = 0; + uint32_t prefix = 0; + uint32_t skipped = 0; + uint32_t after_reset = 0; + const auto predicate_address = reinterpret_cast(&predicate); + const auto label_address = reinterpret_cast(&label); + std::array before{}; + before[0] = KYTY_PM4(4, Pm4::IT_SET_PREDICATION, 0); + before[1] = (3u << 16u) | (1u << 8u); + before[2] = static_cast(predicate_address); + before[3] = static_cast(predicate_address >> 32u); + write(before.data() + 4, &skipped, 11, true); + write(before.data() + 9, &prefix, 22); + before[14] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0); + before[15] = 0x10u | 3u; + before[16] = static_cast(label_address); + before[17] = static_cast(label_address >> 32u); + before[18] = 1; + before[19] = UINT32_MAX; + std::array after{}; + write(after.data(), &after_reset, 44, true); + std::binary_semaphore lane_entered{0}; + std::binary_semaphore release_lane{0}; + gpu.SendCommand([&] { + lane_entered.release(); + release_lane.acquire(); + }); + lane_entered.acquire(); + gpu.Submit(before, {}); + std::binary_semaphore queued_returned{0}; + std::jthread queued([&] { + gpu.SuspendPoint(); + queued_returned.release(); + }); + const bool queued_nonblocking = + queued_returned.try_acquire_for(std::chrono::seconds(2)); + if (!queued_nonblocking) { + release_lane.release(); + gpu.SendCommandSync([&] { label = 1; }); + queued.join(); + finish(); + Require(name, "queued insertion", false, + "suspend-point insertion drained pending guest graphics"); + } + queued.join(); + gpu.Submit(after, {}); + release_lane.release(); + bool prefix_reached = false; + bool graphics_ordered = false; + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::seconds(2); + do { + gpu.SendCommandSync([&] { + prefix_reached = prefix == 22; + graphics_ordered = skipped == 0 && after_reset == 0; + }); + std::this_thread::yield(); + } while (!prefix_reached && std::chrono::steady_clock::now() < deadline); + std::array release_graphics{}; + write(release_graphics.data(), &label, 1); + gpu.SubmitCompute(0x20, release_graphics); + finish(); + Require(name, "graphics FIFO", + prefix_reached && graphics_ordered && skipped == 0, + "suspend point or later graphics overtook a stalled graphics stream"); + Require(name, "graphics reset", after_reset == 44, + "suspend point preserved the previous frame's predicate"); + + // Delay the completion callback beyond the owner's shutdown when possible. + // Its credit must remain alive independently of the GuestGpu object. + std::binary_semaphore priority_entered{0}; + std::binary_semaphore release_priority{0}; + gpu.SendCommandSync([&] { + scheduler.DeferPriorityOperation([&] { + priority_entered.release(); + release_priority.acquire(); + }); + SubmitInfo blocked; + blocked.AddWait(gate, 2); + scheduler.Flush(blocked); + }); + gpu.SuspendPoint(); + gpu.WaitForIdle(); + std::binary_semaphore shutdown_returned{0}; + std::jthread shutdown([&] { + context.ShutdownGpu(); + shutdown_returned.release(); + }); + const bool shutdown_overtook_native = + shutdown_returned.try_acquire_for(std::chrono::milliseconds(100)); + signal(2); + priority_entered.acquire(); + if (!shutdown_overtook_native) { + (void)shutdown_returned.try_acquire_for(std::chrono::seconds(2)); + } + release_priority.release(); + shutdown.join(); + scheduler.DrainPriorityOperations(); + m_runtime_context.device.destroySemaphore(gate, nullptr); + Require(name, "shutdown native completion", !shutdown_overtook_native, + "shutdown returned while the suspend point was still executing"); + std::printf("[host] %-32s ok\n", name); + } + void CheckGpuCommandLane() { EnsureRuntimeContext(); auto &context = Renderer(); @@ -2647,7 +2873,7 @@ public: graphics_gate_entered.acquire(); live_graphics_commands[4] = 22; graphics_gate_release.release(); - gpu.Done(); + gpu.WaitForIdle(); Require("GpuCommandLane", "borrowed graphics commands", live_graphics_value == 22, "graphics submission executed a copied PM4 stream"); @@ -2665,7 +2891,7 @@ public: compute_gate_entered.acquire(); live_compute_commands[4] = 44; compute_gate_release.release(); - gpu.Done(); + gpu.WaitForIdle(); Require("GpuCommandLane", "borrowed compute commands", live_compute_value == 44, "compute submission executed a copied PM4 stream"); @@ -2721,7 +2947,7 @@ public: write_packet(no_interrupt_graphics_commands.data(), &no_interrupt_graphics_value, 55); gpu.Submit(no_interrupt_graphics_commands, {}); - gpu.Done(); + gpu.WaitForIdle(); finish_gpu(); LibKernel::EventQueue::KernelEvent interrupt_event{}; @@ -2745,7 +2971,7 @@ public: interrupt_gate_entered.acquire(); live_interrupt_commands[2] = 0; interrupt_gate_release.release(); - gpu.Done(); + gpu.WaitForIdle(); finish_gpu(); interrupt_count = 0; @@ -2761,7 +2987,7 @@ public: auto graphics_interrupt_commands = make_interrupt_packet( 1, 1, &graphics_interrupt_label, 0x11223344u, 0); gpu.Submit(graphics_interrupt_commands, {}); - gpu.Done(); + gpu.WaitForIdle(); finish_gpu(); interrupt_count = 0; @@ -2787,7 +3013,7 @@ public: compute_interrupt_gate_entered.acquire(); compute_interrupt_commands[2] |= 1u << 24u; compute_interrupt_gate_release.release(); - gpu.Done(); + gpu.WaitForIdle(); finish_gpu(); interrupt_count = 0; @@ -2803,7 +3029,7 @@ public: auto compute_clock_commands = make_interrupt_packet( 3, 1, &compute_clock_label, 0, 0x567u); gpu.SubmitCompute(0x20, compute_clock_commands); - gpu.Done(); + gpu.WaitForIdle(); finish_gpu(); interrupt_count = 0; @@ -2821,7 +3047,7 @@ public: 2, 2, &compute_done_label, compute_done_value, 0x678u); compute_done_commands[1] = 0x62fu; // CS_DONE, shader-done index, no GCR action. gpu.SubmitCompute(0x20, compute_done_commands); - gpu.Done(); + gpu.WaitForIdle(); finish_gpu(); interrupt_count = 0; @@ -2896,7 +3122,7 @@ public: }); std::this_thread::yield(); } - gpu.Done(); + gpu.WaitForIdle(); Require("GpuCommandLane", "packet-boundary command polling", packet_marker_a_at_callback == 11 && packet_marker_b_at_callback == 0 && packet_marker_a == 11 && @@ -2934,21 +3160,21 @@ public: uint32_t ordered_suffix = 0; std::jthread ordered([&] { ordered_started.release(); - gpu.Done(); + gpu.WaitForIdle(); ordered_suffix = suffix; ordered_finished = true; }); ordered_started.acquire(); gpu.SendCommandSync([&] { - Require("GpuCommandLane", "submit done barrier", !ordered_finished.load(), - "submit done returned before a queued submission"); + Require("GpuCommandLane", "guest queue idle fence", !ordered_finished.load(), + "idle fence returned before a queued submission"); label = 1; }); ordered.join(); Require("GpuCommandLane", "ordered completion", prefix == 11 && suffix == 22 && ordered_suffix == 22 && ordered_finished.load(), - "submit done did not drain prior PM4 work"); + "idle fence did not drain prior PM4 work"); auto &resources = context; constexpr uint64_t empty_unmap_base = 0x0000000200400000ull; @@ -2971,7 +3197,7 @@ public: unmap_complete.acquire(); } unmap_thread.join(); - gpu.Done(); + gpu.WaitForIdle(); Require("GpuCommandLane", "unmap queue progress", unmap_returned && !resources.IsMapped(empty_unmap_base, empty_unmap_size) && @@ -3098,7 +3324,7 @@ public: dma_cursor == dma_commands.size(), "DMA_DATA packet stream has the wrong size"); gpu.Submit(dma_commands, {}); - gpu.Done(); + gpu.WaitForIdle(); constexpr uint32_t clean_fill_value = 0xdecafbad; gpu.SendCommandSync([&] { auto &buffer_cache = resources.GetBufferCache(); @@ -3336,15 +3562,12 @@ public: std::binary_semaphore command_entered{0}; std::binary_semaphore release_command{0}; std::atomic shutdown_complete{false}; - std::atomic submission_lane_entered{false}; bool shutdown_owner_visible = false; resources.MapMemory(empty_unmap_base, empty_unmap_size); gpu.SendCommand([&] { command_entered.release(); release_command.acquire(); shutdown_owner_visible = &context.GetGpu() == &gpu; - gpu.Done(); - submission_lane_entered = true; }); command_entered.acquire(); std::jthread shutdown_thread([&] { @@ -3360,8 +3583,7 @@ public: shutdown_thread.join(); Require( "GpuCommandLane", "owned shutdown completion", - shutdown_complete.load() && submission_lane_entered.load() && - shutdown_owner_visible, + shutdown_complete.load() && shutdown_owner_visible, "GPU owner did not remain available while draining queued work"); resources.UnmapMemory(empty_unmap_base, empty_unmap_size); Require("GpuCommandLane", "unmap after shutdown", @@ -33981,12 +34203,15 @@ void CheckPm4Predication(RenderContext &renderer) { uint32_t wait, const void *source, bool skip) { predicated = unconditional = 0; const auto packet = commands(op, condition, wait, source); + const auto tick = renderer.GetCommandScheduler().CurrentTick(); 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"); + Require(name, stage, renderer.GetCommandScheduler().CurrentTick() == tick, + "predication unnecessarily submitted and drained the host queue"); }; struct Case { uint64_t delta; @@ -35506,6 +35731,11 @@ int main(int argc, char **argv) { CheckPm4CeCompletion(vulkan.RuntimeRenderer()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--suspend-point-only") == 0) { + VulkanHarness vulkan; + vulkan.CheckGpuSuspendPoint(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--alignbyte-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, VectorAlignByteUsesFiveBitByteOffset()); @@ -35915,6 +36145,7 @@ int main(int argc, char **argv) { for (const auto &test : graphics_tests) { RunGraphicsCase(&vulkan, test); } + vulkan.CheckGpuSuspendPoint(); vulkan.CheckGpuCommandLane(); if (skipped_device_checks) { std::printf(