diff options
| author | MaranBr <maranbr@outlook.com> | 2026-02-12 00:23:19 +0100 |
|---|---|---|
| committer | crueter <crueter@eden-emu.dev> | 2026-02-12 00:23:19 +0100 |
| commit | 5f676a6a55aa4d748e545b0fc6956e9a5dbd2f7f (patch) | |
| tree | c6771e1dbfc36e0ab0236f69275454542585e7be /src/video_core/renderer_vulkan | |
| parent | 2ab5b37137c1e1aa8e96d93bd0992aee872de44c (diff) | |
[vulkan] Add support for target FPS frame pacing (#3494)
This allows users to choose how the emulator manages frame pacing to reduce stuttering and provide a smoother and more consistent frame rate.
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3494
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
Diffstat (limited to 'src/video_core/renderer_vulkan')
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.cpp | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.h | 32 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_swapchain.cpp | 42 |
3 files changed, 68 insertions, 8 deletions
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index aafcfdf65b..2a69d6d244 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -347,7 +347,7 @@ void Scheduler::EndRenderPass() Record([num_images = num_renderpass_images, images = renderpass_images, ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) { - std::array<VkImageMemoryBarrier, 9> barriers; + std::vector<VkImageMemoryBarrier> barriers(num_images); VkPipelineStageFlags src_stages = 0; for (size_t i = 0; i < num_images; ++i) { const VkImageSubresourceRange& range = ranges[i]; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 5216a436c8..fb0ac9b008 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -16,6 +16,7 @@ #include "common/alignment.h" #include "common/common_types.h" +#include "common/settings.h" #include "common/polyfill_thread.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -111,15 +112,34 @@ public: return master_semaphore->IsFree(tick); } - /// Waits for the given tick to trigger on the GPU. - void Wait(u64 tick) { - if (tick >= master_semaphore->CurrentTick()) { - // Make sure we are not waiting for the current tick without signalling + /// Waits for the given GPU tick, optionally pacing frames. + void Wait(u64 tick, double target_fps = 0.0) { + if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) { + auto frame_duration = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / target_fps)); + auto now = std::chrono::steady_clock::now(); + if (now < next_frame_time) { + std::this_thread::sleep_until(next_frame_time); + next_frame_time += frame_duration; + } else { + next_frame_time = now + frame_duration; + } + } + if (tick > master_semaphore->CurrentTick() && !chunk->Empty()) { Flush(); } master_semaphore->Wait(tick); } + /// Resets the frame pacing state by setting the next frame time. + void ResetFramePacing(double target_fps = 0.0) { + if (target_fps > 0.0) { + auto frame_duration = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / target_fps)); + next_frame_time = std::chrono::steady_clock::now() + frame_duration; + } else { + next_frame_time = std::chrono::steady_clock::time_point{}; + } + } + /// Returns the master timeline semaphore. [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept { return *master_semaphore; @@ -261,6 +281,8 @@ private: std::mutex queue_mutex; std::condition_variable_any event_cv; std::jthread worker_thread; + + std::chrono::steady_clock::time_point next_frame_time{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 7418ad934e..7e7e67639d 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -146,6 +146,25 @@ void Swapchain::Create( { is_outdated = false; is_suboptimal = false; + + switch (Settings::values.frame_pacing_mode.GetValue()) { + case Settings::FramePacingMode::Target_Auto: + scheduler.ResetFramePacing(); + break; + case Settings::FramePacingMode::Target_30: + scheduler.ResetFramePacing(30.0); + break; + case Settings::FramePacingMode::Target_60: + scheduler.ResetFramePacing(60.0); + break; + case Settings::FramePacingMode::Target_120: + scheduler.ResetFramePacing(120.0); + break; + case Settings::FramePacingMode::Target_240: + scheduler.ResetFramePacing(240.0); + break; + } + width = width_; height = height_; #ifdef ANDROID @@ -194,7 +213,26 @@ bool Swapchain::AcquireNextImage() { break; } - scheduler.Wait(resource_ticks[image_index]); + if (resource_ticks[image_index] != 0 && !scheduler.IsFree(resource_ticks[image_index])) { + switch (Settings::values.frame_pacing_mode.GetValue()) { + case Settings::FramePacingMode::Target_Auto: + scheduler.Wait(resource_ticks[image_index]); + break; + case Settings::FramePacingMode::Target_30: + scheduler.Wait(resource_ticks[image_index], 30.0); + break; + case Settings::FramePacingMode::Target_60: + scheduler.Wait(resource_ticks[image_index], 60.0); + break; + case Settings::FramePacingMode::Target_120: + scheduler.Wait(resource_ticks[image_index], 120.0); + break; + case Settings::FramePacingMode::Target_240: + scheduler.Wait(resource_ticks[image_index], 240.0); + break; + } + } + resource_ticks[image_index] = scheduler.CurrentTick(); return is_suboptimal || is_outdated; |
