diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 604dfbc21..a318ac954 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -43,13 +43,14 @@ Scheduler::Scheduler(const Instance& instance, RenderpassCache& renderpass_cache Scheduler::~Scheduler() = default; void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) { + // When flushing, we only send data to the worker thread; no waiting is necessary. SubmitExecution(signal, wait); } void Scheduler::Finish(vk::Semaphore signal, vk::Semaphore wait) { + // When finishing, we need to wait for the submission to have executed on the device. const u64 presubmit_tick = CurrentTick(); SubmitExecution(signal, wait); - WaitWorker(); Wait(presubmit_tick); } @@ -61,8 +62,15 @@ void Scheduler::WaitWorker() { MICROPROFILE_SCOPE(Vulkan_WaitForWorker); DispatchWork(); - std::unique_lock lock{work_mutex}; - wait_cv.wait(lock, [this] { return work_queue.empty(); }); + // Ensure the queue is drained. + { + std::unique_lock ql{queue_mutex}; + event_cv.wait(ql, [this] { return work_queue.empty(); }); + } + + // Now wait for execution to finish. + // This needs to be done in the same order as WorkerThread. + std::scoped_lock el{execution_mutex}; } void Scheduler::Wait(u64 tick) { @@ -79,40 +87,65 @@ void Scheduler::DispatchWork() { } { - std::scoped_lock lock{work_mutex}; + std::scoped_lock ql{queue_mutex}; work_queue.push(std::move(chunk)); } - work_cv.notify_one(); + event_cv.notify_all(); AcquireNewChunk(); } void Scheduler::WorkerThread(std::stop_token stop_token) { Common::SetCurrentThreadName("VulkanWorker"); - do { - std::unique_ptr work; - bool has_submit{false}; - { - std::unique_lock lock{work_mutex}; - if (work_queue.empty()) { - wait_cv.notify_all(); - } - Common::CondvarWait(work_cv, lock, stop_token, [&] { return !work_queue.empty(); }); - if (stop_token.stop_requested()) { - continue; - } - work = std::move(work_queue.front()); - work_queue.pop(); - has_submit = work->HasSubmit(); + const auto TryPopQueue{[this](auto& work) -> bool { + if (work_queue.empty()) { + return false; + } + + work = std::move(work_queue.front()); + work_queue.pop(); + event_cv.notify_all(); + return true; + }}; + + while (!stop_token.stop_requested()) { + std::unique_ptr work; + + { + std::unique_lock lk{queue_mutex}; + + // Wait for work. + Common::CondvarWait(event_cv, lk, stop_token, [&] { return TryPopQueue(work); }); + + // If we've been asked to stop, we're done. + if (stop_token.stop_requested()) { + return; + } + + // Exchange lock ownership so that we take the execution lock before + // the queue lock goes out of scope. This allows us to force execution + // to complete in the next step. + std::exchange(lk, std::unique_lock{execution_mutex}); + + // Perform the work, tracking whether the chunk was a submission + // before executing. + const bool has_submit = work->HasSubmit(); work->ExecuteAll(current_cmdbuf); + + // If the chunk was a submission, reallocate the command buffer. + if (has_submit) { + AllocateWorkerCommandBuffers(); + } } - if (has_submit) { - AllocateWorkerCommandBuffers(); + + { + std::scoped_lock rl{reserve_mutex}; + + // Recycle the chunk back to the reserve. + chunk_reserve.emplace_back(std::move(work)); } - std::scoped_lock reserve_lock{reserve_mutex}; - chunk_reserve.push_back(std::move(work)); - } while (!stop_token.stop_requested()); + } } void Scheduler::AllocateWorkerCommandBuffers() { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index fb4473c52..4743c8a9c 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -207,11 +207,10 @@ private: std::vector> chunk_reserve; vk::CommandBuffer current_cmdbuf; StateFlags state{}; + std::mutex execution_mutex; std::mutex reserve_mutex; - std::mutex work_mutex; std::mutex queue_mutex; - std::condition_variable_any work_cv; - std::condition_variable wait_cv; + std::condition_variable_any event_cv; std::jthread worker_thread; bool use_worker_thread; };