diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 8bd48aff74..e28c1e0f46 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -103,11 +103,12 @@ static bool s_hardware_initialized = false; static bool s_is_started = false; static Common::Flag s_is_booting; static std::thread s_emu_thread; -static StateChangedCallbackFunc s_on_state_changed_callback; +static std::vector s_on_state_changed_callbacks; static std::thread s_cpu_thread; static bool s_request_refresh_info = false; static bool s_is_throttler_temp_disabled = false; +static std::atomic s_last_actual_emulation_speed{1.0}; static bool s_frame_step = false; static std::atomic s_stop_frame_step; @@ -138,6 +139,11 @@ void SetIsThrottlerTempDisabled(bool disable) s_is_throttler_temp_disabled = disable; } +double GetActualEmulationSpeed() +{ + return s_last_actual_emulation_speed; +} + void FrameUpdateOnCPUThread() { if (NetPlay::IsNetPlayRunning()) @@ -269,9 +275,9 @@ void Stop() // - Hammertime! s_is_stopping = true; - // Notify state changed callback - if (s_on_state_changed_callback) - s_on_state_changed_callback(State::Stopping); + s_timer.Stop(); + + CallOnStateChangedCallbacks(State::Stopping); // Dump left over jobs HostDispatchJobs(); @@ -293,6 +299,8 @@ void Stop() // - Hammertime! g_video_backend->Video_ExitLoop(); } + + s_last_actual_emulation_speed = 1.0; } void DeclareAsCPUThread() @@ -424,16 +432,14 @@ static void FifoPlayerThread(const std::optional& savestate_path, static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi) { const SConfig& core_parameter = SConfig::GetInstance(); - if (s_on_state_changed_callback) - s_on_state_changed_callback(State::Starting); + CallOnStateChangedCallbacks(State::Starting); Common::ScopeGuard flag_guard{[] { s_is_booting.Clear(); s_is_started = false; s_is_stopping = false; s_wants_determinism = false; - if (s_on_state_changed_callback) - s_on_state_changed_callback(State::Uninitialized); + CallOnStateChangedCallbacks(State::Uninitialized); INFO_LOG_FMT(CONSOLE, "Stop\t\t---- Shutdown complete ----"); }}; @@ -661,18 +667,27 @@ void SetState(State state) CPU::EnableStepping(true); // Break Wiimote::Pause(); ResetRumble(); + s_timer.Update(); break; case State::Running: CPU::EnableStepping(false); Wiimote::Resume(); + if (!s_timer.IsRunning()) + { + s_timer.Start(); + } + else + { + // Add time difference from the last pause + s_timer.AddTimeDifference(); + } break; default: PanicAlertFmt("Invalid state"); break; } - if (s_on_state_changed_callback) - s_on_state_changed_callback(GetState()); + CallOnStateChangedCallbacks(GetState()); } State GetState() @@ -855,13 +870,13 @@ void RunOnCPUThread(std::function function, bool wait_for_completion) void VideoThrottle() { // Update info per second - u32 ElapseTime = (u32)s_timer.GetTimeDifference(); + u32 ElapseTime = (u32)s_timer.GetTimeElapsed(); if ((ElapseTime >= 1000 && s_drawn_video.load() > 0) || s_request_refresh_info) { - UpdateTitle(); + s_timer.Start(); + + UpdateTitle(ElapseTime); - // Reset counter - s_timer.Update(); s_drawn_frame.store(0); s_drawn_video.store(0); } @@ -873,8 +888,10 @@ void VideoThrottle() // Called from Renderer::Swap (GPU thread) when a new (non-duplicate) // frame is presented to the host screen -void Callback_FramePresented() +void Callback_FramePresented(double actual_emulation_speed) { + s_last_actual_emulation_speed = actual_emulation_speed; + s_drawn_frame++; s_stop_frame_step.store(true); } @@ -895,15 +912,13 @@ void Callback_NewField() { s_frame_step = false; CPU::Break(); - if (s_on_state_changed_callback) - s_on_state_changed_callback(Core::GetState()); + CallOnStateChangedCallbacks(Core::GetState()); } } } -void UpdateTitle() +void UpdateTitle(u32 ElapseTime) { - u32 ElapseTime = (u32)s_timer.GetTimeDifference(); s_request_refresh_info = false; SConfig& _CoreParameter = SConfig::GetInstance(); @@ -993,9 +1008,38 @@ void Shutdown() HostDispatchJobs(); } -void SetOnStateChangedCallback(StateChangedCallbackFunc callback) +int AddOnStateChangedCallback(StateChangedCallbackFunc callback) { - s_on_state_changed_callback = std::move(callback); + for (size_t i = 0; i < s_on_state_changed_callbacks.size(); ++i) + { + if (!s_on_state_changed_callbacks[i]) + { + s_on_state_changed_callbacks[i] = std::move(callback); + return int(i); + } + } + s_on_state_changed_callbacks.emplace_back(std::move(callback)); + return int(s_on_state_changed_callbacks.size()) - 1; +} + +bool RemoveOnStateChangedCallback(int* handle) +{ + if (handle && *handle >= 0 && s_on_state_changed_callbacks.size() > *handle) + { + s_on_state_changed_callbacks[*handle] = StateChangedCallbackFunc(); + *handle = -1; + return true; + } + return false; +} + +void CallOnStateChangedCallbacks(Core::State state) +{ + for (const StateChangedCallbackFunc& on_state_changed_callback : s_on_state_changed_callbacks) + { + if (on_state_changed_callback) + on_state_changed_callback(state); + } } void UpdateWantDeterminism(bool initial) diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index 5ea1948a9c..d21719f218 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -25,7 +25,10 @@ namespace Core bool GetIsThrottlerTempDisabled(); void SetIsThrottlerTempDisabled(bool disable); -void Callback_FramePresented(); +// Returns the latest emulation speed (1 is full speed) (swings a lot) +double GetActualEmulationSpeed(); + +void Callback_FramePresented(double actual_emulation_speed = 1.0); void Callback_NewField(); enum class State @@ -123,7 +126,7 @@ void OnFrameEnd(); void VideoThrottle(); void RequestRefreshInfo(); -void UpdateTitle(); +void UpdateTitle(u32 ElapseTime); // Run a function as the CPU thread. // @@ -140,7 +143,11 @@ void RunOnCPUThread(std::function function, bool wait_for_completion); // for calling back into UI code without introducing a dependency on it in core using StateChangedCallbackFunc = std::function; -void SetOnStateChangedCallback(StateChangedCallbackFunc callback); +// Returns a handle +int AddOnStateChangedCallback(StateChangedCallbackFunc callback); +// Also invalidates the handle +bool RemoveOnStateChangedCallback(int* handle); +void CallOnStateChangedCallbacks(Core::State state); // Run on the Host thread when the factors change. [NOT THREADSAFE] void UpdateWantDeterminism(bool initial = false); diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 0bf4df23b8..eaef16c444 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -216,7 +216,7 @@ int main(int argc, char* argv[]) return 1; } - Core::SetOnStateChangedCallback([](Core::State state) { + Core::AddOnStateChangedCallback([](Core::State state) { if (state == Core::State::Uninitialized) s_platform->Stop(); }); diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index ce2b5a7e47..cc92a67c38 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -36,7 +36,7 @@ Settings::Settings() { qRegisterMetaType(); - Core::SetOnStateChangedCallback([this](Core::State new_state) { + Core::AddOnStateChangedCallback([this](Core::State new_state) { QueueOnObject(this, [this, new_state] { emit EmulationStateChanged(new_state); }); }); diff --git a/Source/Core/VideoCommon/FPSCounter.cpp b/Source/Core/VideoCommon/FPSCounter.cpp index 24d49548df..b7c452ec4e 100644 --- a/Source/Core/VideoCommon/FPSCounter.cpp +++ b/Source/Core/VideoCommon/FPSCounter.cpp @@ -8,6 +8,7 @@ #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Common/Timer.h" +#include "Core/Core.h" #include "VideoCommon/FPSCounter.h" #include "VideoCommon/VideoConfig.h" @@ -16,6 +17,18 @@ static constexpr u64 FPS_REFRESH_INTERVAL = 250000; FPSCounter::FPSCounter() { m_last_time = Common::Timer::GetTimeUs(); + + m_on_state_changed_handle = Core::AddOnStateChangedCallback([this](Core::State state) { + if (state == Core::State::Paused) + SetPaused(true); + else if (state == Core::State::Running) + SetPaused(false); + }); +} + +FPSCounter::~FPSCounter() +{ + Core::RemoveOnStateChangedCallback(&m_on_state_changed_handle); } void FPSCounter::LogRenderTimeToFile(u64 val) @@ -31,8 +44,9 @@ void FPSCounter::LogRenderTimeToFile(u64 val) void FPSCounter::Update() { - u64 time = Common::Timer::GetTimeUs(); - u64 diff = time - m_last_time; + const u64 time = Common::Timer::GetTimeUs(); + const u64 diff = time - m_last_time; + m_time_diff_secs = static_cast(diff / 1000000.0); if (g_ActiveConfig.bLogRenderTimeToFile) LogRenderTimeToFile(diff); @@ -47,3 +61,17 @@ void FPSCounter::Update() m_time_since_update = 0; } } + +void FPSCounter::SetPaused(bool paused) +{ + if (paused) + { + m_last_time_pause = Common::Timer::GetTimeUs(); + } + else + { + const u64 time = Common::Timer::GetTimeUs(); + const u64 diff = time - m_last_time_pause; + m_last_time += diff; + } +} diff --git a/Source/Core/VideoCommon/FPSCounter.h b/Source/Core/VideoCommon/FPSCounter.h index a539c690ab..059f6cce6d 100644 --- a/Source/Core/VideoCommon/FPSCounter.h +++ b/Source/Core/VideoCommon/FPSCounter.h @@ -11,20 +11,30 @@ class FPSCounter { public: - // Initializes the FPS counter. FPSCounter(); + ~FPSCounter(); + FPSCounter(const FPSCounter&) = delete; + FPSCounter& operator=(const FPSCounter&) = delete; + FPSCounter(FPSCounter&&) = delete; + FPSCounter& operator=(FPSCounter&&) = delete; // Called when a frame is rendered (updated every second). void Update(); float GetFPS() const { return m_fps; } + double GetDeltaTime() const { return m_time_diff_secs; } private: + void SetPaused(bool paused); + u64 m_last_time = 0; u64 m_time_since_update = 0; + u64 m_last_time_pause = 0; u32 m_frame_counter = 0; - float m_fps = 0; + int m_on_state_changed_handle = -1; + float m_fps = 0.f; std::ofstream m_bench_file; + double m_time_diff_secs = 0.0; void LogRenderTimeToFile(u64 val); }; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index c16dcd95c3..fdcc0f4a99 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -1329,7 +1329,12 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 { // Remove stale EFB/XFB copies. g_texture_cache->Cleanup(m_frame_count); - Core::Callback_FramePresented(); + const double last_speed_denominator = + m_fps_counter.GetDeltaTime() * VideoInterface::GetTargetRefreshRate(); + // The denominator should always be > 0 but if it's not, just return 1 + const double last_speed = + last_speed_denominator > 0.0 ? (1.0 / last_speed_denominator) : 1.0; + Core::Callback_FramePresented(last_speed); } // Handle any config changes, this gets propogated to the backend.