From cb214d10349e4a4cdd67589af9601f402366715d Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Thu, 5 Nov 2026 19:46:22 +0100 Subject: [PATCH] Core, VideoCommon: drop std::this_thread waits Waits that polled now block on an eventcount that the waited-on thread notifies: - Booting in dual core waits for the CPU thread to reach its run loop instead of polling every millisecond. - AsyncShaderCompiler::WaitUntilCompletion wakes as each worker finishes an item instead of every 33 ms. Timed sleeps go through Common::SleepUntil and SleepCurrentThreadUs, built on retro_sleep_us: the CoreTiming throttle, PrecisionTimer, real Wiimote report pacing, Bluetooth timed transfers, the Linux L2CAP connect retry and the BBA read thread's idle wait. FrameTiming::ThrottleFrame had no callers and is deleted. --- Source/Core/Common/Thread.cpp | 6 ++++ Source/Core/Common/Thread.h | 4 +++ Source/Core/Common/Timer.cpp | 12 +++++-- Source/Core/Common/Timer.h | 3 ++ Source/Core/Core/CoreTiming.cpp | 5 ++- Source/Core/Core/HW/CPU.cpp | 5 +++ Source/Core/Core/HW/CPU.h | 15 ++++++++ Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp | 3 +- Source/Core/Core/HW/WiimoteReal/IOLinux.cpp | 3 +- .../Core/Core/HW/WiimoteReal/WiimoteReal.cpp | 4 +-- .../USB/Bluetooth/LibUSBBluetoothAdapter.cpp | 4 +-- Source/Core/DolphinLibretro/Audio.cpp | 31 ----------------- Source/Core/DolphinLibretro/Audio.h | 1 - Source/Core/DolphinLibretro/Main.cpp | 5 +-- .../Core/VideoCommon/AsyncShaderCompiler.cpp | 34 ++++++++++++++----- Source/Core/VideoCommon/AsyncShaderCompiler.h | 5 +++ 16 files changed, 82 insertions(+), 58 deletions(-) diff --git a/Source/Core/Common/Thread.cpp b/Source/Core/Common/Thread.cpp index bb4add2dd3..b5bc9bbc75 100644 --- a/Source/Core/Common/Thread.cpp +++ b/Source/Core/Common/Thread.cpp @@ -44,6 +44,12 @@ void SleepCurrentThread(int ms) retro_sleep(ms); } +void SleepCurrentThreadUs(long long us) +{ + if (us > 0) + retro_sleep_us((unsigned)us); +} + void YieldCPU() { sthread_yield(); diff --git a/Source/Core/Common/Thread.h b/Source/Core/Common/Thread.h index 20362c725e..13c4426e73 100644 --- a/Source/Core/Common/Thread.h +++ b/Source/Core/Common/Thread.h @@ -25,6 +25,10 @@ int CurrentThreadId(); void SleepCurrentThread(int ms); +// Sleeps for us microseconds; returns at once when us is not positive. Callers that +// sleep until a time point pass the distance to it. +void SleepCurrentThreadUs(long long us); + // Use this function during a spin-wait to make the current thread // relax while another thread is working. This may be more efficient // than using events because event functions use kernel calls. diff --git a/Source/Core/Common/Timer.cpp b/Source/Core/Common/Timer.cpp index 8d10cd5aea..85d61004e7 100644 --- a/Source/Core/Common/Timer.cpp +++ b/Source/Core/Common/Timer.cpp @@ -4,8 +4,8 @@ #include "Common/Timer.h" #include -#include #include "Common/CommonFuncs.h" +#include "Common/Thread.h" #ifdef _WIN32 #include @@ -152,6 +152,12 @@ PrecisionTimer::~PrecisionTimer() #endif } +void SleepUntil(TimePoint target) +{ + SleepCurrentThreadUs( + std::chrono::duration_cast(target - Clock::now()).count()); +} + void PrecisionTimer::SleepUntil(Clock::time_point target) { constexpr auto SPIN_TIME = @@ -179,7 +185,7 @@ void PrecisionTimer::SleepUntil(Clock::time_point target) } #else // Sleeping on Linux generally isn't as terrible as it is on Windows. - std::this_thread::sleep_until(target - SPIN_TIME); + Common::SleepUntil(target - SPIN_TIME); #endif // Spin for the remaining time. @@ -188,7 +194,7 @@ void PrecisionTimer::SleepUntil(Clock::time_point target) #if defined(_WIN32) YieldProcessor(); #else - std::this_thread::yield(); + YieldCPU(); #endif } } diff --git a/Source/Core/Common/Timer.h b/Source/Core/Common/Timer.h index 3f5a51ad7f..efe8ade81d 100644 --- a/Source/Core/Common/Timer.h +++ b/Source/Core/Common/Timer.h @@ -32,6 +32,9 @@ private: bool m_running{false}; }; +// Sleeps until target, with the OS sleep's granularity; returns at once if it has passed. +void SleepUntil(TimePoint target); + class PrecisionTimer { public: diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index b8ce2b3f80..738af8ae8e 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -435,7 +434,7 @@ void CoreTimingManager::SleepUntil(TimePoint time_point) if (use_precision_timer) m_precision_cpu_timer.SleepUntil(time_point); else - std::this_thread::sleep_until(time_point); + Common::SleepUntil(time_point); // Count amount of time sleeping for performance metrics const TimePoint time_after_sleep = Clock::now(); @@ -446,7 +445,7 @@ void CoreTimingManager::SleepUntil(TimePoint time_point) if (use_precision_timer) m_precision_gpu_timer.SleepUntil(time_point); else - std::this_thread::sleep_until(time_point); + Common::SleepUntil(time_point); } } diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index 3b32d612a6..5a98c0453b 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -41,6 +41,7 @@ CPUManager::CPUManager(Core::System& system) : m_system(system) retro_eventcount_init(&m_cpu_idle); #ifdef __LIBRETRO__ retro_atomic_int_init(&m_cpu_run_state_reached, 0); + retro_eventcount_init(&m_run_state_event); #endif } @@ -52,6 +53,9 @@ CPUManager::~CPUManager() node = node->next; delete job; } +#ifdef __LIBRETRO__ + retro_eventcount_free(&m_run_state_event); +#endif retro_eventcount_free(&m_cpu_idle); retro_eventcount_free(&m_cpu_wake); retro_eventcount_free(&m_stepping_released); @@ -205,6 +209,7 @@ void CPUManager::Run() #ifdef __LIBRETRO__ retro_atomic_store_release_int(&m_cpu_run_state_reached, 1); + retro_eventcount_notify(&m_run_state_event); #endif while (LoadState() != State::PowerDown) { diff --git a/Source/Core/Core/HW/CPU.h b/Source/Core/Core/HW/CPU.h index 660e60247f..122b0f443b 100644 --- a/Source/Core/Core/HW/CPU.h +++ b/Source/Core/Core/HW/CPU.h @@ -113,6 +113,20 @@ public: { return retro_atomic_load_acquire_int(&m_cpu_run_state_reached) != 0; } + // Blocks until the CPU thread has entered its run loop. + void WaitForCPURunState() + { + for (;;) + { + const int key = retro_eventcount_prepare_wait(&m_run_state_event); + if (HasCPURunStateBeenReached()) + { + retro_eventcount_cancel_wait(&m_run_state_event); + return; + } + retro_eventcount_commit_wait(&m_run_state_event, key); + } + } #endif private: struct Job @@ -167,6 +181,7 @@ private: Core::System& m_system; #ifdef __LIBRETRO__ retro_atomic_int_t m_cpu_run_state_reached; + retro_eventcount_t m_run_state_event; #endif }; } // namespace CPU diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp index 8b52d09045..fa15f9c6ce 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp @@ -5,7 +5,6 @@ #include #include -#include #include "SFML/Network/IpAddress.hpp" #include "SFML/Network/Socket.hpp" @@ -760,7 +759,7 @@ void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(void* data) if (datasize == 0) { // Make thread less CPU hungry - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Common::SleepCurrentThread(1); } if (!self->m_read_enabled.IsSet()) diff --git a/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp b/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp index c37aa42fc9..56ae6703ab 100644 --- a/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp +++ b/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp @@ -21,6 +21,7 @@ #include "Common/Logging/Log.h" #include "Common/Network.h" #include "Common/ScopeGuard.h" +#include "Common/Thread.h" #include "Common/UnixUtil.h" #include "Core/Config/MainSettings.h" @@ -313,7 +314,7 @@ bool WiimoteLinux::ConnectInternal() Common::LastStrerrorString()); // A socket state is unspecified after connect() fails, so close and recreate it. close(descriptor); - std::this_thread::sleep_for(std::chrono::milliseconds{500}); + Common::SleepCurrentThread(500); } return -1; diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp index cafa700033..32507a6c71 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp @@ -4,7 +4,6 @@ #include "Core/HW/WiimoteReal/WiimoteReal.h" #include -#include #include #include @@ -19,6 +18,7 @@ #include "Common/Quiesce.h" #include "Common/Swap.h" #include "Common/Thread.h" +#include "Common/Timer.h" #include "Core/Config/MainSettings.h" #include "Core/Config/WiimoteSettings.h" @@ -438,7 +438,7 @@ bool Wiimote::Write(const WriteRecord& record) } // Write the report at the proper time, mainly for speaker data, not that it will help much. - std::this_thread::sleep_until(record.time); + Common::SleepUntil(record.time); int ret = IOWrite(record.data, record.size); return ret != 0; diff --git a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp index 4e6f9cb13d..dae199bc01 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp @@ -3,6 +3,7 @@ #include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h" #include "Common/Thread.h" +#include "Common/Timer.h" #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include @@ -410,7 +410,7 @@ void LibUSBBluetoothAdapter::SubmitTimedTransfer(TimedTransfer transfer) if (Config::Get(Config::MAIN_PRECISION_FRAME_TIMING)) m_precision_timer.SleepUntil(transfer.target_time); else - std::this_thread::sleep_until(transfer.target_time); + Common::SleepUntil(transfer.target_time); SubmitTransfer(transfer.transfer); } diff --git a/Source/Core/DolphinLibretro/Audio.cpp b/Source/Core/DolphinLibretro/Audio.cpp index e2c23ff77d..b611542d04 100644 --- a/Source/Core/DolphinLibretro/Audio.cpp +++ b/Source/Core/DolphinLibretro/Audio.cpp @@ -1,7 +1,6 @@ #include #include "Audio.h" #include "Common/Logging/Log.h" -#include "Common/Thread.h" #include "AudioCommon/SoundStream.h" #include "VideoCommon/Present.h" #include "DolphinLibretro/Common/Globals.h" @@ -304,7 +303,6 @@ namespace FrameTiming bool g_have_frame_time_cb {false}; static retro_frame_time_callback ftcb = {}; - static auto last_frame_time = std::chrono::steady_clock::now(); void Reset() { @@ -340,7 +338,6 @@ namespace FrameTiming return; } - last_frame_time = std::chrono::steady_clock::now(); DEBUG_LOG_FMT(VIDEO, "frame timing enabled: target={} usec ({} Hz)", ftcb.reference, refresh_rate); @@ -375,34 +372,6 @@ namespace FrameTiming // Environment call not supported Libretro::Audio::g_is_fast_forwarding = false; } - - void ThrottleFrame() - { - if (!g_have_frame_time_cb) - return; - - auto now = std::chrono::steady_clock::now(); - auto elapsed_us = std::chrono::duration_cast( - now - last_frame_time).count(); - - retro_usec_t target_us = target_frame_duration_usec.load(std::memory_order_relaxed); - - if (elapsed_us < target_us) - { - auto sleep_us = target_us - elapsed_us; - if (sleep_us > 1000) - { - Common::SleepCurrentThread((sleep_us - 500) / 1000); - } - - while (std::chrono::duration_cast( - std::chrono::steady_clock::now() - last_frame_time).count() < target_us) - { - } - } - - last_frame_time = std::chrono::steady_clock::now(); - } } // namespace FrameTiming } // namespace Libretro diff --git a/Source/Core/DolphinLibretro/Audio.h b/Source/Core/DolphinLibretro/Audio.h index 029bf8c819..dac1f0e8e9 100644 --- a/Source/Core/DolphinLibretro/Audio.h +++ b/Source/Core/DolphinLibretro/Audio.h @@ -32,7 +32,6 @@ namespace FrameTiming bool IsEnabled(); void CheckForFastForwarding(); bool IsFastForwarding(); - void ThrottleFrame(); } // namespace FrameTiming } // namespace Libretro diff --git a/Source/Core/DolphinLibretro/Main.cpp b/Source/Core/DolphinLibretro/Main.cpp index 87fc76341a..5876915744 100644 --- a/Source/Core/DolphinLibretro/Main.cpp +++ b/Source/Core/DolphinLibretro/Main.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -291,9 +290,7 @@ void retro_run(void) Core::StartEmuThread(system, std::move(Core::g_boot_params), wsi); // Wait until CPU thread has reached Run() - auto& cpu_manager = system.GetCPU(); - while (!cpu_manager.HasCPURunStateBeenReached()) - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + system.GetCPU().WaitForCPURunState(); } else Core::EmuThread(system, std::move(Core::g_boot_params), wsi); diff --git a/Source/Core/VideoCommon/AsyncShaderCompiler.cpp b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp index df22841fec..3324499747 100644 --- a/Source/Core/VideoCommon/AsyncShaderCompiler.cpp +++ b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp @@ -3,8 +3,6 @@ #include "VideoCommon/AsyncShaderCompiler.h" -#include - #include "Common/Assert.h" #include "Common/Logging/Log.h" #include "Common/Thread.h" @@ -16,6 +14,7 @@ namespace VideoCommon { AsyncShaderCompiler::AsyncShaderCompiler() { + retro_eventcount_init(&m_work_done); } AsyncShaderCompiler::~AsyncShaderCompiler() @@ -24,6 +23,19 @@ AsyncShaderCompiler::~AsyncShaderCompiler() // The work item classes are expected to clean up after themselves. ASSERT(!HasWorkerThreads()); ClearAllWork(); + retro_eventcount_free(&m_work_done); +} + +bool AsyncShaderCompiler::WaitForWorkDone(s64 timeout_us) +{ + const int key = retro_eventcount_prepare_wait(&m_work_done); + if (!HasPendingWork()) + { + retro_eventcount_cancel_wait(&m_work_done); + return true; + } + retro_eventcount_commit_wait_timeout(&m_work_done, key, timeout_us); + return !HasPendingWork(); } void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item, u32 priority) @@ -135,14 +147,17 @@ bool AsyncShaderCompiler::WaitUntilCompletion( if (!HasPendingWork()) return true; - // Wait a second before opening a progress dialog. + // Wait a second before reporting progress, woken as each item finishes. // This way, if the operation completes quickly, we don't annoy the user. - constexpr u32 CHECK_INTERVAL_MS = 1000 / 30; - constexpr auto CHECK_INTERVAL = std::chrono::milliseconds(CHECK_INTERVAL_MS); - for (u32 i = 0; i < (1000 / CHECK_INTERVAL_MS); i++) + constexpr s64 PROGRESS_INTERVAL_US = 1000000 / 30; + const TimePoint quiet_until = Clock::now() + std::chrono::seconds(1); + for (;;) { - std::this_thread::sleep_for(std::chrono::milliseconds(CHECK_INTERVAL)); - if (!HasPendingWork()) + const s64 left_us = + std::chrono::duration_cast(quiet_until - Clock::now()).count(); + if (left_us <= 0) + break; + if (WaitForWorkDone(left_us)) return true; } @@ -158,7 +173,7 @@ bool AsyncShaderCompiler::WaitUntilCompletion( const size_t remaining_items = m_pending_work.size() + ItemsInSlots(); progress_callback(total_items - remaining_items, total_items); - std::this_thread::sleep_for(CHECK_INTERVAL); + WaitForWorkDone(PROGRESS_INTERVAL_US); } return false; } @@ -331,6 +346,7 @@ void AsyncShaderCompiler::WorkerThreadRun(Worker* worker) mpsc_stack_push(&m_completed_work, &done->link); } retro_atomic_fetch_sub_int(&m_busy_workers, 1); + retro_eventcount_notify(&m_work_done); } } diff --git a/Source/Core/VideoCommon/AsyncShaderCompiler.h b/Source/Core/VideoCommon/AsyncShaderCompiler.h index 72742e8e4c..e02bc364a2 100644 --- a/Source/Core/VideoCommon/AsyncShaderCompiler.h +++ b/Source/Core/VideoCommon/AsyncShaderCompiler.h @@ -67,6 +67,9 @@ protected: virtual void WorkerThreadExit(void* param); private: + // Waits up to timeout_us for a worker to finish an item; true once nothing is pending. + bool WaitForWorkDone(s64 timeout_us); + void WorkerThreadStart(); void WorkerThreadEntryPoint(void* param); void WorkerThreadRun(); @@ -107,6 +110,8 @@ private: // the unique_ptr be moved out. std::multimap m_pending_work; retro_atomic_int_t m_busy_workers{}; + // Notified each time a worker finishes an item. + retro_eventcount_t m_work_done; mpsc_stack_t m_completed_work{}; // Items finished synchronously, when there are no workers. Video thread only.