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.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-05 20:11:34 +01:00
1 parent f7d5095739
commit cb214d1034
16 files changed
+82 -58

No files matched your search

+6
View File
@@ -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();
+4
View File
@@ -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.
+9 -3
View File
@@ -4,8 +4,8 @@
#include "Common/Timer.h"
#include <chrono>
#include <thread>
#include "Common/CommonFuncs.h"
#include "Common/Thread.h"
#ifdef _WIN32
#include <windows.h>
@@ -152,6 +152,12 @@ PrecisionTimer::~PrecisionTimer()
#endif
}
void SleepUntil(TimePoint target)
{
SleepCurrentThreadUs(
std::chrono::duration_cast<std::chrono::microseconds>(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
}
}
+3
View File
@@ -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:
+2 -3
View File
@@ -5,7 +5,6 @@
#include <algorithm>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -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);
}
}
+5
View File
@@ -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)
{
+15
View File
@@ -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
+1 -2
View File
@@ -5,7 +5,6 @@
#include <bit>
#include <optional>
#include <thread>
#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())
+2 -1
View File
@@ -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;
@@ -4,7 +4,6 @@
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include <algorithm>
#include <thread>
#include <unordered_set>
#include <queues/mpsc_stack.h>
@@ -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;
@@ -3,6 +3,7 @@
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
#include "Common/Thread.h"
#include "Common/Timer.h"
#include <cassert>
@@ -11,7 +12,6 @@
#include <cstdlib>
#include <cstring>
#include <ranges>
#include <thread>
#include <utility>
#include <libusb.h>
@@ -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);
}
-31
View File
@@ -1,7 +1,6 @@
#include <libretro.h>
#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<std::chrono::microseconds>(
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::microseconds>(
std::chrono::steady_clock::now() - last_frame_time).count() < target_us)
{
}
}
last_frame_time = std::chrono::steady_clock::now();
}
} // namespace FrameTiming
} // namespace Libretro
-1
View File
@@ -32,7 +32,6 @@ namespace FrameTiming
bool IsEnabled();
void CheckForFastForwarding();
bool IsFastForwarding();
void ThrottleFrame();
} // namespace FrameTiming
} // namespace Libretro
+1 -4
View File
@@ -3,7 +3,6 @@
#include <cstdint>
#include <libretro.h>
#include <string>
#include <thread>
#include <fstream>
#include <vector>
@@ -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);
@@ -3,8 +3,6 @@
#include "VideoCommon/AsyncShaderCompiler.h"
#include <thread>
#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<std::chrono::microseconds>(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);
}
}
@@ -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<u32, WorkItemPtr> 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.