diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp
index cbc2fb1a9749c..3a97feddd7a7f 100644
--- a/pcsx2-gsrunner/Main.cpp
+++ b/pcsx2-gsrunner/Main.cpp
@@ -473,8 +473,8 @@ static void PrintCommandLineHelp(const char* progname)
std::fprintf(stderr, " -help: Displays this information and exits.\n");
std::fprintf(stderr, " -version: Displays version information and exits.\n");
std::fprintf(stderr, " -dumpdir
: Frame dump directory (will be dumped as filename_frameN.png).\n");
- std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr]: Enabling dumping of render target, texture, z buffer, frame, "
- "alphas, and info (context, vertices, transfers (list)), transfers (images), respectively, per draw. Generates lots of data.\n");
+ std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr|ds|fs]: Enabling dumping of render target, texture, z buffer, frame, "
+ "alphas, and info (context, vertices, list of transfers), transfers images, draw stats, frame stats, respectively, per draw. Generates lots of data.\n");
std::fprintf(stderr, " -dumprange N[,L,B]: Start dumping from draw N (base 0), stops after L draws, and only "
"those draws that are multiples of B (intersection of -dumprange and -dumprangef used)."
"Defaults to 0,-1,1 (all draws). Only used if -dump used.\n");
@@ -560,6 +560,10 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveInfo", true);
if (str.find("tr") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveTransferImages", true);
+ if (str.find("ds") != std::string::npos)
+ s_settings_interface.SetBoolValue("EmuCore/GS", "SaveDrawStats", true);
+ if (str.find("fs") != std::string::npos)
+ s_settings_interface.SetBoolValue("EmuCore/GS", "SaveFrameStats", true);
continue;
}
else if (CHECK_ARG_PARAM("-dumprange"))
diff --git a/pcsx2-qt/Settings/DebugGSSettingsTab.ui b/pcsx2-qt/Settings/DebugGSSettingsTab.ui
index f262c2eb37108..7b97c64ee15cb 100644
--- a/pcsx2-qt/Settings/DebugGSSettingsTab.ui
+++ b/pcsx2-qt/Settings/DebugGSSettingsTab.ui
@@ -71,7 +71,21 @@
- -
+
-
+
+
+ Save Draw Stats
+
+
+
+ -
+
+
+ Save Frame Stats
+
+
+
+ -
Save Transfer Image Data
diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.cpp b/pcsx2-qt/Settings/DebugSettingsWidget.cpp
index 2cd0e63849b07..5630f12ecf0b8 100644
--- a/pcsx2-qt/Settings/DebugSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/DebugSettingsWidget.cpp
@@ -110,6 +110,8 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* settings_dialog, QWidge
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveAlpha, "EmuCore/GS", "SaveAlpha", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveInfo, "EmuCore/GS", "SaveInfo", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveTransferImages, "EmuCore/GS", "SaveTransferImages", false);
+ SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveDrawStats, "EmuCore/GS", "SaveDrawStats", false);
+ SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveFrameStats, "EmuCore/GS", "SaveFrameStats", false);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawStart, "EmuCore/GS", "SaveDrawStart", 0);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawCount, "EmuCore/GS", "SaveDrawCount", 5000);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveFrameStart, "EmuCore/GS", "SaveFrameStart", 0);
@@ -215,6 +217,8 @@ void DebugSettingsWidget::onDrawDumpingChanged()
m_gs.saveAlpha->setEnabled(enabled);
m_gs.saveInfo->setEnabled(enabled);
m_gs.saveTransferImages->setEnabled(enabled);
+ m_gs.saveDrawStats->setEnabled(enabled);
+ m_gs.saveFrameStats->setEnabled(enabled);
m_gs.saveDrawStart->setEnabled(enabled);
m_gs.saveDrawCount->setEnabled(enabled);
m_gs.saveFrameStart->setEnabled(enabled);
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index a447934501319..2affcb8f37f49 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -775,6 +775,8 @@ struct Pcsx2Config
SaveAlpha : 1,
SaveInfo : 1,
SaveTransferImages : 1,
+ SaveDrawStats : 1,
+ SaveFrameStats : 1,
DumpReplaceableTextures : 1,
DumpReplaceableMipmaps : 1,
DumpTexturesWithFMVActive : 1,
diff --git a/pcsx2/GS/GSPerfMon.cpp b/pcsx2/GS/GSPerfMon.cpp
index a3e5624107cf1..cf8ae3eb17aff 100644
--- a/pcsx2/GS/GSPerfMon.cpp
+++ b/pcsx2/GS/GSPerfMon.cpp
@@ -3,8 +3,10 @@
#include "GSPerfMon.h"
#include "GS.h"
+#include "GSUtil.h"
#include
+#include
GSPerfMon g_perfmon;
@@ -41,3 +43,28 @@ void GSPerfMon::Update()
memset(m_counters, 0, sizeof(m_counters));
}
+
+GSPerfMon GSPerfMon::operator-(const GSPerfMon& other)
+{
+ GSPerfMon diff;
+ for (std::size_t i = 0; i < std::size(diff.m_counters); i++)
+ {
+ diff.m_counters[i] = m_counters[i] - other.m_counters[i];
+ }
+ return diff;
+}
+
+void GSPerfMon::Dump(const std::string& filename, bool hw)
+{
+ FILE* fp = fopen(filename.c_str(), "w");
+ if (!fp)
+ return;
+
+ std::size_t last = hw ? CounterLastHW : CounterLastSW;
+ for (std::size_t i = 0; i < last; i++)
+ {
+ fprintf(fp, "%s: %" PRIu64 "\n", GSUtil::GetPerfMonCounterName(static_cast(i), hw), static_cast(m_counters[i]));
+ }
+
+ fclose(fp);
+}
diff --git a/pcsx2/GS/GSPerfMon.h b/pcsx2/GS/GSPerfMon.h
index ad0534bf473d0..eb47c727bdbd3 100644
--- a/pcsx2/GS/GSPerfMon.h
+++ b/pcsx2/GS/GSPerfMon.h
@@ -6,6 +6,7 @@
#include "common/Pcsx2Defs.h"
#include
+#include
class GSPerfMon
{
@@ -27,6 +28,9 @@ class GSPerfMon
// Reused counters for HW.
TextureCopies = Fillrate,
TextureUploads = SyncPoint,
+
+ CounterLastHW = CounterLast,
+ CounterLastSW = SyncPoint + 1
};
protected:
@@ -58,6 +62,10 @@ class GSPerfMon
m_disp_fb_sprite_blits = 0;
return blits;
}
+
+ GSPerfMon operator-(const GSPerfMon& other);
+
+ void Dump(const std::string& filename, bool hw);
};
extern GSPerfMon g_perfmon;
\ No newline at end of file
diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp
index 425a5dabaf3ed..e4a8e6f444d4f 100644
--- a/pcsx2/GS/GSState.cpp
+++ b/pcsx2/GS/GSState.cpp
@@ -202,6 +202,9 @@ void GSState::Reset(bool hardware_reset)
m_backed_up_ctx = -1;
memcpy(&m_prev_env, &m_env, sizeof(m_prev_env));
+
+ m_perfmon_draw.Reset();
+ m_perfmon_frame.Reset();
}
template
@@ -2125,6 +2128,16 @@ void GSState::FlushPrim()
g_perfmon.Put(GSPerfMon::Draw, 1);
g_perfmon.Put(GSPerfMon::Prim, m_index.tail / GSUtil::GetVertexCount(PRIM->PRIM));
+ if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
+ {
+ if (GSConfig.SaveDrawStats)
+ {
+ m_perfmon_draw = g_perfmon - m_perfmon_draw;
+ m_perfmon_draw.Dump(GetDrawDumpPath("%05d_draw_stats.txt", s_n), GSIsHardwareRenderer());
+ m_perfmon_draw = g_perfmon;
+ }
+ }
+
m_index.tail = 0;
m_vertex.head = 0;
diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h
index 7e69d85583d1e..31f9fb14e3bd3 100644
--- a/pcsx2/GS/GSState.h
+++ b/pcsx2/GS/GSState.h
@@ -4,6 +4,7 @@
#pragma once
#include "GS/GS.h"
+#include "GS/GSPerfMon.h"
#include "GS/GSLocalMemory.h"
#include "GS/GSDrawingContext.h"
#include "GS/GSDrawingEnvironment.h"
@@ -262,6 +263,9 @@ class GSState : public GSAlignedClass<32>
static int s_last_transfer_draw_n;
static int s_transfer_n;
+ GSPerfMon m_perfmon_frame; // Track stat across a frame.
+ GSPerfMon m_perfmon_draw; // Track stat across a draw.
+
static constexpr u32 STATE_VERSION = 9;
enum REG_DIRTY
diff --git a/pcsx2/GS/GSUtil.cpp b/pcsx2/GS/GSUtil.cpp
index 17603d50a55db..3f4cd6090a5f1 100644
--- a/pcsx2/GS/GSUtil.cpp
+++ b/pcsx2/GS/GSUtil.cpp
@@ -176,6 +176,40 @@ const char* GSUtil::GetACName(u32 ac)
return (ac < std::size(names)) ? names[ac] : "";
}
+const char* GSUtil::GetPerfMonCounterName(GSPerfMon::counter_t counter, bool hw)
+{
+ if (hw)
+ {
+ static constexpr const char* names_hw[GSPerfMon::CounterLastHW] = {
+ "Prim",
+ "Draw",
+ "DrawCalls",
+ "Readbacks",
+ "Swizzle",
+ "Unswizzle",
+ "TextureCopies",
+ "TextureUploads",
+ "Barriers",
+ "RenderPasses"
+ };
+ return counter < std::size(names_hw) ? names_hw[counter] : "";
+ }
+ else
+ {
+ static constexpr const char* names_sw[GSPerfMon::CounterLastSW] = {
+ "Prim",
+ "Draw",
+ "DrawCalls",
+ "Readbacks",
+ "Swizzle",
+ "Unswizzle",
+ "Fillrate",
+ "SyncPoint"
+ };
+ return counter < std::size(names_sw) ? names_sw[counter] : "";
+ }
+}
+
const u32* GSUtil::HasSharedBitsPtr(u32 dpsm)
{
return s_maps.SharedBitsField[dpsm];
diff --git a/pcsx2/GS/GSUtil.h b/pcsx2/GS/GSUtil.h
index b37407eb50789..773b4d82259be 100644
--- a/pcsx2/GS/GSUtil.h
+++ b/pcsx2/GS/GSUtil.h
@@ -5,6 +5,7 @@
#include "GS.h"
#include "GSRegs.h"
+#include "GSPerfMon.h"
class GSUtil
{
@@ -25,6 +26,7 @@ class GSUtil
static const char* GetTFXName(u32 tfx);
static const char* GetTCCName(u32 tcc);
static const char* GetACName(u32 ac);
+ static const char* GetPerfMonCounterName(GSPerfMon::counter_t counter, bool hw = true);
static const u32* HasSharedBitsPtr(u32 dpsm);
static bool HasSharedBits(u32 spsm, const u32* ptr);
diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp
index 35abfb183547f..c9fbf7752391b 100644
--- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp
+++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp
@@ -591,6 +591,13 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
if (GSConfig.SaveTransferImages)
DumpTransferImages();
+
+ if (GSConfig.SaveFrameStats)
+ {
+ m_perfmon_frame = g_perfmon - m_perfmon_frame;
+ m_perfmon_frame.Dump(GetDrawDumpPath("%05d_f%05lld_frame_stats.txt", s_n, g_perfmon.GetFrame()), GSIsHardwareRenderer());
+ m_perfmon_frame = g_perfmon;
+ }
}
const int fb_sprite_blits = g_perfmon.GetDisplayFramebufferSpriteBlits();
diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp
index 13cd12578e104..47a0b33540123 100644
--- a/pcsx2/Pcsx2Config.cpp
+++ b/pcsx2/Pcsx2Config.cpp
@@ -989,6 +989,8 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBoolEx(SaveAlpha, "SaveAlpha");
SettingsWrapBitBoolEx(SaveInfo, "SaveInfo");
SettingsWrapBitBoolEx(SaveTransferImages, "SaveTransferImages");
+ SettingsWrapBitBoolEx(SaveDrawStats, "SaveDrawStats");
+ SettingsWrapBitBoolEx(SaveFrameStats, "SaveFrameStats");
SettingsWrapBitBool(DumpReplaceableTextures);
SettingsWrapBitBool(DumpReplaceableMipmaps);
SettingsWrapBitBool(DumpTexturesWithFMVActive);