diff --git a/CMakeModules/GenerateSettingKeys.cmake b/CMakeModules/GenerateSettingKeys.cmake
index 90110cd33..f4e99cc1f 100644
--- a/CMakeModules/GenerateSettingKeys.cmake
+++ b/CMakeModules/GenerateSettingKeys.cmake
@@ -40,6 +40,7 @@ foreach(KEY IN ITEMS
"use_disk_shader_cache"
"shaders_accurate_mul"
"use_vsync"
+ "use_skip_duplicate_frames"
"use_display_refresh_rate_detection"
"use_shader_jit"
"resolution_factor"
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt
index 1d6e0dcee..b4acd2cbe 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt
@@ -38,6 +38,7 @@ object SettingKeys {
external fun use_disk_shader_cache(): String
external fun shaders_accurate_mul(): String
external fun use_vsync(): String
+ external fun use_skip_duplicate_frames(): String
external fun use_shader_jit(): String
external fun resolution_factor(): String
external fun frame_limit(): String
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index e0fa260c5..b88f0d29c 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -152,6 +152,7 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.resolution_factor);
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
ReadSetting("Renderer", Settings::values.use_vsync);
+ ReadSetting("Renderer", Settings::values.use_skip_duplicate_frames);
ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.texture_sampling);
ReadSetting("Renderer", Settings::values.turbo_limit);
diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h
index ce93d0e6f..accd6bdf9 100644
--- a/src/android/app/src/main/jni/default_ini.h
+++ b/src/android/app/src/main/jni/default_ini.h
@@ -127,6 +127,10 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
# 0 (default): Off, 1: On
)") DECLARE_KEY(use_vsync) BOOST_HANA_STRING(R"(
+# Skips display of duplicated frames in 30 fps games
+# 0 (default): Off, 1: On
+)") DECLARE_KEY(use_skip_duplicate_frames) BOOST_HANA_STRING(R"(
+
# Reduce stuttering by storing and loading generated shaders to disk
# 0: Off, 1 (default. On)
)") DECLARE_KEY(use_disk_shader_cache) BOOST_HANA_STRING(R"(
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index a5aa9896b..7643aa604 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -706,6 +706,7 @@ void QtConfig::ReadRendererValues() {
ReadGlobalSetting(Settings::values.shaders_accurate_mul);
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
ReadGlobalSetting(Settings::values.use_vsync);
+ ReadGlobalSetting(Settings::values.use_skip_duplicate_frames);
ReadGlobalSetting(Settings::values.use_display_refresh_rate_detection);
ReadGlobalSetting(Settings::values.resolution_factor);
ReadGlobalSetting(Settings::values.use_integer_scaling);
@@ -1250,6 +1251,7 @@ void QtConfig::SaveRendererValues() {
WriteGlobalSetting(Settings::values.shaders_accurate_mul);
WriteGlobalSetting(Settings::values.use_disk_shader_cache);
WriteGlobalSetting(Settings::values.use_vsync);
+ WriteGlobalSetting(Settings::values.use_skip_duplicate_frames);
WriteGlobalSetting(Settings::values.use_display_refresh_rate_detection);
WriteGlobalSetting(Settings::values.resolution_factor);
WriteGlobalSetting(Settings::values.use_integer_scaling);
diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp
index aac27ab16..bbf529757 100644
--- a/src/citra_qt/configuration/configure_graphics.cpp
+++ b/src/citra_qt/configuration/configure_graphics.cpp
@@ -7,6 +7,7 @@
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_graphics.h"
#include "common/settings.h"
+#include "configuration/ui_configure_graphics.h"
#include "ui_configure_graphics.h"
#ifdef ENABLE_VULKAN
#include "video_core/renderer_vulkan/vk_instance.h"
@@ -144,6 +145,7 @@ void ConfigureGraphics::SetConfiguration() {
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue());
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
ui->toggle_vsync->setChecked(Settings::values.use_vsync.GetValue());
+ ui->toggle_skip_duplicate_frames->setChecked(Settings::values.use_skip_duplicate_frames.GetValue());
ui->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue());
ui->disable_spirv_optimizer->setChecked(Settings::values.disable_spirv_optimizer.GetValue());
ui->toggle_async_shaders->setChecked(Settings::values.async_shader_compilation.GetValue());
@@ -179,6 +181,8 @@ void ConfigureGraphics::ApplyConfiguration() {
ui->toggle_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->toggle_vsync,
use_vsync);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_skip_duplicate_frames, ui->toggle_skip_duplicate_frames,
+ use_skip_duplicate_frames);
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.delay_game_render_thread_us, ui->delay_render_combo,
[this](s32) { return ui->delay_render_slider->value(); });
@@ -204,6 +208,8 @@ void ConfigureGraphics::SetupPerGameUI() {
Settings::values.use_disk_shader_cache.UsingGlobal());
ui->toggle_vsync->setEnabled(ui->toggle_vsync->isEnabled() &&
Settings::values.use_vsync.UsingGlobal());
+ ui->toggle_skip_duplicate_frames->setEnabled(ui->toggle_skip_duplicate_frames->isEnabled() &&
+ Settings::values.use_skip_duplicate_frames.UsingGlobal());
ui->toggle_async_shaders->setEnabled(
Settings::values.async_shader_compilation.UsingGlobal());
ui->widget_texture_sampling->setEnabled(Settings::values.texture_sampling.UsingGlobal());
@@ -244,6 +250,8 @@ void ConfigureGraphics::SetupPerGameUI() {
use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(ui->toggle_vsync, Settings::values.use_vsync,
use_vsync);
+ ConfigurationShared::SetColoredTristate(ui->toggle_skip_duplicate_frames, Settings::values.use_skip_duplicate_frames,
+ use_skip_duplicate_frames);
ConfigurationShared::SetColoredTristate(ui->toggle_async_shaders,
Settings::values.async_shader_compilation,
async_shader_compilation);
diff --git a/src/citra_qt/configuration/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h
index 61a462a67..8b387254a 100644
--- a/src/citra_qt/configuration/configure_graphics.h
+++ b/src/citra_qt/configuration/configure_graphics.h
@@ -39,6 +39,7 @@ private:
ConfigurationShared::CheckState shaders_accurate_mul;
ConfigurationShared::CheckState use_disk_shader_cache;
ConfigurationShared::CheckState use_vsync;
+ ConfigurationShared::CheckState use_skip_duplicate_frames;
ConfigurationShared::CheckState use_display_refresh_rate_detection;
ConfigurationShared::CheckState async_shader_compilation;
ConfigurationShared::CheckState async_presentation;
diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui
index ff5ec613f..8a516e125 100644
--- a/src/citra_qt/configuration/configure_graphics.ui
+++ b/src/citra_qt/configuration/configure_graphics.ui
@@ -7,7 +7,7 @@
0
0
400
- 509
+ 559
@@ -138,12 +138,12 @@
-
-
- Disable GLSL -> SPIR-V optimizer
-
<html><head/><body><p>Disables the SPIR-V optimization pass, reducing stuttering considerably while barely affecting performance.</p></body></html>
+
+ Disable GLSL -> SPIR-V optimizer
+
@@ -317,6 +317,16 @@
+ -
+
+
+ <html><head/><body><p>This detects and skips the presentation of frames that are not unique. It also allows external frame generation tools to work correctly with 30fps games. Works in OpenGL and Vulkan.</p></body></html>
+
+
+ Skip Presenting Duplicate Frames
+
+
+
-
@@ -328,81 +338,81 @@
-
-
-
-
- 0
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
-
+
+ Use global
-
- 0
+
+ -
+
+ Use per-application
-
- 0
-
-
- 0
-
-
-
-
-
-
-
- Use global
-
-
- -
-
- Use per-application
-
-
-
-
- -
-
-
- Delay Application Render Thread
-
-
- <html><head/><body><p>Delays the emulated application render thread the specified amount of milliseconds every time it submits render commands to the GPU.</p><p>Adjust this feature in the (very few) dynamic framerate applications to fix performance issues.</p></body></html>
-
-
-
- -
-
-
- 0
-
-
- 16000
-
-
- 100
-
-
- 250
-
-
- 0
-
-
- Qt::Horizontal
-
-
- QSlider::TicksBelow
-
-
-
- -
-
-
-
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
-
-
+
+
+
+ -
+
+
+ <html><head/><body><p>Delays the emulated application render thread the specified amount of milliseconds every time it submits render commands to the GPU.</p><p>Adjust this feature in the (very few) dynamic framerate applications to fix performance issues.</p></body></html>
+
+
+ Delay Application Render Thread
+
+
+
+ -
+
+
+ 0
+
+
+ 16000
+
+
+ 100
+
+
+ 250
+
+
+ 0
+
+
+ Qt::Orientation::Horizontal
+
+
+ QSlider::TickPosition::TicksBelow
+
+
+
+ -
+
+
+
+
+
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
+
+
+
+
+
@@ -410,7 +420,7 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 2ac231f50..f0906711b 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -100,6 +100,7 @@ void LogSettings() {
log_setting("Renderer_UseIntegerScaling", values.use_integer_scaling.GetValue());
log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
log_setting("Renderer_VSyncNew", values.use_vsync.GetValue());
+ log_setting("Renderer_SkipDuplicateFrames", values.use_skip_duplicate_frames.GetValue());
log_setting("Renderer_PostProcessingShader", values.pp_shader_name.GetValue());
log_setting("Renderer_FilterMode", values.filter_mode.GetValue());
log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.GetValue()));
@@ -209,6 +210,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.use_disk_shader_cache.SetGlobal(true);
values.shaders_accurate_mul.SetGlobal(true);
values.use_vsync.SetGlobal(true);
+ values.use_skip_duplicate_frames.SetGlobal(true);
values.resolution_factor.SetGlobal(true);
values.use_integer_scaling.SetGlobal(true);
values.frame_limit.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index 5eca53421..91a55c161 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -524,6 +524,7 @@ struct Values {
SwitchableSetting async_presentation{true, Keys::async_presentation};
SwitchableSetting use_hw_shader{true, Keys::use_hw_shader};
SwitchableSetting use_disk_shader_cache{true, Keys::use_disk_shader_cache};
+ SwitchableSetting use_skip_duplicate_frames{true, Keys::use_skip_duplicate_frames};
SwitchableSetting shaders_accurate_mul{true, Keys::shaders_accurate_mul};
#ifdef ANDROID // TODO: Fuck this -OS
SwitchableSetting use_vsync{false, Keys::use_vsync};
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 8d7724003..05449cd98 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -29,6 +29,8 @@ constexpr std::size_t IgnoreFrames = 5;
namespace Core {
+bool PerfStats::game_frames_updated = true;
+
PerfStats::PerfStats(u64 title_id) : title_id(title_id) {}
PerfStats::~PerfStats() {
@@ -109,6 +111,7 @@ void PerfStats::EndGameFrame() {
std::scoped_lock lock{object_mutex};
game_frames += 1;
+ PerfStats::game_frames_updated = true;
}
double PerfStats::GetMeanFrametime() const {
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h
index de358de66..e335db7f3 100644
--- a/src/core/perf_stats.h
+++ b/src/core/perf_stats.h
@@ -120,6 +120,8 @@ public:
artic_events.Set(event, set);
}
}
+ /// Boolean representing whether game_frames has been updated since last time it was presented
+ static bool game_frames_updated;
private:
mutable std::mutex object_mutex;
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index c8cb6c000..7309d5bd5 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -196,6 +196,7 @@ void RendererOpenGL::PrepareRendertarget() {
void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr& mailbox,
bool flipped) {
+ if ((Core::PerfStats::game_frames_updated && Settings::values.use_skip_duplicate_frames.GetValue()) || !Settings::values.use_skip_duplicate_frames.GetValue()){
Frontend::Frame* frame;
{
@@ -241,6 +242,9 @@ void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
glFlush();
mailbox->ReleaseRenderFrame(frame);
}
+
+ Core::PerfStats::game_frames_updated = false;
+ }
}
/**
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 0a25c2036..58f2b20cb 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -24,7 +24,6 @@
#include "video_core/host_shaders/vulkan_cursor_vert.h"
#include
-
#if defined(__APPLE__) && !defined(HAVE_LIBRETRO)
#include "common/apple_utils.h"
#endif
@@ -237,23 +236,26 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::FramebufferLayout& layout,
bool flipped) {
- Frame* frame = window.GetRenderFrame();
- if (layout.width != frame->width || layout.height != frame->height) {
- window.WaitPresent();
- scheduler.Finish();
- window.RecreateFrame(frame, layout.width, layout.height);
+ if ((Core::PerfStats::game_frames_updated && Settings::values.use_skip_duplicate_frames.GetValue()) || !Settings::values.use_skip_duplicate_frames.GetValue()){
+ Frame* frame = window.GetRenderFrame();
+
+ if (layout.width != frame->width || layout.height != frame->height) {
+ window.WaitPresent();
+ scheduler.Finish();
+ window.RecreateFrame(frame, layout.width, layout.height);
+ }
+
+ clear_color.float32[0] = Settings::values.bg_red.GetValue();
+ clear_color.float32[1] = Settings::values.bg_green.GetValue();
+ clear_color.float32[2] = Settings::values.bg_blue.GetValue();
+ clear_color.float32[3] = 1.0f;
+
+ DrawScreens(frame, layout, flipped);
+ scheduler.Flush(frame->render_ready);
+ window.Present(frame);
+ Core::PerfStats::game_frames_updated = false;
}
-
- clear_color.float32[0] = Settings::values.bg_red.GetValue();
- clear_color.float32[1] = Settings::values.bg_green.GetValue();
- clear_color.float32[2] = Settings::values.bg_blue.GetValue();
- clear_color.float32[3] = 1.0f;
-
- DrawScreens(frame, layout, flipped);
- scheduler.Flush(frame->render_ready);
-
- window.Present(frame);
}
void RendererVulkan::LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuffer,
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 14c9bd34f..39f918051 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -134,7 +134,6 @@ private:
DescriptorUpdateQueue update_queue;
RasterizerVulkan rasterizer;
std::unique_ptr secondary_present_window_ptr;
-
DescriptorHeap present_heap;
vk::UniquePipelineLayout present_pipeline_layout;
std::array present_pipelines;