diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.cpp | 84 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.h | 6 |
2 files changed, 62 insertions, 28 deletions
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index e03685af1..c636a1625 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp | |||
| @@ -47,14 +47,15 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) | |||
| 47 | Scheduler::~Scheduler() = default; | 47 | Scheduler::~Scheduler() = default; |
| 48 | 48 | ||
| 49 | void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { | 49 | void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { |
| 50 | // When flushing, we only send data to the worker thread; no waiting is necessary. | ||
| 50 | SubmitExecution(signal_semaphore, wait_semaphore); | 51 | SubmitExecution(signal_semaphore, wait_semaphore); |
| 51 | AllocateNewContext(); | 52 | AllocateNewContext(); |
| 52 | } | 53 | } |
| 53 | 54 | ||
| 54 | void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { | 55 | void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { |
| 56 | // When finishing, we need to wait for the submission to have executed on the device. | ||
| 55 | const u64 presubmit_tick = CurrentTick(); | 57 | const u64 presubmit_tick = CurrentTick(); |
| 56 | SubmitExecution(signal_semaphore, wait_semaphore); | 58 | SubmitExecution(signal_semaphore, wait_semaphore); |
| 57 | WaitWorker(); | ||
| 58 | Wait(presubmit_tick); | 59 | Wait(presubmit_tick); |
| 59 | AllocateNewContext(); | 60 | AllocateNewContext(); |
| 60 | } | 61 | } |
| @@ -63,8 +64,13 @@ void Scheduler::WaitWorker() { | |||
| 63 | MICROPROFILE_SCOPE(Vulkan_WaitForWorker); | 64 | MICROPROFILE_SCOPE(Vulkan_WaitForWorker); |
| 64 | DispatchWork(); | 65 | DispatchWork(); |
| 65 | 66 | ||
| 66 | std::unique_lock lock{work_mutex}; | 67 | // Ensure the queue is drained. |
| 67 | wait_cv.wait(lock, [this] { return work_queue.empty(); }); | 68 | std::unique_lock ql{queue_mutex}; |
| 69 | event_cv.wait(ql, [this] { return work_queue.empty(); }); | ||
| 70 | |||
| 71 | // Now wait for execution to finish. | ||
| 72 | // This needs to be done in the same order as WorkerThread. | ||
| 73 | std::unique_lock el{execution_mutex}; | ||
| 68 | } | 74 | } |
| 69 | 75 | ||
| 70 | void Scheduler::DispatchWork() { | 76 | void Scheduler::DispatchWork() { |
| @@ -72,10 +78,10 @@ void Scheduler::DispatchWork() { | |||
| 72 | return; | 78 | return; |
| 73 | } | 79 | } |
| 74 | { | 80 | { |
| 75 | std::scoped_lock lock{work_mutex}; | 81 | std::scoped_lock ql{queue_mutex}; |
| 76 | work_queue.push(std::move(chunk)); | 82 | work_queue.push(std::move(chunk)); |
| 77 | } | 83 | } |
| 78 | work_cv.notify_one(); | 84 | event_cv.notify_all(); |
| 79 | AcquireNewChunk(); | 85 | AcquireNewChunk(); |
| 80 | } | 86 | } |
| 81 | 87 | ||
| @@ -137,30 +143,55 @@ bool Scheduler::UpdateRescaling(bool is_rescaling) { | |||
| 137 | 143 | ||
| 138 | void Scheduler::WorkerThread(std::stop_token stop_token) { | 144 | void Scheduler::WorkerThread(std::stop_token stop_token) { |
| 139 | Common::SetCurrentThreadName("VulkanWorker"); | 145 | Common::SetCurrentThreadName("VulkanWorker"); |
| 140 | do { | 146 | |
| 147 | const auto TryPopQueue{[this](auto& work) -> bool { | ||
| 148 | if (work_queue.empty()) { | ||
| 149 | return false; | ||
| 150 | } | ||
| 151 | |||
| 152 | work = std::move(work_queue.front()); | ||
| 153 | work_queue.pop(); | ||
| 154 | event_cv.notify_all(); | ||
| 155 | return true; | ||
| 156 | }}; | ||
| 157 | |||
| 158 | while (!stop_token.stop_requested()) { | ||
| 141 | std::unique_ptr<CommandChunk> work; | 159 | std::unique_ptr<CommandChunk> work; |
| 142 | bool has_submit{false}; | 160 | |
| 143 | { | 161 | { |
| 144 | std::unique_lock lock{work_mutex}; | 162 | std::unique_lock lk{queue_mutex}; |
| 145 | if (work_queue.empty()) { | 163 | |
| 146 | wait_cv.notify_all(); | 164 | // Wait for work. |
| 147 | } | 165 | Common::CondvarWait(event_cv, lk, stop_token, [&] { return TryPopQueue(work); }); |
| 148 | Common::CondvarWait(work_cv, lock, stop_token, [&] { return !work_queue.empty(); }); | 166 | |
| 167 | // If we've been asked to stop, we're done. | ||
| 149 | if (stop_token.stop_requested()) { | 168 | if (stop_token.stop_requested()) { |
| 150 | continue; | 169 | return; |
| 151 | } | 170 | } |
| 152 | work = std::move(work_queue.front()); | ||
| 153 | work_queue.pop(); | ||
| 154 | 171 | ||
| 155 | has_submit = work->HasSubmit(); | 172 | // Exchange lock ownership so that we take the execution lock before |
| 173 | // the queue lock goes out of scope. This allows us to force execution | ||
| 174 | // to complete in the next step. | ||
| 175 | std::exchange(lk, std::unique_lock{execution_mutex}); | ||
| 176 | |||
| 177 | // Perform the work, tracking whether the chunk was a submission | ||
| 178 | // before executing. | ||
| 179 | const bool has_submit = work->HasSubmit(); | ||
| 156 | work->ExecuteAll(current_cmdbuf); | 180 | work->ExecuteAll(current_cmdbuf); |
| 181 | |||
| 182 | // If the chunk was a submission, reallocate the command buffer. | ||
| 183 | if (has_submit) { | ||
| 184 | AllocateWorkerCommandBuffer(); | ||
| 185 | } | ||
| 157 | } | 186 | } |
| 158 | if (has_submit) { | 187 | |
| 159 | AllocateWorkerCommandBuffer(); | 188 | { |
| 189 | std::scoped_lock rl{reserve_mutex}; | ||
| 190 | |||
| 191 | // Recycle the chunk back to the reserve. | ||
| 192 | chunk_reserve.emplace_back(std::move(work)); | ||
| 160 | } | 193 | } |
| 161 | std::scoped_lock reserve_lock{reserve_mutex}; | 194 | } |
| 162 | chunk_reserve.push_back(std::move(work)); | ||
| 163 | } while (!stop_token.stop_requested()); | ||
| 164 | } | 195 | } |
| 165 | 196 | ||
| 166 | void Scheduler::AllocateWorkerCommandBuffer() { | 197 | void Scheduler::AllocateWorkerCommandBuffer() { |
| @@ -289,13 +320,16 @@ void Scheduler::EndRenderPass() { | |||
| 289 | } | 320 | } |
| 290 | 321 | ||
| 291 | void Scheduler::AcquireNewChunk() { | 322 | void Scheduler::AcquireNewChunk() { |
| 292 | std::scoped_lock lock{reserve_mutex}; | 323 | std::scoped_lock rl{reserve_mutex}; |
| 324 | |||
| 293 | if (chunk_reserve.empty()) { | 325 | if (chunk_reserve.empty()) { |
| 326 | // If we don't have anything reserved, we need to make a new chunk. | ||
| 294 | chunk = std::make_unique<CommandChunk>(); | 327 | chunk = std::make_unique<CommandChunk>(); |
| 295 | return; | 328 | } else { |
| 329 | // Otherwise, we can just take from the reserve. | ||
| 330 | chunk = std::make_unique<CommandChunk>(); | ||
| 331 | chunk_reserve.pop_back(); | ||
| 296 | } | 332 | } |
| 297 | chunk = std::move(chunk_reserve.back()); | ||
| 298 | chunk_reserve.pop_back(); | ||
| 299 | } | 333 | } |
| 300 | 334 | ||
| 301 | } // namespace Vulkan | 335 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index bd4cb0f7e..8d75ce987 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h | |||
| @@ -232,10 +232,10 @@ private: | |||
| 232 | 232 | ||
| 233 | std::queue<std::unique_ptr<CommandChunk>> work_queue; | 233 | std::queue<std::unique_ptr<CommandChunk>> work_queue; |
| 234 | std::vector<std::unique_ptr<CommandChunk>> chunk_reserve; | 234 | std::vector<std::unique_ptr<CommandChunk>> chunk_reserve; |
| 235 | std::mutex execution_mutex; | ||
| 235 | std::mutex reserve_mutex; | 236 | std::mutex reserve_mutex; |
| 236 | std::mutex work_mutex; | 237 | std::mutex queue_mutex; |
| 237 | std::condition_variable_any work_cv; | 238 | std::condition_variable_any event_cv; |
| 238 | std::condition_variable wait_cv; | ||
| 239 | std::jthread worker_thread; | 239 | std::jthread worker_thread; |
| 240 | }; | 240 | }; |
| 241 | 241 | ||