From bc1cc9eeb44f48a7333fc5acfefc0f2eaaa30fc3 Mon Sep 17 00:00:00 2001 From: Sam Belliveau Date: Fri, 6 Jan 2023 17:21:17 -0500 Subject: [PATCH 1/5] CoreTiming: Throttle Before Every Event Using Chrono --- Source/Core/AudioCommon/Mixer.cpp | 2 +- Source/Core/Core/Core.cpp | 6 +- Source/Core/Core/CoreTiming.cpp | 57 +++++++ Source/Core/Core/CoreTiming.h | 8 + Source/Core/Core/HW/SystemTimers.cpp | 108 +++--------- .../Core/VideoCommon/PerformanceMetrics.cpp | 160 ++++++++++++------ Source/Core/VideoCommon/PerformanceMetrics.h | 24 ++- 7 files changed, 225 insertions(+), 140 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index f71c1e0c83..ae52dc7bb2 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -162,7 +162,7 @@ unsigned int Mixer::Mix(short* samples, unsigned int num_samples) memset(samples, 0, num_samples * 2 * sizeof(short)); // TODO: Determine how emulation speed will be used in audio - // const float emulation_speed = std::roundf(g_perf_metrics.GetSpeed()) / 100.f; + // const float emulation_speed = g_perf_metrics.GetSpeed(); const float emulation_speed = m_config_emulation_speed; const int timing_variance = m_config_timing_variance; if (m_config_audio_stretch) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 332cc00857..59306619e1 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -891,9 +891,9 @@ void Callback_NewField() void UpdateTitle() { - float FPS = g_perf_metrics.GetFPS(); - float VPS = g_perf_metrics.GetVPS(); - float Speed = g_perf_metrics.GetSpeed(); + const double FPS = g_perf_metrics.GetFPS(); + const double VPS = g_perf_metrics.GetVPS(); + const double Speed = 100.0 * g_perf_metrics.GetSpeed(); // Settings are shown the same for both extended and summary info const std::string SSettings = fmt::format( diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 1d4f085b99..d85db5288d 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -22,6 +22,7 @@ #include "Core/System.h" #include "VideoCommon/Fifo.h" +#include "VideoCommon/PerformanceMetrics.h" #include "VideoCommon/VideoBackendBase.h" namespace CoreTiming @@ -305,6 +306,8 @@ void CoreTimingManager::Advance() Event evt = std::move(m_event_queue.front()); std::pop_heap(m_event_queue.begin(), m_event_queue.end(), std::greater()); m_event_queue.pop_back(); + + Throttle(evt.time); evt.type->callback(system, evt.userdata, m_globals.global_timer - evt.time); } @@ -326,6 +329,58 @@ void CoreTimingManager::Advance() PowerPC::CheckExternalExceptions(); } +void CoreTimingManager::Throttle(const s64 target_cycle) +{ + if (target_cycle <= m_throttle_last_cycle) + return; + + const double speed = + Core::GetIsThrottlerTempDisabled() ? 0.0 : Config::Get(Config::MAIN_EMULATION_SPEED); + + // Based on number of cycles and emulation speed, increase the target deadline + const s64 cycles = target_cycle - m_throttle_last_cycle; + m_throttle_last_cycle = target_cycle; + + if (0.0 < speed) + m_throttle_deadline += std::chrono::duration_cast
((m_throttle_per_clock * cycles) / speed); + + // A maximum fallback is used to prevent the system from sleeping for + // too long or going full speed in an attempt to catch up to timings. + const DT max_fallback = + std::chrono::duration_cast
(DT_ms(Config::Get(Config::MAIN_TIMING_VARIANCE))); + + const TimePoint time = Clock::now(); + const TimePoint min_deadline = time - max_fallback; + const TimePoint max_deadline = time + max_fallback; + + if (m_throttle_deadline > max_deadline) + { + m_throttle_deadline = max_deadline; + } + else if (m_throttle_deadline < min_deadline) + { + DEBUG_LOG_FMT(COMMON, "System can not to keep up with timings! [relaxing timings by {} us]", + DT_us(min_deadline - m_throttle_deadline).count()); + m_throttle_deadline = min_deadline; + } + + // Only sleep if we are behind the deadline + if (time < m_throttle_deadline) + { + std::this_thread::sleep_until(m_throttle_deadline); + + // Count amount of time sleeping for analytics + const TimePoint time_after_sleep = Clock::now(); + g_perf_metrics.CountThrottleSleep(time_after_sleep - time); + } +} + +TimePoint CoreTimingManager::GetCPUTimePoint(s64 cyclesLate) const +{ + return TimePoint( + std::chrono::duration_cast
(m_throttle_per_clock * (m_globals.global_timer - cyclesLate))); +} + void CoreTimingManager::LogPendingEvents() const { auto clone = m_event_queue; @@ -340,6 +395,8 @@ void CoreTimingManager::LogPendingEvents() const // Should only be called from the CPU thread after the PPC clock has changed void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock) { + m_throttle_per_clock = DT_s(1.0) / new_ppc_clock; + for (Event& ev : m_event_queue) { const s64 ticks = (ev.time - m_globals.global_timer) * new_ppc_clock / old_ppc_clock; diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index 737f3889b9..a10525cbbb 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -140,6 +140,8 @@ public: // Directly accessed by the JIT. Globals& GetGlobals() { return m_globals; } + TimePoint GetCPUTimePoint(s64 cyclesLate) const; // Used by Dolphin Analytics + private: Globals m_globals; @@ -173,6 +175,12 @@ private: float m_config_oc_inv_factor = 0.0f; bool m_config_sync_on_skip_idle = false; + s64 m_throttle_last_cycle = 0; + TimePoint m_throttle_deadline = Clock::now(); + DT_s m_throttle_per_clock = DT_s(); + + void Throttle(const s64 target_cycle); + int DowncountToCycles(int downcount) const; int CyclesToDowncount(int cycles) const; }; diff --git a/Source/Core/Core/HW/SystemTimers.cpp b/Source/Core/Core/HW/SystemTimers.cpp index d7908f10e1..de9dc729ae 100644 --- a/Source/Core/Core/HW/SystemTimers.cpp +++ b/Source/Core/Core/HW/SystemTimers.cpp @@ -67,6 +67,7 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule: #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" #include "VideoCommon/Fifo.h" +#include "VideoCommon/PerformanceMetrics.h" namespace SystemTimers { @@ -77,9 +78,10 @@ CoreTiming::EventType* et_VI; CoreTiming::EventType* et_AudioDMA; CoreTiming::EventType* et_DSP; CoreTiming::EventType* et_IPC_HLE; +CoreTiming::EventType* et_GPU_sleeper; +CoreTiming::EventType* et_perf_tracker; // PatchEngine updates every 1/60th of a second by default CoreTiming::EventType* et_PatchEngine; -CoreTiming::EventType* et_Throttle; u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!) @@ -90,16 +92,6 @@ int s_ipc_hle_period; // Custom RTC s64 s_localtime_rtc_offset = 0; -// For each emulated milliseconds, what was the real time timestamp (excluding sleep time). This is -// a "special" ring buffer where we only need to read the first and last value. -std::array s_emu_to_real_time_ring_buffer; -size_t s_emu_to_real_time_index; -std::mutex s_emu_to_real_time_mutex; - -// How much time was spent sleeping since the emulator started. Note: this does not need to be reset -// at initialization (or ever), since only the "derivative" of that value really matters. -u64 s_time_spent_sleeping; - // DSP/CPU timeslicing. void DSPCallback(Core::System& system, u64 userdata, s64 cyclesLate) { @@ -132,6 +124,26 @@ void IPC_HLE_UpdateCallback(Core::System& system, u64 userdata, s64 cyclesLate) } } +void GPUSleepCallback(Core::System& system, u64 userdata, s64 cyclesLate) +{ + auto& core_timing = system.GetCoreTiming(); + system.GetFifo().GpuMaySleep(); + + // We want to call GpuMaySleep at about 1000hz so + // that the thread can sleep while not doing anything. + core_timing.ScheduleEvent(GetTicksPerSecond() / 1000 - cyclesLate, et_GPU_sleeper); +} + +void PerfTrackerCallback(Core::System& system, u64 userdata, s64 cyclesLate) +{ + auto& core_timing = system.GetCoreTiming(); + g_perf_metrics.CountPerformanceMarker(system, cyclesLate); + + // Call this performance tracker again in 1/64th of a second. + // The tracker stores 256 values so this will let us summarize the last 4 seconds. + core_timing.ScheduleEvent(GetTicksPerSecond() / 64 - cyclesLate, et_perf_tracker); +} + void VICallback(Core::System& system, u64 userdata, s64 cyclesLate) { auto& core_timing = system.GetCoreTiming(); @@ -169,50 +181,6 @@ void PatchEngineCallback(Core::System& system, u64 userdata, s64 cycles_late) system.GetCoreTiming().ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned); } - -void ThrottleCallback(Core::System& system, u64 deadline, s64 cyclesLate) -{ - // Allow the GPU thread to sleep. Setting this flag here limits the wakeups to 1 kHz. - system.GetFifo().GpuMaySleep(); - - const u64 time = Common::Timer::NowUs(); - - if (deadline == 0) - deadline = time; - - const s64 diff = deadline - time; - const float emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED); - const bool frame_limiter = emulation_speed > 0.0f && !Core::GetIsThrottlerTempDisabled(); - u32 next_event = GetTicksPerSecond() / 1000; - - { - std::lock_guard lk(s_emu_to_real_time_mutex); - s_emu_to_real_time_ring_buffer[s_emu_to_real_time_index] = time - s_time_spent_sleeping; - s_emu_to_real_time_index = - (s_emu_to_real_time_index + 1) % s_emu_to_real_time_ring_buffer.size(); - } - - if (frame_limiter) - { - if (emulation_speed != 1.0f) - next_event = u32(next_event * emulation_speed); - const s64 max_fallback = Config::Get(Config::MAIN_TIMING_VARIANCE) * 1000; - if (std::abs(diff) > max_fallback) - { - DEBUG_LOG_FMT(COMMON, "system too {}, {} us skipped", diff < 0 ? "slow" : "fast", - std::abs(diff) - max_fallback); - deadline = time - max_fallback; - } - else if (diff > 1000) - { - Common::SleepCurrentThread(diff / 1000); - s_time_spent_sleeping += Common::Timer::NowUs() - time; - } - } - // reschedule 1ms (possibly scaled by emulation_speed) into future on ppc - // add 1ms to the deadline - system.GetCoreTiming().ScheduleEvent(next_event - cyclesLate, et_Throttle, deadline + 1000); -} } // namespace u32 GetTicksPerSecond() @@ -268,27 +236,7 @@ s64 GetLocalTimeRTCOffset() double GetEstimatedEmulationPerformance() { - u64 ts_now, ts_before; // In microseconds - { - std::lock_guard lk(s_emu_to_real_time_mutex); - size_t index_now = s_emu_to_real_time_index == 0 ? s_emu_to_real_time_ring_buffer.size() - 1 : - s_emu_to_real_time_index - 1; - size_t index_before = s_emu_to_real_time_index; - - ts_now = s_emu_to_real_time_ring_buffer[index_now]; - ts_before = s_emu_to_real_time_ring_buffer[index_before]; - } - - if (ts_before == 0) - { - // Not enough data yet to estimate. We could technically provide an estimate based on a shorter - // time horizon, but it's not really worth it. - return 1.0; - } - - u64 delta_us = ts_now - ts_before; - double emulated_us = s_emu_to_real_time_ring_buffer.size() * 1000.0; // For each emulated ms. - return delta_us == 0 ? DBL_MAX : emulated_us / delta_us; + return g_perf_metrics.GetMaxSpeed(); } // split from Init to break a circular dependency between VideoInterface::Init and @@ -345,20 +293,20 @@ void Init() et_DSP = core_timing.RegisterEvent("DSPCallback", DSPCallback); et_AudioDMA = core_timing.RegisterEvent("AudioDMACallback", AudioDMACallback); et_IPC_HLE = core_timing.RegisterEvent("IPC_HLE_UpdateCallback", IPC_HLE_UpdateCallback); + et_GPU_sleeper = core_timing.RegisterEvent("GPUSleeper", GPUSleepCallback); + et_perf_tracker = core_timing.RegisterEvent("PerfTracker", PerfTrackerCallback); et_PatchEngine = core_timing.RegisterEvent("PatchEngine", PatchEngineCallback); - et_Throttle = core_timing.RegisterEvent("Throttle", ThrottleCallback); + core_timing.ScheduleEvent(0, et_perf_tracker); + core_timing.ScheduleEvent(0, et_GPU_sleeper); core_timing.ScheduleEvent(VideoInterface::GetTicksPerHalfLine(), et_VI); core_timing.ScheduleEvent(0, et_DSP); core_timing.ScheduleEvent(GetAudioDMACallbackPeriod(), et_AudioDMA); - core_timing.ScheduleEvent(0, et_Throttle, 0); core_timing.ScheduleEvent(VideoInterface::GetTicksPerField(), et_PatchEngine); if (SConfig::GetInstance().bWii) core_timing.ScheduleEvent(s_ipc_hle_period, et_IPC_HLE); - - s_emu_to_real_time_ring_buffer.fill(0); } void Shutdown() diff --git a/Source/Core/VideoCommon/PerformanceMetrics.cpp b/Source/Core/VideoCommon/PerformanceMetrics.cpp index 23e35623cb..b343af0f4f 100644 --- a/Source/Core/VideoCommon/PerformanceMetrics.cpp +++ b/Source/Core/VideoCommon/PerformanceMetrics.cpp @@ -3,10 +3,14 @@ #include "VideoCommon/PerformanceMetrics.h" +#include + #include #include +#include "Core/CoreTiming.h" #include "Core/HW/VideoInterface.h" +#include "Core/System.h" #include "VideoCommon/VideoConfig.h" PerformanceMetrics g_perf_metrics; @@ -16,6 +20,10 @@ void PerformanceMetrics::Reset() m_fps_counter.Reset(); m_vps_counter.Reset(); m_speed_counter.Reset(); + + m_time_sleeping = DT::zero(); + m_real_times.fill(Clock::now()); + m_cpu_times.fill(Core::System::GetInstance().GetCoreTiming().GetCPUTimePoint(0)); } void PerformanceMetrics::CountFrame() @@ -29,6 +37,20 @@ void PerformanceMetrics::CountVBlank() m_speed_counter.Count(); } +void PerformanceMetrics::CountThrottleSleep(DT sleep) +{ + std::unique_lock lock(m_time_lock); + m_time_sleeping += sleep; +} + +void PerformanceMetrics::CountPerformanceMarker(Core::System& system, s64 cyclesLate) +{ + std::unique_lock lock(m_time_lock); + m_real_times[m_time_index] = Clock::now() - m_time_sleeping; + m_cpu_times[m_time_index] = system.GetCoreTiming().GetCPUTimePoint(cyclesLate); + m_time_index += 1; +} + double PerformanceMetrics::GetFPS() const { return m_fps_counter.GetHzAvg(); @@ -41,7 +63,14 @@ double PerformanceMetrics::GetVPS() const double PerformanceMetrics::GetSpeed() const { - return 100.0 * m_speed_counter.GetHzAvg() / VideoInterface::GetTargetRefreshRate(); + return m_speed_counter.GetHzAvg() / VideoInterface::GetTargetRefreshRate(); +} + +double PerformanceMetrics::GetMaxSpeed() const +{ + std::shared_lock lock(m_time_lock); + return DT_s(m_cpu_times[u8(m_time_index - 1)] - m_cpu_times[m_time_index]) / + DT_s(m_real_times[u8(m_time_index - 1)] - m_real_times[m_time_index]); } double PerformanceMetrics::GetLastSpeedDenominator() const @@ -49,7 +78,7 @@ double PerformanceMetrics::GetLastSpeedDenominator() const return DT_s(m_speed_counter.GetLastRawDt()).count() * VideoInterface::GetTargetRefreshRate(); } -void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const +void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) { const float bg_alpha = 0.7f; const auto imgui_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | @@ -65,9 +94,9 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const float r = 0.0f, g = 1.0f, b = 1.0f; if (g_ActiveConfig.bShowSpeedColors) { - r = 1.0 - (speed - 80.0) / 20.0; - g = speed / 80.0; - b = (speed - 90.0) / 10.0; + r = 1.0 - (speed - 0.8) / 0.2; + g = speed / 0.8; + b = (speed - 0.9) / 0.1; } const float window_padding = 8.f * backbuffer_scale; @@ -79,6 +108,8 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const const float graph_height = std::min(200.f * backbuffer_scale, ImGui::GetIO().DisplaySize.y - 85.f * backbuffer_scale); + const bool stack_vertically = !g_ActiveConfig.bShowGraphs; + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 14.f * backbuffer_scale); if (g_ActiveConfig.bShowGraphs) @@ -93,40 +124,39 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const if (ImGui::Begin("PerformanceGraphs", nullptr, imgui_flags)) { - const static int num_ticks = 17; - const static double tick_marks[num_ticks] = {0.0, - 1000.0 / 360.0, - 1000.0 / 240.0, - 1000.0 / 180.0, - 1000.0 / 120.0, - 1000.0 / 90.00, - 1000.0 / 59.94, - 1000.0 / 40.00, - 1000.0 / 29.97, - 1000.0 / 24.00, - 1000.0 / 20.00, - 1000.0 / 15.00, - 1000.0 / 10.00, - 1000.0 / 5.000, - 1000.0 / 2.000, - 1000.0, - 2000.0}; + static constexpr std::size_t num_ticks = 17; + static constexpr std::array tick_marks = {0.0, + 1000.0 / 360.0, + 1000.0 / 240.0, + 1000.0 / 180.0, + 1000.0 / 120.0, + 1000.0 / 90.00, + 1000.0 / 59.94, + 1000.0 / 40.00, + 1000.0 / 29.97, + 1000.0 / 24.00, + 1000.0 / 20.00, + 1000.0 / 15.00, + 1000.0 / 10.00, + 1000.0 / 5.000, + 1000.0 / 2.000, + 1000.0, + 2000.0}; - const DT vblank_time = m_vps_counter.GetDtAvg() + m_vps_counter.GetDtStd(); - const DT frame_time = m_fps_counter.GetDtAvg() + m_fps_counter.GetDtStd(); + const DT vblank_time = m_vps_counter.GetDtAvg() + 2 * m_vps_counter.GetDtStd(); + const DT frame_time = m_fps_counter.GetDtAvg() + 2 * m_fps_counter.GetDtStd(); const double target_max_time = DT_ms(vblank_time + frame_time).count(); const double a = - std::max(0.0, 1.0 - std::exp(-4000.0 * m_vps_counter.GetLastRawDt().count() / - DT_ms(m_vps_counter.GetSampleWindow()).count())); + std::max(0.0, 1.0 - std::exp(-4.0 * (DT_s(m_vps_counter.GetLastRawDt()) / + DT_s(m_vps_counter.GetSampleWindow())))); - static double max_time = 0.0; - if (std::isfinite(max_time)) - max_time += a * (target_max_time - max_time); + if (std::isfinite(m_graph_max_time)) + m_graph_max_time += a * (target_max_time - m_graph_max_time); else - max_time = target_max_time; + m_graph_max_time = target_max_time; - const double total_frame_time = std::max(DT_ms(m_fps_counter.GetSampleWindow()).count(), - DT_ms(m_vps_counter.GetSampleWindow()).count()); + const double total_frame_time = + DT_ms(std::max(m_fps_counter.GetSampleWindow(), m_vps_counter.GetSampleWindow())).count(); if (ImPlot::BeginPlot("PerformanceGraphs", ImVec2(-1.0, -1.0), ImPlotFlags_NoFrame | ImPlotFlags_NoTitle | ImPlotFlags_NoMenus)) @@ -141,8 +171,8 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const ImPlotAxisFlags_Lock | ImPlotAxisFlags_Invert | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoHighlight); ImPlot::SetupAxisFormat(ImAxis_Y1, "%.1f"); - ImPlot::SetupAxisTicks(ImAxis_Y1, tick_marks, num_ticks); - ImPlot::SetupAxesLimits(0, total_frame_time, 0, max_time, ImGuiCond_Always); + ImPlot::SetupAxisTicks(ImAxis_Y1, tick_marks.data(), num_ticks); + ImPlot::SetupAxesLimits(0, total_frame_time, 0, m_graph_max_time, ImGuiCond_Always); ImPlot::SetupLegend(ImPlotLocation_SouthEast, ImPlotLegendFlags_None); m_vps_counter.ImPlotPlotLines("V-Blank (ms)"); m_fps_counter.ImPlotPlotLines("Frame (ms)"); @@ -155,14 +185,42 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const } } - if (g_ActiveConfig.bShowFPS || g_ActiveConfig.bShowFTimes) + if (g_ActiveConfig.bShowSpeed) { // Position in the top-right corner of the screen. - int count = g_ActiveConfig.bShowFPS + 2 * g_ActiveConfig.bShowFTimes; + float window_height = 47.f * backbuffer_scale; + ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); - ImGui::SetNextWindowSize(ImVec2(window_width, (12.f + 17.f * count) * backbuffer_scale)); + ImGui::SetNextWindowSize(ImVec2(window_width, window_height)); ImGui::SetNextWindowBgAlpha(bg_alpha); - window_x -= window_width + window_padding; + + if (stack_vertically) + window_y += window_height + window_padding; + else + window_x -= window_width + window_padding; + + if (ImGui::Begin("SpeedStats", nullptr, imgui_flags)) + { + ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Speed:%4.0lf%%", 100.0 * speed); + ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Max:%6.0lf%%", 100.0 * GetMaxSpeed()); + ImGui::End(); + } + } + + if (g_ActiveConfig.bShowFPS || g_ActiveConfig.bShowFTimes) + { + int count = g_ActiveConfig.bShowFPS + 2 * g_ActiveConfig.bShowFTimes; + float window_height = (12.f + 17.f * count) * backbuffer_scale; + + // Position in the top-right corner of the screen. + ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); + ImGui::SetNextWindowSize(ImVec2(window_width, window_height)); + ImGui::SetNextWindowBgAlpha(bg_alpha); + + if (stack_vertically) + window_y += window_height + window_padding; + else + window_x -= window_width + window_padding; if (ImGui::Begin("FPSStats", nullptr, imgui_flags)) { @@ -181,12 +239,18 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const if (g_ActiveConfig.bShowVPS || g_ActiveConfig.bShowVTimes) { - // Position in the top-right corner of the screen. int count = g_ActiveConfig.bShowVPS + 2 * g_ActiveConfig.bShowVTimes; + float window_height = (12.f + 17.f * count) * backbuffer_scale; + + // Position in the top-right corner of the screen. ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); ImGui::SetNextWindowSize(ImVec2(window_width, (12.f + 17.f * count) * backbuffer_scale)); ImGui::SetNextWindowBgAlpha(bg_alpha); - window_x -= window_width + window_padding; + + if (stack_vertically) + window_y += window_height + window_padding; + else + window_x -= window_width + window_padding; if (ImGui::Begin("VPSStats", nullptr, imgui_flags)) { @@ -203,19 +267,5 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const } } - if (g_ActiveConfig.bShowSpeed) - { - // Position in the top-right corner of the screen. - ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); - ImGui::SetNextWindowSize(ImVec2(window_width, 29.f * backbuffer_scale)); - ImGui::SetNextWindowBgAlpha(bg_alpha); - - if (ImGui::Begin("SpeedStats", nullptr, imgui_flags)) - { - ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Speed:%4.0lf%%", speed); - ImGui::End(); - } - } - ImGui::PopStyleVar(2); } diff --git a/Source/Core/VideoCommon/PerformanceMetrics.h b/Source/Core/VideoCommon/PerformanceMetrics.h index eb6f8ecb00..009c27fd3f 100644 --- a/Source/Core/VideoCommon/PerformanceMetrics.h +++ b/Source/Core/VideoCommon/PerformanceMetrics.h @@ -3,8 +3,17 @@ #pragma once +#include +#include + +#include "Common/CommonTypes.h" #include "VideoCommon/PerformanceTracker.h" +namespace Core +{ +class System; +} + class PerformanceMetrics { public: @@ -21,20 +30,33 @@ public: void CountFrame(); void CountVBlank(); + void CountThrottleSleep(DT sleep); + void CountPerformanceMarker(Core::System& system, s64 cyclesLate); + // Getter Functions double GetFPS() const; double GetVPS() const; double GetSpeed() const; + double GetMaxSpeed() const; double GetLastSpeedDenominator() const; // ImGui Functions - void DrawImGuiStats(const float backbuffer_scale) const; + void DrawImGuiStats(const float backbuffer_scale); private: PerformanceTracker m_fps_counter{"render_times.txt"}; PerformanceTracker m_vps_counter{"vblank_times.txt"}; PerformanceTracker m_speed_counter{std::nullopt, 500000}; + + double m_graph_max_time = 0.0; + + mutable std::shared_mutex m_time_lock; + + u8 m_time_index = 0; + std::array m_real_times; + std::array m_cpu_times; + DT m_time_sleeping; }; extern PerformanceMetrics g_perf_metrics; From f7de0e4147e13f4ed9463602440a089515b6b5c5 Mon Sep 17 00:00:00 2001 From: Sam Belliveau Date: Sun, 8 Jan 2023 01:20:19 -0500 Subject: [PATCH 2/5] Fix issue that would disable Throttling --- Source/Core/Core/CoreTiming.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index d85db5288d..12aff010cb 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -331,9 +331,6 @@ void CoreTimingManager::Advance() void CoreTimingManager::Throttle(const s64 target_cycle) { - if (target_cycle <= m_throttle_last_cycle) - return; - const double speed = Core::GetIsThrottlerTempDisabled() ? 0.0 : Config::Get(Config::MAIN_EMULATION_SPEED); From 901e4c7697556402a94cc2d1ddaa6a30ac9a18ed Mon Sep 17 00:00:00 2001 From: Sam Belliveau Date: Mon, 9 Jan 2023 11:06:12 -0500 Subject: [PATCH 3/5] Reset Throttle Variables In Init --- Source/Core/Core/CoreTiming.cpp | 13 +++++++++---- Source/Core/Core/CoreTiming.h | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 12aff010cb..54a23f629c 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -100,6 +100,10 @@ void CoreTimingManager::Init() // that slice. m_is_global_timer_sane = true; + // Reset data used by the throttling system + m_throttle_last_cycle = 0; + m_throttle_deadline = Clock::now(); + m_event_fifo_id = 0; m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback); } @@ -339,7 +343,8 @@ void CoreTimingManager::Throttle(const s64 target_cycle) m_throttle_last_cycle = target_cycle; if (0.0 < speed) - m_throttle_deadline += std::chrono::duration_cast
((m_throttle_per_clock * cycles) / speed); + m_throttle_deadline += + std::chrono::duration_cast
(DT_s(cycles) / (speed * m_throttle_clock_per_sec)); // A maximum fallback is used to prevent the system from sleeping for // too long or going full speed in an attempt to catch up to timings. @@ -374,8 +379,8 @@ void CoreTimingManager::Throttle(const s64 target_cycle) TimePoint CoreTimingManager::GetCPUTimePoint(s64 cyclesLate) const { - return TimePoint( - std::chrono::duration_cast
(m_throttle_per_clock * (m_globals.global_timer - cyclesLate))); + return TimePoint(std::chrono::duration_cast
(DT_s(m_globals.global_timer - cyclesLate) / + m_throttle_clock_per_sec)); } void CoreTimingManager::LogPendingEvents() const @@ -392,7 +397,7 @@ void CoreTimingManager::LogPendingEvents() const // Should only be called from the CPU thread after the PPC clock has changed void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock) { - m_throttle_per_clock = DT_s(1.0) / new_ppc_clock; + m_throttle_clock_per_sec = new_ppc_clock; for (Event& ev : m_event_queue) { diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index a10525cbbb..5918e0f353 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -177,7 +177,7 @@ private: s64 m_throttle_last_cycle = 0; TimePoint m_throttle_deadline = Clock::now(); - DT_s m_throttle_per_clock = DT_s(); + s64 m_throttle_clock_per_sec; void Throttle(const s64 target_cycle); From e95c1d55e862047888eba9dc38b9c79656360f33 Mon Sep 17 00:00:00 2001 From: Sam Belliveau Date: Fri, 13 Jan 2023 11:59:32 -0500 Subject: [PATCH 4/5] Limit Sleep Calls to 8192hz at most --- Source/Core/Core/CoreTiming.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 54a23f629c..fb1c15dbe5 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -335,13 +335,18 @@ void CoreTimingManager::Advance() void CoreTimingManager::Throttle(const s64 target_cycle) { - const double speed = - Core::GetIsThrottlerTempDisabled() ? 0.0 : Config::Get(Config::MAIN_EMULATION_SPEED); - // Based on number of cycles and emulation speed, increase the target deadline const s64 cycles = target_cycle - m_throttle_last_cycle; + + // Prevent any throttling code if the amount of time passed is < ~0.122ms + if (cycles < (m_throttle_clock_per_sec >> 13)) + return; + m_throttle_last_cycle = target_cycle; + const double speed = + Core::GetIsThrottlerTempDisabled() ? 0.0 : Config::Get(Config::MAIN_EMULATION_SPEED); + if (0.0 < speed) m_throttle_deadline += std::chrono::duration_cast
(DT_s(cycles) / (speed * m_throttle_clock_per_sec)); From e849172dc9c1c0822ff36693c948bc79b1a8027f Mon Sep 17 00:00:00 2001 From: Sam Belliveau Date: Fri, 13 Jan 2023 18:17:24 -0500 Subject: [PATCH 5/5] Limit Throttling to 1200hz --- Source/Core/Core/CoreTiming.cpp | 3 ++- Source/Core/Core/CoreTiming.h | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index fb1c15dbe5..eb7b537a61 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -339,7 +339,7 @@ void CoreTimingManager::Throttle(const s64 target_cycle) const s64 cycles = target_cycle - m_throttle_last_cycle; // Prevent any throttling code if the amount of time passed is < ~0.122ms - if (cycles < (m_throttle_clock_per_sec >> 13)) + if (cycles < m_throttle_min_clock_per_sleep) return; m_throttle_last_cycle = target_cycle; @@ -403,6 +403,7 @@ void CoreTimingManager::LogPendingEvents() const void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock) { m_throttle_clock_per_sec = new_ppc_clock; + m_throttle_min_clock_per_sleep = new_ppc_clock / 1200; for (Event& ev : m_event_queue) { diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index 5918e0f353..1a860c5fa5 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -140,6 +140,11 @@ public: // Directly accessed by the JIT. Globals& GetGlobals() { return m_globals; } + // Throttle the CPU to the specified target cycle. + // Never used outside of CoreTiming, however it remains public + // in order to allow custom throttling implementations to be tested. + void Throttle(const s64 target_cycle); + TimePoint GetCPUTimePoint(s64 cyclesLate) const; // Used by Dolphin Analytics private: @@ -178,8 +183,7 @@ private: s64 m_throttle_last_cycle = 0; TimePoint m_throttle_deadline = Clock::now(); s64 m_throttle_clock_per_sec; - - void Throttle(const s64 target_cycle); + s64 m_throttle_min_clock_per_sleep; int DowncountToCycles(int downcount) const; int CyclesToDowncount(int cycles) const;