diff --git a/common/Semaphore.cpp b/common/Semaphore.cpp index e961793637..5e24bc6960 100644 --- a/common/Semaphore.cpp +++ b/common/Semaphore.cpp @@ -26,6 +26,30 @@ // Semaphore Implementations // -------------------------------------------------------------------------------------- +bool Threading::WorkSema::CheckForWork() +{ + s32 value = m_state.load(std::memory_order_relaxed); + pxAssert(!IsDead(value)); + + // we want to switch to the running state, but preserve the waiting empty bit for RUNNING_N -> RUNNING_0 + // otherwise, we clear the waiting flag (since we're notifying the waiter that we're empty below) + while (!m_state.compare_exchange_weak(value, + IsReadyForSleep(value) ? STATE_RUNNING_0 : (value & STATE_FLAG_WAITING_EMPTY), + std::memory_order_acq_rel, std::memory_order_relaxed)) + { + } + + // if we're not empty, we have work to do + if (!IsReadyForSleep(value)) + return true; + + // this means we're empty, so notify any waiters + if (value & STATE_FLAG_WAITING_EMPTY) + m_empty_sema.Post(); + + // no work to do + return false; +} void Threading::WorkSema::WaitForWork() { diff --git a/common/Threading.h b/common/Threading.h index d91b58ec01..83bdc55621 100644 --- a/common/Threading.h +++ b/common/Threading.h @@ -211,6 +211,8 @@ namespace Threading m_sema.Post(); } + /// Checks if there's any work in the queue + bool CheckForWork(); /// Wait for work to be added to the queue void WaitForWork(); /// Wait for work to be added to the queue, spinning for a bit before sleeping the thread diff --git a/pcsx2/GS.h b/pcsx2/GS.h index 58c578a640..4b65a66b74 100644 --- a/pcsx2/GS.h +++ b/pcsx2/GS.h @@ -367,6 +367,7 @@ public: Threading::ThreadHandle m_thread_handle; std::atomic_bool m_open_flag{false}; std::atomic_bool m_shutdown_flag{false}; + std::atomic_bool m_run_idle_flag{false}; Threading::KernelSemaphore m_open_or_close_done; public: @@ -416,6 +417,7 @@ public: void SetSoftwareRendering(bool software, bool display_message = true); void ToggleSoftwareRendering(); bool SaveMemorySnapshot(u32 width, u32 height, std::vector* pixels); + void SetRunIdle(bool enabled); protected: bool TryOpenGS(); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.h b/pcsx2/GS/Renderers/Common/GSRenderer.h index faa601891f..d9908e00e7 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.h +++ b/pcsx2/GS/Renderers/Common/GSRenderer.h @@ -79,4 +79,4 @@ public: #endif }; -extern std::unique_ptr g_gs_renderer; \ No newline at end of file +extern std::unique_ptr g_gs_renderer; diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index 0e36b7b2fa..b21bdc980b 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -31,6 +31,8 @@ #ifndef PCSX2_CORE #include "gui/Dialogs/ModalPopups.h" +#else +#include "VMManager.h" #endif // Uncomment this to enable profiling of the GS RingBufferCopy function. @@ -296,9 +298,23 @@ void SysMtgsThread::MainLoop() // is very optimized (only 1 instruction test in most cases), so no point in trying // to avoid it. +#ifdef PCSX2_CORE + if (m_run_idle_flag.load(std::memory_order_acquire) && VMManager::GetState() != VMState::Running) + { + if (!m_sem_event.CheckForWork()) + GSPresentCurrentFrame(); + } + else + { + mtvu_lock.unlock(); + m_sem_event.WaitForWork(); + mtvu_lock.lock(); + } +#else mtvu_lock.unlock(); m_sem_event.WaitForWork(); mtvu_lock.lock(); +#endif if (!m_open_flag.load(std::memory_order_acquire)) break; @@ -1005,3 +1021,9 @@ void SysMtgsThread::PresentCurrentFrame() { GSPresentCurrentFrame(); } + +void SysMtgsThread::SetRunIdle(bool enabled) +{ + // NOTE: Should only be called on the GS thread. + m_run_idle_flag.store(enabled, std::memory_order_release); +}