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);