From b57f1d4a60e9503757b41b513c8e0599e594c65d Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 16 Nov 2019 20:12:03 +1000 Subject: [PATCH] HostInterface: Implement non-vsync based speed throttler Needed for PAL games. --- src/core/host_interface.cpp | 63 ++++++++++++++++++++++---- src/core/host_interface.h | 20 +++++++- src/duckstation/sdl_host_interface.cpp | 23 +++++----- 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 6af5c56c6..c3a211034 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -1,6 +1,7 @@ #include "host_interface.h" #include "YBaseLib/ByteStream.h" #include "YBaseLib/Log.h" +#include "YBaseLib/Timer.h" #include "bios.h" #include "common/audio_stream.h" #include "host_display.h" @@ -8,9 +9,16 @@ #include Log_SetChannel(HostInterface); +#ifdef _WIN32 +#include "YBaseLib/Windows/WindowsHeaders.h" +#else +#include +#endif + HostInterface::HostInterface() { m_settings.SetDefaults(); + m_last_throttle_time = Y_TimerGetValue(); } HostInterface::~HostInterface() = default; @@ -22,7 +30,7 @@ bool HostInterface::CreateSystem() // Pull in any invalid settings which have been reset. m_settings = m_system->GetSettings(); m_paused = true; - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); return true; } @@ -33,7 +41,7 @@ bool HostInterface::BootSystem(const char* filename, const char* state_filename) m_paused = m_settings.start_paused; ConnectControllers(); - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); return true; } @@ -41,7 +49,7 @@ void HostInterface::DestroySystem() { m_system.reset(); m_paused = false; - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); } void HostInterface::ReportError(const char* message) @@ -110,6 +118,44 @@ std::optional> HostInterface::GetBIOSImage(ConsoleRegion region) void HostInterface::ConnectControllers() {} +void HostInterface::Throttle() +{ + // Allow variance of up to 40ms either way. + constexpr s64 MAX_VARIANCE_TIME = INT64_C(40000000); + + // Don't sleep for <1ms or >=period. + constexpr s64 MINIMUM_SLEEP_TIME = INT64_C(1000000); + + // Use unsigned for defined overflow/wrap-around. + const u64 time = static_cast(m_throttle_timer.GetTimeNanoseconds()); + const s64 sleep_time = static_cast(m_last_throttle_time - time); + if (std::abs(sleep_time) >= MAX_VARIANCE_TIME) + { +#ifdef Y_BUILD_CONFIG_RELEASE + // Don't display the slow messages in debug, it'll always be slow... + // Limit how often the messages are displayed. + if (m_speed_lost_time_timestamp.GetTimeSeconds() >= 1.0f) + { + Log_WarningPrintf("System too %s, lost %.2f ms", sleep_time < 0 ? "slow" : "fast", + static_cast(std::abs(sleep_time) - MAX_VARIANCE_TIME) / 1000000.0); + m_speed_lost_time_timestamp.Reset(); + } +#endif + m_last_throttle_time = time - MAX_VARIANCE_TIME; + } + else if (sleep_time >= MINIMUM_SLEEP_TIME && sleep_time <= m_throttle_period) + { +#ifdef WIN32 + Sleep(static_cast(sleep_time / 1000000)); +#else + const struct timespec ts = {0, static_cast(sleep_time)}; + nanosleep(&ts, nullptr); +#endif + } + + m_last_throttle_time += m_throttle_period; +} + bool HostInterface::LoadState(const char* filename) { ByteStream* stream; @@ -156,13 +202,14 @@ bool HostInterface::SaveState(const char* filename) return result; } -void HostInterface::UpdateAudioVisualSync() +void HostInterface::UpdateSpeedLimiterState() { - const bool speed_limiter_enabled = m_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; - const bool audio_sync_enabled = speed_limiter_enabled; - const bool vsync_enabled = !m_system || m_paused || (speed_limiter_enabled && m_settings.gpu_vsync); + m_speed_limiter_enabled = m_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; + + const bool audio_sync_enabled = m_speed_limiter_enabled; + const bool vsync_enabled = !m_system || m_paused || (m_speed_limiter_enabled && m_settings.gpu_vsync); Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "", - (speed_limiter_enabled && vsync_enabled) ? " and video" : ""); + (m_speed_limiter_enabled && vsync_enabled) ? " and video" : ""); m_audio_stream->SetSync(false); m_display->SetVSync(vsync_enabled); diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 8bf69184b..0783ca91f 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -1,6 +1,8 @@ #pragma once -#include "types.h" +#include "YBaseLib/Timer.h" #include "settings.h" +#include "types.h" +#include #include #include #include @@ -25,6 +27,9 @@ public: /// Returns a settings object which can be modified. Settings& GetSettings() { return m_settings; } + /// Adjusts the throttle frequency, i.e. how many times we should sleep per second. + void SetThrottleFrequency(double frequency) { m_throttle_period = static_cast(1000000000.0 / frequency); } + bool CreateSystem(); bool BootSystem(const char* filename, const char* state_filename); void DestroySystem(); @@ -42,16 +47,27 @@ public: bool SaveState(const char* filename); protected: + using ThrottleClock = std::chrono::steady_clock; + /// Connects controllers. TODO: Clean this up later... virtual void ConnectControllers(); - void UpdateAudioVisualSync(); + /// Throttles the system, i.e. sleeps until it's time to execute the next frame. + void Throttle(); + + void UpdateSpeedLimiterState(); std::unique_ptr m_display; std::unique_ptr m_audio_stream; std::unique_ptr m_system; Settings m_settings; + u64 m_last_throttle_time = 0; + s64 m_throttle_period = INT64_C(1000000000) / 60; + Timer m_throttle_timer; + Timer m_speed_lost_time_timestamp; + bool m_paused = false; bool m_speed_limiter_temp_disabled = false; + bool m_speed_limiter_enabled = false; }; diff --git a/src/duckstation/sdl_host_interface.cpp b/src/duckstation/sdl_host_interface.cpp index 5096d6a33..af7ad1246 100644 --- a/src/duckstation/sdl_host_interface.cpp +++ b/src/duckstation/sdl_host_interface.cpp @@ -197,6 +197,7 @@ std::unique_ptr SDLHostInterface::Create(const char* filename ImGui::NewFrame(); + intf->UpdateSpeedLimiterState(); intf->OpenGameControllers(); const bool boot = (filename != nullptr || exp1_filename != nullptr || save_state_filename != nullptr); @@ -209,8 +210,6 @@ std::unique_ptr SDLHostInterface::Create(const char* filename intf->LoadState(save_state_filename); } - intf->UpdateAudioVisualSync(); - intf->UpdateFullscreen(); return intf; @@ -497,7 +496,7 @@ void SDLHostInterface::HandleSDLKeyEvent(const SDL_Event* event) if (!repeat) { m_speed_limiter_temp_disabled = pressed; - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); } } break; @@ -521,7 +520,7 @@ void SDLHostInterface::HandleSDLKeyEvent(const SDL_Event* event) if (pressed && !repeat && m_system) { m_settings.speed_limiter_enabled = !m_settings.speed_limiter_enabled; - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); AddOSDMessage(m_system->GetSettings().speed_limiter_enabled ? "Speed limiter enabled." : "Speed limiter disabled."); } @@ -720,7 +719,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() if (ImGui::MenuItem("Enable Speed Limiter", nullptr, &m_settings.speed_limiter_enabled)) { settings_changed = true; - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); } ImGui::Separator(); @@ -749,7 +748,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() if (ImGui::MenuItem("VSync", nullptr, &m_settings.gpu_vsync)) { settings_changed = true; - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); } ImGui::Separator(); @@ -1008,7 +1007,7 @@ void SDLHostInterface::DrawSettingsWindow() if (ImGui::Checkbox("VSync", &m_settings.gpu_vsync)) { - UpdateAudioVisualSync(); + UpdateSpeedLimiterState(); settings_changed = true; } if (ImGui::Checkbox("Linear Filtering", &m_settings.display_linear_filtering)) @@ -1398,7 +1397,12 @@ void SDLHostInterface::Run() ImGui::NewFrame(); if (m_system) + { m_system->GetGPU()->RestoreGraphicsAPIState(); + + if (m_speed_limiter_enabled) + Throttle(); + } } if (m_system) @@ -1426,10 +1430,7 @@ void SDLHostInterface::Run() if (m_system) { if (!SaveState(RESUME_SAVESTATE_FILENAME)) - { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Save state failed", - "Saving state failed, you will not be able to resume this session.", m_window); - } + ReportError("Saving state failed, you will not be able to resume this session."); DestroySystem(); }