diff --git a/Source/Core/VideoCommon/PerformanceMetrics.cpp b/Source/Core/VideoCommon/PerformanceMetrics.cpp index 773dc44383..753a9e2da9 100644 --- a/Source/Core/VideoCommon/PerformanceMetrics.cpp +++ b/Source/Core/VideoCommon/PerformanceMetrics.cpp @@ -22,10 +22,9 @@ void PerformanceMetrics::Reset() m_fps_counter.Reset(); m_vps_counter.Reset(); m_speed_counter.Reset(); + m_max_speed_counter.Reset(); - m_time_sleeping = DT::zero(); - m_real_times.fill(Clock::now()); - m_cpu_times.fill(Core::System::GetInstance().GetCoreTiming().GetCPUTimePoint(0)); + m_prev_adjusted_time = Clock::now() - m_time_sleeping; } void PerformanceMetrics::CountFrame() @@ -47,11 +46,10 @@ void PerformanceMetrics::CountThrottleSleep(DT sleep) void PerformanceMetrics::CountPerformanceMarker(Core::System& system, s64 cyclesLate) { std::unique_lock lock(m_time_lock); + const TimePoint adjusted_time = Clock::now() - m_time_sleeping; m_speed_counter.Count(); - - 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; + m_max_speed_counter.Count(adjusted_time - m_prev_adjusted_time); + m_prev_adjusted_time = adjusted_time; } double PerformanceMetrics::GetFPS() const @@ -71,9 +69,7 @@ double PerformanceMetrics::GetSpeed() const 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]); + return m_max_speed_counter.GetHzAvg() / 100.0; } double PerformanceMetrics::GetLastSpeedDenominator() const diff --git a/Source/Core/VideoCommon/PerformanceMetrics.h b/Source/Core/VideoCommon/PerformanceMetrics.h index d1f4211a46..66af0163f5 100644 --- a/Source/Core/VideoCommon/PerformanceMetrics.h +++ b/Source/Core/VideoCommon/PerformanceMetrics.h @@ -47,15 +47,13 @@ public: private: PerformanceTracker m_fps_counter{"render_times.txt"}; PerformanceTracker m_vps_counter{"vblank_times.txt"}; - PerformanceTracker m_speed_counter{std::nullopt, 1000000}; + PerformanceTracker m_speed_counter{std::nullopt, 1280000}; + PerformanceTracker m_max_speed_counter{std::nullopt, 1280000}; 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{}; + TimePoint m_prev_adjusted_time{}; DT m_time_sleeping{}; }; diff --git a/Source/Core/VideoCommon/PerformanceTracker.cpp b/Source/Core/VideoCommon/PerformanceTracker.cpp index 93b33cff9a..8fa0410af8 100644 --- a/Source/Core/VideoCommon/PerformanceTracker.cpp +++ b/Source/Core/VideoCommon/PerformanceTracker.cpp @@ -16,7 +16,7 @@ #include "Core/Core.h" #include "VideoCommon/VideoConfig.h" -static constexpr double SAMPLE_RC_RATIO = 0.25; +static constexpr double SAMPLE_RC_RATIO = 0.33; PerformanceTracker::PerformanceTracker(const std::optional log_name, const std::optional sample_window_us) @@ -47,7 +47,7 @@ void PerformanceTracker::Reset() m_dt_std = std::nullopt; } -void PerformanceTracker::Count() +void PerformanceTracker::Count(std::optional
custom_value, bool value_is_duration) { std::unique_lock lock{m_mutex}; @@ -57,26 +57,31 @@ void PerformanceTracker::Count() const DT window{GetSampleWindow()}; const TimePoint time{Clock::now()}; - const DT diff{time - m_last_time}; - + const DT duration{time - m_last_time}; + const DT value{custom_value.value_or(duration)}; + const TimeDataPair data_point{value_is_duration ? value : duration, value}; m_last_time = time; - QueuePush(diff); - m_dt_total += diff; + QueuePush(data_point); + m_dt_total += data_point; if (m_dt_queue_begin == m_dt_queue_end) m_dt_total -= QueuePop(); - while (window <= m_dt_total - QueueTop()) + while (window <= m_dt_total.duration - QueueTop().duration) m_dt_total -= QueuePop(); // Simple Moving Average Throughout the Window - m_dt_avg = m_dt_total / QueueSize(); - const double hz = DT_s(1.0) / m_dt_avg; + // We want the average value, so we use the value + m_dt_avg = m_dt_total.value / QueueSize(); + + // Even though the frequency does not make sense if the value + // is not the duration, it is still useful to have the value + const double hz = DT_s(QueueSize()) / m_dt_total.value; // Exponential Moving Average - const DT_s rc = SAMPLE_RC_RATIO * std::min(window, m_dt_total); - const double a = 1.0 - std::exp(-(DT_s(diff) / rc)); + const DT_s rc = SAMPLE_RC_RATIO * window; + const double a = 1.0 - std::exp(-(DT_s(data_point.duration) / rc)); // Sometimes euler averages can break when the average is inf/nan if (std::isfinite(m_hz_avg)) @@ -86,7 +91,7 @@ void PerformanceTracker::Count() m_dt_std = std::nullopt; - LogRenderTimeToFile(diff); + LogRenderTimeToFile(data_point.value); } DT PerformanceTracker::GetSampleWindow() const @@ -121,7 +126,7 @@ DT PerformanceTracker::GetDtStd() const double total = 0.0; for (std::size_t i = m_dt_queue_begin; i != m_dt_queue_end; i = IncrementIndex(i)) { - double diff = DT_s(m_dt_queue[i] - m_dt_avg).count(); + double diff = DT_s(m_dt_queue[i].value - m_dt_avg).count(); total += diff * diff; } @@ -136,7 +141,7 @@ DT PerformanceTracker::GetLastRawDt() const if (QueueEmpty()) return DT::zero(); - return QueueBottom(); + return QueueBottom().value; } void PerformanceTracker::ImPlotPlotLines(const char* label) const @@ -152,35 +157,32 @@ void PerformanceTracker::ImPlotPlotLines(const char* label) const const bool quality = QueueSize() < MAX_QUALITY_GRAPH_SIZE; const DT update_time = Clock::now() - m_last_time; - const float predicted_frame_time = DT_ms(std::max(update_time, QueueBottom())).count(); std::size_t points = 0; - if (quality) - { - x[points] = 0.f; - y[points] = predicted_frame_time; - ++points; - } + x[points] = 0.f; + y[points] = DT_ms(QueueBottom().value).count(); + ++points; x[points] = DT_ms(update_time).count(); - y[points] = predicted_frame_time; + y[points] = y[points - 1]; ++points; const std::size_t begin = DecrementIndex(m_dt_queue_end); const std::size_t end = DecrementIndex(m_dt_queue_begin); for (std::size_t i = begin; i != end; i = DecrementIndex(i)) { - const float frame_time_ms = DT_ms(m_dt_queue[i]).count(); + const float frame_duration_ms = DT_ms(m_dt_queue[i].duration).count(); + const float frame_value_ms = DT_ms(m_dt_queue[i].value).count(); if (quality) { x[points] = x[points - 1]; - y[points] = frame_time_ms; + y[points] = frame_value_ms; ++points; } - x[points] = x[points - 1] + frame_time_ms; - y[points] = frame_time_ms; + x[points] = x[points - 1] + frame_duration_ms; + y[points] = frame_value_ms; ++points; } @@ -194,25 +196,25 @@ void PerformanceTracker::QueueClear() m_dt_queue_end = 0; } -void PerformanceTracker::QueuePush(DT dt) +void PerformanceTracker::QueuePush(TimeDataPair dt) { m_dt_queue[m_dt_queue_end] = dt; m_dt_queue_end = IncrementIndex(m_dt_queue_end); } -const DT& PerformanceTracker::QueuePop() +const PerformanceTracker::TimeDataPair& PerformanceTracker::QueuePop() { const std::size_t top = m_dt_queue_begin; m_dt_queue_begin = IncrementIndex(m_dt_queue_begin); return m_dt_queue[top]; } -const DT& PerformanceTracker::QueueTop() const +const PerformanceTracker::TimeDataPair& PerformanceTracker::QueueTop() const { return m_dt_queue[m_dt_queue_begin]; } -const DT& PerformanceTracker::QueueBottom() const +const PerformanceTracker::TimeDataPair& PerformanceTracker::QueueBottom() const { return m_dt_queue[DecrementIndex(m_dt_queue_end)]; } diff --git a/Source/Core/VideoCommon/PerformanceTracker.h b/Source/Core/VideoCommon/PerformanceTracker.h index f24601c845..b0eec8c328 100644 --- a/Source/Core/VideoCommon/PerformanceTracker.h +++ b/Source/Core/VideoCommon/PerformanceTracker.h @@ -15,7 +15,7 @@ class PerformanceTracker { private: // Must be powers of 2 for masking to work - static constexpr u64 MAX_DT_QUEUE_SIZE = 1UL << 12; + static constexpr u64 MAX_DT_QUEUE_SIZE = 1UL << 13; static constexpr u64 MAX_QUALITY_GRAPH_SIZE = 1UL << 8; static inline std::size_t IncrementIndex(const std::size_t index) @@ -33,6 +33,31 @@ private: return (end - begin) & (MAX_DT_QUEUE_SIZE - 1); } + struct TimeDataPair + { + public: + TimeDataPair(DT duration, DT value) : duration{duration}, value{value} {} + TimeDataPair(DT duration) : TimeDataPair{duration, duration} {} + TimeDataPair() : TimeDataPair{DT::zero()} {} + + TimeDataPair& operator+=(const TimeDataPair& other) + { + duration += other.duration; + value += other.value; + return *this; + } + + TimeDataPair& operator-=(const TimeDataPair& other) + { + duration -= other.duration; + value -= other.value; + return *this; + } + + public: + DT duration, value; + }; + public: PerformanceTracker(const std::optional log_name = std::nullopt, const std::optional sample_window_us = std::nullopt); @@ -45,7 +70,20 @@ public: // Functions for recording performance information void Reset(); - void Count(); + + /** + * custom_value can be used if you are recording something with it's own DT. For example, + * if you are recording the fallback of the throttler or the latency of the frame. + * + * If a custom_value is not supplied, the value will be set to the time between calls aka, + * duration. This is the most common use case of this class, as an FPS counter. + * + * The boolean value_is_duration should be set to true if the custom DTs you are providing + * represent a continuous duration. For example, the present times from a render backend + * would set value_is_duration to true. Things like throttler fallback or frame latency + * are not continuous, so they should not represent duration. + */ + void Count(std::optional
custom_value = std::nullopt, bool value_is_duration = false); // Functions for reading performance information DT GetSampleWindow() const; @@ -61,13 +99,13 @@ public: private: // Functions for managing dt queue inline void QueueClear(); - inline void QueuePush(DT dt); - inline const DT& QueuePop(); - inline const DT& QueueTop() const; - inline const DT& QueueBottom() const; + inline void QueuePush(TimeDataPair dt); + inline const TimeDataPair& QueuePop(); + inline const TimeDataPair& QueueTop() const; + inline const TimeDataPair& QueueBottom() const; - std::size_t inline QueueSize() const; - bool inline QueueEmpty() const; + inline std::size_t QueueSize() const; + inline bool QueueEmpty() const; // Handle pausing and logging void LogRenderTimeToFile(DT val); @@ -87,8 +125,8 @@ private: // Functions for managing dt queue const std::optional m_sample_window_us; // Queue + Running Total used to calculate average dt - DT m_dt_total = DT::zero(); - std::array m_dt_queue; + TimeDataPair m_dt_total; + std::array m_dt_queue; std::size_t m_dt_queue_begin = 0; std::size_t m_dt_queue_end = 0;