mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
PerformanceMetrics: count the GS back thread
Under GSBackThreadMode >= Lockstep roughly half the GS work moves to a second thread, and every surface that reports GS cost -- OSD, PerfLog, the Qt status bar, PINE stats, gsrunner's @HWSTAT@ block -- measured the MTGS thread alone. So the split read as a large GS saving. It is not: on a Rogue Galaxy savestate here, mode 0 costs 15.8% / 2.63 ms and mode 3 costs 17.0% / 2.84 ms plus 14.2% / 2.37 ms on the back thread -- about twice the total GS CPU time, bought to halve the critical path. That is a real trade, but nobody could see it. The back thread registers its own handle at entry, as the SW rasterizer workers do; StopBackThread clears it after the join. Unlike every other handle here it is written by a thread other than the one sampling it, so the handle and its running total sit behind a mutex taken twice a second. Installing a handle rebases the total off it, so the first window after a GSreopen respawn measures the new thread rather than its difference against the retired one's. The figure is omitted, not reported as zero, wherever a back thread does not exist -- otherwise a mode 0 vs mode 3 comparison reads a permanent 0% as meaningful. gsrunner latches the presence flag during the run because DumpStats executes after VMManager::Shutdown, by which point the thread has joined.
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "common/Console.h"
|
||||
@@ -61,10 +63,31 @@ static u32 s_gs_privileged_register_writes_since_last_update = 0;
|
||||
static Threading::ThreadHandle s_cpu_thread_handle;
|
||||
static u64 s_last_cpu_time = 0;
|
||||
static u64 s_last_gs_time = 0;
|
||||
static u64 s_last_gs_back_time = 0;
|
||||
static u64 s_last_vu_time = 0;
|
||||
static u64 s_last_capture_time = 0;
|
||||
static u64 s_last_ticks = 0;
|
||||
|
||||
// The GS back thread registers itself from its own entry point and is cleared from the MTGS
|
||||
// thread after the join, so unlike every other handle here this one is written by a thread
|
||||
// other than the one sampling it. Handle and running total are therefore both owned by the
|
||||
// mutex, which is taken twice a second at most.
|
||||
static std::mutex s_gs_back_thread_mutex;
|
||||
static Threading::ThreadHandle s_gs_back_thread_handle;
|
||||
|
||||
/// CPU time consumed by the back thread since the last call, in Threading tick units.
|
||||
/// A thread that started or stopped inside the window contributes only the part of it that
|
||||
/// the handle was installed for; the alternative is a u64 subtraction that wraps into a
|
||||
/// nonsense percentage on the first window after a GSreopen.
|
||||
static u64 SampleGSBackThreadCPUTime()
|
||||
{
|
||||
std::unique_lock lock(s_gs_back_thread_mutex);
|
||||
const u64 now = s_gs_back_thread_handle ? s_gs_back_thread_handle.GetCPUTime() : s_last_gs_back_time;
|
||||
const u64 delta = (now > s_last_gs_back_time) ? (now - s_last_gs_back_time) : 0;
|
||||
s_last_gs_back_time = now;
|
||||
return delta;
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// ---- Android ADPF (PerformanceHintManager) ---------------------------------
|
||||
// Tells the OS "these threads produce a frame every N ns, clock them to hit it."
|
||||
@@ -166,6 +189,8 @@ static double s_cpu_thread_usage = 0.0f;
|
||||
static double s_cpu_thread_time = 0.0f;
|
||||
static float s_gs_thread_usage = 0.0f;
|
||||
static float s_gs_thread_time = 0.0f;
|
||||
static float s_gs_back_thread_usage = 0.0f;
|
||||
static float s_gs_back_thread_time = 0.0f;
|
||||
static float s_vu_thread_usage = 0.0f;
|
||||
static float s_vu_thread_time = 0.0f;
|
||||
static float s_capture_thread_usage = 0.0f;
|
||||
@@ -208,6 +233,8 @@ void PerformanceMetrics::Clear()
|
||||
s_cpu_thread_time = 0.0f;
|
||||
s_gs_thread_usage = 0.0f;
|
||||
s_gs_thread_time = 0.0f;
|
||||
s_gs_back_thread_usage = 0.0f;
|
||||
s_gs_back_thread_time = 0.0f;
|
||||
s_vu_thread_usage = 0.0f;
|
||||
s_vu_thread_time = 0.0f;
|
||||
s_capture_thread_usage = 0.0f;
|
||||
@@ -255,6 +282,7 @@ void PerformanceMetrics::Reset()
|
||||
|
||||
s_last_cpu_time = s_cpu_thread_handle.GetCPUTime();
|
||||
s_last_gs_time = MTGS::GetThreadHandle().GetCPUTime();
|
||||
SampleGSBackThreadCPUTime(); // rebases the running total; the delta is deliberately dropped
|
||||
s_last_vu_time = THREAD_VU1 ? vu1Thread.GetThreadHandle().GetCPUTime() : 0;
|
||||
s_last_ticks = GetCPUTicks();
|
||||
s_last_capture_time = GSCapture::IsCapturing() ? GSCapture::GetEncoderThreadHandle().GetCPUTime() : 0;
|
||||
@@ -332,6 +360,7 @@ void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit, bool is_sk
|
||||
|
||||
const u64 cpu_time = s_cpu_thread_handle.GetCPUTime();
|
||||
const u64 gs_time = MTGS::GetThreadHandle().GetCPUTime();
|
||||
const u64 gs_back_delta = SampleGSBackThreadCPUTime();
|
||||
const u64 vu_time = THREAD_VU1 ? vu1Thread.GetThreadHandle().GetCPUTime() : 0;
|
||||
const u64 capture_time = GSCapture::IsCapturing() ? GSCapture::GetEncoderThreadHandle().GetCPUTime() : 0;
|
||||
|
||||
@@ -346,10 +375,12 @@ void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit, bool is_sk
|
||||
|
||||
s_cpu_thread_usage = static_cast<double>(cpu_delta) * pct_divider;
|
||||
s_gs_thread_usage = static_cast<double>(gs_delta) * pct_divider;
|
||||
s_gs_back_thread_usage = static_cast<double>(gs_back_delta) * pct_divider;
|
||||
s_vu_thread_usage = static_cast<double>(vu_delta) * pct_divider;
|
||||
s_capture_thread_usage = static_cast<double>(capture_delta) * pct_divider;
|
||||
s_cpu_thread_time = static_cast<double>(cpu_delta) * time_divider;
|
||||
s_gs_thread_time = static_cast<double>(gs_delta) * time_divider;
|
||||
s_gs_back_thread_time = static_cast<double>(gs_back_delta) * time_divider;
|
||||
s_vu_thread_time = static_cast<double>(vu_delta) * time_divider;
|
||||
s_capture_thread_time = static_cast<double>(capture_delta) * time_divider;
|
||||
|
||||
@@ -367,9 +398,15 @@ void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit, bool is_sk
|
||||
s_log_accum_frames += s_frames_since_last_update;
|
||||
if (s_log_accum_time >= LOG_INTERVAL)
|
||||
{
|
||||
Console.WriteLn("PerfLog: %.1f fps | EE %.0f%% GS %.0f%% VU %.0f%% GPU %.0f%% | frame %llu",
|
||||
// The back thread only exists under GSBackThreadMode >= Lockstep, so the field is
|
||||
// omitted rather than logged as a permanent 0% in the default configuration.
|
||||
char gs_back[32] = {};
|
||||
if (HasGSBackThread())
|
||||
std::snprintf(gs_back, sizeof(gs_back), " GSB %.0f%%", s_gs_back_thread_usage);
|
||||
|
||||
Console.WriteLn("PerfLog: %.1f fps | EE %.0f%% GS %.0f%%%s VU %.0f%% GPU %.0f%% | frame %llu",
|
||||
static_cast<float>(s_log_accum_frames) / s_log_accum_time, s_cpu_thread_usage,
|
||||
s_gs_thread_usage, s_vu_thread_usage, s_gpu_usage,
|
||||
s_gs_thread_usage, gs_back, s_vu_thread_usage, s_gpu_usage,
|
||||
static_cast<unsigned long long>(s_frame_number));
|
||||
s_log_accum_time = 0.0f;
|
||||
s_log_accum_frames = 0;
|
||||
@@ -530,6 +567,15 @@ void PerformanceMetrics::SetGSSWThread(u32 index, Threading::ThreadHandle thread
|
||||
s_gs_sw_threads[index].handle = std::move(thread);
|
||||
}
|
||||
|
||||
void PerformanceMetrics::SetGSBackThread(Threading::ThreadHandle thread)
|
||||
{
|
||||
std::unique_lock lock(s_gs_back_thread_mutex);
|
||||
// Rebase off the incoming handle, so the first window after a GSreopen respawn measures
|
||||
// this thread's time rather than its difference against the retired thread's total.
|
||||
s_last_gs_back_time = thread ? thread.GetCPUTime() : 0;
|
||||
s_gs_back_thread_handle = std::move(thread);
|
||||
}
|
||||
|
||||
u64 PerformanceMetrics::GetFrameNumber()
|
||||
{
|
||||
return s_frame_number;
|
||||
@@ -595,6 +641,22 @@ float PerformanceMetrics::GetGSThreadAverageTime()
|
||||
return s_gs_thread_time;
|
||||
}
|
||||
|
||||
bool PerformanceMetrics::HasGSBackThread()
|
||||
{
|
||||
std::unique_lock lock(s_gs_back_thread_mutex);
|
||||
return static_cast<bool>(s_gs_back_thread_handle);
|
||||
}
|
||||
|
||||
float PerformanceMetrics::GetGSBackThreadUsage()
|
||||
{
|
||||
return s_gs_back_thread_usage;
|
||||
}
|
||||
|
||||
float PerformanceMetrics::GetGSBackThreadAverageTime()
|
||||
{
|
||||
return s_gs_back_thread_time;
|
||||
}
|
||||
|
||||
float PerformanceMetrics::GetVUThreadUsage()
|
||||
{
|
||||
return s_vu_thread_usage;
|
||||
|
||||
Reference in New Issue
Block a user