GS: Add heuristic-based internal FPS

This commit is contained in:
Connor McLaughlin 2022-01-09 19:21:59 +10:00 committed by refractionpcsx2
parent a504b429bd
commit d3152bee9c
18 changed files with 140 additions and 24 deletions

View File

@ -552,8 +552,6 @@ static __fi void VSyncStart(u32 sCycle)
}
#endif
PerformanceMetrics::Update();
frameLimit(); // limit FPS
gsPostVsyncStart(); // MUST be after framelimit; doing so before causes funk with frame times!

View File

@ -509,7 +509,21 @@ static void DrawPerformanceOverlay()
const float speed = PerformanceMetrics::GetSpeed();
if (GSConfig.OsdShowFPS)
{
text.Write("%.2f", PerformanceMetrics::GetFPS());
switch (PerformanceMetrics::GetInternalFPSMethod())
{
case PerformanceMetrics::InternalFPSMethod::GSPrivilegedRegister:
text.Write("G: %.2f [P] | V: %.2f", PerformanceMetrics::GetInternalFPS(), PerformanceMetrics::GetFPS());
break;
case PerformanceMetrics::InternalFPSMethod::DISPFBBlit:
text.Write("G: %.2f [B] | V: %.2f", PerformanceMetrics::GetInternalFPS(), PerformanceMetrics::GetFPS());
break;
case PerformanceMetrics::InternalFPSMethod::None:
default:
text.Write("V: %.2f", PerformanceMetrics::GetFPS());
break;
}
first = false;
}
if (GSConfig.OsdShowSpeed)

View File

@ -26,6 +26,7 @@ using namespace Threading;
using namespace R5900;
alignas(16) u8 g_RealGSMem[Ps2MemSize::GSregs];
static bool s_GSRegistersWritten = false;
void gsSetVideoMode(GS_VideoMode mode)
{
@ -222,6 +223,8 @@ void __fastcall gsWrite64_generic( u32 mem, const mem64_t* value )
void __fastcall gsWrite64_page_00( u32 mem, const mem64_t* value )
{
s_GSRegistersWritten |= (mem == GS_DISPFB1 || mem == GS_DISPFB2 || mem == GS_PMODE);
gsWrite64_generic( mem, value );
}
@ -433,7 +436,9 @@ void gsPostVsyncStart()
{
//gifUnit.FlushToMTGS(); // Needed for some (broken?) homebrew game loaders
GetMTGS().PostVsyncStart();
const bool registers_written = s_GSRegistersWritten;
s_GSRegistersWritten = false;
GetMTGS().PostVsyncStart(registers_written);
}
void _gs_ResetFrameskip()

View File

@ -385,7 +385,7 @@ public:
u8* GetDataPacketPtr() const;
void SetEvent();
void PostVsyncStart();
void PostVsyncStart(bool registers_written);
bool IsGSOpened() const { return m_Opened; }

View File

@ -464,11 +464,11 @@ void GSgifTransfer3(u8* mem, u32 size)
}
}
void GSvsync(u32 field)
void GSvsync(u32 field, bool registers_written)
{
try
{
s_gs->VSync(field);
s_gs->VSync(field, registers_written);
}
catch (GSRecoverableError)
{

View File

@ -68,7 +68,7 @@ void GSgifTransfer(const u8* mem, u32 size);
void GSgifTransfer1(u8* mem, u32 addr);
void GSgifTransfer2(u8* mem, u32 size);
void GSgifTransfer3(u8* mem, u32 size);
void GSvsync(u32 field);
void GSvsync(u32 field, bool registers_written);
u32 GSmakeSnapshot(char* path);
int GSfreeze(FreezeAction mode, freezeData* data);
#ifndef PCSX2_CORE

View File

@ -52,6 +52,7 @@ protected:
u64 m_frame;
clock_t m_lastframe;
int m_count;
int m_disp_fb_sprite_blits;
friend class GSPerfMonAutoTimer;
@ -69,6 +70,14 @@ public:
void Start(int timer = Main);
void Stop(int timer = Main);
__fi void AddDisplayFramebufferSpriteBlit() { m_disp_fb_sprite_blits++; }
__fi int GetDisplayFramebufferSpriteBlits()
{
const int blits = m_disp_fb_sprite_blits;
m_disp_fb_sprite_blits = 0;
return blits;
}
};
class GSPerfMonAutoTimer

View File

@ -1406,6 +1406,16 @@ void GSState::FlushPrim()
{
GL_REG("FlushPrim ctxt %d", PRIM->CTXT);
// internal frame rate detection based on sprite blits to the display framebuffer
{
const u32 FRAME_FBP = m_context->FRAME.FBP;
if ((m_regs->DISP[0].DISPFB.FBP == FRAME_FBP && m_regs->PMODE.EN1) ||
(m_regs->DISP[1].DISPFB.FBP == FRAME_FBP && m_regs->PMODE.EN2))
{
g_perfmon.AddDisplayFramebufferSpriteBlit();
}
}
GSVertex buff[2];
s_n++;

View File

@ -388,7 +388,7 @@ static GSVector4 CalculateDrawRect(s32 window_width, s32 window_height, s32 text
return ret;
}
void GSRenderer::VSync(u32 field)
void GSRenderer::VSync(u32 field, bool registers_written)
{
GSPerfMonAutoTimer pmat(&g_perfmon);
@ -399,6 +399,10 @@ void GSRenderer::VSync(u32 field)
m_regs->Dump(root_sw + format("%05d_f%lld_gs_reg.txt", s_n, g_perfmon.GetFrame()));
}
const int fb_sprite_blits = g_perfmon.GetDisplayFramebufferSpriteBlits();
const bool fb_sprite_frame = (fb_sprite_blits > 0);
PerformanceMetrics::Update(registers_written, fb_sprite_frame);
g_gs_device->AgePool();
const bool blank_frame = !Merge(field ? 1 : 0);

View File

@ -44,7 +44,7 @@ public:
virtual void Destroy();
virtual void VSync(u32 field);
virtual void VSync(u32 field, bool registers_written);
virtual bool MakeSnapshot(const std::string& path);
virtual void KeyEvent(const HostKeyEvent& e);
virtual bool CanUpscale() { return false; }

View File

@ -291,7 +291,7 @@ void GSRendererHW::Reset()
GSRenderer::Reset();
}
void GSRendererHW::VSync(u32 field)
void GSRendererHW::VSync(u32 field, bool registers_written)
{
//Check if the frame buffer width or display width has changed
SetScaling();
@ -303,7 +303,7 @@ void GSRendererHW::VSync(u32 field)
m_reset = false;
}
GSRenderer::VSync(field);
GSRenderer::VSync(field, registers_written);
m_tc->IncAge();

View File

@ -182,7 +182,7 @@ public:
GSVector2 GetTextureScaleFactor() override;
void Reset() override;
void VSync(u32 field) override;
void VSync(u32 field, bool registers_written) override;
GSTexture* GetOutput(int i, int& y_offset) override;
GSTexture* GetFeedbackOutput() override;

View File

@ -91,7 +91,7 @@ void GSRendererSW::Reset()
GSRenderer::Reset();
}
void GSRendererSW::VSync(u32 field)
void GSRendererSW::VSync(u32 field, bool registers_written)
{
Sync(0); // IncAge might delete a cached texture in use
@ -128,7 +128,7 @@ void GSRendererSW::VSync(u32 field)
//
*/
GSRenderer::VSync(field);
GSRenderer::VSync(field, registers_written);
m_tc->IncAge();

View File

@ -80,7 +80,7 @@ protected:
std::atomic<u16> m_tex_pages[512];
void Reset() override;
void VSync(u32 field) override;
void VSync(u32 field, bool registers_written) override;
GSTexture* GetOutput(int i, int& y_offset) override;
GSTexture* GetFeedbackOutput() override;

View File

@ -129,9 +129,13 @@ struct RingCmdPacket_Vsync
u32 csr;
u32 imr;
GSRegSIGBLID siglblid;
// must be 16 byte aligned
u32 registers_written;
u32 pad[3];
};
void SysMtgsThread::PostVsyncStart()
void SysMtgsThread::PostVsyncStart(bool registers_written)
{
// Optimization note: Typically regset1 isn't needed. The regs in that area are typically
// changed infrequently, usually during video mode changes. However, on modern systems the
@ -146,7 +150,8 @@ void SysMtgsThread::PostVsyncStart()
remainder[0] = GSCSRr;
remainder[1] = GSIMR._u32;
(GSRegSIGBLID&)remainder[2] = GSSIGLBLID;
m_packet_writepos = (m_packet_writepos + 1) & RingBufferMask;
remainder[4] = static_cast<u32>(registers_written);
m_packet_writepos = (m_packet_writepos + 2) & RingBufferMask;
SendDataPacket();
@ -436,7 +441,7 @@ void SysMtgsThread::ExecuteTaskInThread()
((GSRegSIGBLID&)RingBuffer.Regs[0x1080]) = (GSRegSIGBLID&)remainder[2];
// CSR & 0x2000; is the pageflip id.
GSvsync(((u32&)RingBuffer.Regs[0x1000]) & 0x2000);
GSvsync(((u32&)RingBuffer.Regs[0x1000]) & 0x2000, remainder[4] != 0);
gsFrameSkip();
m_QueuedFrameCount.fetch_sub(1);

View File

@ -28,6 +28,7 @@ static const float UPDATE_INTERVAL = 0.5f;
static float s_vertical_frequency = 0.0f;
static float s_fps = 0.0f;
static float s_internal_fps = 0.0f;
static float s_worst_frame_time = 0.0f;
static float s_average_frame_time = 0.0f;
static float s_average_frame_time_accumulator = 0.0f;
@ -36,6 +37,14 @@ static u32 s_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 Common::ThreadCPUTimer s_cpu_thread_timer;
static u64 s_last_gs_time = 0;
static u64 s_last_vu_time = 0;
@ -53,8 +62,10 @@ void PerformanceMetrics::Clear()
Reset();
s_fps = 0.0f;
s_internal_fps = 0.0f;
s_worst_frame_time = 0.0f;
s_average_frame_time = 0.0f;
s_internal_fps_method = PerformanceMetrics::InternalFPSMethod::None;
s_cpu_thread_usage = 0.0f;
s_cpu_thread_time = 0.0f;
@ -62,10 +73,15 @@ void PerformanceMetrics::Clear()
s_gs_thread_time = 0.0f;
s_vu_thread_usage = 0.0f;
s_vu_thread_time = 0.0f;
s_frame_number = 0;
}
void PerformanceMetrics::Reset()
{
s_frames_since_last_update = 0;
s_gs_framebuffer_blits_since_last_update = 0;
s_gs_privileged_register_writes_since_last_update = 0;
s_average_frame_time_accumulator = 0.0f;
s_worst_frame_time_accumulator = 0.0f;
@ -78,12 +94,15 @@ void PerformanceMetrics::Reset()
s_last_ticks = GetCPUTicks();
}
void PerformanceMetrics::Update()
void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit)
{
const float frame_time = s_last_frame_time.GetTimeMillisecondsAndReset();
s_average_frame_time_accumulator += frame_time;
s_worst_frame_time_accumulator = std::max(s_worst_frame_time_accumulator, frame_time);
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++;
const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue();
const Common::Timer::Value ticks_diff = now_ticks - s_last_update_time.GetStartValue();
@ -97,6 +116,26 @@ void PerformanceMetrics::Update()
s_average_frame_time_accumulator = 0.0f;
s_fps = static_cast<float>(s_frames_since_last_update) / time;
// 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)
{
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;
s_cpu_thread_timer.GetUsageInMillisecondsAndReset(ticks_diff, &s_cpu_thread_time, &s_cpu_thread_usage);
s_cpu_thread_time /= static_cast<double>(s_frames_since_last_update);
@ -137,11 +176,31 @@ void PerformanceMetrics::SetVerticalFrequency(float rate)
s_vertical_frequency = rate;
}
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 / s_vertical_frequency) * 100.0;

View File

@ -18,9 +18,16 @@
namespace PerformanceMetrics
{
enum class InternalFPSMethod
{
None,
GSPrivilegedRegister,
DISPFBBlit
};
void Clear();
void Reset();
void Update();
void Update(bool gs_register_write, bool fb_blit);
/// Sets the EE thread for CPU usage calculations.
void SetCPUThreadTimer(Common::ThreadCPUTimer timer);
@ -28,7 +35,13 @@ namespace PerformanceMetrics
/// Sets the vertical frequency, used in speed calculations.
void SetVerticalFrequency(float rate);
u64 GetFrameNumber();
InternalFPSMethod GetInternalFPSMethod();
bool IsInternalFPSValid();
float GetFPS();
float GetInternalFPS();
float GetSpeed();
float GetAverageFrameTime();
float GetWorstFrameTime();

View File

@ -662,8 +662,7 @@ void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSData& event, char* regs)
}
case VSync:
{
GSvsync((*((int*)(regs + 4096)) & 0x2000) > 0 ? (u8)1 : (u8)0);
PerformanceMetrics::Update();
GSvsync((*((int*)(regs + 4096)) & 0x2000) > 0 ? (u8)1 : (u8)0, false);
g_FrameCount++;
break;
}
@ -813,7 +812,7 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
if (GSfreeze(FreezeAction::Load, &fd))
GSDump::isRunning = false;
GSvsync(1);
GSvsync(1, false);
GSreset();
GSfreeze(FreezeAction::Load, &fd);