From d5776e8946894822f0207d6c631af4c224f122ed Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 20 Jul 2023 18:37:36 +1000 Subject: [PATCH] GSRunner: Dump/compare stats --- pcsx2-gsrunner/Main.cpp | 33 +++++++++++++++++++++++ pcsx2-gsrunner/test_check_dumps.py | 43 ++++++++++++++++++++++++++++-- pcsx2-gsrunner/test_run_dumps.py | 2 ++ pcsx2/GS/GSPerfMon.h | 1 + 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index 68557f133a..681b41ee83 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -13,6 +13,7 @@ * If not, see . */ +#include #include #include #include @@ -39,6 +40,7 @@ #include "pcsx2/Achievements.h" #include "pcsx2/CDVD/CDVD.h" #include "pcsx2/GS.h" +#include "pcsx2/GS/GSPerfMon.h" #include "pcsx2/GSDumpReplayer.h" #include "pcsx2/Host.h" #include "pcsx2/INISettingsInterface.h" @@ -57,6 +59,7 @@ namespace GSRunner static void InitializeConsole(); static bool InitializeConfig(); static bool ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& params); + static void DumpStats(); static bool CreatePlatformWindow(); static void DestroyPlatformWindow(); @@ -77,6 +80,12 @@ static bool s_no_console = false; // Owned by the GS thread. static u32 s_dump_frame_number = 0; static u32 s_loop_number = s_loop_count; +static u64 s_total_draws = 0; +static u64 s_total_render_passes = 0; +static u64 s_total_barriers = 0; +static u64 s_total_copies = 0; +static u64 s_total_uploads = 0; +static u32 s_total_frames = 0; bool GSRunner::InitializeConfig() { @@ -265,6 +274,17 @@ void Host::BeginPresentFrame() std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number)); GSQueueSnapshot(dump_path); } + + if (GSConfig.UseHardwareRenderer()) + { + s_total_draws += static_cast(g_perfmon.GetCounter(GSPerfMon::DrawCalls)); + s_total_render_passes += static_cast(g_perfmon.GetCounter(GSPerfMon::RenderPasses)); + s_total_barriers += static_cast(g_perfmon.GetCounter(GSPerfMon::Barriers)); + s_total_copies += static_cast(g_perfmon.GetCounter(GSPerfMon::TextureCopies)); + s_total_uploads += static_cast(g_perfmon.GetCounter(GSPerfMon::TextureUploads)); + s_total_frames++; + std::atomic_thread_fence(std::memory_order_release); + } } void Host::RequestResizeHostDisplay(s32 width, s32 height) @@ -601,6 +621,18 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa return true; } +void GSRunner::DumpStats() +{ + std::atomic_thread_fence(std::memory_order_acquire); + Console.WriteLn(fmt::format("======= HW STATISTICS FOR {} FRAMES ========", s_total_frames)); + Console.WriteLn(fmt::format("@HWSTAT@ Draw Calls: {} (avg {})", s_total_draws, static_cast(std::ceil(s_total_draws / static_cast(s_total_frames))))); + Console.WriteLn(fmt::format("@HWSTAT@ Render Passes: {} (avg {})", s_total_render_passes, static_cast(std::ceil(s_total_render_passes / static_cast(s_total_frames))))); + Console.WriteLn(fmt::format("@HWSTAT@ Barriers: {} (avg {})", s_total_barriers, static_cast(std::ceil(s_total_barriers / static_cast(s_total_frames))))); + Console.WriteLn(fmt::format("@HWSTAT@ Copies: {} (avg {})", s_total_copies, static_cast(std::ceil(s_total_copies / static_cast(s_total_frames))))); + Console.WriteLn(fmt::format("@HWSTAT@ Uploads: {} (avg {})", s_total_uploads, static_cast(std::ceil(s_total_uploads / static_cast(s_total_frames))))); + Console.WriteLn("============================================"); +} + int main(int argc, char* argv[]) { GSRunner::InitializeConsole(); @@ -636,6 +668,7 @@ int main(int argc, char* argv[]) while (VMManager::GetState() == VMState::Running) VMManager::Execute(); VMManager::Shutdown(false); + GSRunner::DumpStats(); } VMManager::Internal::CPUThreadShutdown(); diff --git a/pcsx2-gsrunner/test_check_dumps.py b/pcsx2-gsrunner/test_check_dumps.py index 5ad060b04e..938d79d687 100644 --- a/pcsx2-gsrunner/test_check_dumps.py +++ b/pcsx2-gsrunner/test_check_dumps.py @@ -39,6 +39,36 @@ def compare_frames(path1, path2): return False +def extract_stats(file): + stats = {} + try: + with open(file, "r") as f: + for line in f.readlines(): + m = re.match(".*@HWSTAT@ ([^:]+): (.*) \(avg ([^)]+)\)$", line) + if m is None: + continue + stats[m[1]] = int(m[3]) + except FileNotFoundError: + pass + except IOError: + pass + return stats + + +def compare_stats(baselinedir, testdir): + stats1 = extract_stats(os.path.join(baselinedir, "emulog.txt")) + stats2 = extract_stats(os.path.join(testdir, "emulog.txt")) + res = [] + for statname in stats1.keys(): + if statname not in stats2 or stats1[statname] == stats2[statname]: + continue + v2 = stats2[statname] + v1 = stats1[statname] + delta = v2 - v1 + res.append("%s: %s%d [%d=>%d]" % (statname, "+" if delta > 0 else "", delta, v1, v2)) + return res + + def check_regression_test(baselinedir, testdir, name): #print("Checking '%s'..." % name) @@ -51,6 +81,7 @@ def check_regression_test(baselinedir, testdir, name): images = glob.glob(os.path.join(dir1, "*_frame*.png")) diff_frames = [] first_fail = True + stats = compare_stats(dir1, dir2) for imagepath in images: imagename = Path(imagepath).name @@ -85,9 +116,15 @@ def check_regression_test(baselinedir, testdir, name): write("") write("
Difference in frames [%s] for %s
" % (",".join(map(str, diff_frames)), name)) print("*** Difference in frames [%s] for %s" % (",".join(map(str, diff_frames)), name)) - return False + if len(stats) > 0: + write("
%s
" % "\n".join(stats)) + print(stats) + elif len(stats) > 0: + write("

{}

".format(name)) + write("
%s
" % "\n".join(stats)) + print(name, stats) - return True + return len(diff_frames) == 0 def check_regression_tests(baselinedir, testdir): @@ -103,6 +140,8 @@ def check_regression_tests(baselinedir, testdir): else: failure += 1 + print("%d dumps unchanged" % success) + print("%d dumps changed" % failure) return (failure == 0) diff --git a/pcsx2-gsrunner/test_run_dumps.py b/pcsx2-gsrunner/test_run_dumps.py index b9426defdb..0a001790a8 100644 --- a/pcsx2-gsrunner/test_run_dumps.py +++ b/pcsx2-gsrunner/test_run_dumps.py @@ -24,6 +24,8 @@ def run_regression_test(runner, dumpdir, renderer, upscale, renderhacks, paralle real_dumpdir = os.path.join(dumpdir, gsname).strip() if not os.path.exists(real_dumpdir): os.mkdir(real_dumpdir) + else: + return if renderer is not None: args.extend(["-renderer", renderer]) diff --git a/pcsx2/GS/GSPerfMon.h b/pcsx2/GS/GSPerfMon.h index 75e8bff84e..9932017d6c 100644 --- a/pcsx2/GS/GSPerfMon.h +++ b/pcsx2/GS/GSPerfMon.h @@ -55,6 +55,7 @@ public: void EndFrame(); void Put(counter_t c, double val) { m_counters[c] += val; } + double GetCounter(counter_t c) { return m_counters[c]; } double Get(counter_t c) { return m_stats[c]; } void Update();