System: Add a new throttler/pacer which can catch up on lost time

This can result in worse frame pacing, so if you have a decent machine
you'll probably want to turn on "display all frames" in display
settings.

But, it's sadly needed for Android.
This commit is contained in:
Connor McLaughlin
2021-01-28 20:20:15 +10:00
parent 4e583890ea
commit 4bb3fb48f9
14 changed files with 111 additions and 42 deletions

View File

@ -545,6 +545,7 @@ 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);

View File

@ -196,6 +196,7 @@ 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);
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);
@ -353,6 +354,7 @@ 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_speed);
si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames);
si.SetBoolValue("Display", "VSync", video_sync_enabled);
if (display_post_process_chain.empty())
si.DeleteValue("Display", "PostProcessChain");

View File

@ -138,6 +138,7 @@ 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;

View File

@ -37,6 +37,7 @@
#include <deque>
#include <fstream>
#include <limits>
#include <thread>
Log_SetChannel(System);
SystemBootParameters::SystemBootParameters() = default;
@ -95,10 +96,8 @@ static std::string s_running_game_title;
static float s_throttle_frequency = 60.0f;
static float s_target_speed = 1.0f;
static s32 s_throttle_period = 0;
static u64 s_last_throttle_time = 0;
static Common::Timer s_throttle_timer;
static Common::Timer s_speed_lost_time_timestamp;
static Common::Timer::Value s_frame_period = 0;
static Common::Timer::Value s_next_frame_time = 0;
static float s_average_frame_time_accumulator = 0.0f;
static float s_worst_frame_time_accumulator = 0.0f;
@ -781,10 +780,8 @@ bool Initialize(bool force_software_renderer)
s_internal_frame_number = 1;
s_throttle_frequency = 60.0f;
s_throttle_period = 0;
s_last_throttle_time = 0;
s_throttle_timer.Reset();
s_speed_lost_time_timestamp.Reset();
s_frame_period = 0;
s_next_frame_time = 0;
s_average_frame_time_accumulator = 0.0f;
s_worst_frame_time_accumulator = 0.0f;
@ -1316,6 +1313,8 @@ void RunFrame()
DoRunFrame();
s_next_frame_time += s_frame_period;
if (s_memory_saves_enabled)
DoMemorySaveStates();
}
@ -1339,15 +1338,23 @@ void SetThrottleFrequency(float frequency)
void UpdateThrottlePeriod()
{
s_throttle_period =
static_cast<s32>(1000000000.0 / static_cast<double>(s_throttle_frequency) / static_cast<double>(s_target_speed));
if (s_target_speed > std::numeric_limits<double>::epsilon())
{
const double target_speed = std::max(static_cast<double>(s_target_speed), std::numeric_limits<double>::epsilon());
s_frame_period =
Common::Timer::ConvertSecondsToValue(1.0 / (static_cast<double>(s_throttle_frequency) * target_speed));
}
else
{
s_frame_period = 1;
}
ResetThrottler();
}
void ResetThrottler()
{
s_last_throttle_time = 0;
s_throttle_timer.Reset();
s_next_frame_time = Common::Timer::GetValue();
}
void Throttle()
@ -1355,44 +1362,60 @@ void Throttle()
// Reset the throttler on audio buffer overflow, so we don't end up out of phase.
if (g_host_interface->GetAudioStream()->DidUnderflow() && s_target_speed >= 1.0f)
{
Log_DevPrintf("Audio buffer underflowed, resetting throttler");
Log_VerbosePrintf("Audio buffer underflowed, resetting throttler");
ResetThrottler();
return;
}
// Allow variance of up to 40ms either way.
constexpr s64 MAX_VARIANCE_TIME = INT64_C(40000000);
#ifndef __ANDROID__
static constexpr double MAX_VARIANCE_TIME_NS = 40 * 1000000;
#else
static constexpr double MAX_VARIANCE_TIME_NS = 50 * 1000000;
#endif
// Don't sleep for <1ms or >=period.
constexpr s64 MINIMUM_SLEEP_TIME = INT64_C(1000000);
static constexpr double MINIMUM_SLEEP_TIME_NS = 1 * 1000000;
// Use unsigned for defined overflow/wrap-around.
const u64 time = static_cast<u64>(s_throttle_timer.GetTimeNanoseconds());
const s64 sleep_time = static_cast<s64>(s_last_throttle_time - time);
if (sleep_time < -MAX_VARIANCE_TIME)
const Common::Timer::Value time = Common::Timer::GetValue();
const double sleep_time = (s_next_frame_time >= time) ?
Common::Timer::ConvertValueToNanoseconds(s_next_frame_time - time) :
-Common::Timer::ConvertValueToNanoseconds(time - s_next_frame_time);
if (sleep_time < -MAX_VARIANCE_TIME_NS)
{
#ifndef _DEBUG
// Don't display the slow messages in debug, it'll always be slow...
// Limit how often the messages are displayed.
if (s_speed_lost_time_timestamp.GetTimeSeconds() >= 1.0f)
{
Log_WarningPrintf("System too slow, lost %.2f ms",
static_cast<double>(-sleep_time - MAX_VARIANCE_TIME) / 1000000.0);
s_speed_lost_time_timestamp.Reset();
}
#ifndef _DEBUG
Log_VerbosePrintf("System too slow, lost %.2f ms", (-sleep_time - MAX_VARIANCE_TIME_NS) / 1000000.0);
#endif
ResetThrottler();
}
else if (sleep_time >= MINIMUM_SLEEP_TIME)
else
{
#ifdef __ANDROID__
Common::Timer::HybridSleep(sleep_time);
#else
Common::Timer::NanoSleep(sleep_time);
#endif
Common::Timer::SleepUntil(s_next_frame_time, true);
}
}
void RunFrames()
{
// If we're running more than this in a single loop... we're in for a bad time.
const u32 max_frames_to_run = 2;
u32 frames_run = 0;
Common::Timer::Value value = Common::Timer::GetValue();
while (frames_run < max_frames_to_run)
{
if (value < s_next_frame_time)
break;
RunFrame();
frames_run++;
value = Common::Timer::GetValue();
}
s_last_throttle_time += s_throttle_period;
if (frames_run != 1)
Log_VerbosePrintf("Ran %u frames in a single host frame", frames_run);
}
void UpdatePerformanceCounters()

View File

@ -153,6 +153,7 @@ bool RecreateGPU(GPURenderer renderer, bool update_display = true);
void SingleStepCPU();
void RunFrame();
void RunFrames();
/// Sets target emulation speed.
float GetTargetSpeed();

View File

@ -36,6 +36,8 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayIntegerScaling, "Display",
"IntegerScaling");
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);
@ -96,6 +98,10 @@ 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("Display All Frames"), 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."));
dialog->registerWidgetHelp(m_ui.threadedPresentation, tr("Threaded Presentation"), tr("Checked"),
tr("Presents frames on a background thread when fast forwarding or vsync is disabled. "
"This can measurably improve performance in the Vulkan renderer."));
@ -120,7 +126,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, 1, 1, 1, 1);
m_ui.basicCheckboxGridLayout->addWidget(cb, 2, 0, 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 "

View File

@ -85,6 +85,13 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="displayAllFrames">
<property name="text">
<string>Display All Frames</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -125,7 +132,7 @@
</item>
<item row="2" column="1">
<widget class="QComboBox" name="gpuDownsampleMode"/>
</item>
</item>
<item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">

View File

@ -1415,7 +1415,11 @@ void QtHostInterface::threadEntryPoint()
continue;
}
System::RunFrame();
if (m_display_all_frames)
System::RunFrame();
else
System::RunFrames();
UpdateControllerRumble();
if (m_frame_step_request)
{

View File

@ -1856,7 +1856,11 @@ void SDLHostInterface::Run()
if (System::IsRunning())
{
System::RunFrame();
if (m_display_all_frames)
System::RunFrame();
else
System::RunFrames();
UpdateControllerRumble();
if (m_frame_step_request)
{

View File

@ -614,10 +614,11 @@ 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;
bool syncing_to_host = false;
if (g_settings.sync_to_host_refresh_rate && g_settings.audio_resampling && target_speed == 1.0f &&
g_settings.video_sync_enabled && m_display && System::IsRunning())
if (g_settings.sync_to_host_refresh_rate && g_settings.audio_resampling && target_speed == 1.0f && m_display &&
System::IsRunning())
{
float host_refresh_rate;
if (m_display->GetHostRefreshRate(&host_refresh_rate))
@ -640,7 +641,8 @@ void CommonHostInterface::UpdateSpeedLimiterState()
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", max_display_fps);
Log_InfoPrintf("Max display fps: %f (%s)", max_display_fps,
m_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed");
if (System::IsValid())
{
@ -656,6 +658,7 @@ void CommonHostInterface::UpdateSpeedLimiterState()
Log_InfoPrintf("Audio input sample rate: %u hz", input_sample_rate);
m_audio_stream->SetInputSampleRate(input_sample_rate);
m_audio_stream->SetWaitForBufferFill(!m_display_all_frames);
m_audio_stream->SetOutputVolume(GetAudioOutputVolume());
m_audio_stream->SetSync(audio_sync_enabled);
if (audio_sync_enabled)
@ -671,8 +674,12 @@ void CommonHostInterface::UpdateSpeedLimiterState()
if (g_settings.increase_timer_resolution)
SetTimerResolutionIncreased(m_throttler_enabled);
if (syncing_to_host)
// When syncing to host and using vsync, we don't need to sleep.
if (syncing_to_host && video_sync_enabled && m_display_all_frames)
{
Log_InfoPrintf("Using host vsync for throttling.");
m_throttler_enabled = false;
}
}
void CommonHostInterface::RecreateSystem()
@ -2305,6 +2312,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.audio_resampling != old_settings.audio_resampling ||
g_settings.sync_to_host_refresh_rate != old_settings.sync_to_host_refresh_rate)
{

View File

@ -355,6 +355,7 @@ protected:
bool m_turbo_enabled = false;
bool m_timer_resolution_increased = false;
bool m_throttler_enabled = true;
bool m_display_all_frames = true;
private:
void InitializeUserDirectory();