mirror of https://github.com/PCSX2/pcsx2.git
GS: Add heuristic-based internal FPS
This commit is contained in:
parent
a504b429bd
commit
d3152bee9c
|
@ -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!
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -385,7 +385,7 @@ public:
|
|||
|
||||
u8* GetDataPacketPtr() const;
|
||||
void SetEvent();
|
||||
void PostVsyncStart();
|
||||
void PostVsyncStart(bool registers_written);
|
||||
|
||||
bool IsGSOpened() const { return m_Opened; }
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue