Android: add FRAME_SKIPPING and FRAME_INTERPOLATION
This commit is contained in:
parent
f9f311c014
commit
d25bea5762
8 changed files with 155 additions and 6 deletions
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue