mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-10 17:55:47 -04:00
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:
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -153,6 +153,7 @@ bool RecreateGPU(GPURenderer renderer, bool update_display = true);
|
||||
|
||||
void SingleStepCPU();
|
||||
void RunFrame();
|
||||
void RunFrames();
|
||||
|
||||
/// Sets target emulation speed.
|
||||
float GetTargetSpeed();
|
||||
|
@ -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 "
|
||||
|
@ -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">
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
Reference in New Issue
Block a user