diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 5e7f7d456..c1b4013e7 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -3454,6 +3454,14 @@ void FullscreenUI::DrawEmulationSettingsPage() bsi, FSUI_ICONSTR(ICON_FA_STOPWATCH_20, "Reduce Input Latency"), FSUI_CSTR("Reduces input latency by delaying the start of frame until closer to the presentation time."), "Display", "PreFrameSleep", false, optimal_frame_pacing_active); + + DrawToggleSetting( + bsi, FSUI_ICONSTR(ICON_FA_CHARGING_STATION, "Skip Duplicate Frame Display"), + FSUI_CSTR("Skips the presentation/display of frames that are not unique. Can result in worse frame pacing."), + "Display", "SkipPresentingDuplicateFrames", false, + !(GetEffectiveBoolSetting(bsi, "Display", "VSync", false) && + GetEffectiveBoolSetting(bsi, "Main", "SyncToHostRefreshRate", false))); + const bool pre_frame_sleep_active = (optimal_frame_pacing_active && GetEffectiveBoolSetting(bsi, "Display", "PreFrameSleep", false)); DrawFloatRangeSetting( @@ -7652,6 +7660,8 @@ TRANSLATE_NOOP("FullscreenUI", "Shows the number of frames (or v-syncs) displaye TRANSLATE_NOOP("FullscreenUI", "Simulates the CPU's instruction cache in the recompiler. Can help with games running too fast."); TRANSLATE_NOOP("FullscreenUI", "Simulates the region check present in original, unmodified consoles."); TRANSLATE_NOOP("FullscreenUI", "Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements."); +TRANSLATE_NOOP("FullscreenUI", "Skip Duplicate Frame Display"); +TRANSLATE_NOOP("FullscreenUI", "Skips the presentation/display of frames that are not unique. Can result in worse frame pacing."); TRANSLATE_NOOP("FullscreenUI", "Slow Boot"); TRANSLATE_NOOP("FullscreenUI", "Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. Only applies to the hardware renderers."); TRANSLATE_NOOP("FullscreenUI", "Smooths out the blockiness of magnified textures on 3D objects."); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 5db63718d..5d563133c 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -271,6 +271,7 @@ void Settings::Load(SettingsInterface& si) display_pre_frame_sleep = si.GetBoolValue("Display", "PreFrameSleep", false); display_pre_frame_sleep_buffer = si.GetFloatValue("Display", "PreFrameSleepBuffer", DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER); + display_skip_presenting_duplicate_frames = si.GetBoolValue("Display", "SkipPresentingDuplicateFrames", false); display_vsync = si.GetBoolValue("Display", "VSync", false); display_force_4_3_for_24bit = si.GetBoolValue("Display", "Force4_3For24Bit", false); display_active_start_offset = static_cast(si.GetIntValue("Display", "ActiveStartOffset", 0)); @@ -519,6 +520,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling)); si.SetBoolValue("Display", "OptimalFramePacing", display_optimal_frame_pacing); si.SetBoolValue("Display", "PreFrameSleep", display_pre_frame_sleep); + si.SetBoolValue("Display", "SkipPresentingDuplicateFrames", display_skip_presenting_duplicate_frames); si.SetFloatValue("Display", "PreFrameSleepBuffer", display_pre_frame_sleep_buffer); si.SetBoolValue("Display", "VSync", display_vsync); si.SetStringValue("Display", "ExclusiveFullscreenControl", diff --git a/src/core/settings.h b/src/core/settings.h index 5ab9c5458..80203ba88 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -148,6 +148,7 @@ struct Settings s8 display_line_end_offset = 0; bool display_optimal_frame_pacing : 1 = false; bool display_pre_frame_sleep : 1 = false; + bool display_skip_presenting_duplicate_frames : 1 = false; bool display_vsync : 1 = false; bool display_force_4_3_for_24bit : 1 = false; bool gpu_24bit_chroma_smoothing : 1 = false; diff --git a/src/core/system.cpp b/src/core/system.cpp index 6959b8091..33d684bca 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -150,6 +150,7 @@ static void PollDiscordPresence(); static constexpr const float PERFORMANCE_COUNTER_UPDATE_INTERVAL = 1.0f; static constexpr const char FALLBACK_EXE_NAME[] = "PSX.EXE"; +static constexpr u32 MAX_SKIPPED_FRAME_COUNT = 2; // 20fps minimum static std::unique_ptr s_game_settings_interface; static std::unique_ptr s_input_settings_interface; @@ -183,7 +184,10 @@ static bool s_throttler_enabled = false; static bool s_optimal_frame_pacing = false; static bool s_pre_frame_sleep = false; static bool s_syncing_to_host = false; -static bool s_last_frame_skipped = false; +static bool s_syncing_to_host_with_vsync = false; +static bool s_skip_presenting_duplicate_frames = false; +static u32 s_skipped_frame_count = 0; +static u32 s_last_presented_internal_frame_number = 0; static float s_throttle_frequency = 0.0f; static float s_target_speed = 0.0f; @@ -1963,32 +1967,43 @@ void System::FrameDone() AccumulatePreFrameSleepTime(); // explicit present (frame pacing) - if (current_time < s_next_frame_time || s_syncing_to_host || s_optimal_frame_pacing || s_last_frame_skipped) + const bool is_unique_frame = (s_last_presented_internal_frame_number != s_internal_frame_number); + s_last_presented_internal_frame_number = s_internal_frame_number; + + const bool skip_this_frame = + (((s_skip_presenting_duplicate_frames && !is_unique_frame) || + (!s_optimal_frame_pacing && (current_time > s_next_frame_time || g_gpu_device->ShouldSkipDisplayingFrame()))) && + !s_syncing_to_host_with_vsync && (s_skipped_frame_count < MAX_SKIPPED_FRAME_COUNT) && !IsExecutionInterrupted()); + if (!skip_this_frame) { + s_skipped_frame_count = 0; + const bool throttle_before_present = (s_optimal_frame_pacing && s_throttler_enabled && !IsExecutionInterrupted()); const bool explicit_present = (throttle_before_present && g_gpu_device->GetFeatures().explicit_present); if (explicit_present) { - s_last_frame_skipped = !PresentDisplay(!throttle_before_present, true); + const bool do_present = PresentDisplay(false, true); Throttle(current_time); - g_gpu_device->SubmitPresent(); + if (do_present) + g_gpu_device->SubmitPresent(); } else { if (throttle_before_present) Throttle(current_time); - s_last_frame_skipped = !PresentDisplay(!throttle_before_present, false); + PresentDisplay(false, false); if (!throttle_before_present && s_throttler_enabled && !IsExecutionInterrupted()) Throttle(current_time); } } - else if (current_time >= s_next_frame_time) + else { - Log_DebugPrintf("Skipping displaying frame"); - s_last_frame_skipped = true; - Throttle(current_time); + Log_DebugPrint("Skipping displaying frame"); + s_skipped_frame_count++; + if (s_throttler_enabled) + Throttle(current_time); } // pre-frame sleep (input lag reduction) @@ -2810,15 +2825,20 @@ void System::FormatLatencyStats(SmallStringBase& str) void System::UpdateSpeedLimiterState() { + DebugAssert(IsValid()); + const float old_target_speed = s_target_speed; s_target_speed = s_turbo_enabled ? g_settings.turbo_speed : (s_fast_forward_enabled ? g_settings.fast_forward_speed : g_settings.emulation_speed); s_throttler_enabled = (s_target_speed != 0.0f); - s_optimal_frame_pacing = s_throttler_enabled && g_settings.display_optimal_frame_pacing; - s_pre_frame_sleep = s_throttler_enabled && g_settings.display_pre_frame_sleep; + s_optimal_frame_pacing = (s_throttler_enabled && g_settings.display_optimal_frame_pacing) || + g_gpu_device->GetWindowInfo().IsSurfaceless(); // surfaceless check for regtest + s_skip_presenting_duplicate_frames = s_throttler_enabled && g_settings.display_skip_presenting_duplicate_frames; + s_pre_frame_sleep = s_optimal_frame_pacing && g_settings.display_pre_frame_sleep; s_syncing_to_host = false; + s_syncing_to_host_with_vsync = false; if (g_settings.sync_to_host_refresh_rate && (g_settings.audio_stream_parameters.stretch_mode != AudioStretchMode::Off) && s_target_speed == 1.0f && IsValid()) { @@ -2835,7 +2855,8 @@ void System::UpdateSpeedLimiterState() } // When syncing to host and using vsync, we don't need to sleep. - if (s_syncing_to_host && IsVSyncEffectivelyEnabled()) + s_syncing_to_host_with_vsync = (s_syncing_to_host && IsVSyncEffectivelyEnabled()); + if (s_syncing_to_host_with_vsync) { Log_InfoPrintf("Using host vsync for throttling."); s_throttler_enabled = false; @@ -2843,25 +2864,21 @@ void System::UpdateSpeedLimiterState() Log_VerbosePrintf("Target speed: %f%%", s_target_speed * 100.0f); - if (IsValid()) - { - // Update audio output. - AudioStream* stream = SPU::GetOutputStream(); - stream->SetOutputVolume(GetAudioOutputVolume()); + // Update audio output. + AudioStream* stream = SPU::GetOutputStream(); + stream->SetOutputVolume(GetAudioOutputVolume()); - // Adjust nominal rate when resampling, or syncing to host. - const bool rate_adjust = - (s_syncing_to_host || g_settings.audio_stream_parameters.stretch_mode == AudioStretchMode::Resample) && - s_target_speed > 0.0f; - stream->SetNominalRate(rate_adjust ? s_target_speed : 1.0f); + // Adjust nominal rate when resampling, or syncing to host. + const bool rate_adjust = + (s_syncing_to_host || g_settings.audio_stream_parameters.stretch_mode == AudioStretchMode::Resample) && + s_target_speed > 0.0f; + stream->SetNominalRate(rate_adjust ? s_target_speed : 1.0f); - if (old_target_speed < s_target_speed) - stream->UpdateTargetTempo(s_target_speed); - - UpdateThrottlePeriod(); - ResetThrottler(); - } + if (old_target_speed < s_target_speed) + stream->UpdateTargetTempo(s_target_speed); + UpdateThrottlePeriod(); + ResetThrottler(); UpdateDisplaySync(); if (g_settings.increase_timer_resolution) @@ -3959,6 +3976,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.fast_forward_speed != old_settings.fast_forward_speed || g_settings.display_max_fps != old_settings.display_max_fps || g_settings.display_optimal_frame_pacing != old_settings.display_optimal_frame_pacing || + g_settings.display_skip_presenting_duplicate_frames != old_settings.display_skip_presenting_duplicate_frames || g_settings.display_pre_frame_sleep != old_settings.display_pre_frame_sleep || g_settings.display_pre_frame_sleep_buffer != old_settings.display_pre_frame_sleep_buffer || g_settings.display_vsync != old_settings.display_vsync || @@ -5082,10 +5100,8 @@ void System::HostDisplayResized() g_gpu->UpdateResolutionScale(); } -bool System::PresentDisplay(bool allow_skip_present, bool explicit_present) +bool System::PresentDisplay(bool skip_present, bool explicit_present) { - const bool skip_present = allow_skip_present && g_gpu_device->ShouldSkipDisplayingFrame(); - Host::BeginPresentFrame(); // acquire for IO.MousePos. diff --git a/src/core/system.h b/src/core/system.h index cc25ffa36..d1b436f4e 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -478,7 +478,7 @@ void RequestDisplaySize(float scale = 0.0f); void HostDisplayResized(); /// Renders the display. -bool PresentDisplay(bool allow_skip_present, bool explicit_present); +bool PresentDisplay(bool skip_present, bool explicit_present); void InvalidateDisplay(); ////////////////////////////////////////////////////////////////////////// diff --git a/src/duckstation-qt/emulationsettingswidget.cpp b/src/duckstation-qt/emulationsettingswidget.cpp index 7c9611aa4..8d98e412f 100644 --- a/src/duckstation-qt/emulationsettingswidget.cpp +++ b/src/duckstation-qt/emulationsettingswidget.cpp @@ -20,6 +20,8 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.syncToHostRefreshRate, "Main", "SyncToHostRefreshRate", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.optimalFramePacing, "Display", "OptimalFramePacing", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preFrameSleep, "Display", "PreFrameSleep", false); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.skipPresentingDuplicateFrames, "Display", + "SkipPresentingDuplicateFrames", false); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.preFrameSleepBuffer, "Display", "PreFrameSleepBuffer", Settings::DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.rewindEnable, "Main", "RewindEnable", false); @@ -71,7 +73,9 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget } connect(m_ui.turboSpeed, QOverload::of(&QComboBox::currentIndexChanged), this, &EmulationSettingsWidget::onTurboSpeedIndexChanged); - connect(m_ui.vsync, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::onVSyncChanged); + connect(m_ui.vsync, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::updateSkipDuplicateFramesEnabled); + connect(m_ui.syncToHostRefreshRate, &QCheckBox::checkStateChanged, this, + &EmulationSettingsWidget::updateSkipDuplicateFramesEnabled); connect(m_ui.optimalFramePacing, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::onOptimalFramePacingChanged); connect(m_ui.preFrameSleep, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::onPreFrameSleepChanged); @@ -121,6 +125,11 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget tr("Specifies the amount of buffer time added, which reduces the additional sleep time " "introduced. Higher values increase input latency, but decrease the risk of overrun, " "or missed frames. Lower values require faster hardware.")); + dialog->registerWidgetHelp( + m_ui.skipPresentingDuplicateFrames, tr("Skip Duplicate Frame Display"), tr("Unchecked"), + tr("Skips the presentation/display of frames that are not unique. Can be combined with driver-level frame " + "generation to increase perceptible frame rate. Can result in worse frame pacing, and is not compatible with " + "syncing to host refresh.")); dialog->registerWidgetHelp( m_ui.rewindEnable, tr("Rewinding"), tr("Unchecked"), tr("Enable Rewinding: Saves state periodically so you can rewind any mistakes while playing.
" @@ -133,8 +142,8 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget tr( "Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements.")); - onVSyncChanged(); onOptimalFramePacingChanged(); + updateSkipDuplicateFramesEnabled(); updateRewind(); } @@ -200,12 +209,6 @@ void EmulationSettingsWidget::onTurboSpeedIndexChanged(int index) m_dialog->setFloatSettingValue("Main", "TurboSpeed", okay ? value : 0.0f); } -void EmulationSettingsWidget::onVSyncChanged() -{ - const bool vsync = m_dialog->getEffectiveBoolValue("Display", "VSync", false); - m_ui.syncToHostRefreshRate->setEnabled(vsync); -} - void EmulationSettingsWidget::onOptimalFramePacingChanged() { const bool optimal_frame_pacing_enabled = m_dialog->getEffectiveBoolValue("Display", "OptimalFramePacing", false); @@ -221,6 +224,13 @@ void EmulationSettingsWidget::onPreFrameSleepChanged() m_ui.preFrameSleepBufferLabel->setVisible(show_buffer_size); } +void EmulationSettingsWidget::updateSkipDuplicateFramesEnabled() +{ + const bool vsync = m_dialog->getEffectiveBoolValue("Display", "VSync", false); + const bool sync_to_host = m_dialog->getEffectiveBoolValue("Main", "SyncToHostRefreshRate", false) && vsync; + m_ui.skipPresentingDuplicateFrames->setEnabled(!sync_to_host); +} + void EmulationSettingsWidget::updateRewind() { const bool rewind_enabled = m_dialog->getEffectiveBoolValue("Main", "RewindEnable", false); diff --git a/src/duckstation-qt/emulationsettingswidget.h b/src/duckstation-qt/emulationsettingswidget.h index d67ae6a15..2eab3df4a 100644 --- a/src/duckstation-qt/emulationsettingswidget.h +++ b/src/duckstation-qt/emulationsettingswidget.h @@ -21,9 +21,9 @@ private Q_SLOTS: void onEmulationSpeedIndexChanged(int index); void onFastForwardSpeedIndexChanged(int index); void onTurboSpeedIndexChanged(int index); - void onVSyncChanged(); void onOptimalFramePacingChanged(); void onPreFrameSleepChanged(); + void updateSkipDuplicateFramesEnabled(); void updateRewind(); private: diff --git a/src/duckstation-qt/emulationsettingswidget.ui b/src/duckstation-qt/emulationsettingswidget.ui index 28cd56a91..37a4d3ebe 100644 --- a/src/duckstation-qt/emulationsettingswidget.ui +++ b/src/duckstation-qt/emulationsettingswidget.ui @@ -68,10 +68,10 @@ Latency Control - - + + - Vertical Sync (VSync) + Reduce Input Latency @@ -82,6 +82,13 @@ + + + + Vertical Sync (VSync) + + + @@ -89,23 +96,23 @@ - - + + - Reduce Input Latency + Skip Duplicate Frame Display - - - + + + Frame Time Buffer: - + Milliseconds