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 119517d99b..03286dfca8 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
@@ -51,7 +51,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
ENABLE_RAII("enable_raii"),
FRAME_INTERPOLATION("frame_interpolation"),
-// FRAME_SKIPPING("frame_skipping"),
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
@@ -68,8 +67,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
USE_LRU_CACHE("use_lru_cache");
external fun isRaiiEnabled(): Boolean
-// 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 13dd32fcdd..dfd65cd6d7 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
@@ -234,15 +234,6 @@ abstract class SettingsItem(
descriptionId = R.string.frame_interpolation_description
)
)
-
-// put(
-// SwitchSetting(
-// BooleanSetting.FRAME_SKIPPING,
-// titleId = R.string.frame_skipping,
-// descriptionId = R.string.frame_skipping_description
-// )
-// )
-
put(
SwitchSetting(
dockedModeSetting,
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 1d2163555b..1bcf76371a 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -479,8 +479,6 @@
Display
Post-Processing
- WIP: Frameskip
- Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.
Accuracy level
Resolution (Handheld/Docked)
VSync mode
diff --git a/src/common/settings.h b/src/common/settings.h
index 41c042bcf1..910e46a447 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -322,12 +322,8 @@ struct Values {
SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
Specialization::RuntimeList};
SwitchableSetting enable_raii{linkage, false, "enable_raii", Category::Renderer};
-#ifdef __ANDROID__
SwitchableSetting frame_interpolation{linkage, true, "frame_interpolation", Category::Renderer,
Specialization::RuntimeList};
- SwitchableSetting frame_skipping{linkage, false, "frame_skipping", Category::Renderer,
- Specialization::RuntimeList};
-#endif
SwitchableSetting use_disk_shader_cache{linkage, true, "use_disk_shader_cache",
Category::Renderer};
SwitchableSetting optimize_spirv_output{linkage,
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index a91fba6725..86ffe44074 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -38,9 +38,7 @@
#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 {
@@ -189,144 +187,97 @@ RendererVulkan::~RendererVulkan() {
void(device.GetLogical().WaitIdle());
}
-#ifdef __ANDROID__
-class BooleanSetting {
- public:
-// static BooleanSetting FRAME_SKIPPING;
- static BooleanSetting 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;
- }
+void RendererVulkan::InterpolateFrames(Frame* prev_frame, Frame* interpolated_frame) {
+ if (!prev_frame || !interpolated_frame || !prev_frame->image || !interpolated_frame->image) {
+ return;
+ }
- private:
- bool value;
+ const auto& framebuffer_layout = render_window.GetFramebufferLayout();
+ // Fixed aggressive downscale (50%)
+ VkExtent2D dst_extent{
+ .width = framebuffer_layout.width / 2,
+ .height = framebuffer_layout.height / 2
};
- // Initialize static members
-// BooleanSetting BooleanSetting::FRAME_SKIPPING(false);
- BooleanSetting BooleanSetting::FRAME_INTERPOLATION(false);
-
-// extern "C" JNIEXPORT jboolean JNICALL
-// Java_org_yuzu_yuzu_1emu_features_settings_model_BooleanSetting_isFrameSkippingEnabled(JNIEnv* env, jobject /* this */) {
-// return static_cast(BooleanSetting::FRAME_SKIPPING.getBoolean());
-// }
-
- extern "C" JNIEXPORT jboolean JNICALL
- Java_org_yuzu_yuzu_1emu_features_settings_model_BooleanSetting_isFrameInterpolationEnabled(JNIEnv* env, jobject /* this */) {
- return static_cast(BooleanSetting::FRAME_INTERPOLATION.getBoolean());
+ // Check if we need to recreate the destination frame
+ bool needs_recreation = false; // Only recreate when necessary
+ if (!interpolated_frame->image_view) {
+ needs_recreation = true; // Need to create initially
+ } else {
+ // Check if dimensions have changed
+ if (interpolated_frame->framebuffer) {
+ needs_recreation = (framebuffer_layout.width / 2 != dst_extent.width) ||
+ (framebuffer_layout.height / 2 != dst_extent.height);
+ } else {
+ needs_recreation = true;
+ }
}
- void RendererVulkan::InterpolateFrames(Frame* prev_frame, Frame* interpolated_frame) {
- if (!prev_frame || !interpolated_frame || !prev_frame->image || !interpolated_frame->image) {
- return;
- }
+ if (needs_recreation) {
+ interpolated_frame->image = CreateWrappedImage(memory_allocator, dst_extent, swapchain.GetImageViewFormat());
+ interpolated_frame->image_view = CreateWrappedImageView(device, interpolated_frame->image, swapchain.GetImageViewFormat());
+ interpolated_frame->framebuffer = blit_swapchain.CreateFramebuffer(
+ Layout::FramebufferLayout{dst_extent.width, dst_extent.height},
+ *interpolated_frame->image_view,
+ swapchain.GetImageViewFormat());
+ }
- const auto& framebuffer_layout = render_window.GetFramebufferLayout();
- // Fixed aggressive downscale (50%)
- VkExtent2D dst_extent{
- .width = framebuffer_layout.width / 2,
- .height = framebuffer_layout.height / 2
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([&](vk::CommandBuffer cmdbuf) {
+ // Transition images to transfer layouts
+ TransitionImageLayout(cmdbuf, *prev_frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+ TransitionImageLayout(cmdbuf, *interpolated_frame->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+ // Perform the downscale blit
+ VkImageBlit blit_region{};
+ blit_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
+ blit_region.srcOffsets[0] = {0, 0, 0};
+ blit_region.srcOffsets[1] = {
+ static_cast(framebuffer_layout.width),
+ static_cast(framebuffer_layout.height),
+ 1
+ };
+ blit_region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
+ blit_region.dstOffsets[0] = {0, 0, 0};
+ blit_region.dstOffsets[1] = {
+ static_cast(dst_extent.width),
+ static_cast(dst_extent.height),
+ 1
};
- // Check if we need to recreate the destination frame
- bool needs_recreation = false; // Only recreate when necessary
- if (!interpolated_frame->image_view) {
- needs_recreation = true; // Need to create initially
- } else {
- // Check if dimensions have changed
- if (interpolated_frame->framebuffer) {
- needs_recreation = (framebuffer_layout.width / 2 != dst_extent.width) ||
- (framebuffer_layout.height / 2 != dst_extent.height);
- } else {
- needs_recreation = true;
- }
- }
-
- if (needs_recreation) {
- interpolated_frame->image = CreateWrappedImage(memory_allocator, dst_extent, swapchain.GetImageViewFormat());
- interpolated_frame->image_view = CreateWrappedImageView(device, interpolated_frame->image, swapchain.GetImageViewFormat());
- interpolated_frame->framebuffer = blit_swapchain.CreateFramebuffer(
- Layout::FramebufferLayout{dst_extent.width, dst_extent.height},
- *interpolated_frame->image_view,
- swapchain.GetImageViewFormat());
- }
+ // Using the wrapper's BlitImage with proper parameters
+ cmdbuf.BlitImage(
+ *prev_frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ *interpolated_frame->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ blit_region, VK_FILTER_NEAREST
+ );
- scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([&](vk::CommandBuffer cmdbuf) {
- // Transition images to transfer layouts
- TransitionImageLayout(cmdbuf, *prev_frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
- TransitionImageLayout(cmdbuf, *interpolated_frame->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
-
- // Perform the downscale blit
- VkImageBlit blit_region{};
- blit_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
- blit_region.srcOffsets[0] = {0, 0, 0};
- blit_region.srcOffsets[1] = {
- static_cast(framebuffer_layout.width),
- static_cast(framebuffer_layout.height),
- 1
- };
- blit_region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
- blit_region.dstOffsets[0] = {0, 0, 0};
- blit_region.dstOffsets[1] = {
- static_cast(dst_extent.width),
- static_cast(dst_extent.height),
- 1
- };
-
- // Using the wrapper's BlitImage with proper parameters
- cmdbuf.BlitImage(
- *prev_frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
- *interpolated_frame->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
- blit_region, VK_FILTER_NEAREST
- );
-
- // Transition back to general layout
- TransitionImageLayout(cmdbuf, *prev_frame->image, VK_IMAGE_LAYOUT_GENERAL);
- TransitionImageLayout(cmdbuf, *interpolated_frame->image, VK_IMAGE_LAYOUT_GENERAL);
- });
- }
-#endif
+ // Transition back to general layout
+ TransitionImageLayout(cmdbuf, *prev_frame->image, VK_IMAGE_LAYOUT_GENERAL);
+ TransitionImageLayout(cmdbuf, *interpolated_frame->image, VK_IMAGE_LAYOUT_GENERAL);
+ });
+}
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 frame_skipping = false; //BooleanSetting::FRAME_SKIPPING.getBoolean();
- bool frame_interpolation = BooleanSetting::FRAME_INTERPOLATION.getBoolean();
- #endif
-
if (framebuffers.empty()) {
return;
}
- #ifdef __ANDROID__
- if (frame_skipping) {
- frame_skip_threshold = (target_fps == 30) ? 2 : 2;
- }
+ bool frame_interpolation_enabled = Settings::values.frame_interpolation.GetValue();
- frame_counter++;
- if (frame_counter % frame_skip_threshold != 0) {
- if (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);
- }
+ if (frame_interpolation_enabled && 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);
+
+ // Optionally, update previous_frame here if you want to chain interpolations
+ previous_frame = interpolated_frame;
return;
}
- #endif
SCOPE_EXIT {
render_window.OnFrameDisplayed();
@@ -346,6 +297,11 @@ void RendererVulkan::Composite(std::span framebu
scheduler.Flush(*frame->render_ready);
present_manager.Present(frame);
+ // Store the current frame for interpolation on the next call
+ if (frame_interpolation_enabled) {
+ previous_frame = frame;
+ }
+
gpu.RendererFrameEndNotify();
rasterizer.TickFrame();
}
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 770a16a481..3f51050606 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -250,6 +250,12 @@ std::unique_ptr InitializeTranslations(QWidget* parent)
"of available video memory for performance. Has no effect on integrated graphics. "
"Aggressive mode may severely impact the performance of other applications such as "
"recording software."));
+ INSERT(Settings,
+ frame_interpolation,
+ tr("Enhanced Frame Pacing"),
+ tr("Ensures smooth and consistent frame delivery by synchronizing the timing between frames, "
+ "reducing stuttering and uneven animation. Ideal for games that experience frame timing "
+ "instability or micro-stutters during gameplay."));
INSERT(Settings,
skip_cpu_inner_invalidation,
tr("Skip CPU Inner Invalidation"),
@@ -333,14 +339,13 @@ std::unique_ptr InitializeTranslations(QWidget* parent)
barrier_feedback_loops,
tr("Barrier feedback loops"),
tr("Improves rendering of transparency effects in specific games."));
-
- // Renderer (Extensions)
INSERT(Settings,
enable_raii,
tr("RAII"),
tr("A method of automatic resource management in Vulkan "
"that ensures proper release of resources "
"when they are no longer needed, but may cause crashes in bundled games."));
+ // Renderer (Extensions)
INSERT(Settings,
dyna_state,
tr("Extended Dynamic State"),