diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java
index 79d76ada40..9ee01f8b96 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java
@@ -175,7 +175,10 @@ public enum BooleanSetting implements AbstractBooleanSetting
GFX_WIDESCREEN_HACK(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "wideScreenHack", false),
GFX_CROP(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "Crop", false),
GFX_SHOW_FPS(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowFPS", false),
+ GFX_SHOW_FTIMES(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowFTimes", false),
GFX_SHOW_VPS(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowVPS", false),
+ GFX_SHOW_VTIMES(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowVTimes", false),
+ GFX_SHOW_GRAPHS(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowGraphs", false),
GFX_SHOW_SPEED(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowSpeed", false),
GFX_SHOW_SPEED_COLORS(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowSpeedColors", true),
GFX_OVERLAY_STATS(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "OverlayStats", false),
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java
index 5a83729f17..e2751f37fd 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java
@@ -770,8 +770,14 @@ public final class SettingsFragmentPresenter
R.string.video_backend, 0, R.array.videoBackendEntries, R.array.videoBackendValues));
sl.add(new SwitchSetting(mContext, BooleanSetting.GFX_SHOW_FPS, R.string.show_fps,
R.string.show_fps_description));
+ sl.add(new SwitchSetting(mContext, BooleanSetting.GFX_SHOW_FTIMES, R.string.show_ftimes,
+ R.string.show_ftimes_description));
sl.add(new SwitchSetting(mContext, BooleanSetting.GFX_SHOW_VPS, R.string.show_vps,
R.string.show_vps_description));
+ sl.add(new SwitchSetting(mContext, BooleanSetting.GFX_SHOW_VTIMES, R.string.show_vtimes,
+ R.string.show_vtimes_description));
+ sl.add(new SwitchSetting(mContext, BooleanSetting.GFX_SHOW_GRAPHS, R.string.show_graphs,
+ R.string.show_graphs_description));
sl.add(new SwitchSetting(mContext, BooleanSetting.GFX_SHOW_SPEED, R.string.show_speed,
R.string.show_speed_description));
sl.add(new SwitchSetting(mContext, BooleanSetting.GFX_SHOW_SPEED_COLORS,
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index e970976b8b..69c6029060 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -245,8 +245,14 @@
Select the API used for graphics rendering.
Show FPS
Shows the number of distinct frames rendered per second as a measure of visual smoothness.
+ Show Frame Times
+ Shows the average time in ms between each distinct rendered frame alongside the standard deviation.
Show VPS
Show the number of frames rendered per second as a measure of emulation speed.
+ Show VBlank Times
+ Shows the average time in ms between each rendered frame alongside the standard deviation.
+ Show Performance Graphs
+ Shows frametime graph along with statistics as a representation of emulation performance.
Show % Speed
Shows the % speed of emulation compared to full speed.
Show Speed Color
diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp
index ccec2db5c9..f71c1e0c83 100644
--- a/Source/Core/AudioCommon/Mixer.cpp
+++ b/Source/Core/AudioCommon/Mixer.cpp
@@ -14,6 +14,7 @@
#include "Common/Swap.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
+#include "VideoCommon/PerformanceMetrics.h"
static u32 DPL2QualityToFrameBlockSize(AudioCommon::DPL2Quality quality)
{
@@ -160,6 +161,8 @@ 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 = m_config_emulation_speed;
const int timing_variance = m_config_timing_variance;
if (m_config_audio_stretch)
diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h
index 7191fd6ab0..11039443ca 100644
--- a/Source/Core/AudioCommon/Mixer.h
+++ b/Source/Core/AudioCommon/Mixer.h
@@ -48,9 +48,6 @@ public:
void StartLogDSPAudio(const std::string& filename);
void StopLogDSPAudio();
- float GetCurrentSpeed() const { return m_speed.load(); }
- void UpdateSpeed(float val) { m_speed.store(val); }
-
// 54000000 doesn't work here as it doesn't evenly divide with 32000, but 108000000 does
static constexpr u64 FIXED_SAMPLE_RATE_DIVIDEND = 54000000 * 2;
@@ -117,9 +114,6 @@ private:
bool m_log_dtk_audio = false;
bool m_log_dsp_audio = false;
- // Current rate of emulation (1.0 = 100% speed)
- std::atomic m_speed{0.0f};
-
float m_config_emulation_speed;
int m_config_timing_variance;
bool m_config_audio_stretch;
diff --git a/Source/Core/Common/CommonTypes.h b/Source/Core/Common/CommonTypes.h
index 726a1b4f95..68397c1c3f 100644
--- a/Source/Core/Common/CommonTypes.h
+++ b/Source/Core/Common/CommonTypes.h
@@ -7,6 +7,7 @@
#pragma once
+#include
#include
#ifdef _WIN32
@@ -26,3 +27,10 @@ using s8 = std::int8_t;
using s16 = std::int16_t;
using s32 = std::int32_t;
using s64 = std::int64_t;
+
+using Clock = std::chrono::steady_clock;
+using TimePoint = Clock::time_point;
+using DT = Clock::duration;
+using DT_us = std::chrono::duration;
+using DT_ms = std::chrono::duration;
+using DT_s = std::chrono::duration>;
diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp
index 5db6e93830..c52403d476 100644
--- a/Source/Core/Core/Config/GraphicsSettings.cpp
+++ b/Source/Core/Core/Config/GraphicsSettings.cpp
@@ -27,7 +27,10 @@ const Info GFX_CROP{{System::GFX, "Settings", "Crop"}, false};
const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES{
{System::GFX, "Settings", "SafeTextureCacheColorSamples"}, 128};
const Info GFX_SHOW_FPS{{System::GFX, "Settings", "ShowFPS"}, false};
+const Info GFX_SHOW_FTIMES{{System::GFX, "Settings", "ShowFTimes"}, false};
const Info GFX_SHOW_VPS{{System::GFX, "Settings", "ShowVPS"}, false};
+const Info GFX_SHOW_VTIMES{{System::GFX, "Settings", "ShowVTimes"}, false};
+const Info GFX_SHOW_GRAPHS{{System::GFX, "Settings", "ShowGraphs"}, false};
const Info GFX_SHOW_SPEED{{System::GFX, "Settings", "ShowSpeed"}, false};
const Info GFX_SHOW_SPEED_COLORS{{System::GFX, "Settings", "ShowSpeedColors"}, true};
const Info GFX_PERF_SAMP_WINDOW{{System::GFX, "Settings", "PerfSampWindowMS"}, 1000};
diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h
index 601ae56263..df97bd12c2 100644
--- a/Source/Core/Core/Config/GraphicsSettings.h
+++ b/Source/Core/Core/Config/GraphicsSettings.h
@@ -30,7 +30,10 @@ extern const Info GFX_SUGGESTED_ASPECT_RATIO;
extern const Info GFX_CROP;
extern const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES;
extern const Info GFX_SHOW_FPS;
+extern const Info GFX_SHOW_FTIMES;
extern const Info GFX_SHOW_VPS;
+extern const Info GFX_SHOW_VTIMES;
+extern const Info GFX_SHOW_GRAPHS;
extern const Info GFX_SHOW_SPEED;
extern const Info GFX_SHOW_SPEED_COLORS;
extern const Info GFX_PERF_SAMP_WINDOW;
diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp
index 5607a80dc2..9a65f3af95 100644
--- a/Source/Core/Core/Core.cpp
+++ b/Source/Core/Core/Core.cpp
@@ -86,6 +86,7 @@
#include "VideoCommon/Fifo.h"
#include "VideoCommon/HiresTextures.h"
#include "VideoCommon/OnScreenDisplay.h"
+#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoBackendBase.h"
@@ -100,8 +101,6 @@ static bool s_wants_determinism;
// Declarations and definitions
static Common::Timer s_timer;
static u64 s_timer_offset;
-static std::atomic s_drawn_frame;
-static std::atomic s_drawn_video;
static bool s_is_stopping = false;
static bool s_hardware_initialized = false;
@@ -347,6 +346,9 @@ static void CpuThread(const std::optional& savestate_path, bool del
// This needs to be delayed until after the video backend is ready.
DolphinAnalytics::Instance().ReportGameStart();
+ // Clear performance data collected from previous threads.
+ g_perf_metrics.Reset();
+
#ifdef ANDROID
// For some reason, calling the JNI function AttachCurrentThread from the CPU thread after a
// certain point causes a crash if fastmem is enabled. Let's call it early to avoid that problem.
@@ -843,19 +845,15 @@ void RunOnCPUThread(std::function function, bool wait_for_completion)
// This should only be called from VI
void VideoThrottle()
{
+ g_perf_metrics.CountVBlank();
+
// Update info per second
u64 elapsed_ms = s_timer.ElapsedMs();
- if ((elapsed_ms >= 1000 && s_drawn_video.load() > 0) || s_frame_step)
+ if ((elapsed_ms >= 500) || s_frame_step)
{
s_timer.Start();
-
- UpdateTitle(elapsed_ms);
-
- s_drawn_frame.store(0);
- s_drawn_video.store(0);
+ UpdateTitle();
}
-
- s_drawn_video++;
}
// --- Callbacks for backends / engine ---
@@ -864,9 +862,9 @@ void VideoThrottle()
// frame is presented to the host screen
void Callback_FramePresented(double actual_emulation_speed)
{
- s_last_actual_emulation_speed = actual_emulation_speed;
+ g_perf_metrics.CountFrame();
- s_drawn_frame++;
+ s_last_actual_emulation_speed = actual_emulation_speed;
s_stop_frame_step.store(true);
}
@@ -891,15 +889,11 @@ void Callback_NewField()
}
}
-void UpdateTitle(u64 elapsed_ms)
+void UpdateTitle()
{
- if (elapsed_ms == 0)
- elapsed_ms = 1;
-
- float FPS = (float)(s_drawn_frame.load() * 1000.0 / elapsed_ms);
- float VPS = (float)(s_drawn_video.load() * 1000.0 / elapsed_ms);
- float Speed = (float)(s_drawn_video.load() * (100 * 1000.0) /
- (VideoInterface::GetTargetRefreshRate() * elapsed_ms));
+ float FPS = g_perf_metrics.GetFPS();
+ float VPS = g_perf_metrics.GetVPS();
+ float Speed = g_perf_metrics.GetSpeed();
// Settings are shown the same for both extended and summary info
const std::string SSettings = fmt::format(
@@ -956,15 +950,6 @@ void UpdateTitle(u64 elapsed_ms)
message += " | " + title;
}
- // Update the audio timestretcher with the current speed
- auto& system = Core::System::GetInstance();
- SoundStream* sound_stream = system.GetSoundStream();
- if (sound_stream)
- {
- Mixer* mixer = sound_stream->GetMixer();
- mixer->UpdateSpeed((float)Speed / 100);
- }
-
Host_UpdateTitle(message);
}
diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h
index 54c7692e39..89aad7d2de 100644
--- a/Source/Core/Core/Core.h
+++ b/Source/Core/Core/Core.h
@@ -126,7 +126,7 @@ void OnFrameEnd();
void VideoThrottle();
-void UpdateTitle(u64 elapsed_ms);
+void UpdateTitle();
// Run a function as the CPU thread.
//
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index 14b6f1b5cc..fefb6b5fe4 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -637,7 +637,6 @@
-
@@ -672,6 +671,8 @@
+
+
@@ -1244,7 +1245,6 @@
-
@@ -1272,6 +1272,8 @@
+
+
diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt
index 79bd9c7509..31f8153c2e 100644
--- a/Source/Core/DolphinQt/CMakeLists.txt
+++ b/Source/Core/DolphinQt/CMakeLists.txt
@@ -377,6 +377,7 @@ PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
uicommon
imgui
+ implot
)
if (WIN32)
diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp
index bd51c9e372..ebc08a9345 100644
--- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp
+++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp
@@ -51,7 +51,10 @@ void AdvancedWidget::CreateWidgets()
performance_box->setLayout(performance_layout);
m_show_fps = new GraphicsBool(tr("Show FPS"), Config::GFX_SHOW_FPS);
+ m_show_ftimes = new GraphicsBool(tr("Show Frame Times"), Config::GFX_SHOW_FTIMES);
m_show_vps = new GraphicsBool(tr("Show VPS"), Config::GFX_SHOW_VPS);
+ m_show_vtimes = new GraphicsBool(tr("Show VBlank Times"), Config::GFX_SHOW_VTIMES);
+ m_show_graphs = new GraphicsBool(tr("Show Performance Graphs"), Config::GFX_SHOW_GRAPHS);
m_show_speed = new GraphicsBool(tr("Show % Speed"), Config::GFX_SHOW_SPEED);
m_show_speed_colors = new GraphicsBool(tr("Show Speed Colors"), Config::GFX_SHOW_SPEED_COLORS);
m_perf_samp_window = new GraphicsInteger(0, 10000, Config::GFX_PERF_SAMP_WINDOW, 100);
@@ -59,12 +62,15 @@ void AdvancedWidget::CreateWidgets()
new GraphicsBool(tr("Log Render Time to File"), Config::GFX_LOG_RENDER_TIME_TO_FILE);
performance_layout->addWidget(m_show_fps, 0, 0);
+ performance_layout->addWidget(m_show_ftimes, 0, 1);
performance_layout->addWidget(m_show_vps, 1, 0);
- performance_layout->addWidget(m_show_speed, 0, 1);
- performance_layout->addWidget(new QLabel(tr("Performance Sample Window (ms):")), 2, 0);
- performance_layout->addWidget(m_perf_samp_window, 2, 1);
- performance_layout->addWidget(m_log_render_time, 3, 0);
- performance_layout->addWidget(m_show_speed_colors, 3, 1);
+ performance_layout->addWidget(m_show_vtimes, 1, 1);
+ performance_layout->addWidget(m_show_speed, 2, 0);
+ performance_layout->addWidget(m_show_graphs, 2, 1);
+ performance_layout->addWidget(new QLabel(tr("Performance Sample Window (ms):")), 3, 0);
+ performance_layout->addWidget(m_perf_samp_window, 3, 1);
+ performance_layout->addWidget(m_log_render_time, 4, 0);
+ performance_layout->addWidget(m_show_speed_colors, 4, 1);
// Debugging
auto* debugging_box = new QGroupBox(tr("Debugging"));
@@ -240,10 +246,22 @@ void AdvancedWidget::AddDescriptions()
QT_TR_NOOP("Shows the number of distinct frames rendered per second as a measure of "
"visual smoothness.
If unsure, leave this "
"unchecked.");
+ static const char TR_SHOW_FTIMES_DESCRIPTION[] =
+ QT_TR_NOOP("Shows the average time in ms between each distinct rendered frame alongside "
+ "the standard deviation.
If unsure, leave this "
+ "unchecked.");
static const char TR_SHOW_VPS_DESCRIPTION[] =
QT_TR_NOOP("Shows the number of frames rendered per second as a measure of "
"emulation speed.
If unsure, leave this "
"unchecked.");
+ static const char TR_SHOW_VTIMES_DESCRIPTION[] =
+ QT_TR_NOOP("Shows the average time in ms between each rendered frame alongside "
+ "the standard deviation.
If unsure, leave this "
+ "unchecked.");
+ static const char TR_SHOW_GRAPHS_DESCRIPTION[] =
+ QT_TR_NOOP("Shows frametime graph along with statistics as a representation of "
+ "emulation performance.
If unsure, leave this "
+ "unchecked.");
static const char TR_SHOW_SPEED_DESCRIPTION[] =
QT_TR_NOOP("Shows the % speed of emulation compared to full speed."
"
If unsure, leave this "
@@ -380,7 +398,10 @@ void AdvancedWidget::AddDescriptions()
QT_TR_NOOP("If unsure, leave this unchecked.");
m_show_fps->SetDescription(tr(TR_SHOW_FPS_DESCRIPTION));
+ m_show_ftimes->SetDescription(tr(TR_SHOW_FTIMES_DESCRIPTION));
m_show_vps->SetDescription(tr(TR_SHOW_VPS_DESCRIPTION));
+ m_show_vtimes->SetDescription(tr(TR_SHOW_VTIMES_DESCRIPTION));
+ m_show_graphs->SetDescription(tr(TR_SHOW_GRAPHS_DESCRIPTION));
m_show_speed->SetDescription(tr(TR_SHOW_SPEED_DESCRIPTION));
m_log_render_time->SetDescription(tr(TR_LOG_RENDERTIME_DESCRIPTION));
m_show_speed_colors->SetDescription(tr(TR_SHOW_SPEED_COLORS_DESCRIPTION));
diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h
index 58b7107cbb..156cf568d8 100644
--- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h
+++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h
@@ -36,7 +36,10 @@ private:
GraphicsBool* m_enable_format_overlay;
GraphicsBool* m_enable_api_validation;
GraphicsBool* m_show_fps;
+ GraphicsBool* m_show_ftimes;
GraphicsBool* m_show_vps;
+ GraphicsBool* m_show_vtimes;
+ GraphicsBool* m_show_graphs;
GraphicsBool* m_show_speed;
GraphicsBool* m_show_speed_colors;
GraphicsInteger* m_perf_samp_window;
diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index 3150cb49f9..85949f6a5f 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -425,6 +425,7 @@
+
diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt
index fc34f7a378..a7a52c5e40 100644
--- a/Source/Core/VideoCommon/CMakeLists.txt
+++ b/Source/Core/VideoCommon/CMakeLists.txt
@@ -27,8 +27,6 @@ add_library(videocommon
DriverDetails.h
Fifo.cpp
Fifo.h
- FPSCounter.cpp
- FPSCounter.h
FramebufferManager.cpp
FramebufferManager.h
FramebufferShaderGen.cpp
@@ -85,6 +83,10 @@ add_library(videocommon
OpcodeDecoding.h
PerfQueryBase.cpp
PerfQueryBase.h
+ PerformanceMetrics.cpp
+ PerformanceMetrics.h
+ PerformanceTracker.cpp
+ PerformanceTracker.h
PixelEngine.cpp
PixelEngine.h
PixelShaderGen.cpp
@@ -168,6 +170,7 @@ PRIVATE
spng
xxhash
imgui
+ implot
glslang
)
diff --git a/Source/Core/VideoCommon/FPSCounter.cpp b/Source/Core/VideoCommon/FPSCounter.cpp
deleted file mode 100644
index e5f396a094..0000000000
--- a/Source/Core/VideoCommon/FPSCounter.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2012 Dolphin Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "VideoCommon/FPSCounter.h"
-
-#include
-#include
-
-#include "Common/CommonTypes.h"
-#include "Common/FileUtil.h"
-#include "Common/Timer.h"
-#include "Core/Core.h"
-#include "VideoCommon/VideoConfig.h"
-
-static constexpr double US_TO_MS = 1000.0;
-static constexpr double US_TO_S = 1000000.0;
-
-static constexpr double FPS_SAMPLE_RC_RATIO = 0.25;
-
-FPSCounter::FPSCounter(const char* log_name)
-{
- m_last_time = Common::Timer::NowUs();
- m_log_name = log_name;
-
- 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(s64 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) << (val / US_TO_MS) << std::endl;
-}
-
-void FPSCounter::Update()
-{
- if (m_paused)
- return;
-
- const s64 time = Common::Timer::NowUs();
- const s64 diff = std::max(0, time - m_last_time);
- const s64 window = std::max(1, g_ActiveConfig.iPerfSampleUSec);
-
- m_raw_dt = diff / US_TO_S;
- m_last_time = time;
-
- m_dt_total += diff;
- m_dt_queue.push_back(diff);
-
- while (window <= m_dt_total - m_dt_queue.front())
- {
- m_dt_total -= m_dt_queue.front();
- m_dt_queue.pop_front();
- }
-
- // This frame count takes into account frames that are partially in the sample window
- const double fps = (m_dt_queue.size() * US_TO_S) / m_dt_total;
- const double rc = FPS_SAMPLE_RC_RATIO * std::min(window, m_dt_total) / US_TO_S;
- const double a = std::max(0.0, 1.0 - std::exp(-m_raw_dt / rc));
-
- // Sometimes euler averages can break when the average is inf/nan
- // This small check makes sure that if it does break, it gets fixed
- if (std::isfinite(m_avg_fps))
- m_avg_fps += a * (fps - m_avg_fps);
- else
- m_avg_fps = fps;
-
- if (g_ActiveConfig.bLogRenderTimeToFile)
- LogRenderTimeToFile(diff);
-}
-
-void FPSCounter::SetPaused(bool paused)
-{
- m_paused = paused;
- if (m_paused)
- {
- m_last_time_pause = Common::Timer::NowUs();
- }
- else
- {
- const s64 time = Common::Timer::NowUs();
- const s64 diff = time - m_last_time_pause;
- m_last_time += diff;
- }
-}
diff --git a/Source/Core/VideoCommon/FPSCounter.h b/Source/Core/VideoCommon/FPSCounter.h
deleted file mode 100644
index 852c39fd3b..0000000000
--- a/Source/Core/VideoCommon/FPSCounter.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2008 Dolphin Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include
-#include
-
-#include "Common/CommonTypes.h"
-
-class FPSCounter
-{
-public:
- explicit FPSCounter(const char* log_name = "log.txt");
- ~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();
-
- double GetFPS() const { return m_avg_fps; }
-
- double GetDeltaTime() const { return m_raw_dt; }
-
-private:
- void LogRenderTimeToFile(s64 val);
- void SetPaused(bool paused);
-
- const char* m_log_name;
- std::ofstream m_bench_file;
-
- bool m_paused = false;
- s64 m_last_time = 0;
-
- double m_avg_fps = 0.0;
- double m_raw_dt = 0.0;
-
- s64 m_dt_total = 0;
- std::deque m_dt_queue;
-
- int m_on_state_changed_handle = -1;
- s64 m_last_time_pause = 0;
-};
diff --git a/Source/Core/VideoCommon/PerformanceMetrics.cpp b/Source/Core/VideoCommon/PerformanceMetrics.cpp
new file mode 100644
index 0000000000..3b9cc81791
--- /dev/null
+++ b/Source/Core/VideoCommon/PerformanceMetrics.cpp
@@ -0,0 +1,221 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/PerformanceMetrics.h"
+
+#include
+#include
+
+#include "Core/HW/VideoInterface.h"
+#include "VideoCommon/VideoConfig.h"
+
+PerformanceMetrics g_perf_metrics;
+
+void PerformanceMetrics::Reset()
+{
+ m_fps_counter.Reset();
+ m_vps_counter.Reset();
+ m_speed_counter.Reset();
+}
+
+void PerformanceMetrics::CountFrame()
+{
+ m_fps_counter.Count();
+}
+
+void PerformanceMetrics::CountVBlank()
+{
+ m_vps_counter.Count();
+ m_speed_counter.Count();
+}
+
+double PerformanceMetrics::GetFPS() const
+{
+ return m_fps_counter.GetHzAvg();
+}
+
+double PerformanceMetrics::GetVPS() const
+{
+ return m_vps_counter.GetHzAvg();
+}
+
+double PerformanceMetrics::GetSpeed() const
+{
+ return 100.0 * m_speed_counter.GetHzAvg() / VideoInterface::GetTargetRefreshRate();
+}
+
+double PerformanceMetrics::GetLastSpeedDenominator() const
+{
+ return DT_s(m_speed_counter.GetLastRawDt()).count() * VideoInterface::GetTargetRefreshRate();
+}
+
+void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
+{
+ const float bg_alpha = 0.7f;
+ const auto imgui_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
+ ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
+ ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing;
+
+ const double fps = GetFPS();
+ const double vps = GetVPS();
+ const double speed = GetSpeed();
+
+ // Change Color based on % Speed
+ 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;
+ }
+
+ const float window_padding = 8.f * backbuffer_scale;
+ const float window_width = 93.f * backbuffer_scale;
+ float window_y = window_padding;
+ float window_x = ImGui::GetIO().DisplaySize.x - window_padding;
+
+ const float graph_width = 50.f * backbuffer_scale + 3.f * window_width + 2.f * window_padding;
+ const float graph_height =
+ std::min(200.f * backbuffer_scale, ImGui::GetIO().DisplaySize.y - 85.f * backbuffer_scale);
+
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 14.f * backbuffer_scale);
+ if (g_ActiveConfig.bShowGraphs)
+ {
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 4.f * 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(graph_width, graph_height));
+ ImGui::SetNextWindowBgAlpha(bg_alpha);
+ window_y += graph_height + window_padding;
+
+ 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};
+
+ 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 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()));
+
+ static double max_time = 0.0;
+ if (std::isfinite(max_time))
+ max_time += a * (target_max_time - max_time);
+ else
+ 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());
+
+ if (ImPlot::BeginPlot("PerformanceGraphs", ImVec2(-1.0, -1.0),
+ ImPlotFlags_NoFrame | ImPlotFlags_NoTitle | ImPlotFlags_NoMenus))
+ {
+ ImPlot::PushStyleColor(ImPlotCol_PlotBg, {0, 0, 0, 0});
+ ImPlot::PushStyleColor(ImPlotCol_LegendBg, {0, 0, 0, 0.2f});
+ ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, ImVec2(0.f, 0.f));
+ ImPlot::PushStyleVar(ImPlotStyleVar_LineWeight, 3.f);
+ ImPlot::SetupAxes(nullptr, nullptr,
+ ImPlotAxisFlags_Lock | ImPlotAxisFlags_Invert |
+ ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight,
+ 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::SetupLegend(ImPlotLocation_SouthEast, ImPlotLegendFlags_None);
+ m_vps_counter.ImPlotPlotLines("V-Blank (ms)");
+ m_fps_counter.ImPlotPlotLines("Frame (ms)");
+ ImPlot::EndPlot();
+ ImPlot::PopStyleVar(2);
+ ImPlot::PopStyleColor(2);
+ }
+ ImGui::PopStyleVar();
+ ImGui::End();
+ }
+ }
+
+ if (g_ActiveConfig.bShowFPS || g_ActiveConfig.bShowFTimes)
+ {
+ // Position in the top-right corner of the screen.
+ int count = g_ActiveConfig.bShowFPS + 2 * g_ActiveConfig.bShowFTimes;
+ 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 (ImGui::Begin("FPSStats", nullptr, imgui_flags))
+ {
+ if (g_ActiveConfig.bShowFPS)
+ ImGui::TextColored(ImVec4(r, g, b, 1.0f), "FPS:%7.2lf", fps);
+ if (g_ActiveConfig.bShowFTimes)
+ {
+ ImGui::TextColored(ImVec4(r, g, b, 1.0f), "dt:%6.2lfms",
+ DT_ms(m_fps_counter.GetDtAvg()).count());
+ ImGui::TextColored(ImVec4(r, g, b, 1.0f), " ±:%6.2lfms",
+ DT_ms(m_fps_counter.GetDtStd()).count());
+ }
+ ImGui::End();
+ }
+ }
+
+ 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;
+ 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 (ImGui::Begin("VPSStats", nullptr, imgui_flags))
+ {
+ if (g_ActiveConfig.bShowVPS)
+ ImGui::TextColored(ImVec4(r, g, b, 1.0f), "VPS:%7.2lf", vps);
+ if (g_ActiveConfig.bShowVTimes)
+ {
+ ImGui::TextColored(ImVec4(r, g, b, 1.0f), "dt:%6.2lfms",
+ DT_ms(m_vps_counter.GetDtAvg()).count());
+ ImGui::TextColored(ImVec4(r, g, b, 1.0f), " ±:%6.2lfms",
+ DT_ms(m_vps_counter.GetDtStd()).count());
+ }
+ ImGui::End();
+ }
+ }
+
+ 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
new file mode 100644
index 0000000000..e5dc87b801
--- /dev/null
+++ b/Source/Core/VideoCommon/PerformanceMetrics.h
@@ -0,0 +1,40 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "VideoCommon/PerformanceTracker.h"
+
+class PerformanceMetrics
+{
+public:
+ PerformanceMetrics() = default;
+ ~PerformanceMetrics() = default;
+
+ PerformanceMetrics(const PerformanceMetrics&) = delete;
+ PerformanceMetrics& operator=(const PerformanceMetrics&) = delete;
+ PerformanceMetrics(PerformanceMetrics&&) = delete;
+ PerformanceMetrics& operator=(PerformanceMetrics&&) = delete;
+
+public: // Count Functions
+ void Reset();
+ void CountFrame();
+ void CountVBlank();
+
+public: // Getter Functions
+ double GetFPS() const;
+ double GetVPS() const;
+ double GetSpeed() const;
+
+ double GetLastSpeedDenominator() const;
+
+public: // ImGui Functions
+ void DrawImGuiStats(const float backbuffer_scale) const;
+
+private: // Member Variables
+ PerformanceTracker m_fps_counter{"render_times.txt"};
+ PerformanceTracker m_vps_counter{"vblank_times.txt"};
+ PerformanceTracker m_speed_counter{nullptr, 6000000};
+};
+
+extern PerformanceMetrics g_perf_metrics;
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
+#include
+#include
+#include
+
+#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 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_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_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 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(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();
+ }
+}
diff --git a/Source/Core/VideoCommon/PerformanceTracker.h b/Source/Core/VideoCommon/PerformanceTracker.h
new file mode 100644
index 0000000000..d1b6ce01ee
--- /dev/null
+++ b/Source/Core/VideoCommon/PerformanceTracker.h
@@ -0,0 +1,105 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include "Common/CommonTypes.h"
+
+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_QUALITY_GRAPH_SIZE = 1UL << 8;
+
+ static inline std::size_t IncrementIndex(const std::size_t index)
+ {
+ return (index + 1) & (MAX_DT_QUEUE_SIZE - 1);
+ }
+
+ static inline std::size_t DecrementIndex(const std::size_t index)
+ {
+ return (index - 1) & (MAX_DT_QUEUE_SIZE - 1);
+ }
+
+ static inline std::size_t GetDifference(const std::size_t begin, const std::size_t end)
+ {
+ return (end - begin) & (MAX_DT_QUEUE_SIZE - 1);
+ }
+
+public:
+ PerformanceTracker(const char* log_name = nullptr,
+ const std::optional sample_window_us = {});
+ ~PerformanceTracker();
+
+ PerformanceTracker(const PerformanceTracker&) = delete;
+ PerformanceTracker& operator=(const PerformanceTracker&) = delete;
+ PerformanceTracker(PerformanceTracker&&) = delete;
+ PerformanceTracker& operator=(PerformanceTracker&&) = delete;
+
+public: // Functions for recording and accessing information
+ void Reset();
+ void Count();
+
+ DT GetSampleWindow() const;
+
+ double GetHzAvg() const;
+
+ DT GetDtAvg() const;
+ DT GetDtStd() const;
+
+ DT GetLastRawDt() const;
+
+ void ImPlotPlotLines(const char* label) const;
+
+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;
+
+ std::size_t inline QueueSize() const;
+ bool inline QueueEmpty() const;
+
+private: // Handle pausing and logging
+ void LogRenderTimeToFile(DT val);
+ void SetPaused(bool paused);
+
+private:
+ // Handle not counting time during pauses
+ bool m_paused = false;
+ int m_on_state_changed_handle;
+
+ // Name of log file and file stream
+ const char* m_log_name;
+ std::ofstream m_bench_file;
+
+ // Last time Count() was called
+ TimePoint m_last_time;
+
+ // Amount of time to sample dt's over (defaults to config)
+ 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;
+ std::size_t m_dt_queue_begin = 0;
+ std::size_t m_dt_queue_end = 0;
+
+ // Average rate/time throughout the window
+ DT m_dt_avg = DT::zero(); // Uses Moving Average
+ double m_hz_avg = 0.0; // Uses Moving Average + Euler Average
+
+ // Used to initialize this on demand instead of on every Count()
+ mutable std::optional m_dt_std = std::nullopt;
+
+ // Used to enable thread safety with the performance tracker
+ mutable std::mutex m_mutex;
+};
diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp
index f6322bc36f..43e7de5e10 100644
--- a/Source/Core/VideoCommon/RenderBase.cpp
+++ b/Source/Core/VideoCommon/RenderBase.cpp
@@ -23,6 +23,7 @@
#include
#include
+#include
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
@@ -63,7 +64,6 @@
#include "VideoCommon/BoundingBox.h"
#include "VideoCommon/CPMemory.h"
#include "VideoCommon/CommandProcessor.h"
-#include "VideoCommon/FPSCounter.h"
#include "VideoCommon/FrameDump.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/FramebufferShaderGen.h"
@@ -592,46 +592,6 @@ void Renderer::CheckForConfigChanges()
// Create On-Screen-Messages
void Renderer::DrawDebugText()
{
- if (g_ActiveConfig.bShowFPS || g_ActiveConfig.bShowVPS || g_ActiveConfig.bShowSpeed)
- {
- // Position in the top-right corner of the screen.
- ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - (10.0f * m_backbuffer_scale),
- 10.f * m_backbuffer_scale),
- ImGuiCond_Always, ImVec2(1.0f, 0.0f));
-
- int count = g_ActiveConfig.bShowFPS + g_ActiveConfig.bShowVPS + g_ActiveConfig.bShowSpeed;
- ImGui::SetNextWindowSize(
- ImVec2(94.f * m_backbuffer_scale, (12.f + 17.f * count) * m_backbuffer_scale));
-
- if (ImGui::Begin("Performance", nullptr,
- ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
- ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
- ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
- ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
- {
- const double fps = m_fps_counter.GetFPS();
- const double vps = m_vps_counter.GetFPS();
- const double speed = 100.0 * vps / VideoInterface::GetTargetRefreshRate();
-
- // Change Color based on % Speed
- 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;
- }
-
- if (g_ActiveConfig.bShowFPS)
- ImGui::TextColored(ImVec4(r, g, b, 1.0f), "FPS:%7.2lf", fps);
- if (g_ActiveConfig.bShowVPS)
- ImGui::TextColored(ImVec4(r, g, b, 1.0f), "VPS:%7.2lf", vps);
- if (g_ActiveConfig.bShowSpeed)
- ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Speed:%4.0lf%%", speed);
- }
- ImGui::End();
- }
-
const bool show_movie_window =
Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) ||
Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) ||
@@ -1046,6 +1006,11 @@ bool Renderer::InitializeImGui()
PanicAlertFmt("Creating ImGui context failed");
return false;
}
+ if (!ImPlot::CreateContext())
+ {
+ PanicAlertFmt("Creating ImPlot context failed");
+ return false;
+ }
// Don't create an ini file. TODO: Do we want this in the future?
ImGui::GetIO().IniFilename = nullptr;
@@ -1159,6 +1124,7 @@ void Renderer::ShutdownImGui()
std::unique_lock imgui_lock(m_imgui_mutex);
ImGui::EndFrame();
+ ImPlot::DestroyContext();
ImGui::DestroyContext();
m_imgui_pipeline.reset();
m_imgui_vertex_format.reset();
@@ -1390,10 +1356,6 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6
g_texture_cache->GetXFBTexture(xfb_addr, fb_width, fb_height, fb_stride, &xfb_rect);
const bool is_duplicate_frame = xfb_entry->id == m_last_xfb_id;
- m_vps_counter.Update();
- if (!is_duplicate_frame)
- m_fps_counter.Update();
-
if (xfb_entry && (!g_ActiveConfig.bSkipPresentingDuplicateXFBs || !is_duplicate_frame))
{
m_last_xfb_id = xfb_entry->id;
@@ -1407,6 +1369,7 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6
{
auto lock = GetImGuiLock();
+ g_perf_metrics.DrawImGuiStats(m_backbuffer_scale);
DrawDebugText();
OSD::DrawMessages();
ImGui::Render();
@@ -1485,8 +1448,7 @@ 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);
- const double last_speed_denominator =
- m_fps_counter.GetDeltaTime() * VideoInterface::GetTargetRefreshRate();
+ const double last_speed_denominator = g_perf_metrics.GetLastSpeedDenominator();
// 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;
diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h
index c44e2ebabe..e45cc244c1 100644
--- a/Source/Core/VideoCommon/RenderBase.h
+++ b/Source/Core/VideoCommon/RenderBase.h
@@ -28,9 +28,9 @@
#include "Common/MathUtil.h"
#include "VideoCommon/AsyncShaderCompiler.h"
#include "VideoCommon/BPMemory.h"
-#include "VideoCommon/FPSCounter.h"
#include "VideoCommon/FrameDump.h"
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
+#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/TextureConfig.h"
@@ -338,9 +338,6 @@ protected:
MathUtil::Rectangle m_target_rectangle = {};
int m_frame_count = 0;
- FPSCounter m_fps_counter = FPSCounter("render_times.txt");
- FPSCounter m_vps_counter = FPSCounter("v_blank_times.txt");
-
std::unique_ptr m_post_processor;
void* m_new_surface_handle = nullptr;
diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp
index 9769b46668..66ba09c581 100644
--- a/Source/Core/VideoCommon/VideoConfig.cpp
+++ b/Source/Core/VideoCommon/VideoConfig.cpp
@@ -64,7 +64,10 @@ void VideoConfig::Refresh()
bCrop = Config::Get(Config::GFX_CROP);
iSafeTextureCache_ColorSamples = Config::Get(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES);
bShowFPS = Config::Get(Config::GFX_SHOW_FPS);
+ bShowFTimes = Config::Get(Config::GFX_SHOW_FTIMES);
bShowVPS = Config::Get(Config::GFX_SHOW_VPS);
+ bShowVTimes = Config::Get(Config::GFX_SHOW_VTIMES);
+ bShowGraphs = Config::Get(Config::GFX_SHOW_GRAPHS);
bShowSpeed = Config::Get(Config::GFX_SHOW_SPEED);
bShowSpeedColors = Config::Get(Config::GFX_SHOW_SPEED_COLORS);
iPerfSampleUSec = Config::Get(Config::GFX_PERF_SAMP_WINDOW) * 1000;
diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h
index b1b776ab80..d60f969b8d 100644
--- a/Source/Core/VideoCommon/VideoConfig.h
+++ b/Source/Core/VideoCommon/VideoConfig.h
@@ -89,7 +89,10 @@ struct VideoConfig final
// Information
bool bShowFPS = false;
+ bool bShowFTimes = false;
bool bShowVPS = false;
+ bool bShowVTimes = false;
+ bool bShowGraphs = false;
bool bShowSpeed = false;
bool bShowSpeedColors = false;
int iPerfSampleUSec = 0;