From df16444e3599772dbd9c34806d22bd20cd12e78c Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 16 Mar 2021 02:25:33 +1000 Subject: [PATCH] Add skip presenting duplicate frames option --- android/app/src/main/res/values/arrays.xml | 12 ++++++ android/app/src/main/res/values/strings.xml | 1 + .../src/main/res/xml/advanced_preferences.xml | 12 +++--- src/core/gpu.cpp | 10 ++++- src/core/gpu.h | 11 +++-- src/core/host_display.cpp | 4 ++ src/core/host_display.h | 16 ++++---- src/core/host_interface.cpp | 3 +- src/core/settings.cpp | 40 ++++++++++++++++++- src/core/settings.h | 8 +++- src/core/types.h | 9 +++++ src/duckstation-qt/displaysettingswidget.cpp | 16 ++++++-- src/duckstation-qt/displaysettingswidget.ui | 19 +++++---- src/frontend-common/common_host_interface.cpp | 20 +++++++--- src/frontend-common/d3d11_host_display.cpp | 1 + src/frontend-common/fullscreen_ui.cpp | 10 +++-- src/frontend-common/opengl_host_display.cpp | 1 + src/frontend-common/vulkan_host_display.cpp | 1 + 18 files changed, 152 insertions(+), 42 deletions(-) diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml index 01a9ae0c5..bcbead1f9 100644 --- a/android/app/src/main/res/values/arrays.xml +++ b/android/app/src/main/res/values/arrays.xml @@ -485,4 +485,16 @@ Port2Only BothPorts + + Always Display All Frames + Display All Frames When Possible + Skip Duplicate Frames + Clamp To Max FPS + + + DisplayAllFrames + DisplayAllFramesWhenPossible + SkipDuplicateFrames + ClampToMaxFPS + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 861f7bc3e..554f88b5b 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -276,4 +276,5 @@ Ports Port %d Port %1$d%2$c + Frame Pacing diff --git a/android/app/src/main/res/xml/advanced_preferences.xml b/android/app/src/main/res/xml/advanced_preferences.xml index 96b41425a..316a47a0f 100644 --- a/android/app/src/main/res/xml/advanced_preferences.xml +++ b/android/app/src/main/res/xml/advanced_preferences.xml @@ -56,11 +56,13 @@ app:summary="@string/settings_summary_general_sync_to_host_refresh_rate" app:dependency="Display/VSync" app:iconSpaceReserved="false" /> - Y1; BitField Y2; }; - } regs; + }; + + bool in_hblank; + bool in_vblank; + bool display_changed; + + Regs regs; u16 dot_clock_divider; @@ -523,9 +529,6 @@ protected: TickCount fractional_dot_ticks; // only used when timer0 is enabled - bool in_hblank; - bool in_vblank; - u8 interlaced_field; // 0 = odd, 1 = even u8 interlaced_display_field; u8 active_line_lsb; diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp index 141fcad5e..ae86918ff 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -5,6 +5,7 @@ #include "common/log.h" #include "common/string_util.h" #include "common/timer.h" +#include "settings.h" #include "host_interface.h" #include "stb_image.h" #include "stb_image_resize.h" @@ -27,6 +28,9 @@ void HostDisplay::SetDisplayMaxFPS(float max_fps) bool HostDisplay::ShouldSkipDisplayingFrame() { + if (!m_display_duplicate_frames && !m_display_changed) + return true; + if (m_display_frame_interval == 0.0f) return false; diff --git a/src/core/host_display.h b/src/core/host_display.h index 1c683eb74..a2c59d036 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -8,7 +8,7 @@ #include #include -enum class HostDisplayPixelFormat : u32 +enum class HostDisplayPixelFormat : u8 { Unknown, RGBA8, @@ -46,7 +46,7 @@ public: OpenGLES }; - enum class Alignment + enum class Alignment : u8 { LeftOrTop, Center, @@ -135,6 +135,7 @@ public: const float GetDisplayAspectRatio() const { return m_display_aspect_ratio; } void SetDisplayMaxFPS(float max_fps); + void SetDisplayDuplicateFrames(bool enabled) { m_display_duplicate_frames = enabled; } bool ShouldSkipDisplayingFrame(); void ClearDisplayTexture() @@ -267,7 +268,7 @@ protected: float m_display_frame_interval = 0.0f; void* m_display_texture_handle = nullptr; - HostDisplayPixelFormat m_display_texture_format = HostDisplayPixelFormat::Count; + std::unique_ptr m_cursor_texture; s32 m_display_texture_width = 0; s32 m_display_texture_height = 0; s32 m_display_texture_view_x = 0; @@ -276,13 +277,14 @@ protected: s32 m_display_texture_view_height = 0; s32 m_display_top_margin = 0; - Alignment m_display_alignment = Alignment::Center; - - std::unique_ptr m_cursor_texture; float m_cursor_texture_scale = 1.0f; - bool m_display_linear_filtering = false; + HostDisplayPixelFormat m_display_texture_format = HostDisplayPixelFormat::Count; + Alignment m_display_alignment = Alignment::Center; + bool m_display_changed = false; + bool m_display_linear_filtering = false; bool m_display_integer_scaling = false; bool m_display_stretch = false; + bool m_display_duplicate_frames = false; }; diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index bbb65fe1f..8fb82afa4 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -539,6 +539,8 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetIntValue("Display", "LineEndOffset", 0); si.SetStringValue("Display", "AspectRatio", Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)); + si.SetStringValue("Display", "FramePacingMode", + Settings::GetDisplayFramePacingModeName(Settings::DEFAULT_DISPLAY_FRAME_PACING_MODE)); si.SetBoolValue("Display", "Force4_3For24Bit", false); si.SetBoolValue("Display", "LinearFiltering", true); si.SetBoolValue("Display", "IntegerScaling", false); @@ -551,7 +553,6 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("Display", "ShowResolution", false); si.SetBoolValue("Display", "Fullscreen", false); si.SetBoolValue("Display", "VSync", true); - si.SetBoolValue("Display", "DisplayAllFrames", false); si.SetStringValue("Display", "PostProcessChain", ""); si.SetFloatValue("Display", "MaxFPS", 0.0f); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 061477cf9..e38287203 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -227,7 +227,11 @@ void Settings::Load(SettingsInterface& si) display_show_vps = si.GetBoolValue("Display", "ShowVPS", false); display_show_speed = si.GetBoolValue("Display", "ShowSpeed", false); display_show_resolution = si.GetBoolValue("Display", "ShowResolution", false); - display_all_frames = si.GetBoolValue("Display", "DisplayAllFrames", false); + display_frame_pacing_mode = + ParseDisplayFramePacingMode( + si.GetStringValue("Display", "FramePacingMode", GetDisplayFramePacingModeName(DEFAULT_DISPLAY_FRAME_PACING_MODE)) + .c_str()) + .value_or(DEFAULT_DISPLAY_FRAME_PACING_MODE); video_sync_enabled = si.GetBoolValue("Display", "VSync", true); display_post_process_chain = si.GetStringValue("Display", "PostProcessChain", ""); display_max_fps = si.GetFloatValue("Display", "MaxFPS", 0.0f); @@ -387,6 +391,7 @@ void Settings::Save(SettingsInterface& si) const si.SetIntValue("Display", "LineEndOffset", display_line_end_offset); si.SetBoolValue("Display", "Force4_3For24Bit", display_force_4_3_for_24bit); si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio)); + si.SetStringValue("Display", "FramePacingMode", GetDisplayAspectRatioName(display_aspect_ratio)); si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering); si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling); si.SetBoolValue("Display", "Stretch", display_stretch); @@ -396,7 +401,6 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Display", "ShowVPS", display_show_vps); si.SetBoolValue("Display", "ShowSpeed", display_show_speed); si.SetBoolValue("Display", "ShowResolution", display_show_resolution); - si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames); si.SetBoolValue("Display", "VSync", video_sync_enabled); if (display_post_process_chain.empty()) si.DeleteValue("Display", "PostProcessChain"); @@ -778,6 +782,38 @@ float Settings::GetDisplayAspectRatioValue(DisplayAspectRatio ar) return s_display_aspect_ratio_values[static_cast(ar)]; } +static std::array s_display_frame_pacing_mode_names = { + {"DisplayAllFrames", "DisplayAllFramesWhenPossible", "SkipDuplicateFrames", "ClampToMaxFPS"}}; +static std::array s_display_frame_pacing_mode_display_names = { + {TRANSLATABLE("DisplayFramePacingMode", "Display All Frames"), + TRANSLATABLE("DisplayFramePacingMode", "Display All Frames When Possible"), + TRANSLATABLE("DisplayFramePacingMode", "Skip Duplicate Frames"), + TRANSLATABLE("DisplayFramePacingMode", "Clamp To Max FPS")}}; + +std::optional Settings::ParseDisplayFramePacingMode(const char* str) +{ + int index = 0; + for (const char* name : s_display_frame_pacing_mode_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetDisplayFramePacingModeName(DisplayFramePacingMode mode) +{ + return s_display_frame_pacing_mode_names[static_cast(mode)]; +} + +const char* Settings::GetDisplayFramePacingModeDisplayName(DisplayFramePacingMode mode) +{ + return s_display_frame_pacing_mode_display_names[static_cast(mode)]; +} + static std::array s_audio_backend_names = {{ "Null", "Cubeb", diff --git a/src/core/settings.h b/src/core/settings.h index 1531bb11e..f503a243d 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -127,6 +127,7 @@ struct Settings bool gpu_pgxp_depth_buffer = false; DisplayCropMode display_crop_mode = DisplayCropMode::None; DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::Auto; + DisplayFramePacingMode display_frame_pacing_mode = DisplayFramePacingMode::DisplayAllFramesWhenPossible; s16 display_active_start_offset = 0; s16 display_active_end_offset = 0; s8 display_line_start_offset = 0; @@ -142,7 +143,6 @@ struct Settings bool display_show_vps = false; bool display_show_speed = false; bool display_show_resolution = false; - bool display_all_frames = false; bool video_sync_enabled = true; float display_max_fps = 0.0f; float gpu_pgxp_tolerance = -1.0f; @@ -317,6 +317,10 @@ struct Settings static const char* GetDisplayAspectRatioName(DisplayAspectRatio ar); static float GetDisplayAspectRatioValue(DisplayAspectRatio ar); + static std::optional ParseDisplayFramePacingMode(const char* str); + static const char* GetDisplayFramePacingModeName(DisplayFramePacingMode mode); + static const char* GetDisplayFramePacingModeDisplayName(DisplayFramePacingMode mode); + static std::optional ParseAudioBackend(const char* str); static const char* GetAudioBackendName(AudioBackend backend); static const char* GetAudioBackendDisplayName(AudioBackend backend); @@ -364,6 +368,8 @@ struct Settings static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan; static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto; + static constexpr DisplayFramePacingMode DEFAULT_DISPLAY_FRAME_PACING_MODE = + DisplayFramePacingMode::DisplayAllFramesWhenPossible; static constexpr ControllerType DEFAULT_CONTROLLER_1_TYPE = ControllerType::DigitalController; static constexpr ControllerType DEFAULT_CONTROLLER_2_TYPE = ControllerType::None; static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle; diff --git a/src/core/types.h b/src/core/types.h index 30a383aee..f94065936 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -110,6 +110,15 @@ enum class DisplayAspectRatio : u8 Count }; +enum class DisplayFramePacingMode : u8 +{ + DisplayAllFrames, + DisplayAllFramesWhenPossible, + SkipDuplicateFrames, + ClampToFPS, + Count +}; + enum class AudioBackend : u8 { Null, diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp index 9a9312108..d82dfab40 100644 --- a/src/duckstation-qt/displaysettingswidget.cpp +++ b/src/duckstation-qt/displaysettingswidget.cpp @@ -28,6 +28,9 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.displayCropMode, "Display", "CropMode", &Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName, Settings::DEFAULT_DISPLAY_CROP_MODE); + SettingWidgetBinder::BindWidgetToEnumSetting( + m_host_interface, m_ui.framePacingMode, "Display", "FramePacingMode", &Settings::ParseDisplayFramePacingMode, + &Settings::GetDisplayFramePacingModeName, Settings::DEFAULT_DISPLAY_FRAME_PACING_MODE); SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode", &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); @@ -39,8 +42,6 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.internalResolutionScreenshots, "Display", "InternalResolutionScreenshots", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.vsync, "Display", "VSync"); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayAllFrames, "Display", "DisplayAllFrames", - false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuThread, "GPU", "UseThread", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.threadedPresentation, "GPU", "ThreadedPresentation", true); @@ -112,7 +113,7 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW m_ui.vsync, tr("VSync"), tr("Checked"), tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. " "VSync is automatically disabled when it is not possible (e.g. running at non-100% speed).")); - dialog->registerWidgetHelp(m_ui.displayAllFrames, tr("Optimal Frame Pacing"), tr("Unchecked"), + dialog->registerWidgetHelp(m_ui.framePacingMode, tr("Optimal Frame Pacing"), tr("Unchecked"), tr("Enable this option will ensure every frame the console renders is displayed to the " "screen, for optimal frame pacing. If you are having difficulties maintaining full " "speed, or are getting audio glitches, try disabling this option.")); @@ -140,7 +141,7 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW { QCheckBox* cb = new QCheckBox(tr("Use Blit Swap Chain"), m_ui.basicGroupBox); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, cb, "Display", "UseBlitSwapChain", false); - m_ui.basicCheckboxGridLayout->addWidget(cb, 2, 0, 1, 1); + m_ui.basicCheckboxGridLayout->addWidget(cb, 1, 1, 1, 1); dialog->registerWidgetHelp(cb, tr("Use Blit Swap Chain"), tr("Unchecked"), tr("Uses a blit presentation model instead of flipping when using the Direct3D 11 " "renderer. This usually results in slower performance, but may be required for some " @@ -171,6 +172,13 @@ void DisplaySettingsWidget::setupAdditionalUi() qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast(i)))); } + for (u32 i = 0; i < static_cast(DisplayFramePacingMode::Count); i++) + { + m_ui.framePacingMode->addItem( + qApp->translate("DisplayFramePacingMode", + Settings::GetDisplayFramePacingModeDisplayName(static_cast(i)))); + } + for (u32 i = 0; i < static_cast(GPUDownsampleMode::Count); i++) { m_ui.gpuDownsampleMode->addItem( diff --git a/src/duckstation-qt/displaysettingswidget.ui b/src/duckstation-qt/displaysettingswidget.ui index c8385b2e1..8db2b9a1a 100644 --- a/src/duckstation-qt/displaysettingswidget.ui +++ b/src/duckstation-qt/displaysettingswidget.ui @@ -62,7 +62,17 @@ - + + + + Frame Pacing Mode: + + + + + + + @@ -85,13 +95,6 @@ - - - - Optimal Frame Pacing - - - diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index c35e574d0..1953b90c0 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -792,7 +792,8 @@ void CommonHostInterface::UpdateSpeedLimiterState() g_settings.turbo_speed : (m_fast_forward_enabled ? g_settings.fast_forward_speed : g_settings.emulation_speed); m_throttler_enabled = (target_speed != 0.0f); - m_display_all_frames = !m_throttler_enabled || g_settings.display_all_frames; + m_display_all_frames = + (!m_throttler_enabled || (g_settings.display_frame_pacing_mode == DisplayFramePacingMode::DisplayAllFrames)); bool syncing_to_host = false; if (g_settings.sync_to_host_refresh_rate && g_settings.audio_resampling && target_speed == 1.0f && m_display && @@ -815,12 +816,18 @@ void CommonHostInterface::UpdateSpeedLimiterState() !System::IsRunning() || (m_throttler_enabled && g_settings.audio_sync_enabled && !is_non_standard_speed); const bool video_sync_enabled = !System::IsRunning() || (m_throttler_enabled && g_settings.video_sync_enabled && !is_non_standard_speed); - const float max_display_fps = (!System::IsValid() || m_throttler_enabled) ? 0.0f : g_settings.display_max_fps; + const float max_display_fps = (g_settings.display_frame_pacing_mode != DisplayFramePacingMode::ClampToFPS && + (!System::IsValid() || m_throttler_enabled)) ? + 0.0f : + g_settings.display_max_fps; + const bool display_duplicate_frames = + (g_settings.display_frame_pacing_mode != DisplayFramePacingMode::SkipDuplicateFrames) || !System::IsRunning(); Log_InfoPrintf("Target speed: %f%%", target_speed * 100.0f); Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "", (audio_sync_enabled && video_sync_enabled) ? " and video" : (video_sync_enabled ? "video" : "")); - Log_InfoPrintf("Max display fps: %f (%s)", max_display_fps, - m_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed"); + Log_InfoPrintf("Max display fps: %f", max_display_fps); + Log_InfoPrint(m_display_all_frames ? "Displaying all frames" : "Skipping displaying frames when needed"); + Log_InfoPrint(display_duplicate_frames ? "Displaying duplicate frames" : "Skipping displaying duplicate frames"); if (System::IsValid()) { @@ -979,7 +986,10 @@ void CommonHostInterface::OnSystemDestroyed() { // Restore present-all-frames behavior. if (m_display) + { m_display->SetDisplayMaxFPS(0.0f); + m_display->SetDisplayDuplicateFrames(true); + } HostInterface::OnSystemDestroyed(); @@ -2697,7 +2707,7 @@ void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings) g_settings.emulation_speed != old_settings.emulation_speed || g_settings.fast_forward_speed != old_settings.fast_forward_speed || g_settings.display_max_fps != old_settings.display_max_fps || - g_settings.display_all_frames != old_settings.display_all_frames || + g_settings.display_frame_pacing_mode != old_settings.display_frame_pacing_mode || g_settings.audio_resampling != old_settings.audio_resampling || g_settings.sync_to_host_refresh_rate != old_settings.sync_to_host_refresh_rate) { diff --git a/src/frontend-common/d3d11_host_display.cpp b/src/frontend-common/d3d11_host_display.cpp index 12faaf857..5c4f195cd 100644 --- a/src/frontend-common/d3d11_host_display.cpp +++ b/src/frontend-common/d3d11_host_display.cpp @@ -706,6 +706,7 @@ bool D3D11HostDisplay::Render() else m_swap_chain->Present(BoolToUInt32(m_vsync), 0); + m_display_changed = false; return true; } diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index f90575c64..0d1a283de 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -1895,10 +1895,12 @@ void DrawSettingsWindow() break; } - settings_changed |= ToggleButton("Optimal Frame Pacing", - "Ensures every frame generated is displayed for optimal pacing. Disable if " - "you are having speed or sound issues.", - &s_settings_copy.display_all_frames); + settings_changed |= + EnumChoiceButton("Frame Pacing Mode", + "Ensures every frame generated is displayed for optimal pacing. Disable if " + "you are having speed or sound issues.", + &s_settings_copy.display_frame_pacing_mode, &Settings::GetDisplayFramePacingModeDisplayName, + DisplayFramePacingMode::Count); MenuHeading("Screen Display"); diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index 2e2e7f7d1..d7b0a41d6 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -761,6 +761,7 @@ bool OpenGLHostDisplay::Render() RenderSoftwareCursor(); m_gl_context->SwapBuffers(); + m_display_changed = false; return true; } diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp index 8d9529977..8d0cc96f9 100644 --- a/src/frontend-common/vulkan_host_display.cpp +++ b/src/frontend-common/vulkan_host_display.cpp @@ -636,6 +636,7 @@ bool VulkanHostDisplay::Render() m_swap_chain->GetRenderingFinishedSemaphore(), m_swap_chain->GetSwapChain(), m_swap_chain->GetCurrentImageIndex(), !m_swap_chain->IsVSyncEnabled()); g_vulkan_context->MoveToNextCommandBuffer(); + m_display_changed = false; return true; }