mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
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.
105 lines
3.9 KiB
C++
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
|