Android: add FRAME_SKIPPING and FRAME_INTERPOLATION

This commit is contained in:
Pavel Barabanov 2025-04-10 22:24:15 +03:00 committed by MrPurple666
parent 91b3d4aaf1
commit 4fb7481fa2
8 changed files with 155 additions and 6 deletions

View file

@ -26,7 +26,13 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
SHOW_INPUT_OVERLAY("show_input_overlay"), SHOW_INPUT_OVERLAY("show_input_overlay"),
TOUCHSCREEN("touchscreen"), 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 = override fun getBoolean(needsGlobal: Boolean): Boolean =
NativeConfig.getBoolean(key, needsGlobal) NativeConfig.getBoolean(key, needsGlobal)

View file

@ -172,6 +172,56 @@ abstract class SettingsItem(
override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset() 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( put(
SwitchSetting( SwitchSetting(
dockedModeSetting, dockedModeSetting,

View file

@ -169,6 +169,8 @@ class SettingsFragmentPresenter(
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
sl.apply { sl.apply {
add(BooleanSetting.ENABLE_FRAME_INTERPOLATION.key)
add(BooleanSetting.ENABLE_FRAME_SKIPPING.key)
add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_RESOLUTION.key)
add(IntSetting.RENDERER_VSYNC.key) add(IntSetting.RENDERER_VSYNC.key)

View file

@ -534,10 +534,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// Calculate used memory // Calculate used memory
val usedMegs = (mi.totalMem - mi.availMem) / 1048576L // Convert bytes to megabytes 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) { if (_binding != null) {
binding.showFpsText.text = String.format( binding.showFpsText.text = String.format(
"%.1f FPS • %d MB • %s/%s", "%.1f %s FPS • %d MB • %s/%s",
perfStats[FPS], usedMegs, cpuBackend, gpuDriver actualFps, generatedFpsText, usedMegs, cpuBackend, gpuDriver
) )
} }
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)

View file

@ -230,6 +230,10 @@
<string name="set_custom_rtc">Set custom RTC</string> <string name="set_custom_rtc">Set custom RTC</string>
<!-- Graphics settings strings --> <!-- Graphics settings strings -->
<string name="enable_frame_skipping">Enable Frame Skipping</string>
<string name="enable_frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames.</string>
<string name="enable_frame_interpolation">Enable Frame Interpolation</string>
<string name="enable_frame_interpolation_description">Toggle frame interpolation to improve visual smoothness by generating intermediate frames.</string>
<string name="renderer_accuracy">Accuracy level</string> <string name="renderer_accuracy">Accuracy level</string>
<string name="renderer_resolution">Resolution (Handheld/Docked)</string> <string name="renderer_resolution">Resolution (Handheld/Docked)</string>
<string name="renderer_vsync">VSync mode</string> <string name="renderer_vsync">VSync mode</string>

View file

@ -250,7 +250,6 @@ struct Values {
Category::CpuDebug}; Category::CpuDebug};
Setting<bool> cpuopt_ignore_memory_aborts{linkage, true, "cpuopt_ignore_memory_aborts", Setting<bool> cpuopt_ignore_memory_aborts{linkage, true, "cpuopt_ignore_memory_aborts",
Category::CpuDebug}; Category::CpuDebug};
SwitchableSetting<bool> cpuopt_unsafe_unfuse_fma{linkage, true, "cpuopt_unsafe_unfuse_fma", SwitchableSetting<bool> cpuopt_unsafe_unfuse_fma{linkage, true, "cpuopt_unsafe_unfuse_fma",
Category::CpuUnsafe}; Category::CpuUnsafe};
SwitchableSetting<bool> cpuopt_unsafe_reduce_fp_error{ SwitchableSetting<bool> cpuopt_unsafe_reduce_fp_error{
@ -273,7 +272,10 @@ struct Values {
"shader_backend", Category::Renderer, Specialization::RuntimeList}; "shader_backend", Category::Renderer, Specialization::RuntimeList};
SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
Specialization::RuntimeList}; Specialization::RuntimeList};
SwitchableSetting<bool> enable_frame_interpolation{linkage, true, "enable_frame_interpolation", Category::Renderer,
Specialization::RuntimeList};
SwitchableSetting<bool> enable_frame_skipping{linkage, true, "enable_frame_skipping", Category::Renderer,
Specialization::RuntimeList};
SwitchableSetting<bool> use_disk_shader_cache{linkage, true, "use_disk_shader_cache", SwitchableSetting<bool> use_disk_shader_cache{linkage, true, "use_disk_shader_cache",
Category::Renderer}; Category::Renderer};
SwitchableSetting<bool> use_asynchronous_gpu_emulation{ SwitchableSetting<bool> use_asynchronous_gpu_emulation{

View file

@ -35,7 +35,9 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_surface.h" #include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
#ifdef __ANDROID__
#include <jni.h>
#endif
namespace Vulkan { namespace Vulkan {
namespace { namespace {
@ -136,11 +138,82 @@ RendererVulkan::~RendererVulkan() {
void(device.GetLogical().WaitIdle()); 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<jboolean>(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<jboolean>(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<const Tegra::FramebufferConfig> framebuffers) { void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> 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()) { if (framebuffers.empty()) {
return; 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 { SCOPE_EXIT {
render_window.OnFrameDisplayed(); render_window.OnFrameDisplayed();
}; };

View file

@ -54,6 +54,10 @@ public:
} }
private: 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; void Report() const;
vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers, vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,