From d25bea576212d70fb89e3d4476379a433f818ee1 Mon Sep 17 00:00:00 2001 From: Pavel Barabanov Date: Thu, 10 Apr 2025 22:24:15 +0300 Subject: [PATCH] Android: add FRAME_SKIPPING and FRAME_INTERPOLATION --- .../features/settings/model/BooleanSetting.kt | 8 +- .../settings/model/view/SettingsItem.kt | 50 +++++++++++++ .../settings/ui/SettingsFragmentPresenter.kt | 2 + .../yuzu_emu/fragments/EmulationFragment.kt | 12 ++- .../app/src/main/res/values/strings.xml | 4 + src/common/settings.h | 6 +- .../renderer_vulkan/renderer_vulkan.cpp | 75 ++++++++++++++++++- .../renderer_vulkan/renderer_vulkan.h | 4 + 8 files changed, 155 insertions(+), 6 deletions(-) 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 6644784729..34f417434f 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 @@ -26,7 +26,13 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), SHOW_INPUT_OVERLAY("show_input_overlay"), TOUCHSCREEN("touchscreen"), - SHOW_THERMAL_OVERLAY("show_thermal_overlay"); + SHOW_THERMAL_OVERLAY("show_thermal_overlay"), + ENABLE_FRAME_INTERPOLATION("enable_frame_interpolation"), + ENABLE_FRAME_SKIPPING("enable_frame_skipping"), + CORE_USE_MULTI_CORE("use_multi_core"); + + external fun isFrameSkippingEnabled(): Boolean + external fun isFrameInterpolationEnabled(): Boolean override fun getBoolean(needsGlobal: Boolean): Boolean = NativeConfig.getBoolean(key, needsGlobal) 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 5fdf983185..2780b550e7 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 @@ -172,6 +172,56 @@ abstract class SettingsItem( override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset() } + val enableInterpolationSetting = object : AbstractBooleanSetting { + override val key = BooleanSetting.ENABLE_FRAME_INTERPOLATION.key + + override fun getBoolean(needsGlobal: Boolean): Boolean = + BooleanSetting.ENABLE_FRAME_INTERPOLATION.getBoolean(needsGlobal) + + override fun setBoolean(value: Boolean) = + BooleanSetting.ENABLE_FRAME_INTERPOLATION.setBoolean(value) + + override val defaultValue = BooleanSetting.ENABLE_FRAME_INTERPOLATION.defaultValue + + override fun getValueAsString(needsGlobal: Boolean): String = + BooleanSetting.ENABLE_FRAME_INTERPOLATION.getValueAsString(needsGlobal) + + override fun reset() = BooleanSetting.ENABLE_FRAME_INTERPOLATION.reset() + } + + val enableFrameSkippingSetting = object : AbstractBooleanSetting { + override val key = BooleanSetting.ENABLE_FRAME_SKIPPING.key + + override fun getBoolean(needsGlobal: Boolean): Boolean = + BooleanSetting.ENABLE_FRAME_SKIPPING.getBoolean(needsGlobal) + + override fun setBoolean(value: Boolean) = + BooleanSetting.ENABLE_FRAME_SKIPPING.setBoolean(value) + + override val defaultValue = BooleanSetting.ENABLE_FRAME_SKIPPING.defaultValue + + override fun getValueAsString(needsGlobal: Boolean): String = + BooleanSetting.ENABLE_FRAME_SKIPPING.getValueAsString(needsGlobal) + + override fun reset() = BooleanSetting.ENABLE_FRAME_SKIPPING.reset() + } + + put( + SwitchSetting( + BooleanSetting.ENABLE_FRAME_INTERPOLATION, + titleId = R.string.enable_frame_interpolation, + descriptionId = R.string.enable_frame_interpolation_description + ) + ) + + put( + SwitchSetting( + BooleanSetting.ENABLE_FRAME_SKIPPING, + titleId = R.string.enable_frame_skipping, + descriptionId = R.string.enable_frame_skipping_description + ) + ) + put( SwitchSetting( dockedModeSetting, 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 7fa22b272f..1af631ce1a 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 @@ -169,6 +169,8 @@ class SettingsFragmentPresenter( private fun addGraphicsSettings(sl: ArrayList) { sl.apply { + add(BooleanSetting.ENABLE_FRAME_INTERPOLATION.key) + add(BooleanSetting.ENABLE_FRAME_SKIPPING.key) add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_VSYNC.key) 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 2c99f6a2ac..e316d96a2a 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 @@ -534,10 +534,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // Calculate used memory val usedMegs = (mi.totalMem - mi.availMem) / 1048576L // Convert bytes to megabytes + val actualFps = perfStats[FPS] + val enableFrameInterpolation = BooleanSetting.ENABLE_FRAME_INTERPOLATION.getBoolean() + val generatedFpsText = if (enableFrameInterpolation) { + val generatedFps = actualFps * 2 + String.format("(Generated: %.1f)", generatedFps) + } else { + "" + } if (_binding != null) { binding.showFpsText.text = String.format( - "%.1f FPS • %d MB • %s/%s", - perfStats[FPS], usedMegs, cpuBackend, gpuDriver + "%.1f %s FPS • %d MB • %s/%s", + actualFps, generatedFpsText, usedMegs, cpuBackend, gpuDriver ) } perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index bb0b5c58dc..51495df2fc 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -230,6 +230,10 @@ Set custom RTC + Enable Frame Skipping + Toggle frame skipping to improve performance by reducing the number of rendered frames. + Enable Frame Interpolation + Toggle frame interpolation to improve visual smoothness by generating intermediate frames. Accuracy level Resolution (Handheld/Docked) VSync mode diff --git a/src/common/settings.h b/src/common/settings.h index cf3579892c..e0788abda6 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -250,7 +250,6 @@ struct Values { Category::CpuDebug}; Setting cpuopt_ignore_memory_aborts{linkage, true, "cpuopt_ignore_memory_aborts", Category::CpuDebug}; - SwitchableSetting cpuopt_unsafe_unfuse_fma{linkage, true, "cpuopt_unsafe_unfuse_fma", Category::CpuUnsafe}; SwitchableSetting cpuopt_unsafe_reduce_fp_error{ @@ -273,7 +272,10 @@ struct Values { "shader_backend", Category::Renderer, Specialization::RuntimeList}; SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, Specialization::RuntimeList}; - + SwitchableSetting enable_frame_interpolation{linkage, true, "enable_frame_interpolation", Category::Renderer, + Specialization::RuntimeList}; + SwitchableSetting enable_frame_skipping{linkage, true, "enable_frame_skipping", Category::Renderer, + Specialization::RuntimeList}; SwitchableSetting use_disk_shader_cache{linkage, true, "use_disk_shader_cache", Category::Renderer}; SwitchableSetting use_asynchronous_gpu_emulation{ diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 4a2d4b23ef..3ca69de530 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -35,7 +35,9 @@ #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_surface.h" #include "video_core/vulkan_common/vulkan_wrapper.h" - +#ifdef __ANDROID__ +#include +#endif namespace Vulkan { namespace { @@ -136,11 +138,82 @@ RendererVulkan::~RendererVulkan() { void(device.GetLogical().WaitIdle()); } +#ifdef __ANDROID__ +class BooleanSetting { + public: + static BooleanSetting ENABLE_FRAME_SKIPPING; + static BooleanSetting ENABLE_FRAME_INTERPOLATION; + explicit BooleanSetting(bool initial_value = false) : value(initial_value) {} + + [[nodiscard]] bool getBoolean() const { + return value; + } + + void setBoolean(bool new_value) { + value = new_value; + } + + private: + bool value; + }; + + // Initialize static members + BooleanSetting BooleanSetting::ENABLE_FRAME_SKIPPING(false); + BooleanSetting BooleanSetting::ENABLE_FRAME_INTERPOLATION(false); + + extern "C" JNIEXPORT jboolean JNICALL + Java_org_uzuy_uzuy_1emu_features_settings_model_BooleanSetting_isFrameSkippingEnabled(JNIEnv* env, jobject /* this */) { + return static_cast(BooleanSetting::ENABLE_FRAME_SKIPPING.getBoolean()); + } + + extern "C" JNIEXPORT jboolean JNICALL + Java_org_uzuy_uzuy_1emu_features_settings_model_BooleanSetting_isFrameInterpolationEnabled(JNIEnv* env, jobject /* this */) { + return static_cast(BooleanSetting::ENABLE_FRAME_INTERPOLATION.getBoolean()); + } + + void RendererVulkan::InterpolateFrames(Frame* prev_frame, Frame* interpolated_frame) { + if (prev_frame && interpolated_frame) { + *interpolated_frame = std::move(*prev_frame); // Use move assignment to avoid copy issues + + // TODO: Implement actual interpolation logic here + } + } + #endif + void RendererVulkan::Composite(std::span framebuffers) { + #ifdef __ANDROID__ + static int frame_counter = 0; + static int target_fps = 60; // Target FPS (30 or 60) + int frame_skip_threshold = 1; + + bool enable_frame_skipping = BooleanSetting::ENABLE_FRAME_SKIPPING.getBoolean(); + bool enable_frame_interpolation = BooleanSetting::ENABLE_FRAME_INTERPOLATION.getBoolean(); + #endif + if (framebuffers.empty()) { return; } + #ifdef __ANDROID__ + if (enable_frame_skipping) { + frame_skip_threshold = (target_fps == 30) ? 2 : 2; + } + + frame_counter++; + if (frame_counter % frame_skip_threshold != 0) { + if (enable_frame_interpolation && previous_frame) { + Frame* interpolated_frame = present_manager.GetRenderFrame(); + InterpolateFrames(previous_frame, interpolated_frame); + blit_swapchain.DrawToFrame(rasterizer, interpolated_frame, framebuffers, + render_window.GetFramebufferLayout(), swapchain.GetImageCount(), + swapchain.GetImageViewFormat()); + scheduler.Flush(*interpolated_frame->render_ready); + present_manager.Present(interpolated_frame); + } + return; + } + #endif + SCOPE_EXIT { render_window.OnFrameDisplayed(); }; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 0603627a07..fe1f6ce53d 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -54,6 +54,10 @@ public: } private: + void InterpolateFrames(Frame* prev_frame, Frame* curr_frame); + Frame* previous_frame = nullptr; // Store the previous frame for interpolation + VkCommandBuffer BeginSingleTimeCommands(); + void EndSingleTimeCommands(VkCommandBuffer command_buffer); void Report() const; vk::Buffer RenderToBuffer(std::span framebuffers,