Compare commits

...
7 Commits
Author SHA1 Message Date
refractionpcsx2 8bde17a35f Build: Namespace and cleanup for PerformanceMetrics 2026-09-13 08:44:28 +02:00
refractionpcsx2 0924f266cd PerfMon/ImGui: Add average VPS value to OSD 2026-09-13 08:44:28 +02:00
chaoticgd e1bbcfc414 VIF: Fix out of bounds read caused by broken alignment logic 2026-09-13 08:41:13 +02:00
lightningterror 7a9af33f7d GS: Update intel igpu/dgpu auto renderer.
Ivy Bridge and below default to Direct3D11.

Haswell/Broadwell default to OpenGL.

Skylake and above default to Direct3D12.
2026-09-13 06:01:25 +02:00
lightningterror f8ea1477f6 GS/GL: Check if RGBA16 UNORM is hw blendable, if not then fallback to RGBA16 Float. 2026-09-13 05:48:46 +02:00
lightningterror f13b70f6a0 GS/GL: Use correct formats for ColorHQ, ColorHDR.
ColorHQ: GL_RGB10_A2.
ColorHDR: GL_RGBA16F.
2026-09-13 05:48:46 +02:00
lightningterror 393278fe4d GS: Make sure ColorHDR is treated as a feedbackloop format.
It is used as a fallback when hw blending isn't supported on RGBA16 UNORM.
2026-09-13 05:48:46 +02:00
9 changed files with 589 additions and 485 deletions
+1 -1
View File
@@ -200,7 +200,7 @@ u32 GSTexture::CalcUploadSize(Format format, u32 height, u32 pitch)
bool GSTexture::IsFeedbackFormat(Format format)
{
return format == Format::Color || format == Format::ColorClip ||
return format == Format::Color || format == Format::ColorClip || format == Format::ColorHDR ||
format == Format::DepthColor || format == Format::DepthStencil;
}
+29 -14
View File
@@ -374,6 +374,8 @@ GSRendererType D3D::GetPreferredRenderer()
Console.WriteLn("D3D11 feature level for autodetection: %x", static_cast<unsigned>(feature_level));
return feature_level;
};
/*
const auto get_d3d12_device = [&adapter]() {
wil::com_ptr_nothrow<ID3D12Device> device;
const HRESULT hr = D3D12CreateDevice(adapter.get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.put()));
@@ -381,6 +383,7 @@ GSRendererType D3D::GetPreferredRenderer()
Console.Error("D3D12CreateDevice() for automatic renderer failed: %08X", hr);
return device;
};
#ifdef ENABLE_VULKAN
static constexpr auto check_vulkan_supported = []() {
if (!GSDeviceVK::EnumerateGPUs().empty())
@@ -395,6 +398,7 @@ GSRendererType D3D::GetPreferredRenderer()
#else
static constexpr auto check_vulkan_supported = []() { return false; };
#endif
*/
switch (GetVendorID(adapter.get()))
{
@@ -432,26 +436,37 @@ GSRendererType D3D::GetPreferredRenderer()
// Sampler feedback Tier 0.9 is only present in Tiger Lake/Xe/Arc, so we can use that to
// differentiate between them. Unfortunately, that requires a D3D12 device.
const auto device12 = get_d3d12_device();
if (device12)
const std::optional<D3D_FEATURE_LEVEL> feature_level = get_d3d11_feature_level();
if (!feature_level.has_value())
return GSRendererType::DX11;
else if (feature_level == D3D_FEATURE_LEVEL_12_0)
{
D3D12_FEATURE_DATA_D3D12_OPTIONS7 opts = {};
if (SUCCEEDED(device12->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &opts, sizeof(opts))) &&
(opts.SamplerFeedbackTier >= D3D12_SAMPLER_FEEDBACK_TIER_0_9) &&
check_vulkan_supported())
return GSRendererType::DX12;
/*
// Keep the old code as a reference if we need it.
const auto device12 = get_d3d12_device();
if (device12)
{
Console.WriteLn("Sampler feedback tier 0.9 found for Intel GPU, defaulting to Vulkan.");
return GSRendererType::VK;
D3D12_FEATURE_DATA_D3D12_OPTIONS7 opts = {};
if (SUCCEEDED(device12->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &opts, sizeof(opts))) &&
(opts.SamplerFeedbackTier >= D3D12_SAMPLER_FEEDBACK_TIER_0_9) &&
check_vulkan_supported())
{
Console.WriteLn("Sampler feedback tier 0.9 found for Intel GPU, defaulting to Vulkan.");
return GSRendererType::VK;
}
else
return GSRendererType::OGL;
}
else
{
Console.WriteLn("Sampler feedback tier 0.9 or Vulkan not found for Intel GPU, using OpenGL.");
return GSRendererType::OGL;
}
*/
}
Console.WriteLn("Sampler feedback tier 0.9 or Direct3D 12 not found for Intel GPU, using Direct3D 11.");
return GSRendererType::DX11;
else if (feature_level == D3D_FEATURE_LEVEL_11_1)
return GSRendererType::OGL;
else
return GSRendererType::DX11;
}
break;
+23 -11
View File
@@ -724,9 +724,9 @@ bool GSDeviceOGL::CreateTextureFX()
bool GSDeviceOGL::CheckFeatures()
{
//bool vendor_id_amd = false;
//bool vendor_id_nvidia = false;
//bool vendor_id_intel = false;
bool vendor_id_amd = false;
bool vendor_id_nvidia = false;
bool vendor_id_intel = false;
memset(&m_bugs, 0, sizeof(m_bugs));
@@ -735,18 +735,18 @@ bool GSDeviceOGL::CheckFeatures()
std::strstr(vendor, "ATI"))
{
Console.WriteLn(Color_StrongRed, "GL: AMD GPU detected.");
//vendor_id_amd = true;
vendor_id_amd = true;
}
else if (std::strstr(vendor, "NVIDIA Corporation"))
{
Console.WriteLn(Color_StrongGreen, "GL: NVIDIA GPU detected.");
//vendor_id_nvidia = true;
vendor_id_nvidia = true;
m_bugs.broken_blend_coherency = true;
}
else if (std::strstr(vendor, "Intel"))
{
Console.WriteLn(Color_StrongBlue, "GL: Intel GPU detected.");
//vendor_id_intel = true;
vendor_id_intel = true;
}
GLint major_gl = 0;
@@ -906,15 +906,27 @@ bool GSDeviceOGL::CheckFeatures()
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
m_max_texture_size = std::max(1024u, static_cast<u32>(max_texture_size));
Console.WriteLn("GL: Using %s for point expansion, %s for line expansion and %s for sprite expansion.",
m_features.point_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
m_features.line_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
m_features.vs_expand ? "vertex expanding" : "CPU");
// Unlikely to be supported on Windows if device is stuck on GL 3.3 which is usually equivalent to feature level 10.0.
// This should also target any proprietary drivers on linux.
// Open source drivers shouldn't be hit since they have different vendor names.
if ((vendor_id_amd || vendor_id_nvidia || vendor_id_intel) && GLAD_GL_VERSION_3_3 && !GLAD_GL_VERSION_4_0)
m_rgba16_unorm_hw_blend = false;
else
m_rgba16_unorm_hw_blend = true;
if (!GLAD_GL_ARB_conservative_depth)
{
Console.Warning("GLAD_GL_ARB_conservative_depth is not supported. This will reduce performance.");
}
Console.WriteLn("GL: Using %s for point expansion, %s for line expansion and %s for sprite expansion.",
m_features.point_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
m_features.line_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
m_features.vs_expand ? "vertex expanding" : "CPU");
Console.WriteLnFmt("GL: DXTn Texture Compression: {}", m_features.dxt_textures ? "Supported" : "Not Supported");
Console.WriteLnFmt("GL: BC6/7 Texture Compression: {}", m_features.bptc_textures ? "Supported" : "Not Supported");
Console.WriteLnFmt("GL: RGBA16 UNORM Hardware Blending: {}", m_rgba16_unorm_hw_blend ? "Supported" : "Not Supported");
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
@@ -2898,7 +2910,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
{
config.colclip_update_area = config.drawarea;
colclip_rt = CreateFeedbackTarget(rtsize.x, rtsize.y, GSTexture::Format::ColorClip, false);
colclip_rt = CreateFeedbackTarget(rtsize.x, rtsize.y, m_rgba16_unorm_hw_blend ? GSTexture::Format::ColorClip : GSTexture::Format::ColorHDR, false);
if (!colclip_rt)
{
+1
View File
@@ -159,6 +159,7 @@ private:
} m_bugs;
bool m_disable_download_pbo = false;
bool m_rgba16_unorm_hw_blend = false;
GLuint m_fbo = 0; // frame buffer container
GLuint m_fbo_read = 0; // frame buffer container only for reading
+18 -4
View File
@@ -34,7 +34,7 @@ GSTextureOGL::GSTextureOGL(Usage usage, int width, int height, int levels, Forma
// Bunch of constant parameter
switch (m_format)
{
// 1 Channel integer
// 1 channel integer
case Format::PrimID:
m_gl_format = GL_R32F;
m_int_format = GL_RED;
@@ -54,7 +54,7 @@ GSTextureOGL::GSTextureOGL(Usage usage, int width, int height, int levels, Forma
m_int_shift = 1;
break;
// 1 Channel normalized
// 1 channel normalized
case Format::UNorm8:
m_gl_format = GL_R8;
m_int_format = GL_RED;
@@ -72,15 +72,29 @@ GSTextureOGL::GSTextureOGL(Usage usage, int width, int height, int levels, Forma
// 4 channel normalized
case Format::Color:
case Format::ColorHQ:
case Format::ColorHDR:
m_gl_format = GL_RGBA8;
m_int_format = GL_RGBA;
m_int_type = GL_UNSIGNED_BYTE;
m_int_shift = 2;
break;
// 4 channel normalized with 2 bits of alpha
case Format::ColorHQ:
m_gl_format = GL_RGB10_A2;
m_int_format = GL_RGBA;
m_int_type = GL_UNSIGNED_INT_2_10_10_10_REV;
m_int_shift = 2;
break;
// 4 channel float
case Format::ColorHDR:
m_gl_format = GL_RGBA16F;
m_int_format = GL_RGBA;
m_int_type = GL_HALF_FLOAT;
m_int_shift = 3;
break;
// 4 channel normalized
case Format::ColorClip:
m_gl_format = GL_RGBA16;
m_int_format = GL_RGBA;
+1 -1
View File
@@ -363,7 +363,7 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f
}
if (GSConfig.OsdShowVPS)
s_speed_line.append_format("{}VPS: {:.2f}", s_speed_line.empty() ? "" : " | ", PerformanceMetrics::GetFPS());
s_speed_line.append_format("{}VPS: {:.2f} (Avg. {:.2f})", s_speed_line.empty() ? "" : " | ", PerformanceMetrics::GetFPS(), PerformanceMetrics::GetAvgVPS());
if (GSConfig.OsdShowSpeed)
{
+465 -453
View File
@@ -16,484 +16,496 @@
#include "MTVU.h"
#include "VMManager.h"
static const float UPDATE_INTERVAL = 0.5f;
static float s_fps = 0.0f;
static float s_internal_fps = 0.0f;
static float s_minimum_frame_time = 0.0f;
static float s_minimum_frame_time_accumulator = 0.0f;
static float s_average_frame_time = 0.0f;
static float s_average_frame_time_accumulator = 0.0f;
static float s_maximum_frame_time = 0.0f;
static float s_maximum_frame_time_accumulator = 0.0f;
static u32 s_frames_since_last_update = 0;
static u32 s_unskipped_frames_since_last_update = 0;
static Common::Timer s_last_update_time;
static Common::Timer s_last_frame_time;
// frame number, updated by the GS thread
static u64 s_frame_number = 0;
// internal fps heuristics
static PerformanceMetrics::InternalFPSMethod s_internal_fps_method = PerformanceMetrics::InternalFPSMethod::None;
static u32 s_gs_framebuffer_blits_since_last_update = 0;
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_vu_time = 0;
static u64 s_last_capture_time = 0;
static u64 s_last_ticks = 0;
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_vu_thread_usage = 0.0f;
static float s_vu_thread_time = 0.0f;
static float s_capture_thread_usage = 0.0f;
static float s_capture_thread_time = 0.0f;
static PerformanceMetrics::FrameTimeHistory s_frame_time_history;
static u32 s_frame_time_history_pos = 0;
struct GSSWThreadStats
namespace PerformanceMetrics
{
Threading::ThreadHandle handle;
u64 last_cpu_time = 0;
double usage = 0.0;
double time = 0.0;
};
std::vector<GSSWThreadStats> s_gs_sw_threads;
static const float UPDATE_INTERVAL = 0.5f;
static float s_average_gpu_time = 0.0f;
static float s_accumulated_gpu_time = 0.0f;
static float s_gpu_usage = 0.0f;
static u32 s_presents_since_last_update = 0;
static double s_average_gpu_vs_invocations = 0.0;
static double s_average_gpu_ps_invocations = 0.0;
static u64 s_accumulated_gpu_vs_invocations = 0;
static u64 s_accumulated_gpu_ps_invocations = 0;
static float s_fps = 0.0f;
static AvgFPS s_avg_vps;
static float s_internal_fps = 0.0f;
static float s_minimum_frame_time = 0.0f;
static float s_minimum_frame_time_accumulator = 0.0f;
static float s_average_frame_time = 0.0f;
static float s_average_frame_time_accumulator = 0.0f;
static float s_maximum_frame_time = 0.0f;
static float s_maximum_frame_time_accumulator = 0.0f;
static u32 s_frames_since_last_update = 0;
static u32 s_unskipped_frames_since_last_update = 0;
static Common::Timer s_last_update_time;
static Common::Timer s_last_frame_time;
static PerformanceMetrics::SavedMetrics s_saved_metrics;
static PerformanceMetrics::SavedMetrics s_saved_metrics_std;
static float s_saved_metrics_remaining_seconds = 0.0f;
// frame number, updated by the GS thread
static u64 s_frame_number = 0;
void PerformanceMetrics::Clear()
{
Reset();
// internal fps heuristics
static InternalFPSMethod s_internal_fps_method = InternalFPSMethod::None;
static u32 s_gs_framebuffer_blits_since_last_update = 0;
static u32 s_gs_privileged_register_writes_since_last_update = 0;
s_fps = 0.0f;
s_internal_fps = 0.0f;
s_minimum_frame_time = 0.0f;
s_average_frame_time = 0.0f;
s_maximum_frame_time = 0.0f;
s_internal_fps_method = PerformanceMetrics::InternalFPSMethod::None;
s_average_gpu_vs_invocations = 0.0;
s_average_gpu_ps_invocations = 0.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_vu_time = 0;
static u64 s_last_capture_time = 0;
static u64 s_last_ticks = 0;
s_cpu_thread_usage = 0.0f;
s_cpu_thread_time = 0.0f;
s_gs_thread_usage = 0.0f;
s_gs_thread_time = 0.0f;
s_vu_thread_usage = 0.0f;
s_vu_thread_time = 0.0f;
s_capture_thread_usage = 0.0f;
s_capture_thread_time = 0.0f;
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_vu_thread_usage = 0.0f;
static float s_vu_thread_time = 0.0f;
static float s_capture_thread_usage = 0.0f;
static float s_capture_thread_time = 0.0f;
s_average_gpu_time = 0.0f;
s_gpu_usage = 0.0f;
static FrameTimeHistory s_frame_time_history;
static u32 s_frame_time_history_pos = 0;
s_frame_number = 0;
s_frame_time_history.fill(0.0f);
s_frame_time_history_pos = 0;
s_saved_metrics = {};
s_saved_metrics_std = {};
s_saved_metrics_remaining_seconds = 0.0f;
}
void PerformanceMetrics::Reset()
{
s_frames_since_last_update = 0;
s_unskipped_frames_since_last_update = 0;
s_gs_framebuffer_blits_since_last_update = 0;
s_gs_privileged_register_writes_since_last_update = 0;
s_minimum_frame_time_accumulator = 0.0f;
s_average_frame_time_accumulator = 0.0f;
s_maximum_frame_time_accumulator = 0.0f;
s_accumulated_gpu_vs_invocations = 0;
s_accumulated_gpu_ps_invocations = 0;
s_accumulated_gpu_time = 0.0f;
s_presents_since_last_update = 0;
s_last_update_time.Reset();
s_last_frame_time.Reset();
s_last_cpu_time = s_cpu_thread_handle.GetCPUTime();
s_last_gs_time = MTGS::GetThreadHandle().GetCPUTime();
s_last_vu_time = THREAD_VU1 ? vu1Thread.GetThreadHandle().GetCPUTime() : 0;
s_last_ticks = GetCPUTicks();
s_last_capture_time = GSCapture::IsCapturing() ? GSCapture::GetEncoderThreadHandle().GetCPUTime() : 0;
for (GSSWThreadStats& stat : s_gs_sw_threads)
stat.last_cpu_time = stat.handle.GetCPUTime();
}
static double GetCPUTimeToPCTFactor(u64 ticks_delta)
{
return 100.0 * static_cast<double>(GetTickFrequency()) /
(static_cast<double>(ticks_delta) * static_cast<double>(Threading::GetThreadTicksPerSecond()));
}
static double GetCPUTimeToMSFactor(u64 frames)
{
return 1000.0 / static_cast<double>(Threading::GetThreadTicksPerSecond()) / static_cast<double>(frames);
}
void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit, bool is_skipping_present)
{
if (!is_skipping_present)
struct GSSWThreadStats
{
const float frame_time = s_last_frame_time.GetTimeMillisecondsAndReset();
s_minimum_frame_time_accumulator = (s_minimum_frame_time_accumulator == 0.0f) ? frame_time : std::min(s_minimum_frame_time_accumulator, frame_time);
s_average_frame_time_accumulator += frame_time;
s_maximum_frame_time_accumulator = std::max(s_maximum_frame_time_accumulator, frame_time);
s_frame_time_history[s_frame_time_history_pos] = frame_time;
s_frame_time_history_pos = (s_frame_time_history_pos + 1) % NUM_FRAME_TIME_SAMPLES;
s_unskipped_frames_since_last_update++;
}
Threading::ThreadHandle handle;
u64 last_cpu_time = 0;
double usage = 0.0;
double time = 0.0;
};
std::vector<GSSWThreadStats> s_gs_sw_threads;
s_frames_since_last_update++;
s_gs_privileged_register_writes_since_last_update += static_cast<u32>(gs_register_write);
s_gs_framebuffer_blits_since_last_update += static_cast<u32>(fb_blit);
s_frame_number++;
static float s_average_gpu_time = 0.0f;
static float s_accumulated_gpu_time = 0.0f;
static float s_gpu_usage = 0.0f;
static u32 s_presents_since_last_update = 0;
static double s_average_gpu_vs_invocations = 0.0;
static double s_average_gpu_ps_invocations = 0.0;
static u64 s_accumulated_gpu_vs_invocations = 0;
static u64 s_accumulated_gpu_ps_invocations = 0;
const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue();
const Common::Timer::Value ticks_diff = now_ticks - s_last_update_time.GetStartValue();
const float time = Common::Timer::ConvertValueToSeconds(ticks_diff);
if (time < UPDATE_INTERVAL)
return;
static SavedMetrics s_saved_metrics;
static SavedMetrics s_saved_metrics_std;
static float s_saved_metrics_remaining_seconds = 0.0f;
s_last_update_time.ResetTo(now_ticks);
s_minimum_frame_time = std::exchange(s_minimum_frame_time_accumulator, 0.0f);
s_average_frame_time = std::exchange(s_average_frame_time_accumulator, 0.0f) / static_cast<float>(s_unskipped_frames_since_last_update);
s_maximum_frame_time = std::exchange(s_maximum_frame_time_accumulator, 0.0f);
s_fps = static_cast<float>(s_frames_since_last_update) / time;
s_average_gpu_time = s_accumulated_gpu_time / static_cast<float>(s_unskipped_frames_since_last_update);
s_average_gpu_vs_invocations = static_cast<double>(s_accumulated_gpu_vs_invocations) / static_cast<double>(s_unskipped_frames_since_last_update);
s_average_gpu_ps_invocations = static_cast<double>(s_accumulated_gpu_ps_invocations) / static_cast<double>(s_unskipped_frames_since_last_update);
s_gpu_usage = s_accumulated_gpu_time / (time * 10.0f);
s_accumulated_gpu_time = 0.0f;
s_accumulated_gpu_vs_invocations = 0;
s_accumulated_gpu_ps_invocations = 0;
// prefer privileged register write based framerate detection, it's less likely to have false positives
if (s_gs_privileged_register_writes_since_last_update > 0 && !EmuConfig.Gamefixes.BlitInternalFPSHack)
void Clear()
{
s_internal_fps = static_cast<float>(s_gs_privileged_register_writes_since_last_update) / time;
s_internal_fps_method = InternalFPSMethod::GSPrivilegedRegister;
}
else if (s_gs_framebuffer_blits_since_last_update > 0)
{
s_internal_fps = static_cast<float>(s_gs_framebuffer_blits_since_last_update) / time;
s_internal_fps_method = InternalFPSMethod::DISPFBBlit;
}
else
{
s_internal_fps = 0;
Reset();
s_fps = 0.0f;
s_avg_vps.ClearStats();
s_internal_fps = 0.0f;
s_minimum_frame_time = 0.0f;
s_average_frame_time = 0.0f;
s_maximum_frame_time = 0.0f;
s_internal_fps_method = InternalFPSMethod::None;
s_average_gpu_vs_invocations = 0.0;
s_average_gpu_ps_invocations = 0.0;
s_cpu_thread_usage = 0.0f;
s_cpu_thread_time = 0.0f;
s_gs_thread_usage = 0.0f;
s_gs_thread_time = 0.0f;
s_vu_thread_usage = 0.0f;
s_vu_thread_time = 0.0f;
s_capture_thread_usage = 0.0f;
s_capture_thread_time = 0.0f;
s_average_gpu_time = 0.0f;
s_gpu_usage = 0.0f;
s_frame_number = 0;
s_frame_time_history.fill(0.0f);
s_frame_time_history_pos = 0;
s_saved_metrics = {};
s_saved_metrics_std = {};
s_saved_metrics_remaining_seconds = 0.0f;
}
s_gs_privileged_register_writes_since_last_update = 0;
s_gs_framebuffer_blits_since_last_update = 0;
const u64 ticks = GetCPUTicks();
const u64 ticks_delta = ticks - s_last_ticks;
s_last_ticks = ticks;
const double pct_divider = GetCPUTimeToPCTFactor(ticks_delta);
const double time_divider = GetCPUTimeToMSFactor(s_frames_since_last_update);
const u64 cpu_time = s_cpu_thread_handle.GetCPUTime();
const u64 gs_time = MTGS::GetThreadHandle().GetCPUTime();
const u64 vu_time = THREAD_VU1 ? vu1Thread.GetThreadHandle().GetCPUTime() : 0;
const u64 capture_time = GSCapture::IsCapturing() ? GSCapture::GetEncoderThreadHandle().GetCPUTime() : 0;
const u64 cpu_delta = cpu_time - s_last_cpu_time;
const u64 gs_delta = gs_time - s_last_gs_time;
const u64 vu_delta = vu_time - s_last_vu_time;
const u64 capture_delta = capture_time - s_last_capture_time;
s_last_cpu_time = cpu_time;
s_last_gs_time = gs_time;
s_last_vu_time = vu_time;
s_last_capture_time = capture_time;
s_cpu_thread_usage = static_cast<double>(cpu_delta) * pct_divider;
s_gs_thread_usage = static_cast<double>(gs_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_vu_thread_time = static_cast<double>(vu_delta) * time_divider;
s_capture_thread_time = static_cast<double>(capture_delta) * time_divider;
for (GSSWThreadStats& thread : s_gs_sw_threads)
void Reset()
{
const u64 time = thread.handle.GetCPUTime();
const u64 delta = time - thread.last_cpu_time;
thread.last_cpu_time = time;
thread.usage = static_cast<double>(delta) * pct_divider;
thread.time = static_cast<double>(delta) * time_divider;
s_frames_since_last_update = 0;
s_unskipped_frames_since_last_update = 0;
s_gs_framebuffer_blits_since_last_update = 0;
s_gs_privileged_register_writes_since_last_update = 0;
s_minimum_frame_time_accumulator = 0.0f;
s_average_frame_time_accumulator = 0.0f;
s_maximum_frame_time_accumulator = 0.0f;
s_accumulated_gpu_vs_invocations = 0;
s_accumulated_gpu_ps_invocations = 0;
s_accumulated_gpu_time = 0.0f;
s_presents_since_last_update = 0;
s_last_update_time.Reset();
s_last_frame_time.Reset();
s_last_cpu_time = s_cpu_thread_handle.GetCPUTime();
s_last_gs_time = MTGS::GetThreadHandle().GetCPUTime();
s_last_vu_time = THREAD_VU1 ? vu1Thread.GetThreadHandle().GetCPUTime() : 0;
s_last_ticks = GetCPUTicks();
s_last_capture_time = GSCapture::IsCapturing() ? GSCapture::GetEncoderThreadHandle().GetCPUTime() : 0;
for (GSSWThreadStats& stat : s_gs_sw_threads)
stat.last_cpu_time = stat.handle.GetCPUTime();
}
if (s_saved_metrics_remaining_seconds > 0.0f)
static double GetCPUTimeToPCTFactor(u64 ticks_delta)
{
auto UpdateSavedMetrics = [&](PerformanceMetrics::SavedMetrics& metrics, bool square) {
auto Transform = [square](float f) { return square ? f * f : f; };
metrics.num_samples += 1.0f;
metrics.frames += static_cast<float>(s_frames_since_last_update);
metrics.time += time;
metrics.fps += Transform(s_fps);
metrics.internal_fps += Transform(s_internal_fps);
metrics.cpu_thread_usage += Transform(s_cpu_thread_usage);
metrics.cpu_thread_time += Transform(s_cpu_thread_time);
metrics.gs_thread_usage += Transform(s_gs_thread_usage);
metrics.gs_thread_time += Transform(s_gs_thread_time);
metrics.gpu_time += Transform(s_average_gpu_time);;
metrics.gpu_usage += Transform(s_gpu_usage);
};
UpdateSavedMetrics(s_saved_metrics, false);
UpdateSavedMetrics(s_saved_metrics_std, true);
s_saved_metrics_remaining_seconds -= std::min(s_saved_metrics_remaining_seconds, time);
std::atomic_thread_fence(std::memory_order_release); // results might be read on another thread
return 100.0 * static_cast<double>(GetTickFrequency()) /
(static_cast<double>(ticks_delta) * static_cast<double>(Threading::GetThreadTicksPerSecond()));
}
s_frames_since_last_update = 0;
s_unskipped_frames_since_last_update = 0;
s_presents_since_last_update = 0;
Host::OnPerformanceMetricsUpdated();
}
void PerformanceMetrics::OnGPUPresent(float gpu_time, u64 vs_invocations, u64 ps_invocations)
{
s_accumulated_gpu_time += gpu_time;
s_accumulated_gpu_vs_invocations += vs_invocations;
s_accumulated_gpu_ps_invocations += ps_invocations;
s_presents_since_last_update++;
}
void PerformanceMetrics::SetCPUThread(Threading::ThreadHandle thread)
{
s_last_cpu_time = thread ? thread.GetCPUTime() : 0;
s_cpu_thread_handle = std::move(thread);
}
void PerformanceMetrics::SetGSSWThreadCount(u32 count)
{
s_gs_sw_threads.clear();
s_gs_sw_threads.resize(count);
}
void PerformanceMetrics::SetGSSWThread(u32 index, Threading::ThreadHandle thread)
{
s_gs_sw_threads[index].last_cpu_time = thread ? thread.GetCPUTime() : 0;
s_gs_sw_threads[index].handle = std::move(thread);
}
u64 PerformanceMetrics::GetFrameNumber()
{
return s_frame_number;
}
PerformanceMetrics::InternalFPSMethod PerformanceMetrics::GetInternalFPSMethod()
{
return s_internal_fps_method;
}
bool PerformanceMetrics::IsInternalFPSValid()
{
return s_internal_fps_method != InternalFPSMethod::None;
}
float PerformanceMetrics::GetFPS()
{
return s_fps;
}
float PerformanceMetrics::GetInternalFPS()
{
return s_internal_fps;
}
float PerformanceMetrics::GetSpeed()
{
return (s_fps / VMManager::GetFrameRate()) * 100.0;
}
float PerformanceMetrics::GetAverageFrameTime()
{
return s_average_frame_time;
}
float PerformanceMetrics::GetMinimumFrameTime()
{
return s_minimum_frame_time;
}
float PerformanceMetrics::GetMaximumFrameTime()
{
return s_maximum_frame_time;
}
double PerformanceMetrics::GetCPUThreadUsage()
{
return s_cpu_thread_usage;
}
double PerformanceMetrics::GetCPUThreadAverageTime()
{
return s_cpu_thread_time;
}
float PerformanceMetrics::GetGSThreadUsage()
{
return s_gs_thread_usage;
}
float PerformanceMetrics::GetGSThreadAverageTime()
{
return s_gs_thread_time;
}
float PerformanceMetrics::GetVUThreadUsage()
{
return s_vu_thread_usage;
}
float PerformanceMetrics::GetVUThreadAverageTime()
{
return s_vu_thread_time;
}
float PerformanceMetrics::GetCaptureThreadUsage()
{
return s_capture_thread_usage;
}
float PerformanceMetrics::GetCaptureThreadAverageTime()
{
return s_capture_thread_time;
}
u32 PerformanceMetrics::GetGSSWThreadCount()
{
return static_cast<u32>(s_gs_sw_threads.size());
}
double PerformanceMetrics::GetGSSWThreadUsage(u32 index)
{
return s_gs_sw_threads[index].usage;
}
double PerformanceMetrics::GetGSSWThreadAverageTime(u32 index)
{
return s_gs_sw_threads[index].time;
}
float PerformanceMetrics::GetGPUUsage()
{
return s_gpu_usage;
}
float PerformanceMetrics::GetGPUAverageTime()
{
return s_average_gpu_time;
}
double PerformanceMetrics::GetGPUAverageVSInvocations()
{
return s_average_gpu_vs_invocations;
}
double PerformanceMetrics::GetGPUAveragePSInvocations()
{
return s_average_gpu_ps_invocations;
}
const PerformanceMetrics::FrameTimeHistory& PerformanceMetrics::GetFrameTimeHistory()
{
return s_frame_time_history;
}
u32 PerformanceMetrics::GetFrameTimeHistoryPos()
{
return s_frame_time_history_pos;
}
void PerformanceMetrics::StartSavingMetrics(u32 seconds)
{
s_saved_metrics_remaining_seconds = static_cast<float>(seconds);
s_saved_metrics = {};
s_saved_metrics_std = {};
}
bool PerformanceMetrics::IsSavingMetrics()
{
return s_saved_metrics_remaining_seconds > 0.0f;
}
void PerformanceMetrics::DumpSavedMetrics()
{
s_saved_metrics_remaining_seconds = 0.0f;
SavedMetrics metrics = std::exchange(s_saved_metrics, SavedMetrics{});
SavedMetrics metrics_std = std::exchange(s_saved_metrics_std, SavedMetrics{});
const float num_samples = metrics.num_samples;
if (num_samples > 0.0f)
static double GetCPUTimeToMSFactor(u64 frames)
{
const auto Square = [](float f) { return f * f; };
const auto Average = [num_samples](float f) { return f / num_samples; };
return 1000.0 / static_cast<double>(Threading::GetThreadTicksPerSecond()) / static_cast<double>(frames);
}
metrics.fps = Average(metrics.fps);
metrics.internal_fps = Average(metrics.internal_fps);
metrics.cpu_thread_usage = Average(metrics.cpu_thread_usage);
metrics.cpu_thread_time = Average(metrics.cpu_thread_time);
metrics.gs_thread_usage = Average(metrics.gs_thread_usage);
metrics.gs_thread_time = Average(metrics.gs_thread_time);
metrics.gpu_time = Average(metrics.gpu_time);
metrics.gpu_usage = Average(metrics.gpu_usage);
void Update(bool gs_register_write, bool fb_blit, bool is_skipping_present)
{
if (!is_skipping_present)
{
const float frame_time = s_last_frame_time.GetTimeMillisecondsAndReset();
s_minimum_frame_time_accumulator = (s_minimum_frame_time_accumulator == 0.0f) ? frame_time : std::min(s_minimum_frame_time_accumulator, frame_time);
s_average_frame_time_accumulator += frame_time;
s_maximum_frame_time_accumulator = std::max(s_maximum_frame_time_accumulator, frame_time);
s_frame_time_history[s_frame_time_history_pos] = frame_time;
s_frame_time_history_pos = (s_frame_time_history_pos + 1) % NUM_FRAME_TIME_SAMPLES;
s_unskipped_frames_since_last_update++;
}
metrics_std.fps = std::sqrt((Average(metrics_std.fps) - Square(metrics.fps)));
metrics_std.internal_fps = std::sqrt((Average(metrics_std.internal_fps) - Square(metrics.internal_fps)));
metrics_std.cpu_thread_usage = std::sqrt((Average(metrics_std.cpu_thread_usage) - Square(metrics.cpu_thread_usage)));
metrics_std.cpu_thread_time = std::sqrt((Average(metrics_std.cpu_thread_time) - Square(metrics.cpu_thread_time)));
metrics_std.gs_thread_usage = std::sqrt((Average(metrics_std.gs_thread_usage) - Square(metrics.gs_thread_usage)));
metrics_std.gs_thread_time = std::sqrt((Average(metrics_std.gs_thread_time) - Square(metrics.gs_thread_time)));
metrics_std.gpu_time = std::sqrt((Average(metrics_std.gpu_time) - Square(metrics.gpu_time)));
metrics_std.gpu_usage = std::sqrt((Average(metrics_std.gpu_usage) - Square(metrics.gpu_usage)));
s_frames_since_last_update++;
s_gs_privileged_register_writes_since_last_update += static_cast<u32>(gs_register_write);
s_gs_framebuffer_blits_since_last_update += static_cast<u32>(fb_blit);
s_frame_number++;
Console.WriteLnFmt("@HWSTAT@ Frames: {} ({} samples)", metrics.frames, metrics.num_samples);
Console.WriteLnFmt("@HWSTAT@ Time: {:.3f} sec", metrics.time);
Console.WriteLnFmt("@HWSTAT@ FPS: {:.3f} ± {:.3f} ({:.3f} ± {:.3f} internal)", metrics.fps, metrics_std.fps, metrics.internal_fps, metrics_std.internal_fps);
Console.WriteLnFmt("@HWSTAT@ Minimum Frame Time: {:.3f} ms ({:.3f} FPS)", GetMinimumFrameTime(), 1000.0f / GetMinimumFrameTime());
Console.WriteLnFmt("@HWSTAT@ Average Frame Time: {:.3f} ms ({:.3f} FPS)", GetAverageFrameTime(), 1000.0f / GetAverageFrameTime());
Console.WriteLnFmt("@HWSTAT@ Maximum Frame Time: {:.3f} ms ({:.3f} FPS)", GetMaximumFrameTime(), 1000.0f / GetMaximumFrameTime());
Console.WriteLnFmt("@HWSTAT@ Average CPU Thread Usage: {:.3f} ± {:.3f} %", metrics.cpu_thread_usage, metrics_std.cpu_thread_usage);
Console.WriteLnFmt("@HWSTAT@ Average GS Thread Usage: {:.3f} ± {:.3f} %", metrics.gs_thread_usage, metrics_std.gs_thread_usage);
Console.WriteLnFmt("@HWSTAT@ Average GPU Usage: {:.3f} ± {:.3f} %", metrics.gpu_usage, metrics_std.gpu_usage);
Console.WriteLnFmt("@HWSTAT@ Average CPU Thread Time: {:.3f} ± {:.3f} ms", metrics.cpu_thread_time, metrics_std.cpu_thread_time);
Console.WriteLnFmt("@HWSTAT@ Average GS Thread Time: {:.3f} ± {:.3f} ms", metrics.gs_thread_time, metrics_std.gs_thread_time);
Console.WriteLnFmt("@HWSTAT@ Average GPU Time: {:.3f} ± {:.3f} ms", metrics.gpu_time, metrics_std.gpu_time);
const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue();
const Common::Timer::Value ticks_diff = now_ticks - s_last_update_time.GetStartValue();
const float time = Common::Timer::ConvertValueToSeconds(ticks_diff);
if (time < UPDATE_INTERVAL)
return;
s_last_update_time.ResetTo(now_ticks);
s_minimum_frame_time = std::exchange(s_minimum_frame_time_accumulator, 0.0f);
s_average_frame_time = std::exchange(s_average_frame_time_accumulator, 0.0f) / static_cast<float>(s_unskipped_frames_since_last_update);
s_maximum_frame_time = std::exchange(s_maximum_frame_time_accumulator, 0.0f);
s_fps = static_cast<float>(s_frames_since_last_update) / time;
s_avg_vps.UpdateAvgFPS(s_fps);
s_average_gpu_time = s_accumulated_gpu_time / static_cast<float>(s_unskipped_frames_since_last_update);
s_average_gpu_vs_invocations = static_cast<double>(s_accumulated_gpu_vs_invocations) / static_cast<double>(s_unskipped_frames_since_last_update);
s_average_gpu_ps_invocations = static_cast<double>(s_accumulated_gpu_ps_invocations) / static_cast<double>(s_unskipped_frames_since_last_update);
s_gpu_usage = s_accumulated_gpu_time / (time * 10.0f);
s_accumulated_gpu_time = 0.0f;
s_accumulated_gpu_vs_invocations = 0;
s_accumulated_gpu_ps_invocations = 0;
// prefer privileged register write based framerate detection, it's less likely to have false positives
if (s_gs_privileged_register_writes_since_last_update > 0 && !EmuConfig.Gamefixes.BlitInternalFPSHack)
{
s_internal_fps = static_cast<float>(s_gs_privileged_register_writes_since_last_update) / time;
s_internal_fps_method = InternalFPSMethod::GSPrivilegedRegister;
}
else if (s_gs_framebuffer_blits_since_last_update > 0)
{
s_internal_fps = static_cast<float>(s_gs_framebuffer_blits_since_last_update) / time;
s_internal_fps_method = InternalFPSMethod::DISPFBBlit;
}
else
{
s_internal_fps = 0;
s_internal_fps_method = InternalFPSMethod::None;
}
s_gs_privileged_register_writes_since_last_update = 0;
s_gs_framebuffer_blits_since_last_update = 0;
const u64 ticks = GetCPUTicks();
const u64 ticks_delta = ticks - s_last_ticks;
s_last_ticks = ticks;
const double pct_divider = GetCPUTimeToPCTFactor(ticks_delta);
const double time_divider = GetCPUTimeToMSFactor(s_frames_since_last_update);
const u64 cpu_time = s_cpu_thread_handle.GetCPUTime();
const u64 gs_time = MTGS::GetThreadHandle().GetCPUTime();
const u64 vu_time = THREAD_VU1 ? vu1Thread.GetThreadHandle().GetCPUTime() : 0;
const u64 capture_time = GSCapture::IsCapturing() ? GSCapture::GetEncoderThreadHandle().GetCPUTime() : 0;
const u64 cpu_delta = cpu_time - s_last_cpu_time;
const u64 gs_delta = gs_time - s_last_gs_time;
const u64 vu_delta = vu_time - s_last_vu_time;
const u64 capture_delta = capture_time - s_last_capture_time;
s_last_cpu_time = cpu_time;
s_last_gs_time = gs_time;
s_last_vu_time = vu_time;
s_last_capture_time = capture_time;
s_cpu_thread_usage = static_cast<double>(cpu_delta) * pct_divider;
s_gs_thread_usage = static_cast<double>(gs_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_vu_thread_time = static_cast<double>(vu_delta) * time_divider;
s_capture_thread_time = static_cast<double>(capture_delta) * time_divider;
for (GSSWThreadStats& thread : s_gs_sw_threads)
{
const u64 time = thread.handle.GetCPUTime();
const u64 delta = time - thread.last_cpu_time;
thread.last_cpu_time = time;
thread.usage = static_cast<double>(delta) * pct_divider;
thread.time = static_cast<double>(delta) * time_divider;
}
if (s_saved_metrics_remaining_seconds > 0.0f)
{
auto UpdateSavedMetrics = [&](SavedMetrics& metrics, bool square) {
auto Transform = [square](float f) { return square ? f * f : f; };
metrics.num_samples += 1.0f;
metrics.frames += static_cast<float>(s_frames_since_last_update);
metrics.time += time;
metrics.fps += Transform(s_fps);
metrics.internal_fps += Transform(s_internal_fps);
metrics.cpu_thread_usage += Transform(s_cpu_thread_usage);
metrics.cpu_thread_time += Transform(s_cpu_thread_time);
metrics.gs_thread_usage += Transform(s_gs_thread_usage);
metrics.gs_thread_time += Transform(s_gs_thread_time);
metrics.gpu_time += Transform(s_average_gpu_time);
;
metrics.gpu_usage += Transform(s_gpu_usage);
};
UpdateSavedMetrics(s_saved_metrics, false);
UpdateSavedMetrics(s_saved_metrics_std, true);
s_saved_metrics_remaining_seconds -= std::min(s_saved_metrics_remaining_seconds, time);
std::atomic_thread_fence(std::memory_order_release); // results might be read on another thread
}
s_frames_since_last_update = 0;
s_unskipped_frames_since_last_update = 0;
s_presents_since_last_update = 0;
Host::OnPerformanceMetricsUpdated();
}
void OnGPUPresent(float gpu_time, u64 vs_invocations, u64 ps_invocations)
{
s_accumulated_gpu_time += gpu_time;
s_accumulated_gpu_vs_invocations += vs_invocations;
s_accumulated_gpu_ps_invocations += ps_invocations;
s_presents_since_last_update++;
}
void SetCPUThread(Threading::ThreadHandle thread)
{
s_last_cpu_time = thread ? thread.GetCPUTime() : 0;
s_cpu_thread_handle = std::move(thread);
}
void SetGSSWThreadCount(u32 count)
{
s_gs_sw_threads.clear();
s_gs_sw_threads.resize(count);
}
void SetGSSWThread(u32 index, Threading::ThreadHandle thread)
{
s_gs_sw_threads[index].last_cpu_time = thread ? thread.GetCPUTime() : 0;
s_gs_sw_threads[index].handle = std::move(thread);
}
u64 GetFrameNumber()
{
return s_frame_number;
}
InternalFPSMethod GetInternalFPSMethod()
{
return s_internal_fps_method;
}
bool IsInternalFPSValid()
{
return s_internal_fps_method != InternalFPSMethod::None;
}
float GetFPS()
{
return s_fps;
}
float GetAvgVPS()
{
return s_avg_vps.GetAvgFPS();
}
float GetInternalFPS()
{
return s_internal_fps;
}
float GetSpeed()
{
return (s_fps / VMManager::GetFrameRate()) * 100.0;
}
float GetAverageFrameTime()
{
return s_average_frame_time;
}
float GetMinimumFrameTime()
{
return s_minimum_frame_time;
}
float GetMaximumFrameTime()
{
return s_maximum_frame_time;
}
double GetCPUThreadUsage()
{
return s_cpu_thread_usage;
}
double GetCPUThreadAverageTime()
{
return s_cpu_thread_time;
}
float GetGSThreadUsage()
{
return s_gs_thread_usage;
}
float GetGSThreadAverageTime()
{
return s_gs_thread_time;
}
float GetVUThreadUsage()
{
return s_vu_thread_usage;
}
float GetVUThreadAverageTime()
{
return s_vu_thread_time;
}
float GetCaptureThreadUsage()
{
return s_capture_thread_usage;
}
float GetCaptureThreadAverageTime()
{
return s_capture_thread_time;
}
u32 GetGSSWThreadCount()
{
return static_cast<u32>(s_gs_sw_threads.size());
}
double GetGSSWThreadUsage(u32 index)
{
return s_gs_sw_threads[index].usage;
}
double GetGSSWThreadAverageTime(u32 index)
{
return s_gs_sw_threads[index].time;
}
float GetGPUUsage()
{
return s_gpu_usage;
}
float GetGPUAverageTime()
{
return s_average_gpu_time;
}
double GetGPUAverageVSInvocations()
{
return s_average_gpu_vs_invocations;
}
double GetGPUAveragePSInvocations()
{
return s_average_gpu_ps_invocations;
}
const FrameTimeHistory& GetFrameTimeHistory()
{
return s_frame_time_history;
}
u32 GetFrameTimeHistoryPos()
{
return s_frame_time_history_pos;
}
void StartSavingMetrics(u32 seconds)
{
s_saved_metrics_remaining_seconds = static_cast<float>(seconds);
s_saved_metrics = {};
s_saved_metrics_std = {};
}
bool IsSavingMetrics()
{
return s_saved_metrics_remaining_seconds > 0.0f;
}
void DumpSavedMetrics()
{
s_saved_metrics_remaining_seconds = 0.0f;
SavedMetrics metrics = std::exchange(s_saved_metrics, SavedMetrics{});
SavedMetrics metrics_std = std::exchange(s_saved_metrics_std, SavedMetrics{});
const float num_samples = metrics.num_samples;
if (num_samples > 0.0f)
{
const auto Square = [](float f) { return f * f; };
const auto Average = [num_samples](float f) { return f / num_samples; };
metrics.fps = Average(metrics.fps);
metrics.internal_fps = Average(metrics.internal_fps);
metrics.cpu_thread_usage = Average(metrics.cpu_thread_usage);
metrics.cpu_thread_time = Average(metrics.cpu_thread_time);
metrics.gs_thread_usage = Average(metrics.gs_thread_usage);
metrics.gs_thread_time = Average(metrics.gs_thread_time);
metrics.gpu_time = Average(metrics.gpu_time);
metrics.gpu_usage = Average(metrics.gpu_usage);
metrics_std.fps = std::sqrt((Average(metrics_std.fps) - Square(metrics.fps)));
metrics_std.internal_fps = std::sqrt((Average(metrics_std.internal_fps) - Square(metrics.internal_fps)));
metrics_std.cpu_thread_usage = std::sqrt((Average(metrics_std.cpu_thread_usage) - Square(metrics.cpu_thread_usage)));
metrics_std.cpu_thread_time = std::sqrt((Average(metrics_std.cpu_thread_time) - Square(metrics.cpu_thread_time)));
metrics_std.gs_thread_usage = std::sqrt((Average(metrics_std.gs_thread_usage) - Square(metrics.gs_thread_usage)));
metrics_std.gs_thread_time = std::sqrt((Average(metrics_std.gs_thread_time) - Square(metrics.gs_thread_time)));
metrics_std.gpu_time = std::sqrt((Average(metrics_std.gpu_time) - Square(metrics.gpu_time)));
metrics_std.gpu_usage = std::sqrt((Average(metrics_std.gpu_usage) - Square(metrics.gpu_usage)));
Console.WriteLnFmt("@HWSTAT@ Frames: {} ({} samples)", metrics.frames, metrics.num_samples);
Console.WriteLnFmt("@HWSTAT@ Time: {:.3f} sec", metrics.time);
Console.WriteLnFmt("@HWSTAT@ FPS: {:.3f} ± {:.3f} ({:.3f} ± {:.3f} internal)", metrics.fps, metrics_std.fps, metrics.internal_fps, metrics_std.internal_fps);
Console.WriteLnFmt("@HWSTAT@ Minimum Frame Time: {:.3f} ms ({:.3f} FPS)", GetMinimumFrameTime(), 1000.0f / GetMinimumFrameTime());
Console.WriteLnFmt("@HWSTAT@ Average Frame Time: {:.3f} ms ({:.3f} FPS)", GetAverageFrameTime(), 1000.0f / GetAverageFrameTime());
Console.WriteLnFmt("@HWSTAT@ Maximum Frame Time: {:.3f} ms ({:.3f} FPS)", GetMaximumFrameTime(), 1000.0f / GetMaximumFrameTime());
Console.WriteLnFmt("@HWSTAT@ Average CPU Thread Usage: {:.3f} ± {:.3f} %", metrics.cpu_thread_usage, metrics_std.cpu_thread_usage);
Console.WriteLnFmt("@HWSTAT@ Average GS Thread Usage: {:.3f} ± {:.3f} %", metrics.gs_thread_usage, metrics_std.gs_thread_usage);
Console.WriteLnFmt("@HWSTAT@ Average GPU Usage: {:.3f} ± {:.3f} %", metrics.gpu_usage, metrics_std.gpu_usage);
Console.WriteLnFmt("@HWSTAT@ Average CPU Thread Time: {:.3f} ± {:.3f} ms", metrics.cpu_thread_time, metrics_std.cpu_thread_time);
Console.WriteLnFmt("@HWSTAT@ Average GS Thread Time: {:.3f} ± {:.3f} ms", metrics.gs_thread_time, metrics_std.gs_thread_time);
Console.WriteLnFmt("@HWSTAT@ Average GPU Time: {:.3f} ± {:.3f} ms", metrics.gpu_time, metrics_std.gpu_time);
}
}
}
+50
View File
@@ -15,6 +15,55 @@ namespace PerformanceMetrics
DISPFBBlit
};
class AvgFPS
{
private:
static constexpr size_t FPS_BUFFER_SIZE = 16;
std::array<float, FPS_BUFFER_SIZE> fps_buff{};
size_t count = 0;
size_t pos = 0;
float avg_fps = 0.0f;
public:
void ClearStats()
{
count = 0;
pos = 0;
fps_buff.fill(0.0f);
avg_fps = 0.0f;
}
void UpdateAvgFPS(float new_fps_val)
{
// If the change is quite dramatic, the user has either turned off the frame limiter or the game is dying.
// Clear it to catch it up quickly.
if (count > 0)
{
const float last_fps_val = fps_buff[(pos - 1) & (FPS_BUFFER_SIZE - 1)];
if (std::abs(last_fps_val - new_fps_val) > last_fps_val * 0.20f)
ClearStats();
}
fps_buff[pos] = new_fps_val;
pos = (pos + 1) & (FPS_BUFFER_SIZE - 1); // if you use values which don't become all 1's, make it %.
count = std::min(count + 1, FPS_BUFFER_SIZE);
float avg_sum = 0.0f;
for (size_t i = 0; i < count; i++)
{
avg_sum += fps_buff[i];
}
avg_fps = avg_sum / static_cast<float>(count);
}
float GetAvgFPS()
{
return avg_fps;
}
};
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;
using FrameTimeHistory = std::array<float, NUM_FRAME_TIME_SAMPLES>;
@@ -36,6 +85,7 @@ namespace PerformanceMetrics
bool IsInternalFPSValid();
float GetFPS();
float GetAvgVPS();
float GetInternalFPS();
float GetSpeed();
float GetAverageFrameTime();
+1 -1
View File
@@ -361,7 +361,7 @@ _vifT int nVifUnpack(const u8* data)
_nVifUnpack(idx, data, vifRegs.mode, isFill);
}
else
vu1Thread.VifUnpack(vif, vifRegs, (u8*)data, (size + 4) & ~0x3);
vu1Thread.VifUnpack(vif, vifRegs, (u8*)data, size);
vif.pass = 0;
vif.tag.size = 0;