Files
ARMSX2/pcsx2/PerformanceMetrics.h
Brian Degenhardt 7f93a80dd7 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.
2026-07-30 21:55:58 -07:00

105 lines
3.9 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include <array>
#include "common/Threading.h"
namespace PerformanceMetrics
{
enum class InternalFPSMethod
{
None,
GSPrivilegedRegister,
DISPFBBlit
};
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;
using FrameTimeHistory = std::array<float, NUM_FRAME_TIME_SAMPLES>;
void Clear();
void Reset();
void Update(bool gs_register_write, bool fb_blit, bool is_skipping_present);
void OnGPUPresent(float gpu_time, u64 vs_invocations, u64 ps_invocations);
/// Logs the whole-session average framerate (frames since Clear() over
/// wall time). Called at VM shutdown so every -logfile run records it.
void LogSessionSummary();
/// Android ADPF (PerformanceHintManager): register the calling thread as perf-critical
/// so the OS ramps its CPU/GPU clocks toward the frame deadline instead of leaving them
/// low under emulation's bursty load. Called on the EE (CPU), GS and MTVU threads.
/// No-op on non-Android and below API 33 (symbols resolved via dlsym).
void AdpfRegisterCallingThread();
/// Enable/disable ADPF hinting at runtime (settings toggle). Default OFF (experimental).
void AdpfSetEnabled(bool enabled);
/// Close the ADPF session and forget registered threads (VM shutdown).
void AdpfShutdown();
/// ADPF work-period brackets, driven by the frame limiter (VMManager::Internal::Throttle).
/// The reported duration must be the active EE/GS/VU work per frame, EXCLUDING the deliberate
/// limiter sleep and present wait, per the PerformanceHintManager contract (reportActualWork
/// = the last workload cycle, not the frame interval). OnFrameWorkComplete() reports the
/// period that just ended (called at Throttle entry, before the sleep); BeginFrameWork()
/// opens a new period after the sleep; PauseFrameWork() invalidates it when we are not
/// frame-limiting (unlimited / host-vsync pacing), so no bogus duration is reported.
void AdpfOnFrameWorkComplete();
void AdpfBeginFrameWork();
void AdpfPauseFrameWork();
/// Sets the EE thread for CPU usage calculations.
void SetCPUThread(Threading::ThreadHandle thread);
/// Sets timers for GS software threads.
void SetGSSWThreadCount(u32 count);
void SetGSSWThread(u32 index, Threading::ThreadHandle thread);
/// Sets the timer for the GS back thread (GSBackThreadMode >= Lockstep). Registered by
/// the back thread itself at entry and cleared once it has joined; an empty handle means
/// no such thread exists, which is the default configuration. Under the pipelined split
/// the GS work is roughly halved between this thread and the MTGS thread, so the plain
/// "GS" figure alone reads as a ~50% drop in GS cost that never happened.
void SetGSBackThread(Threading::ThreadHandle thread);
u64 GetFrameNumber();
InternalFPSMethod GetInternalFPSMethod();
bool IsInternalFPSValid();
float GetFPS();
float GetInternalFPS();
float GetSpeed();
float GetAverageFrameTime();
float GetMinimumFrameTime();
float GetMaximumFrameTime();
double GetCPUThreadUsage();
double GetCPUThreadAverageTime();
float GetGSThreadUsage();
float GetGSThreadAverageTime();
/// True while a GS back thread is registered. Both figures below read zero when it is not.
bool HasGSBackThread();
float GetGSBackThreadUsage();
float GetGSBackThreadAverageTime();
float GetVUThreadUsage();
float GetVUThreadAverageTime();
float GetCaptureThreadUsage();
float GetCaptureThreadAverageTime();
u32 GetGSSWThreadCount();
double GetGSSWThreadUsage(u32 index);
double GetGSSWThreadAverageTime(u32 index);
float GetGPUUsage();
float GetGPUAverageTime();
/// GPU time for the most recent present only, not a window average. Used by the
/// per-frame stats series, where an averaged value would hide the spike.
float GetLastGPUTime();
double GetGPUAverageVSInvocations();
double GetGPUAveragePSInvocations();
const FrameTimeHistory& GetFrameTimeHistory();
u32 GetFrameTimeHistoryPos();
} // namespace PerformanceMetrics