From dc04115e87cfd9c7e6c37aaa106082c8bd69c741 Mon Sep 17 00:00:00 2001 From: lollipoponthestick Date: Wed, 9 Sep 2026 05:47:32 +0200 Subject: [PATCH] [renderer, android] Make loseless scaling flow runtime-editable and add QS UI (#4381) - [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- This PR modifies the loseless scaling rendering hook to actually be runtime editable, allowing users much easier time experimenting with diffrent values It also adds a (WIP) Android QS impl Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4381 --- .../yuzu/yuzu_emu/dialogs/QuickSettings.kt | 24 ++++-- .../yuzu_emu/fragments/EmulationFragment.kt | 31 ++++++++ .../src/main/jni/emu_window/emu_window.cpp | 11 ++- src/common/settings.h | 10 +-- src/video_core/CMakeLists.txt | 1 + src/video_core/frame_gen/frame_gen_config.h | 43 +++++++++++ src/video_core/renderer_base.cpp | 11 +++ src/video_core/renderer_base.h | 16 ++++ .../renderer_vulkan/present/frame_gen.cpp | 73 +++++++++++++++---- .../renderer_vulkan/present/frame_gen.h | 5 ++ .../present/frame_gen_pacer.cpp | 9 +-- .../renderer_vulkan/present/frame_gen_pacer.h | 3 +- .../renderer_vulkan/present/lsfg_shaders.cpp | 6 +- .../renderer_vulkan/present/lsfg_shaders.h | 2 +- .../renderer_vulkan/renderer_vulkan.cpp | 5 ++ .../renderer_vulkan/vk_present_manager.cpp | 51 ++++++++----- .../renderer_vulkan/vk_present_manager.h | 13 +++- .../renderer_vulkan/vk_swapchain.cpp | 16 ++-- src/video_core/renderer_vulkan/vk_swapchain.h | 6 ++ 19 files changed, 275 insertions(+), 61 deletions(-) create mode 100644 src/video_core/frame_gen/frame_gen_config.h diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt index 7ba7772bd7..1305274ace 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt @@ -114,7 +114,9 @@ class QuickSettings(val emulationFragment: EmulationFragment) { name: Int, container: ViewGroup, - setting: BooleanSetting + setting: BooleanSetting, + isEnabled: Boolean = true, + onValueChanged: ((Boolean) -> Unit)? = null ) { val inflater = LayoutInflater.from(emulationFragment.requireContext()) val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false) @@ -125,15 +127,21 @@ class QuickSettings(val emulationFragment: EmulationFragment) { titleView.text = YuzuApplication.appContext.getString(name) switchContainer.visibility = View.VISIBLE + switchContainer.isEnabled = isEnabled switchView.isChecked = setting.getBoolean() + switchView.isEnabled = isEnabled + itemView.alpha = if (isEnabled) 1.0f else 0.5f switchView.setOnCheckedChangeListener { _, isChecked -> setting.setBoolean(isChecked) saveSettings() + onValueChanged?.invoke(isChecked) } switchContainer.setOnClickListener { - switchView.toggle() + if (switchView.isEnabled) { + switchView.toggle() + } } container.addView(itemView) } @@ -177,7 +185,9 @@ class QuickSettings(val emulationFragment: EmulationFragment) { setting: AbstractSetting, minValue: Int = 0, maxValue: Int = 100, - units: String = "" + units: String = "", + isEnabled: Boolean = true, + onValueChanged: ((Int) -> Unit)? = null ) { val inflater = LayoutInflater.from(emulationFragment.requireContext()) val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false) @@ -190,10 +200,13 @@ class QuickSettings(val emulationFragment: EmulationFragment) { titleView.text = YuzuApplication.appContext.getString(name) sliderContainer.visibility = View.VISIBLE + sliderContainer.isEnabled = isEnabled slider.valueFrom = minValue.toFloat() slider.valueTo = maxValue.toFloat() slider.stepSize = 1f + slider.isEnabled = isEnabled + itemView.alpha = if (isEnabled) 1.0f else 0.5f val currentValue = when (setting) { is AbstractShortSetting -> setting.getShort(needsGlobal = false).toInt() is AbstractIntSetting -> setting.getInt(needsGlobal = false) @@ -204,8 +217,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) { val displayValue = "${slider.value.toInt()}$units" valueDisplay.text = displayValue - slider.addOnChangeListener { _, value, chanhed -> - if (chanhed) { + slider.addOnChangeListener { _, value, changed -> + if (changed) { val intValue = value.toInt() when (setting) { is AbstractShortSetting -> setting.setShort(intValue.toShort()) @@ -213,6 +226,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) { } saveSettings() valueDisplay.text = "$intValue$units" + onValueChanged?.invoke(intValue) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 016ac57356..8461e7a8ff 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -92,6 +92,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.Log +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig import org.yuzu.yuzu_emu.utils.ViewUtils @@ -1157,6 +1158,36 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { quickSettings.addDivider(container) + val frameGenAvailable = LosslessScalingHelper.isInstalled() && + LosslessScalingHelper.isSupportedByGpu() + val frameGenEnabled = BooleanSetting.RENDERER_FRAME_GEN.getBoolean() + val usesFixedMultiplier = + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt() == 0 + + quickSettings.addBooleanSetting( + R.string.frame_gen, + container, + BooleanSetting.RENDERER_FRAME_GEN, + isEnabled = frameGenAvailable + ) { + NativeLibrary.applySettings() + addQuickSettings() + } + + quickSettings.addSliderSetting( + R.string.frame_gen_multiplier, + container, + IntSetting.RENDERER_FRAME_GEN_MULTIPLIER, + minValue = 2, + maxValue = 4, + units = "x", + isEnabled = frameGenAvailable && frameGenEnabled && usesFixedMultiplier + ) { + NativeLibrary.applySettings() + } + + quickSettings.addDivider(container) + quickSettings.addIntSetting( R.string.renderer_accuracy, container, diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 8dfea40693..ecb7753f29 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -22,6 +22,7 @@ #include "input_common/main.h" #include "jni/emu_window/emu_window.h" #include "jni/native.h" +#include "video_core/renderer_base.h" void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { if (!surface) { @@ -125,10 +126,14 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const { } float EmuWindow_Android::GetPresentedFrameMultiplier() { - if (!Settings::values.frame_gen.GetValue()) { - return 1.0f; + if (EmulationSession::GetInstance().IsRunning()) { + const VideoCore::FrameGenConfig config = + EmulationSession::GetInstance().System().Renderer().Settings().GetFrameGenConfig(); + return config.enabled ? static_cast(config.multiplier) : 1.0f; } - return static_cast(std::clamp(Settings::values.frame_gen_multiplier.GetValue(), 2, 4)); + return Settings::values.frame_gen.GetValue() + ? static_cast(Settings::FrameGenMultiplier()) + : 1.0f; } float EmuWindow_Android::GetFrameRateHint() const { diff --git a/src/common/settings.h b/src/common/settings.h index 60cf874306..8ceaa59d6c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -389,7 +389,7 @@ struct Values { true}; SwitchableSetting frame_gen{linkage, false, "frame_gen", Category::Renderer, - Specialization::Default, true, false}; + Specialization::Default, true, true}; SwitchableSetting frame_gen_multiplier{linkage, 2, @@ -399,7 +399,7 @@ struct Values { Category::Renderer, Specialization::Countable, true, - false, + true, &frame_gen}; SwitchableSetting frame_gen_target_rate{linkage, @@ -419,7 +419,7 @@ struct Values { Category::Renderer, Specialization::Default, true, - false, + true, &frame_gen}; SwitchableSetting frame_gen_flow_scale{linkage, @@ -442,11 +442,11 @@ struct Values { Category::Renderer, Specialization::Countable, true, - false, + true, &frame_gen}; SwitchableSetting frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer, - Specialization::Default, true, false, &frame_gen}; + Specialization::Default, true, true, &frame_gen}; SwitchableSetting frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow", Category::Renderer}; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index c86849837b..0c7db8039a 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -61,6 +61,7 @@ add_library(video_core STATIC engines/puller.h framebuffer_config.cpp framebuffer_config.h + frame_gen/frame_gen_config.h fsr.cpp fsr.h host1x/codecs/decoder.cpp diff --git a/src/video_core/frame_gen/frame_gen_config.h b/src/video_core/frame_gen/frame_gen_config.h new file mode 100644 index 0000000000..7289ffb516 --- /dev/null +++ b/src/video_core/frame_gen/frame_gen_config.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "common/common_types.h" + +namespace VideoCore { + +constexpr u32 MIN_FRAME_GEN_MULTIPLIER = 2; +constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4; + +struct FrameGenConfig { + bool enabled{}; + u32 multiplier{MIN_FRAME_GEN_MULTIPLIER}; + u32 target_rate{}; + bool flow_scale_auto{true}; + u32 flow_scale{75}; + u32 queue_target{1}; + bool fp16{true}; + bool dump_flow{}; + + [[nodiscard]] size_t Generations() const { + return enabled ? std::clamp(multiplier, MIN_FRAME_GEN_MULTIPLIER, + MAX_FRAME_GEN_MULTIPLIER) - + 1 + : 0; + } + + [[nodiscard]] size_t MaxGenerations() const { + if (!enabled) { + return 0; + } + return target_rate != 0 ? MAX_FRAME_GEN_MULTIPLIER - 1 : Generations(); + } + + bool operator==(const FrameGenConfig&) const = default; +}; + +} // namespace VideoCore diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index e19a70ea01..cc3d24f89a 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -7,6 +7,7 @@ #include #include "common/logging.h" +#include "common/settings.h" #include "core/frontend/emu_window.h" #include "core/frontend/graphics_context.h" #include "video_core/renderer_base.h" @@ -22,6 +23,16 @@ RendererBase::RendererBase(Core::Frontend::EmuWindow& window_, RendererBase::~RendererBase() = default; void RendererBase::RefreshBaseSettings() { + renderer_settings.SetFrameGenConfig({ + .enabled = Settings::values.frame_gen.GetValue(), + .multiplier = Settings::FrameGenMultiplier(), + .target_rate = Settings::values.frame_gen_target_rate.GetValue(), + .flow_scale_auto = Settings::values.frame_gen_flow_scale_auto.GetValue(), + .flow_scale = Settings::values.frame_gen_flow_scale.GetValue(), + .queue_target = Settings::values.frame_gen_queue_target.GetValue(), + .fp16 = Settings::values.frame_gen_fp16.GetValue(), + .dump_flow = Settings::values.frame_gen_dump_flow.GetValue(), + }); UpdateCurrentFramebufferLayout(); } diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 46f8276074..745cbe5cd3 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -9,10 +9,12 @@ #include #include #include +#include #include "common/common_funcs.h" #include "common/common_types.h" #include "core/frontend/framebuffer_layout.h" +#include "video_core/frame_gen/frame_gen_config.h" #include "video_core/gpu.h" #include "video_core/rasterizer_interface.h" @@ -30,6 +32,20 @@ struct RendererSettings { std::function screenshot_complete_callback; Layout::FramebufferLayout screenshot_framebuffer_layout; Service::Nvnflinger::LayerStackId screenshot_layer_stack{Service::Nvnflinger::LayerStackId::Default}; + + void SetFrameGenConfig(FrameGenConfig config) { + std::scoped_lock lock{frame_gen_mutex}; + frame_gen_config = config; + } + + [[nodiscard]] FrameGenConfig GetFrameGenConfig() const { + std::scoped_lock lock{frame_gen_mutex}; + return frame_gen_config; + } + +private: + mutable std::mutex frame_gen_mutex; + FrameGenConfig frame_gen_config; }; class RendererBase { diff --git a/src/video_core/renderer_vulkan/present/frame_gen.cpp b/src/video_core/renderer_vulkan/present/frame_gen.cpp index b0d16819e5..992c84cb98 100644 --- a/src/video_core/renderer_vulkan/present/frame_gen.cpp +++ b/src/video_core/renderer_vulkan/present/frame_gen.cpp @@ -23,13 +23,14 @@ constexpr size_t COLOR_CHANNELS = 4; constexpr u64 LSFG_REQUIRED_FRAMES = 2; constexpr u32 LSFG_RECURRENCE_FRAMES = 2; -[[nodiscard]] f32 ManualFlowScale() { - return static_cast(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f; +[[nodiscard]] f32 ManualFlowScale(const VideoCore::FrameGenConfig& config) { + return static_cast(config.flow_scale) / 100.0f; } -[[nodiscard]] f32 ConfiguredFlowScale(VkExtent2D guest_extent, VkExtent2D presented_extent) { - if (!Settings::values.frame_gen_flow_scale_auto.GetValue()) { - return ManualFlowScale(); +[[nodiscard]] f32 ConfiguredFlowScale(const VideoCore::FrameGenConfig& config, + VkExtent2D guest_extent, VkExtent2D presented_extent) { + if (!config.flow_scale_auto) { + return ManualFlowScale(config); } if (guest_extent.width == 0 || presented_extent.width == 0) { return 1.0f; @@ -194,15 +195,61 @@ FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_) FrameGen::~FrameGen() = default; -void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, - VkExtent2D guest_extent) { - generated = false; +void FrameGen::UpdateConfig(const VideoCore::FrameGenConfig& new_config) { + if (!has_config) { + config = new_config; + has_config = true; + return; + } + if (config == new_config) { + return; + } - if (unavailable || !Settings::values.frame_gen.GetValue()) { + const bool has_toggled_lsfg = config.enabled != new_config.enabled; + const bool precision_changed = config.fp16 != new_config.fp16; + const bool enabling = !config.enabled && new_config.enabled; + const bool must_reset_pipeline = has_toggled_lsfg || precision_changed; + const bool must_reload_shaders = precision_changed || enabling; + const bool must_reset_pacer = + has_toggled_lsfg || config.multiplier != new_config.multiplier || + config.target_rate != new_config.target_rate; + + if (must_reset_pipeline) { if (chain) { scheduler.Finish(); chain.reset(); } + if (must_reload_shaders) { + shaders.reset(); + } + + plan = {}; + peak_guest_extent = {}; + built_extent = {}; + built_format = VK_FORMAT_UNDEFINED; + built_flow_scale = 0.0f; + frame_count = 0; + warm_streak = 0; + generated = false; + dumped = false; + + // this will pickup the dll again once toggled + if (must_reload_shaders) { + unavailable = false; + } + } + + if (must_reset_pacer) { + pacer.Reset(); + } + config = new_config; +} + +void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, + VkExtent2D guest_extent) { + generated = false; + + if (unavailable || !config.enabled) { warm_streak = 0; return; } @@ -213,7 +260,7 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, } if (!shaders) { - shaders.emplace(device); + shaders.emplace(device, config.fp16); if (!shaders->IsValid()) { unavailable = true; return; @@ -224,7 +271,7 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, peak_guest_extent.height = std::max(peak_guest_extent.height, guest_extent.height); const VkExtent2D extent{.width = frame->width, .height = frame->height}; - const f32 flow_scale = ConfiguredFlowScale(peak_guest_extent, extent); + const f32 flow_scale = ConfiguredFlowScale(config, peak_guest_extent, extent); if (!chain || built_extent.width != extent.width || built_extent.height != extent.height || built_format != format || built_flow_scale != flow_scale) { Rebuild(device, extent, format, flow_scale); @@ -247,7 +294,7 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, } }); - const bool dump_requested = generated && Settings::values.frame_gen_dump_flow.GetValue(); + const bool dump_requested = generated && config.dump_flow; if (!dump_requested) { dumped = false; } else if (!dumped) { @@ -261,7 +308,7 @@ size_t FrameGen::WantedGenerations(size_t capacity) { plan = {}; return 0; } - plan = pacer.Plan(capacity); + plan = pacer.Plan(capacity, config); return plan.generations; } diff --git a/src/video_core/renderer_vulkan/present/frame_gen.h b/src/video_core/renderer_vulkan/present/frame_gen.h index 19c6d04289..27d77e76da 100644 --- a/src/video_core/renderer_vulkan/present/frame_gen.h +++ b/src/video_core/renderer_vulkan/present/frame_gen.h @@ -6,6 +6,7 @@ #include #include "common/common_types.h" +#include "video_core/frame_gen/frame_gen_config.h" #include "video_core/renderer_vulkan/present/frame_gen_pacer.h" #include "video_core/renderer_vulkan/present/lsfg_chain.h" #include "video_core/renderer_vulkan/present/lsfg_shaders.h" @@ -22,6 +23,8 @@ public: explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler); ~FrameGen(); + void UpdateConfig(const VideoCore::FrameGenConfig& new_config); + void Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent); [[nodiscard]] size_t WantedGenerations(size_t capacity); @@ -39,6 +42,7 @@ private: std::optional shaders; std::optional chain; + VideoCore::FrameGenConfig config; FrameGenPacer pacer; FrameGenPlan plan{}; VkExtent2D peak_guest_extent{}; @@ -52,6 +56,7 @@ private: bool generated{}; bool unavailable{}; bool dumped{}; + bool has_config{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/frame_gen_pacer.cpp b/src/video_core/renderer_vulkan/present/frame_gen_pacer.cpp index 78a8c83b5a..7234b9b8f5 100644 --- a/src/video_core/renderer_vulkan/present/frame_gen_pacer.cpp +++ b/src/video_core/renderer_vulkan/present/frame_gen_pacer.cpp @@ -5,7 +5,6 @@ #include #include -#include "common/settings.h" #include "video_core/renderer_vulkan/present/frame_gen_pacer.h" namespace Vulkan { @@ -46,8 +45,8 @@ constexpr auto PROBE_STEP_DELAY = std::chrono::milliseconds(250); } // Anonymous namespace -FrameGenPlan FrameGenPacer::Plan(size_t capacity) { - const size_t ceiling = std::min(capacity, Settings::FrameGenMaxGenerations()); +FrameGenPlan FrameGenPacer::Plan(size_t capacity, const VideoCore::FrameGenConfig& config) { + const size_t ceiling = std::min(capacity, config.MaxGenerations()); if (ceiling == 0) { Reset(); return {}; @@ -69,7 +68,7 @@ FrameGenPlan FrameGenPacer::Plan(size_t capacity) { return {}; } - const f32 target_rate = static_cast(Settings::values.frame_gen_target_rate.GetValue()); + const f32 target_rate = static_cast(config.target_rate); if (smoothed_interval > 0.0f) { f32 burst_threshold = BURST_CADENCE_RATIO / smoothed_interval; @@ -109,7 +108,7 @@ FrameGenPlan FrameGenPacer::Plan(size_t capacity) { } if (target_rate == 0.0f) { - limit = std::min(Settings::FrameGenGenerations(), ceiling); + limit = std::min(config.Generations(), ceiling); output_credit = 0.0f; issued_generations = limit; return {.generations = limit, .warm = limit > 0}; diff --git a/src/video_core/renderer_vulkan/present/frame_gen_pacer.h b/src/video_core/renderer_vulkan/present/frame_gen_pacer.h index 7f4b23920c..cb346b492d 100644 --- a/src/video_core/renderer_vulkan/present/frame_gen_pacer.h +++ b/src/video_core/renderer_vulkan/present/frame_gen_pacer.h @@ -7,6 +7,7 @@ #include #include "common/common_types.h" +#include "video_core/frame_gen/frame_gen_config.h" namespace Vulkan { @@ -17,7 +18,7 @@ struct FrameGenPlan { class FrameGenPacer { public: - [[nodiscard]] FrameGenPlan Plan(size_t capacity); + [[nodiscard]] FrameGenPlan Plan(size_t capacity, const VideoCore::FrameGenConfig& config); void Reset(); diff --git a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp index aafe0d6419..967834c0f6 100644 --- a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp +++ b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "common/settings.h" #include "video_core/frame_gen/lossless_dll.h" #include "video_core/renderer_vulkan/present/lsfg_shaders.h" #include "video_core/renderer_vulkan/present/util.h" @@ -9,16 +8,15 @@ namespace Vulkan { -LsfgShaders::LsfgShaders(const Device& device) { +LsfgShaders::LsfgShaders(const Device& device, bool prefer_fp16) { if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor()) { return; } const bool allow_fp16 = device.IsFloat16Supported(); - const bool prefer_fp16 = allow_fp16 && Settings::values.frame_gen_fp16.GetValue(); VideoCore::FrameGen::ShaderModules code; - if (VideoCore::FrameGen::LoadShaderModules(code, allow_fp16, prefer_fp16) != + if (VideoCore::FrameGen::LoadShaderModules(code, allow_fp16, allow_fp16 && prefer_fp16) != VideoCore::FrameGen::LosslessStatus::Ok) { return; } diff --git a/src/video_core/renderer_vulkan/present/lsfg_shaders.h b/src/video_core/renderer_vulkan/present/lsfg_shaders.h index cab37110d6..34e2f2bad0 100644 --- a/src/video_core/renderer_vulkan/present/lsfg_shaders.h +++ b/src/video_core/renderer_vulkan/present/lsfg_shaders.h @@ -14,7 +14,7 @@ class Device; class LsfgShaders { public: - explicit LsfgShaders(const Device& device); + explicit LsfgShaders(const Device& device, bool prefer_fp16); [[nodiscard]] bool IsValid() const { return valid; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 6290e79d93..53081fc5b4 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -145,6 +145,7 @@ try , swapchain(*surface, device, scheduler, + Settings(), render_window.GetFramebufferLayout().width, render_window.GetFramebufferLayout().height) , present_manager(instance, @@ -152,6 +153,7 @@ try device, memory_allocator, scheduler, + Settings(), swapchain, surface) , blit_swapchain(device_memory, @@ -206,6 +208,9 @@ void RendererVulkan::Composite(std::span framebu } RenderScreenshot(framebuffers); +#ifdef HAS_LSFG + frame_gen.UpdateConfig(Settings().GetFrameGenConfig()); +#endif Frame* frame = present_manager.GetRenderFrame(); scheduler.RequestOutsideRenderPassOperationContext(); diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 3491f8fb75..3cafb4763e 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -7,6 +7,7 @@ #include "common/settings.h" #include "common/thread.h" #include "core/frontend/emu_window.h" +#include "video_core/renderer_base.h" #ifdef HAS_LSFG #include "video_core/renderer_vulkan/present/lsfg_common.h" #endif @@ -29,9 +30,6 @@ static_assert(MAX_FRAMES_IN_FLIGHT <= LSFG_MAX_TARGETS); bool CanStoreToFrame(const vk::PhysicalDevice& physical_device, VkFormat format) { #ifdef HAS_LSFG - if (!Settings::values.frame_gen.GetValue()) { - return false; - } const VkFormatProperties props{physical_device.GetFormatProperties(format)}; return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; #else @@ -120,6 +118,7 @@ PresentManager::PresentManager(const vk::Instance& instance_, const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, + const VideoCore::RendererSettings& renderer_settings_, Swapchain& swapchain_, vk::SurfaceKHR& surface_) : instance{instance_} @@ -127,13 +126,14 @@ PresentManager::PresentManager(const vk::Instance& instance_, , device{device_} , memory_allocator{memory_allocator_} , scheduler{scheduler_} + , renderer_settings{renderer_settings_} , swapchain{swapchain_} , surface{surface_} , blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())} , storage_supported{CanStoreToFrame(device.GetPhysical(), swapchain.GetImageFormat())} , use_present_thread{Settings::values.async_presentation.GetValue()} { - SetImageCount(); + UpdateSwapchainImageCount(); auto& dld = device.GetLogical(); cmdpool = dld.CreateCommandPool({ @@ -143,9 +143,14 @@ PresentManager::PresentManager(const vk::Instance& instance_, VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = device.GetGraphicsFamily(), }); - auto cmdbuffers = cmdpool.Allocate(image_count); +#ifdef HAS_LSFG + constexpr size_t frame_capacity = MAX_FRAMES_IN_FLIGHT; +#else + const size_t frame_capacity = DesiredFrameCount(); +#endif + auto cmdbuffers = cmdpool.Allocate(frame_capacity); - frames.resize(image_count); + frames.resize(frame_capacity); for (u32 i = 0; i < frames.size(); i++) { Frame& frame = frames[i]; frame.index = i; @@ -174,7 +179,10 @@ Frame* PresentManager::GetRenderFrame() { // Wait for free presentation frames std::unique_lock lock{free_mutex}; - free_cv.wait(lock, [this] { return !free_queue.empty(); }); + free_cv.wait(lock, [this] { + const size_t outstanding = frames.size() - free_queue.size(); + return !free_queue.empty() && outstanding < DesiredFrameCount(); + }); // Take the frame from the queue Frame* frame = free_queue.front(); @@ -202,7 +210,8 @@ void PresentManager::Present(Frame* frame) { } size_t PresentManager::MaxExtraFrames() const { - return image_count - 1; + const size_t frame_count = DesiredFrameCount(); + return frame_count > 0 ? frame_count - 1 : 0; } void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format, @@ -351,22 +360,30 @@ void PresentManager::PresentThread(std::stop_token token) { void PresentManager::RecreateSwapchain(Frame* frame) { swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer - SetImageCount(); + UpdateSwapchainImageCount(); } -void PresentManager::SetImageCount() { +void PresentManager::UpdateSwapchainImageCount() { + swapchain_image_count.store( + std::min(swapchain.GetImageCount(), MAX_FRAMES_IN_FLIGHT), + std::memory_order_release); + free_cv.notify_all(); +} + +size_t PresentManager::DesiredFrameCount() const { // We cannot have more than 7 images in flight at any given time. // FRAMES_IN_FLIGHT is 8, and the cache TICKS_TO_DESTROY is 8. // Mali drivers will give us 6. + const size_t minimum = swapchain_image_count.load(std::memory_order_acquire); #ifdef HAS_LSFG - const size_t generations = Settings::FrameGenMaxGenerations(); - const size_t queued_composites = Settings::values.frame_gen_queue_target.GetValue() + 1; - image_count = - std::clamp((generations + 1) * queued_composites, swapchain.GetImageCount(), - MAX_FRAMES_IN_FLIGHT); -#else - image_count = std::min(swapchain.GetImageCount(), MAX_FRAMES_IN_FLIGHT); + const VideoCore::FrameGenConfig config = renderer_settings.GetFrameGenConfig(); + if (config.enabled) { + const size_t queued_composites = std::clamp(config.queue_target, 0, 2) + 1; + return std::clamp((config.MaxGenerations() + 1) * queued_composites, minimum, + MAX_FRAMES_IN_FLIGHT); + } #endif + return minimum; } void PresentManager::CopyToSwapchain(Frame* frame) { diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index 577a82098a..d50a669ce1 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -19,6 +20,10 @@ namespace Core::Frontend { class EmuWindow; } // namespace Core::Frontend +namespace VideoCore { +struct RendererSettings; +} + namespace Vulkan { class Device; @@ -45,6 +50,7 @@ public: const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler, + const VideoCore::RendererSettings& renderer_settings, Swapchain& swapchain, vk::SurfaceKHR& surface); ~PresentManager(); @@ -74,7 +80,9 @@ private: void RecreateSwapchain(Frame* frame); - void SetImageCount(); + void UpdateSwapchainImageCount(); + + [[nodiscard]] size_t DesiredFrameCount() const; private: const vk::Instance& instance; @@ -82,6 +90,7 @@ private: const Device& device; MemoryAllocator& memory_allocator; Scheduler& scheduler; + const VideoCore::RendererSettings& renderer_settings; Swapchain& swapchain; vk::SurfaceKHR& surface; vk::CommandPool cmdpool; @@ -97,7 +106,7 @@ private: bool blit_supported; bool storage_supported; bool use_present_thread; - std::size_t image_count{}; + std::atomic_size_t swapchain_image_count{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index fea5398557..0ff84dccf5 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -16,6 +16,7 @@ #include "common/logging.h" #include "common/settings.h" #include "common/settings_enums.h" +#include "video_core/renderer_base.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_swapchain.h" #include "video_core/vulkan_common/vk_enum_string_helper.h" @@ -42,13 +43,13 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span formats) } static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox, - bool has_fifo_relaxed) { + bool has_fifo_relaxed, bool frame_gen_enabled) { // Mailbox doesn't lock the application like FIFO (vsync) // FIFO present mode locks the framerate to the monitor's refresh rate - Settings::VSyncMode setting = [has_imm, has_mailbox]() { + Settings::VSyncMode setting = [has_imm, has_mailbox, frame_gen_enabled]() { // Choose Mailbox or Immediate if unlocked and those modes are supported const auto mode = Settings::values.vsync_mode.GetValue(); - if (Settings::values.frame_gen.GetValue()) { + if (frame_gen_enabled) { return mode == Settings::VSyncMode::FifoRelaxed ? mode : Settings::VSyncMode::Fifo; } if (Settings::values.use_speed_limit.GetValue() && @@ -121,11 +122,13 @@ Swapchain::Swapchain( VkSurfaceKHR_T* surface_, const Device& device_, Scheduler& scheduler_, + const VideoCore::RendererSettings& renderer_settings_, u32 width_, u32 height_) : surface(surface_) , device{device_} , scheduler{scheduler_} + , renderer_settings{renderer_settings_} { Create(surface, width_, height_); } @@ -263,7 +266,8 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { const VkCompositeAlphaFlagBitsKHR alpha_flags{ChooseAlphaFlags(capabilities)}; surface_format = ChooseSwapSurfaceFormat(formats); - present_mode = ChooseSwapPresentMode(has_imm, has_mailbox, has_fifo_relaxed); + present_mode = ChooseSwapPresentMode(has_imm, has_mailbox, has_fifo_relaxed, + renderer_settings.GetFrameGenConfig().enabled); u32 requested_image_count{capabilities.minImageCount + 1}; // Ensure Triple buffering if possible. @@ -366,7 +370,9 @@ void Swapchain::Destroy() { } bool Swapchain::NeedsPresentModeUpdate() const { - const auto requested_mode = ChooseSwapPresentMode(has_imm, has_mailbox, has_fifo_relaxed); + const auto requested_mode = ChooseSwapPresentMode( + has_imm, has_mailbox, has_fifo_relaxed, + renderer_settings.GetFrameGenConfig().enabled); return present_mode != requested_mode; } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index d926cc118a..bae2d84efc 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -15,6 +15,10 @@ namespace Layout { struct FramebufferLayout; } +namespace VideoCore { +struct RendererSettings; +} + namespace Vulkan { class Device; @@ -26,6 +30,7 @@ public: VkSurfaceKHR_T* surface, const Device& device, Scheduler& scheduler, + const VideoCore::RendererSettings& renderer_settings, u32 width, u32 height); ~Swapchain(); @@ -122,6 +127,7 @@ private: const Device& device; Scheduler& scheduler; + const VideoCore::RendererSettings& renderer_settings; vk::SwapchainKHR swapchain;