From 8c4e8b77b2d138bf385805f196f9afb902670b14 Mon Sep 17 00:00:00 2001 From: KojoZero <41240068+KojoZero@users.noreply.github.com> Date: Fri, 3 Jul 2026 03:37:27 -0700 Subject: [PATCH] Add Skip Presenting Duplicate Frames Feature (#1867) * added basic skip duplicate frames implementation * fix secondary display and attempt to fix lcd color fill --- CMakeModules/GenerateSettingKeys.cmake | 1 + .../features/settings/SettingKeys.kt | 1 + .../features/settings/model/BooleanSetting.kt | 5 + .../settings/ui/SettingsFragmentPresenter.kt | 9 ++ src/android/app/src/main/jni/config.cpp | 1 + src/android/app/src/main/jni/default_ini.h | 4 + .../app/src/main/res/values/strings.xml | 2 + src/citra_qt/configuration/config.cpp | 2 + .../configuration/configure_graphics.cpp | 11 +++ .../configuration/configure_graphics.h | 1 + .../configuration/configure_graphics.ui | 12 ++- src/common/settings.cpp | 2 + src/common/settings.h | 1 + src/core/perf_stats.cpp | 3 + src/core/perf_stats.h | 2 + .../renderer_opengl/renderer_opengl.cpp | 93 ++++++++++++------- .../renderer_opengl/renderer_opengl.h | 2 + .../renderer_vulkan/renderer_vulkan.cpp | 61 ++++++++---- .../renderer_vulkan/renderer_vulkan.h | 4 +- 19 files changed, 164 insertions(+), 53 deletions(-) diff --git a/CMakeModules/GenerateSettingKeys.cmake b/CMakeModules/GenerateSettingKeys.cmake index d8f8d0bda..b5b02888e 100644 --- a/CMakeModules/GenerateSettingKeys.cmake +++ b/CMakeModules/GenerateSettingKeys.cmake @@ -42,6 +42,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 cd841b3f7..8e8312fde 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 @@ -39,6 +39,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/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt index c6ef0ae31..9fb3775e8 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt @@ -97,6 +97,11 @@ enum class BooleanSetting( HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true), SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true), VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false), + USE_SKIP_DUPLICATE_FRAMES( + SettingKeys.use_skip_duplicate_frames(), + Settings.SECTION_RENDERER, + false + ), USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true), DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false), DISABLE_RIGHT_EYE_RENDER( diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 673354f06..2cf912ff1 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1146,6 +1146,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.TEXTURE_SAMPLING.defaultValue ) ) + add( + SwitchSetting( + BooleanSetting.USE_SKIP_DUPLICATE_FRAMES, + R.string.use_skip_duplicate_frames, + R.string.use_skip_duplicate_frames_description, + BooleanSetting.USE_SKIP_DUPLICATE_FRAMES.key, + BooleanSetting.USE_SKIP_DUPLICATE_FRAMES.defaultValue + ) + ) // Disabled until custom texture implementation gets rewrite, current one overloads RAM // and crashes Citra. diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index c3d9e3b45..3aecd0dfc 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 6cc9edb68..97f7b98d6 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -130,6 +130,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: Off, 1 (default): 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/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 30ba04091..1184f9274 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -271,6 +271,8 @@ Advanced Texture Sampling Overrides the sampling filter used by games. This can be useful in certain cases with poorly behaved games when upscaling. If unsure, set this to Game Controlled. + Skip Presenting Duplicate Frames + Skips the presentation of frames that are not unique. It also allows external frame generation tools to work correctly with 30fps games Accurate Multiplication Uses more accurate multiplication in hardware shaders, which may fix some graphical bugs. When enabled, performance will be reduced. Enable Asynchronous GPU Emulation diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index c3234a657..61045e12a 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -715,6 +715,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); @@ -1265,6 +1266,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 2a2ea3f6c..59472960e 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -144,6 +144,8 @@ 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()); @@ -180,6 +182,9 @@ 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(); }); @@ -208,6 +213,9 @@ 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()); @@ -250,6 +258,9 @@ 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 f4ec4f104..659d4a972 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 7f9a01956..155a3766b 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 @@ -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 + + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index af2aacab9..f2327ea5a 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -104,6 +104,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())); @@ -216,6 +217,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 1065f7957..e481d761f 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -535,6 +535,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 b675b5df4..157b83e42 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -93,11 +93,26 @@ void RendererOpenGL::SwapBuffers() { // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); +#ifdef ANDROID + if (secondary_window) { + secondaryWindowEnabled = true; + } else { + secondaryWindowEnabled = false; + } +#else + if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) { + ASSERT(secondary_window); + secondaryWindowEnabled = true; + } else { + secondaryWindowEnabled = false; + } +#endif render_window.SetupFramebuffer(); PrepareRendertarget(); RenderScreenshot(); + isSecondaryWindow = false; #ifdef HAVE_LIBRETRO DrawScreens(render_window.GetFramebufferLayout(), false); render_window.SwapBuffers(); @@ -110,6 +125,7 @@ void RendererOpenGL::SwapBuffers() { // it means we have a second display if (secondary_window) { const auto& secondary_layout = secondary_window->GetFramebufferLayout(); + isSecondaryWindow = true; RenderToMailbox(secondary_layout, secondary_window->mailbox, false); secondary_window->PollEvents(); } @@ -117,6 +133,7 @@ void RendererOpenGL::SwapBuffers() { if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) { ASSERT(secondary_window); const auto& secondary_layout = secondary_window->GetFramebufferLayout(); + isSecondaryWindow = true; RenderToMailbox(secondary_layout, secondary_window->mailbox, false); secondary_window->PollEvents(); } @@ -196,51 +213,57 @@ void RendererOpenGL::PrepareRendertarget() { void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout, std::unique_ptr& mailbox, bool flipped) { + if (!Settings::values.use_skip_duplicate_frames.GetValue() || + Core::PerfStats::game_frames_updated) { + Frontend::Frame* frame; + { + MICROPROFILE_SCOPE(OpenGL_WaitPresent); - Frontend::Frame* frame; - { - MICROPROFILE_SCOPE(OpenGL_WaitPresent); + frame = mailbox->GetRenderFrame(); - frame = mailbox->GetRenderFrame(); + // Clean up sync objects before drawing - // Clean up sync objects before drawing + // INTEL driver workaround. We can't delete the previous render sync object until we are + // sure that the presentation is done + if (frame->present_fence) { + glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + } - // INTEL driver workaround. We can't delete the previous render sync object until we are - // sure that the presentation is done - if (frame->present_fence) { - glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + // delete the draw fence if the frame wasn't presented + if (frame->render_fence) { + glDeleteSync(frame->render_fence); + frame->render_fence = nullptr; + } + + // wait for the presentation to be done + if (frame->present_fence) { + glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(frame->present_fence); + frame->present_fence = nullptr; + } } - // delete the draw fence if the frame wasn't presented - if (frame->render_fence) { - glDeleteSync(frame->render_fence); - frame->render_fence = nullptr; + { + MICROPROFILE_SCOPE(OpenGL_RenderFrame); + // Recreate the frame if the size of the window has changed + if (layout.width != frame->width || layout.height != frame->height) { + LOG_DEBUG(Render_OpenGL, "Reloading render frame"); + mailbox->ReloadRenderFrame(frame, layout.width, layout.height); + } + + state.draw.draw_framebuffer = frame->render.handle; + state.Apply(); + DrawScreens(layout, flipped); + // Create a fence for the frontend to wait on and swap this frame to OffTex + frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + mailbox->ReleaseRenderFrame(frame); } - // wait for the presentation to be done - if (frame->present_fence) { - glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(frame->present_fence); - frame->present_fence = nullptr; + if ((secondaryWindowEnabled && isSecondaryWindow) || (!secondaryWindowEnabled)) { + Core::PerfStats::game_frames_updated = false; } } - - { - MICROPROFILE_SCOPE(OpenGL_RenderFrame); - // Recreate the frame if the size of the window has changed - if (layout.width != frame->width || layout.height != frame->height) { - LOG_DEBUG(Render_OpenGL, "Reloading render frame"); - mailbox->ReloadRenderFrame(frame, layout.width, layout.height); - } - - state.draw.draw_framebuffer = frame->render.handle; - state.Apply(); - DrawScreens(layout, flipped); - // Create a fence for the frontend to wait on and swap this frame to OffTex - frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - mailbox->ReleaseRenderFrame(frame); - } } /** diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 2f2b318ed..6670f1bc3 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -112,6 +112,8 @@ private: GLuint attrib_tex_coord; FrameDumperOpenGL frame_dumper; + bool isSecondaryWindow; + bool secondaryWindowEnabled; }; } // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index eda273321..1fc098270 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,29 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout& void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::FramebufferLayout& layout, bool flipped) { - Frame* frame = window.GetRenderFrame(); + if (!Settings::values.use_skip_duplicate_frames.GetValue() || + Core::PerfStats::game_frames_updated) { + 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 (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); + if ((secondaryWindowEnabled && isSecondaryWindow) || (!secondaryWindowEnabled)) { + Core::PerfStats::game_frames_updated = false; + screenRendered = true; + } } - - 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, @@ -1116,9 +1121,28 @@ void RendererVulkan::DrawCursor(const Layout::FramebufferLayout& layout) { void RendererVulkan::SwapBuffers() { system.perf_stats->StartSwap(); + screenRendered = false; +#ifndef ANDROID + if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) { + ASSERT(secondary_window); + secondaryWindowEnabled = true; + } else { + secondaryWindowEnabled = false; + } +#endif + +#ifdef ANDROID + if (secondary_window) { + secondaryWindowEnabled = true; + } else { + secondaryWindowEnabled = false; + } +#endif + const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout(); PrepareRendertarget(); RenderScreenshot(); + isSecondaryWindow = false; RenderToWindow(main_present_window, layout, false); #ifndef ANDROID if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) { @@ -1128,6 +1152,7 @@ void RendererVulkan::SwapBuffers() { secondary_present_window_ptr = std::make_unique( *secondary_window, instance, scheduler, IsLowRefreshRate()); } + isSecondaryWindow = true; RenderToWindow(*secondary_present_window_ptr, secondary_layout, false); secondary_window->PollEvents(); } @@ -1140,10 +1165,14 @@ void RendererVulkan::SwapBuffers() { secondary_present_window_ptr = std::make_unique( *secondary_window, instance, scheduler, IsLowRefreshRate()); } + isSecondaryWindow = true; RenderToWindow(*secondary_present_window_ptr, secondary_layout, false); secondary_window->PollEvents(); } #endif + if (!screenRendered) { + scheduler.Finish(); + } system.perf_stats->EndSwap(); rasterizer.TickFrame(); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 14c9bd34f..54f043449 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; @@ -151,6 +150,9 @@ private: vk::ShaderModule cursor_fragment_shader{}; vk::Pipeline cursor_pipeline{}; vk::UniquePipelineLayout cursor_pipeline_layout{}; + bool isSecondaryWindow; + bool secondaryWindowEnabled; + bool screenRendered; }; } // namespace Vulkan