diff options
Diffstat (limited to 'src')
11 files changed, 121 insertions, 8 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index be3b2f4a48..c8985cd45d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -29,6 +29,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { RENDERER_DYNA_STATE("dyna_state"), DMA_ACCURACY("dma_accuracy"), + FRAME_PACING_MODE("frame_pacing_mode"), AUDIO_OUTPUT_ENGINE("output_engine"), MAX_ANISOTROPY("max_anisotropy"), THEME("theme"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index acbfbac337..762f0da262 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -644,6 +644,15 @@ abstract class SettingsItem( ) ) put( + SingleChoiceSetting( + IntSetting.FRAME_PACING_MODE, + titleId = R.string.frame_pacing_mode, + descriptionId = R.string.frame_pacing_mode_description, + choicesId = R.array.framePacingModeNames, + valuesId = R.array.framePacingModeValues + ) + ) + put( SwitchSetting( BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, titleId = R.string.renderer_asynchronous_shaders, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 5ea94d3ac5..06d4d74176 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -265,6 +265,7 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.DMA_ACCURACY.key) + add(IntSetting.FRAME_PACING_MODE.key) add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.RENDERER_VRAM_USAGE_MODE.key) add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 69f1590844..e85bc3592a 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -530,6 +530,21 @@ <item>2</item> </integer-array> + <string-array name="framePacingModeNames"> + <item>@string/frame_pacing_mode_target_Auto</item> + <item>@string/frame_pacing_mode_target_30</item> + <item>@string/frame_pacing_mode_target_60</item> + <item>@string/frame_pacing_mode_target_120</item> + <item>@string/frame_pacing_mode_target_240</item> + </string-array> + <integer-array name="framePacingModeValues"> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>4</item> + </integer-array> + <string-array name="appletEntries"> <item>@string/applet_hle</item> <item>@string/applet_lle</item> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 531b14789c..3d6a27bb3c 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -473,6 +473,8 @@ <string name="renderer_accuracy_description">Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode.</string> <string name="dma_accuracy">DMA Accuracy</string> <string name="dma_accuracy_description">Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default.</string> + <string name="frame_pacing_mode">Frame Pacing Mode</string> + <string name="frame_pacing_mode_description">Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent.</string> <string name="anisotropic_filtering">Anisotropic filtering</string> <string name="anisotropic_filtering_description">Improves the quality of textures when viewed at oblique angles</string> <string name="vram_usage_mode">VRAM Usage Mode</string> @@ -1028,6 +1030,13 @@ <string name="dma_accuracy_unsafe">Unsafe</string> <string name="dma_accuracy_safe">Safe</string> + <!-- Frame Pacing Mode --> + <string name="frame_pacing_mode_target_Auto">Auto</string> + <string name="frame_pacing_mode_target_30">30 FPS</string> + <string name="frame_pacing_mode_target_60">60 FPS</string> + <string name="frame_pacing_mode_target_120">120 FPS</string> + <string name="frame_pacing_mode_target_240">240 FPS</string> + <!-- ASTC Decoding Method Choices --> <string name="accelerate_astc_cpu" translatable="false">CPU</string> <string name="accelerate_astc_gpu" translatable="false">GPU</string> diff --git a/src/common/settings.h b/src/common/settings.h index 770acdbb69..385ae7e1c9 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -434,6 +434,13 @@ struct Values { "accelerate_astc", Category::RendererAdvanced}; + SwitchableSetting<FramePacingMode, true> frame_pacing_mode{linkage, + FramePacingMode::Target_Auto, + FramePacingMode::Target_Auto, + FramePacingMode::Target_240, + "frame_pacing_mode", + Category::RendererAdvanced}; + SwitchableSetting<AstcRecompression, true> astc_recompression{linkage, AstcRecompression::Uncompressed, "astc_recompression", diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 33c553dc3c..7e3bef9bea 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -129,6 +129,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None); ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); +ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_120, Target_240); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 404685b36c..225cdcd10e 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -225,6 +225,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) "intermediate format: RGBA8.\n" "BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n" " saving VRAM but degrading image quality.")); + INSERT(Settings, frame_pacing_mode, tr("Frame Pacing Mode (Vulkan only)"), + tr("Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent.")); INSERT(Settings, vram_usage_mode, tr("VRAM Usage Mode:"), @@ -502,6 +504,14 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) PAIR(AstcRecompression, Bc1, tr("BC1 (Low quality)")), PAIR(AstcRecompression, Bc3, tr("BC3 (Medium quality)")), }}); + translations->insert({Settings::EnumMetadata<Settings::FramePacingMode>::Index(), + { + PAIR(FramePacingMode, Target_Auto, tr("Auto")), + PAIR(FramePacingMode, Target_30, tr("30 FPS")), + PAIR(FramePacingMode, Target_60, tr("60 FPS")), + PAIR(FramePacingMode, Target_120, tr("120 FPS")), + PAIR(FramePacingMode, Target_240, tr("240 FPS")), + }}); translations->insert({Settings::EnumMetadata<Settings::VramUsageMode>::Index(), { PAIR(VramUsageMode, Conservative, tr("Conservative")), 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; |
