diff options
| author | Pierre Bourdon <delroth@gmail.com> | 2022-12-24 02:35:30 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-12-24 02:35:30 +0100 |
| commit | ea19909fd43e9e71a8ba4f70604407f23c81c8fe (patch) | |
| tree | 656a5b06f3b62c28fc96b5ae62e18328e4b0b0f9 /Source/Core/VideoCommon/PerformanceTracker.cpp | |
| parent | 2345ba178d91b172319472add4232d2c97bb180c (diff) | |
| parent | e1d078ada6c92c5619bbf11f654df855e0ca04ff (diff) | |
Merge pull request #11326 from Sam-Belliveau/video-common-frame-pacing
VideoCommon: New FrameTime/VBlank Analyzer + Graph
Diffstat (limited to 'Source/Core/VideoCommon/PerformanceTracker.cpp')
| -rw-r--r-- | Source/Core/VideoCommon/PerformanceTracker.cpp | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/Source/Core/VideoCommon/PerformanceTracker.cpp b/Source/Core/VideoCommon/PerformanceTracker.cpp new file mode 100644 index 0000000000..ffde157883 --- /dev/null +++ b/Source/Core/VideoCommon/PerformanceTracker.cpp @@ -0,0 +1,252 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/PerformanceTracker.h" + +#include <algorithm> +#include <cmath> +#include <implot.h> +#include <iomanip> + +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "Common/Timer.h" +#include "Core/Core.h" +#include "VideoCommon/VideoConfig.h" + +static constexpr double SAMPLE_RC_RATIO = 0.25; + +PerformanceTracker::PerformanceTracker(const char* log_name, + const std::optional<s64> sample_window_us) + : 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); + })}, + m_log_name{log_name}, m_sample_window_us{sample_window_us} +{ + Reset(); +} + +PerformanceTracker::~PerformanceTracker() +{ + Core::RemoveOnStateChangedCallback(&m_on_state_changed_handle); +} + +void PerformanceTracker::Reset() +{ + std::lock_guard lock{m_mutex}; + + QueueClear(); + m_last_time = Clock::now(); + m_hz_avg = 0.0; + m_dt_avg = DT::zero(); + m_dt_std = std::nullopt; +} + +void PerformanceTracker::Count() +{ + std::lock_guard lock{m_mutex}; + + if (m_paused) + return; + + const DT window{GetSampleWindow()}; + + const TimePoint time{Clock::now()}; + const DT diff{time - m_last_time}; + + m_last_time = time; + + QueuePush(diff); + m_dt_total += diff; + + if (m_dt_queue_begin == m_dt_queue_end) + m_dt_total -= QueuePop(); + + while (window <= m_dt_total - QueueTop()) + 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; + + // 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)); + + // Sometimes euler averages can break when the average is inf/nan + if (std::isfinite(m_hz_avg)) + m_hz_avg += a * (hz - m_hz_avg); + else + m_hz_avg = hz; + + m_dt_std = std::nullopt; + + if (m_log_name && g_ActiveConfig.bLogRenderTimeToFile) + LogRenderTimeToFile(diff); +} + +DT PerformanceTracker::GetSampleWindow() const +{ + // This reads a constant value and thus does not need a mutex + return std::chrono::duration_cast<DT>( + DT_us(m_sample_window_us.value_or(std::max(1, g_ActiveConfig.iPerfSampleUSec)))); +} + +double PerformanceTracker::GetHzAvg() const +{ + std::lock_guard lock{m_mutex}; + return m_hz_avg; +} + +DT PerformanceTracker::GetDtAvg() const +{ + std::lock_guard lock{m_mutex}; + return m_dt_avg; +} + +DT PerformanceTracker::GetDtStd() const +{ + std::lock_guard lock{m_mutex}; + + if (m_dt_std) + return *m_dt_std; + + if (QueueEmpty()) + return *(m_dt_std = DT::zero()); + + 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(); + total += diff * diff; + } + + // This is a weighted standard deviation + return *(m_dt_std = std::chrono::duration_cast<DT>(DT_s(std::sqrt(total / QueueSize())))); +} + +DT PerformanceTracker::GetLastRawDt() const +{ + std::lock_guard lock{m_mutex}; + + if (QueueEmpty()) + return DT::zero(); + + return QueueBottom(); +} + +void PerformanceTracker::ImPlotPlotLines(const char* label) const +{ + static std::array<float, MAX_DT_QUEUE_SIZE + 2> x, y; + + std::lock_guard lock{m_mutex}; + + if (QueueEmpty()) + return; + + // Decides if there are too many points to plot using rectangles + 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] = DT_ms(update_time).count(); + y[points] = predicted_frame_time; + ++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(); + + if (quality) + { + x[points] = x[points - 1]; + y[points] = frame_time_ms; + ++points; + } + + x[points] = x[points - 1] + frame_time_ms; + y[points] = frame_time_ms; + ++points; + } + + ImPlot::PlotLine(label, x.data(), y.data(), static_cast<int>(points)); +} + +void PerformanceTracker::QueueClear() +{ + m_dt_total = DT::zero(); + m_dt_queue_begin = 0; + m_dt_queue_end = 0; +} + +void PerformanceTracker::QueuePush(DT dt) +{ + m_dt_queue[m_dt_queue_end] = dt; + m_dt_queue_end = IncrementIndex(m_dt_queue_end); +} + +const DT& 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 +{ + return m_dt_queue[m_dt_queue_begin]; +} + +const DT& PerformanceTracker::QueueBottom() const +{ + return m_dt_queue[DecrementIndex(m_dt_queue_end)]; +} + +std::size_t PerformanceTracker::QueueSize() const +{ + return GetDifference(m_dt_queue_begin, m_dt_queue_end); +} + +bool PerformanceTracker::QueueEmpty() const +{ + return m_dt_queue_begin == m_dt_queue_end; +} + +void PerformanceTracker::LogRenderTimeToFile(DT val) +{ + if (!m_bench_file.is_open()) + { + File::OpenFStream(m_bench_file, File::GetUserPath(D_LOGS_IDX) + m_log_name, std::ios_base::out); + } + + m_bench_file << std::fixed << std::setprecision(8) << DT_ms(val).count() << std::endl; +} + +void PerformanceTracker::SetPaused(bool paused) +{ + std::lock_guard lock{m_mutex}; + + m_paused = paused; + if (m_paused) + { + m_last_time = TimePoint::max(); + } + else + { + m_last_time = Clock::now(); + } +} |
