aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/renderer_vulkan
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 11:15:48 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:48 -0400
commit00c059cd39f4fd9d1244345d6727a171acec4f86 (patch)
tree1da835c61e81dac8fe77769b922431850ba0cd84 /src/video_core/renderer_vulkan
parent24d3a1dbb3f2bb6dc5b8a515c1be1f7bad956b4b (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/video_core/renderer_vulkan')
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h22
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h3
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);