diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index ddeebd6ce0..39a76a8608 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -39,6 +39,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_SAMPLE_SHADING("sample_shading"), RENDERER_FRAME_GEN("frame_gen"), RENDERER_FRAME_GEN_FP16("frame_gen_fp16"), + RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"), RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"), GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"), PICTURE_IN_PICTURE("picture_in_picture"), 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 0630335d27..473f88c9e0 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 @@ -100,6 +100,7 @@ abstract class SettingsItem( BooleanSetting.RENDERER_FRAME_GEN.key, IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key, IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key, + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key, IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key, BooleanSetting.RENDERER_FRAME_GEN_FP16.key, BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key @@ -648,6 +649,13 @@ abstract class SettingsItem( valuesId = R.array.frameGenQueueTargetValues ) ) + put( + SwitchSetting( + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO, + titleId = R.string.frame_gen_flow_scale_auto, + descriptionId = R.string.frame_gen_flow_scale_auto_description + ) + ) put( SliderSetting( IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE, 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 9928fe5645..b241d896f7 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 @@ -127,7 +127,13 @@ class SettingsFragmentPresenter( add(BooleanSetting.RENDERER_FRAME_GEN.key) add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key) add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key) - add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key) + add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key) + if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean( + getNeedsGlobalForKey(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key) + ) + ) { + add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key) + } add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key) } } diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 62e2f2eee5..6d2f2af75f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -311,6 +311,8 @@ Unbuffered (lowest latency) One frame (balanced) Two frames (smoothest) + Match motion estimation to the game + Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail. Motion estimation resolution Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance. Half precision shaders diff --git a/src/common/settings.h b/src/common/settings.h index b413cab9e4..730dbc0e02 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -402,6 +402,15 @@ struct Values { false, &frame_gen}; + SwitchableSetting frame_gen_flow_scale_auto{linkage, + true, + "frame_gen_flow_scale_auto", + Category::Renderer, + Specialization::Default, + true, + false, + &frame_gen}; + SwitchableSetting frame_gen_flow_scale{linkage, 75, 25, diff --git a/src/video_core/renderer_vulkan/present/frame_gen.cpp b/src/video_core/renderer_vulkan/present/frame_gen.cpp index 3dfc34f282..ee65aae069 100644 --- a/src/video_core/renderer_vulkan/present/frame_gen.cpp +++ b/src/video_core/renderer_vulkan/present/frame_gen.cpp @@ -22,10 +22,27 @@ namespace { constexpr size_t COLOR_CHANNELS = 4; constexpr u64 LSFG_REQUIRED_FRAMES = 2; -[[nodiscard]] f32 ConfiguredFlowScale() { +[[nodiscard]] f32 ManualFlowScale() { return static_cast(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f; } +[[nodiscard]] f32 ConfiguredFlowScale(VkExtent2D guest_extent, VkExtent2D presented_extent) { + if (!Settings::values.frame_gen_flow_scale_auto.GetValue()) { + return ManualFlowScale(); + } + if (guest_extent.width == 0 || presented_extent.width == 0) { + return 1.0f; + } + + const f32 rendered_width = static_cast(guest_extent.width) * + Settings::values.resolution_info.up_factor; + const f32 ratio = rendered_width / static_cast(presented_extent.width); + + constexpr f32 FLOW_SCALE_STEPS = 20.0f; + const f32 stepped = std::ceil(ratio * FLOW_SCALE_STEPS) / FLOW_SCALE_STEPS; + return std::clamp(stepped, 0.25f, 1.0f); +} + [[nodiscard]] size_t ConfiguredGenerations() { return Settings::FrameGenGenerations(); } @@ -180,7 +197,8 @@ FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_) FrameGen::~FrameGen() = default; -void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool generate) { +void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent, + bool generate) { generated = false; if (unavailable || ConfiguredGenerations() == 0) { @@ -204,11 +222,15 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool } } + peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width); + 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); if (!chain || built_extent.width != extent.width || built_extent.height != extent.height || - built_format != format || built_flow_scale != ConfiguredFlowScale() || + built_format != format || built_flow_scale != flow_scale || built_generations != ConfiguredGenerations()) { - Rebuild(device, extent, format); + Rebuild(device, extent, format, flow_scale); } const u64 count = frame_count++; @@ -256,11 +278,11 @@ void FrameGen::GenerateInto(const Device& device, Frame* destination, size_t gen }); } -void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format) { +void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale) { scheduler.Finish(); chain.reset(); - built_flow_scale = ConfiguredFlowScale(); + built_flow_scale = flow_scale; built_generations = ConfiguredGenerations(); chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale, diff --git a/src/video_core/renderer_vulkan/present/frame_gen.h b/src/video_core/renderer_vulkan/present/frame_gen.h index e2a490554c..1c35b44734 100644 --- a/src/video_core/renderer_vulkan/present/frame_gen.h +++ b/src/video_core/renderer_vulkan/present/frame_gen.h @@ -21,7 +21,8 @@ public: explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler); ~FrameGen(); - void Process(const Device& device, Frame* frame, VkFormat format, bool generate); + void Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent, + bool generate); [[nodiscard]] size_t WantedGenerations() const; @@ -30,7 +31,7 @@ public: void GenerateInto(const Device& device, Frame* destination, size_t generation); private: - void Rebuild(const Device& device, VkExtent2D extent, VkFormat format); + void Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale); void DumpDebugImages(u64 count); MemoryAllocator& memory_allocator; @@ -38,6 +39,7 @@ private: std::optional shaders; std::optional chain; + VkExtent2D peak_guest_extent{}; VkExtent2D built_extent{}; VkFormat built_format{VK_FORMAT_UNDEFINED}; f32 built_flow_scale{}; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index eeaa46309d..4ff5370019 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -49,6 +49,21 @@ constexpr VkExtent2D CaptureImageSize{ .height = VideoCore::Capture::LinearHeight, }; +[[nodiscard]] VkExtent2D GuestExtent(std::span framebuffers) { + if (framebuffers.empty()) { + return VkExtent2D{}; + } + + const auto& framebuffer = framebuffers.front(); + if (framebuffer.crop_rect.IsEmpty()) { + return VkExtent2D{.width = framebuffer.width, .height = framebuffer.height}; + } + return VkExtent2D{ + .width = static_cast(framebuffer.crop_rect.GetWidth()), + .height = static_cast(framebuffer.crop_rect.GetHeight()), + }; +} + constexpr VkExtent3D CaptureImageExtent{ .width = VideoCore::Capture::LinearWidth, .height = VideoCore::Capture::LinearHeight, @@ -196,7 +211,8 @@ void RendererVulkan::Composite(std::span framebu const size_t wanted = frame_gen.WantedGenerations(); const bool can_present_all = wanted > 0 && present_manager.AvailableExtraFrames() >= wanted; - frame_gen.Process(device, frame, swapchain.GetImageFormat(), can_present_all); + frame_gen.Process(device, frame, swapchain.GetImageFormat(), GuestExtent(framebuffers), + can_present_all); const size_t generated_frames = frame_gen.GeneratedFrameCount(); for (size_t generation = 0; generation < generated_frames; ++generation) {