diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index eb781f7c6b..52ae8b57ce 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -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! diff --git a/pcsx2/Frontend/ImGuiManager.cpp b/pcsx2/Frontend/ImGuiManager.cpp index ccfa366dd3..52be9649d4 100644 --- a/pcsx2/Frontend/ImGuiManager.cpp +++ b/pcsx2/Frontend/ImGuiManager.cpp @@ -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) diff --git a/pcsx2/GS.cpp b/pcsx2/GS.cpp index 4e1b8bc603..abb20d4a39 100644 --- a/pcsx2/GS.cpp +++ b/pcsx2/GS.cpp @@ -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() diff --git a/pcsx2/GS.h b/pcsx2/GS.h index 4120282285..bdc066327b 100644 --- a/pcsx2/GS.h +++ b/pcsx2/GS.h @@ -385,7 +385,7 @@ public: u8* GetDataPacketPtr() const; void SetEvent(); - void PostVsyncStart(); + void PostVsyncStart(bool registers_written); bool IsGSOpened() const { return m_Opened; } diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 16517454ff..8600c62e6e 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -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) { diff --git a/pcsx2/GS/GS.h b/pcsx2/GS/GS.h index 64cc4448dc..d37e4ba04c 100644 --- a/pcsx2/GS/GS.h +++ b/pcsx2/GS/GS.h @@ -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 diff --git a/pcsx2/GS/GSPerfMon.h b/pcsx2/GS/GSPerfMon.h index c5f2bdba7c..ca84ef97d1 100644 --- a/pcsx2/GS/GSPerfMon.h +++ b/pcsx2/GS/GSPerfMon.h @@ -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 diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 8e0add93e6..885b468ea8 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -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++; diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 302f7baf09..db19983b79 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -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); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.h b/pcsx2/GS/Renderers/Common/GSRenderer.h index 3d2b8b012a..a8a116bcf9 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.h +++ b/pcsx2/GS/Renderers/Common/GSRenderer.h @@ -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; } diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index c5afbce5f0..dc7dfdb5fb 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -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(); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 446a9c57bd..7796ea494c 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -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; diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp index ce23d00746..215c185722 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp @@ -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(); diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.h b/pcsx2/GS/Renderers/SW/GSRendererSW.h index d85ac70bf6..7b60a9fe8e 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.h +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.h @@ -80,7 +80,7 @@ protected: std::atomic 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; diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index bbd0348fe4..cb5d2ae141 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -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(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); diff --git a/pcsx2/PerformanceMetrics.cpp b/pcsx2/PerformanceMetrics.cpp index 718285a320..bde3a7a0dd 100644 --- a/pcsx2/PerformanceMetrics.cpp +++ b/pcsx2/PerformanceMetrics.cpp @@ -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(gs_register_write); + s_gs_framebuffer_blits_since_last_update += static_cast(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(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(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(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(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; diff --git a/pcsx2/PerformanceMetrics.h b/pcsx2/PerformanceMetrics.h index 1500a70370..ae7539b790 100644 --- a/pcsx2/PerformanceMetrics.h +++ b/pcsx2/PerformanceMetrics.h @@ -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(); diff --git a/pcsx2/gui/Dialogs/GSDumpDialog.cpp b/pcsx2/gui/Dialogs/GSDumpDialog.cpp index 93ee5095cf..cc7b4e4b96 100644 --- a/pcsx2/gui/Dialogs/GSDumpDialog.cpp +++ b/pcsx2/gui/Dialogs/GSDumpDialog.cpp @@ -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);