diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 11:15:48 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 20:49:48 -0400 |
| commit | 00c059cd39f4fd9d1244345d6727a171acec4f86 (patch) | |
| tree | 1da835c61e81dac8fe77769b922431850ba0cd84 /src | |
| parent | 24d3a1dbb3f2bb6dc5b8a515c1be1f7bad956b4b (diff) | |
vulkan: never flush the scheduler from the async present thread
Swapchain::AcquireNextImage waits for the main scheduler's tick of the
image's last use; when that tick was not submitted yet, Scheduler::Wait
flushed, recording into the current chunk on the present thread while
the GPU thread recorded into it too (corrupt command buffers, crashes
in vkCmdEndRenderPass and in CommandChunk::Record). With async
presentation the present thread now waits up to 20 ms for the
recording thread to submit the tick, and goes on without the wait
otherwise (the copy to the swapchain has its own fences).
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_present_manager.cpp | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.h | 22 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_swapchain.cpp | 16 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_swapchain.h | 3 |
4 files changed, 30 insertions, 13 deletions
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 9298764546..c1724e1ab4 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -434,7 +434,7 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) { RecreateSwapchain(frame); } - while (swapchain.AcquireNextImage()) { + while (swapchain.AcquireNextImage(!use_present_thread)) { RecreateSwapchain(frame); } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 869f8b0af1..4456e9ef83 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -119,6 +119,18 @@ public: }); } + /// Whether the recording thread submitted `tick` within 20 ms. + [[nodiscard]] bool WaitForSubmission(u64 tick) const { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds{20}; + while (tick >= master_semaphore->CurrentTick()) { + if (std::chrono::steady_clock::now() >= deadline) { + return false; + } + std::this_thread::sleep_for(std::chrono::microseconds{100}); + } + return true; + } + /// Returns the current command buffer tick. [[nodiscard]] u64 CurrentTick() const noexcept { return master_semaphore->CurrentTick(); @@ -129,9 +141,13 @@ public: return master_semaphore->IsFree(tick); } - /// Waits for the given GPU tick, optionally pacing frames. - void Wait(u64 tick, double target_fps = 0.0) { - if (tick > 0) { + /// Waits for the given GPU tick, optionally pacing frames. Threads other than the one that + /// records (the async present thread) pass may_flush = false: flushing from there records + /// into the current chunk while that thread does (corrupt command buffers, crashes). They + /// wait for the recording thread to submit the tick instead, and give up after 20 ms (it may + /// be waiting for them). + void Wait(u64 tick, double target_fps = 0.0, bool may_flush = true) { + if (tick > 0 && (may_flush || WaitForSubmission(tick))) { if (tick >= master_semaphore->CurrentTick()) { Flush(); } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index fea5398557..a0ed5a4493 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -158,7 +158,7 @@ void Swapchain::Create( resource_ticks.resize(image_count); } -bool Swapchain::AcquireNextImage() { +bool Swapchain::AcquireNextImage(bool may_flush) { const VkResult result = device.GetLogical().AcquireNextImageKHR( *swapchain, (std::numeric_limits<u64>::max)(), *present_semaphores[frame_index], VK_NULL_HANDLE, &image_index); @@ -179,29 +179,29 @@ bool Swapchain::AcquireNextImage() { break; } - const auto wait_with_frame_pacing = [this] { + const auto wait_with_frame_pacing = [this, may_flush] { switch (Settings::values.frame_pacing_mode.GetValue()) { case Settings::FramePacingMode::Target_Auto: - scheduler.Wait(resource_ticks[image_index]); + scheduler.Wait(resource_ticks[image_index], 0.0, may_flush); break; case Settings::FramePacingMode::Target_30: - scheduler.Wait(resource_ticks[image_index], 30.0); + scheduler.Wait(resource_ticks[image_index], 30.0, may_flush); break; case Settings::FramePacingMode::Target_60: - scheduler.Wait(resource_ticks[image_index], 60.0); + scheduler.Wait(resource_ticks[image_index], 60.0, may_flush); break; case Settings::FramePacingMode::Target_90: - scheduler.Wait(resource_ticks[image_index], 90.0); + scheduler.Wait(resource_ticks[image_index], 90.0, may_flush); break; case Settings::FramePacingMode::Target_120: - scheduler.Wait(resource_ticks[image_index], 120.0); + scheduler.Wait(resource_ticks[image_index], 120.0, may_flush); break; } }; #ifdef __ANDROID__ if (android_get_device_api_level() >= 30) { - scheduler.Wait(resource_ticks[image_index]); + scheduler.Wait(resource_ticks[image_index], 0.0, may_flush); } else { wait_with_frame_pacing(); } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index d926cc118a..415160046c 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -37,7 +37,8 @@ public: u32 height); /// Acquires the next image in the swapchain, waits as needed. - bool AcquireNextImage(); + /// `may_flush`: false on the async present thread (see Scheduler::Wait). + bool AcquireNextImage(bool may_flush = true); /// Presents the rendered image to the swapchain. void Present(VkSemaphore render_semaphore); |
