From 767c898df884fb710a4dbd7e3170021b9e52d184 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Thu, 5 Nov 2026 07:27:14 +0100 Subject: [PATCH] Move every thread onto rthreads and add a lock-free parallel_for No std::thread, std::async, std::future or std::promise is left. Each thread is an sthread with a plain entry point: Common::ThreadEntry<&C::F> turns a member function into one, Common::JoinThread joins and clears a handle, and Common::StartDetachedThread covers the fire-and-forget users. Common/ParallelFor.{h,c} (C89) runs fn(ctx, i) for i in [0, count) on a persistent pool of one worker per extra core plus the caller. Jobs are claimed with an atomic counter, workers park on an eventcount between batches, and the batch hand-off is a seq_cst users/active pair, so there is no lock anywhere. A caller that finds the pool busy runs its batch serially instead of waiting. retro_deinit joins the pool. - VolumeWii::HashGroup spawned 64 threads per 2 MiB group and EncryptGroup another one per core; both are now parallel_for batches on the pool. The H1/H2 tail is a few small hashes and runs inline. - VolumeVerifier spawned up to five std::async threads per chunk; the chunk's tasks now go to one persistent worker that fans them out with parallel_for, still overlapping the next read. The Redump lookup is an sthread joined in Finish or the destructor. - The dual-core "GPU thread" in Core only set a thread_local on itself and exited, since the frontend runs the GPU loop. It is gone, along with the non-libretro branches of Core.cpp. - The emu thread is started through Core::StartEmuThread. - CPU core counts come from cpu_features_get_core_amount(). --- Makefile.common | 2 + Source/Core/Common/ArmCPUDetect.cpp | 6 +- Source/Core/Common/ParallelFor.c | 221 ++++++++++++++++++ Source/Core/Common/ParallelFor.h | 37 +++ Source/Core/Common/Thread.h | 37 +++ Source/Core/Common/x64CPUDetect.cpp | 5 +- Source/Core/Core/AchievementManager.cpp | 10 +- Source/Core/Core/AchievementManager.h | 1 + Source/Core/Core/Core.cpp | 161 ++++--------- Source/Core/Core/Core.h | 7 +- Source/Core/Core/CoreTiming.cpp | 1 + Source/Core/Core/HW/CPU.cpp | 8 +- Source/Core/Core/HW/DSPLLE/DSPLLE.cpp | 8 +- Source/Core/Core/HW/DSPLLE/DSPLLE.h | 7 +- Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp | 10 +- Source/Core/Core/HW/EXI/BBA/IPC.cpp | 7 +- .../Core/HW/EXI/BBA/TAPServerConnection.cpp | 6 +- .../Core/HW/EXI/BBA/TAPServerConnection.h | 5 +- Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp | 9 +- Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp | 9 +- Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp | 9 +- Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp | 13 +- Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h | 17 +- Source/Core/Core/HW/EXI/EXI_DeviceGecko.cpp | 23 +- Source/Core/Core/HW/EXI/EXI_DeviceGecko.h | 9 +- .../Core/HW/GCMemcard/GCMemcardDirectory.cpp | 6 +- .../Core/HW/GCMemcard/GCMemcardDirectory.h | 5 +- .../Core/Core/HW/GCMemcard/GCMemcardRaw.cpp | 7 +- Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h | 5 +- Source/Core/Core/HW/SI/SI_DeviceGBA.cpp | 12 +- .../Core/Core/HW/WiimoteReal/WiimoteReal.cpp | 22 +- Source/Core/Core/HW/WiimoteReal/WiimoteReal.h | 7 +- .../Core/Core/IOS/Network/KD/NetKDRequest.cpp | 7 +- .../Core/Core/IOS/Network/KD/NetKDRequest.h | 5 +- Source/Core/Core/IOS/USB/USBScanner.cpp | 28 +-- Source/Core/Core/IOS/USB/USBScanner.h | 6 +- Source/Core/Core/LibusbUtils.cpp | 9 +- Source/Core/Core/MemTools.cpp | 7 +- Source/Core/Core/Movie.cpp | 11 +- Source/Core/DiscIO/CachedBlob.cpp | 17 +- Source/Core/DiscIO/VolumeVerifier.cpp | 130 ++++++----- Source/Core/DiscIO/VolumeVerifier.h | 30 ++- Source/Core/DiscIO/VolumeWii.cpp | 161 ++++++------- Source/Core/DolphinLibretro/Main.cpp | 5 +- .../DualShockUDPClient/DualShockUDPClient.cpp | 7 +- Source/Core/InputCommon/GCAdapter.cpp | 32 +-- .../VideoCommon/Assets/CustomAssetLoader.cpp | 20 +- .../VideoCommon/Assets/CustomAssetLoader.h | 12 +- .../Core/VideoCommon/AsyncShaderCompiler.cpp | 24 +- Source/Core/VideoCommon/AsyncShaderCompiler.h | 7 +- Source/Core/VideoCommon/FrameDumper.cpp | 9 +- Source/Core/VideoCommon/FrameDumper.h | 3 +- 52 files changed, 771 insertions(+), 451 deletions(-) create mode 100644 Source/Core/Common/ParallelFor.c create mode 100644 Source/Core/Common/ParallelFor.h diff --git a/Makefile.common b/Makefile.common index f833ac3ccf..4603ec159e 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1108,6 +1108,8 @@ DOLPHIN_SOURCES_C := DOLPHIN_SOURCES_CXX := DOLPHIN_SOURCES_ASM := DOLPHIN_SOURCES_OBJCXX := +DOLPHIN_SOURCES_C += \ + $(CORE_DIR)/Source/Core/Common/ParallelFor.c DOLPHIN_SOURCES_CXX += \ $(CORE_DIR)/Source/Core/AudioCommon/AudioCommon.cpp \ $(CORE_DIR)/Source/Core/AudioCommon/Mixer.cpp \ diff --git a/Source/Core/Common/ArmCPUDetect.cpp b/Source/Core/Common/ArmCPUDetect.cpp index 822e5c8db2..2b82f669a7 100644 --- a/Source/Core/Common/ArmCPUDetect.cpp +++ b/Source/Core/Common/ArmCPUDetect.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #ifdef __APPLE__ #include @@ -41,6 +41,8 @@ #include #include +#include + #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Common/StringUtil.h" @@ -228,7 +230,7 @@ void CPUInfo::Detect() bFMA = true; bFlushToZero = true; - num_cores = std::max(static_cast(std::thread::hardware_concurrency()), 1); + num_cores = std::max(static_cast(cpu_features_get_core_amount()), 1); #ifdef __APPLE__ SysctlByName(&model_name, "machdep.cpu.brand_string"); diff --git a/Source/Core/Common/ParallelFor.c b/Source/Core/Common/ParallelFor.c new file mode 100644 index 0000000000..b1b1b6a91a --- /dev/null +++ b/Source/Core/Common/ParallelFor.c @@ -0,0 +1,221 @@ +/* Copyright 2026 Dolphin Emulator Project + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "Common/ParallelFor.h" + +#include + +#include +#include +#include +#include + +/* Never more workers than this, whatever the core count. */ +#define PARALLEL_FOR_MAX_WORKERS 63 + +/* One batch at a time, owned by whichever caller won m_owner. + * + * A worker touches the batch only between raising m_users and lowering it + * again, and only if it saw m_active set in between. The caller clears + * m_active once every index is claimed and then waits for m_users to drop + * to zero; both sides use seq_cst operations, so a worker either is counted + * in m_users or sees m_active clear. Once m_users reads zero no worker can + * still be inside the batch, and the caller is free to return and to rewrite + * the batch fields for the next call. */ +static struct +{ + sthread_t **workers; + unsigned worker_count; + int started; + + /* Batch, written only by the owner while no worker is inside it. */ + parallel_for_fn fn; + void *ctx; + size_t count; + retro_atomic_size_t next; + + retro_atomic_int_t owner; + retro_atomic_int_t active; + retro_atomic_int_t users; + retro_atomic_int_t generation; + retro_atomic_int_t stop; + + retro_eventcount_t wake; + retro_eventcount_t idle; +} s_pool; + +static void parallel_for_run_jobs(void) +{ + for (;;) + { + const size_t i = retro_atomic_fetch_add_size(&s_pool.next, 1); + if (i >= s_pool.count) + return; + s_pool.fn(s_pool.ctx, i); + } +} + +static void parallel_for_worker(void *data) +{ + int seen = 0; + (void)data; + + sthread_setname("parallel_for"); + + for (;;) + { + int gen; + const int key = retro_eventcount_prepare_wait(&s_pool.wake); + + if (retro_atomic_load_acquire_int(&s_pool.stop)) + { + retro_eventcount_cancel_wait(&s_pool.wake); + return; + } + + gen = retro_atomic_load_acquire_int(&s_pool.generation); + if (gen == seen) + { + retro_eventcount_commit_wait(&s_pool.wake, key); + continue; + } + retro_eventcount_cancel_wait(&s_pool.wake); + seen = gen; + + retro_atomic_fetch_add_seq_cst_int(&s_pool.users, 1); + if (retro_atomic_load_seq_cst_int(&s_pool.active)) + parallel_for_run_jobs(); + if (retro_atomic_fetch_add_seq_cst_int(&s_pool.users, -1) == 1) + retro_eventcount_notify(&s_pool.idle); + } +} + +/* Called by the owner. */ +static void parallel_for_start(void) +{ + unsigned cores = cpu_features_get_core_amount(); + unsigned i; + + s_pool.started = 1; + if (cores <= 1) + return; + if (cores - 1 > PARALLEL_FOR_MAX_WORKERS) + cores = PARALLEL_FOR_MAX_WORKERS + 1; + + s_pool.workers = (sthread_t**)calloc(cores - 1, sizeof(*s_pool.workers)); + if (!s_pool.workers) + return; + + retro_atomic_int_init(&s_pool.active, 0); + retro_atomic_int_init(&s_pool.users, 0); + retro_atomic_int_init(&s_pool.generation, 0); + retro_atomic_int_init(&s_pool.stop, 0); + retro_atomic_size_init(&s_pool.next, 0); + if (!retro_eventcount_init(&s_pool.wake)) + goto fail_wake; + if (!retro_eventcount_init(&s_pool.idle)) + goto fail_idle; + + for (i = 0; i < cores - 1; i++) + { + s_pool.workers[i] = sthread_create(parallel_for_worker, NULL); + if (!s_pool.workers[i]) + break; + } + s_pool.worker_count = i; + if (i) + return; + + retro_eventcount_free(&s_pool.idle); +fail_idle: + retro_eventcount_free(&s_pool.wake); +fail_wake: + free(s_pool.workers); + s_pool.workers = NULL; +} + +static void parallel_for_serial(size_t count, parallel_for_fn fn, void *ctx) +{ + size_t i; + for (i = 0; i < count; i++) + fn(ctx, i); +} + +void parallel_for(size_t count, parallel_for_fn fn, void *ctx) +{ + if (count < 2 || !retro_atomic_cas_int(&s_pool.owner, 0, 1)) + { + parallel_for_serial(count, fn, ctx); + return; + } + + if (!s_pool.started) + parallel_for_start(); + + if (!s_pool.worker_count) + { + parallel_for_serial(count, fn, ctx); + retro_atomic_store_release_int(&s_pool.owner, 0); + return; + } + + s_pool.fn = fn; + s_pool.ctx = ctx; + s_pool.count = count; + retro_atomic_store_relaxed_size(&s_pool.next, 0); + + retro_atomic_fetch_add_seq_cst_int(&s_pool.active, 1); + retro_atomic_fetch_add_int(&s_pool.generation, 1); + retro_eventcount_notify(&s_pool.wake); + + parallel_for_run_jobs(); + + /* Every index is claimed; wait for the workers still running one. */ + retro_atomic_fetch_add_seq_cst_int(&s_pool.active, -1); + while (retro_atomic_load_seq_cst_int(&s_pool.users)) + { + const int key = retro_eventcount_prepare_wait(&s_pool.idle); + if (!retro_atomic_load_seq_cst_int(&s_pool.users)) + { + retro_eventcount_cancel_wait(&s_pool.idle); + break; + } + retro_eventcount_commit_wait(&s_pool.idle, key); + } + + retro_atomic_store_release_int(&s_pool.owner, 0); +} + +unsigned parallel_for_threads(void) +{ + unsigned cores = cpu_features_get_core_amount(); + if (cores < 1) + return 1; + if (cores > PARALLEL_FOR_MAX_WORKERS + 1) + return PARALLEL_FOR_MAX_WORKERS + 1; + return cores; +} + +void parallel_for_deinit(void) +{ + unsigned i; + + while (!retro_atomic_cas_int(&s_pool.owner, 0, 1)) + sthread_yield(); + + if (s_pool.worker_count) + { + retro_atomic_store_release_int(&s_pool.stop, 1); + retro_eventcount_notify(&s_pool.wake); + for (i = 0; i < s_pool.worker_count; i++) + sthread_join(s_pool.workers[i]); + retro_eventcount_free(&s_pool.idle); + retro_eventcount_free(&s_pool.wake); + } + free(s_pool.workers); + s_pool.workers = NULL; + s_pool.worker_count = 0; + s_pool.started = 0; + + retro_atomic_store_release_int(&s_pool.owner, 0); +} diff --git a/Source/Core/Common/ParallelFor.h b/Source/Core/Common/ParallelFor.h new file mode 100644 index 0000000000..c2243169ae --- /dev/null +++ b/Source/Core/Common/ParallelFor.h @@ -0,0 +1,37 @@ +/* Copyright 2026 Dolphin Emulator Project + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef DOLPHIN_COMMON_PARALLEL_FOR_H +#define DOLPHIN_COMMON_PARALLEL_FOR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*parallel_for_fn)(void *ctx, size_t index); + +/* Runs fn(ctx, i) for every i in [0, count) and returns once all of them + * have run. The calls are spread over a pool of persistent worker threads + * (one per extra core, started on first use) and the calling thread, which + * takes jobs too. No locks: jobs are claimed with an atomic counter and the + * workers park on an eventcount between batches. + * + * Calls may come from any thread. While one batch runs, a call from another + * thread runs its jobs serially on that thread instead of waiting. A job must + * not call parallel_for itself unless it accepts that serial fallback. */ +void parallel_for(size_t count, parallel_for_fn fn, void *ctx); + +/* Number of threads parallel_for may run jobs on, the caller included. */ +unsigned parallel_for_threads(void); + +/* Joins the worker threads. Call when no batch can be running (the core's + * retro_deinit); the next parallel_for starts the pool again. */ +void parallel_for_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/Core/Common/Thread.h b/Source/Core/Common/Thread.h index 146b304309..20362c725e 100644 --- a/Source/Core/Common/Thread.h +++ b/Source/Core/Common/Thread.h @@ -7,6 +7,8 @@ #include #endif +#include + // Don't include Common.h here as it will break LogManager #include "Common/CommonTypes.h" @@ -30,6 +32,41 @@ void YieldCPU(); void SetCurrentThreadName(const char* name); +// An sthread entry point that calls F on the object passed as the thread's userdata: +// m_thread = sthread_create(Common::ThreadEntry<&Foo::Run>, this); +template +struct ThreadEntryOf; +template +struct ThreadEntryOf +{ + static void Run(void* data) { (static_cast(data)->*F)(); } +}; +template +inline void ThreadEntry(void* data) +{ + ThreadEntryOf::Run(data); +} + +// Starts a thread nobody joins. Returns false if it couldn't be started. +inline bool StartDetachedThread(void (*entry)(void*), void* data) +{ + sthread_t* const thread = sthread_create(entry, data); + if (!thread) + return false; + sthread_detach(thread); + return true; +} + +// Joins and clears an sthread handle; does nothing for a null one. +inline void JoinThread(sthread_t*& thread) +{ + if (thread) + { + sthread_join(thread); + thread = nullptr; + } +} + #ifndef _WIN32 // Returns the lowest address of the stack and the size of the stack std::tuple GetCurrentThreadStack(); diff --git a/Source/Core/Common/x64CPUDetect.cpp b/Source/Core/Common/x64CPUDetect.cpp index fa9cb0c15c..e001f8f9d4 100644 --- a/Source/Core/Common/x64CPUDetect.cpp +++ b/Source/Core/Common/x64CPUDetect.cpp @@ -11,11 +11,12 @@ #include #include #include -#include #include #include +#include + #include "Common/CommonTypes.h" #include "Common/StringUtil.h" #ifdef _WIN32 @@ -116,7 +117,7 @@ void CPUInfo::Detect() // This should be much more reliable and easier than trying to get the number of cores out of the // CPUID data ourselves. - num_cores = std::max(static_cast(std::thread::hardware_concurrency()), 1); + num_cores = std::max(static_cast(cpu_features_get_core_amount()), 1); // Assume CPU supports the CPUID instruction. Those that don't can barely // boot modern OS anyway. diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index e66617ccad..2553183850 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -19,6 +19,7 @@ #include "Common/Logging/Log.h" #include "Common/ScopeGuard.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "Common/Version.h" #include "Common/WorkQueueThread.h" #include "Core/AchievementApprovedHash.h" @@ -374,7 +375,12 @@ bool AchievementManager::CanPause() void AchievementManager::DoIdle() { - std::thread([this] { + Common::StartDetachedThread(Common::ThreadEntry<&AchievementManager::IdleThread>, this); +} + +void AchievementManager::IdleThread() +{ + { while (true) { Common::SleepCurrentThread(1000); @@ -401,7 +407,7 @@ void AchievementManager::DoIdle() rc_client_idle(m_client); }); } - }).detach(); + } } std::recursive_mutex& AchievementManager::GetLock() diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 74f26b9ec7..27d4c0336b 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -130,6 +130,7 @@ public: bool CanPause(); void DoIdle(); + void IdleThread(); std::recursive_mutex& GetLock(); bool IsHardcoreModeActive() const; diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 3de85299fb..0c4e1d0b3e 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -92,25 +91,14 @@ namespace Core { static bool s_wants_determinism; -#ifdef __LIBRETRO__ static std::vector>> s_emu_thread_scope_guards; std::unique_ptr g_boot_params; -#endif // Declarations and definitions -#ifndef __LIBRETRO__ -static -#endif -std::thread s_emu_thread; +sthread_t* s_emu_thread = nullptr; static Common::HookableEvent s_state_changed_event; static bool s_is_throttler_temp_disabled = false; -#ifndef __LIBRETRO__ -static -#endif bool s_frame_step = false; -#ifndef __LIBRETRO__ -static -#endif std::atomic s_stop_frame_step; // Threads other than the CPU thread must hold this when taking on the role of the CPU thread. @@ -139,9 +127,6 @@ static std::queue s_host_jobs_queue; static thread_local bool tls_is_cpu_thread = false; static thread_local bool tls_is_gpu_thread = false; -#ifndef __LIBRETRO__ -static -#endif void EmuThread(Core::System& system, std::unique_ptr boot, WindowSystemInfo wsi); @@ -226,7 +211,7 @@ bool Init(Core::System& system, std::unique_ptr boot, const Wind { std::lock_guard lock(s_core_mutex); - if (s_emu_thread.joinable()) + if (s_emu_thread) { if (!IsUninitialized(system)) { @@ -235,7 +220,7 @@ bool Init(Core::System& system, std::unique_ptr boot, const Wind } // The Emu Thread was stopped, synchronize with it. - s_emu_thread.join(); + Common::JoinThread(s_emu_thread); } // Drain any left over jobs @@ -245,23 +230,12 @@ bool Init(Core::System& system, std::unique_ptr boot, const Wind INFO_LOG_FMT(BOOT, "CPU Thread separate = {}", system.IsDualCoreMode() ? "Yes" : "No"); // Manually reactivate the video backend in case a GameINI overrides the video backend setting. -#ifndef __LIBRETRO__ - VideoBackendBase::PopulateBackendInfo(wsi); - - // Issue any API calls which must occur on the main thread for the graphics backend. - WindowSystemInfo prepared_wsi(wsi); - g_video_backend->PrepareWindow(prepared_wsi); -#endif // Start the emu thread s_state.store(State::Starting); -#ifdef __LIBRETRO__ // Do not launch EmuThread — Libretro handles it manually g_boot_params = std::move(boot); -#else - s_emu_thread = std::thread(EmuThread, std::ref(system), std::move(boot), prepared_wsi); -#endif return true; } @@ -394,11 +368,9 @@ static void CpuThread(Core::System& system, const std::optional sav } } -#ifdef __LIBRETRO__ // dont enter the blocking loop if in single core mode if (!system.IsDualCoreMode()) return; -#endif // Enter CPU run loop. When we leave it - we are done. system.GetCPU().Run(); @@ -461,80 +433,54 @@ static void FifoPlayerThread(Core::System& system, const std::optional>; - using ReturnType = std::unique_ptr; - - const auto init_video = [&] { - DeclareAsGPUThread(); - - AsyncRequests::GetInstance()->SetPassthrough(!system.IsDualCoreMode()); - -#ifdef __LIBRETRO__ - return true; -#else - // Must happen on the proper thread for some video backends, e.g. OpenGL. - return g_video_backend->Initialize(wsi); -#endif - }; - - const auto deinit_video = [] { - // Clear on screen messages that haven't expired -#ifndef __LIBRETRO__ - OSD::ClearMessages(); - - g_video_backend->Shutdown(); -#endif - }; if (system.IsDualCoreMode()) { - std::promise init_from_thread; - - // Spawn the GPU thread. - std::thread gpu_thread{[&] { - Common::SetCurrentThreadName("Video thread"); - - const bool is_init = init_video(); - init_from_thread.set_value(is_init); - - if (!is_init) - return; - -#ifndef __LIBRETRO__ - system.GetFifo().RunGpuLoop(); - INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "Video Loop Ended")); - - deinit_video(); -#endif - }}; - - if (init_from_thread.get_future().get()) - { - // Return a scope guard that signals the GPU thread to stop then joins it. - return std::make_unique([&, gpu_thread = std::move(gpu_thread)]() mutable { - INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "Wait for Video Loop to exit ...")); - system.GetFifo().ExitGpuLoop(); - gpu_thread.join(); - INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "GPU thread stopped.")); - }); - } - - gpu_thread.join(); - } - else // SingleCore mode - { - if (init_video()) - return std::make_unique(deinit_video); + // The frontend runs the GPU loop on its own thread; all that is left here is to make sure + // that loop is told to stop. + AsyncRequests::GetInstance()->SetPassthrough(false); + return std::make_unique([&system] { + INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "Wait for Video Loop to exit ...")); + system.GetFifo().ExitGpuLoop(); + INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "GPU thread stopped.")); + }); } - return ReturnType{}; + DeclareAsGPUThread(); + AsyncRequests::GetInstance()->SetPassthrough(true); + return std::make_unique([] {}); +} + +namespace +{ +struct EmuThreadArgs +{ + Core::System* system; + std::unique_ptr boot; + WindowSystemInfo wsi; +}; + +void EmuThreadEntry(void* data) +{ + std::unique_ptr args(static_cast(data)); + EmuThread(*args->system, std::move(args->boot), args->wsi); +} +} // namespace + +bool StartEmuThread(Core::System& system, std::unique_ptr boot, + const WindowSystemInfo& wsi) +{ + EmuThreadArgs* const args = new EmuThreadArgs{&system, std::move(boot), wsi}; + s_emu_thread = sthread_create(EmuThreadEntry, args); + if (s_emu_thread) + return true; + delete args; + return false; } // Initialize and create emulation thread // Call browser: Init():s_emu_thread(). // See the BootManager.cpp file description for a complete call schedule. -#ifndef __LIBRETRO__ -static -#endif void EmuThread(Core::System& system, std::unique_ptr boot, WindowSystemInfo wsi) { @@ -702,9 +648,8 @@ void EmuThread(Core::System& system, std::unique_ptr boot, UpdateTitle(system); -#ifdef __LIBRETRO__ - // these are not needed if EmuThread is launched as a std::thread - if (!s_emu_thread.joinable()) + // these are not needed if EmuThread is launched on its own thread + if (!s_emu_thread) { s_emu_thread_scope_guards.emplace_back(Common::MoveOnlyFunction( [g = std::move(flag_guard)]() mutable { g.Exit(); })); @@ -730,7 +675,6 @@ void EmuThread(Core::System& system, std::unique_ptr boot, s_emu_thread_scope_guards.emplace_back(Common::MoveOnlyFunction( [g = std::move(wiifs_guard)]() mutable { g.Exit(); })); } -#endif // Become the CPU thread. cpu_thread_func(system, savestate_path, delete_savestate); @@ -885,9 +829,7 @@ static void RestoreStateAndUnlock(Core::System& system, const bool unpause_on_un } void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction function -#ifdef __LIBRETRO__ , bool wait_for_completion -#endif ) { if (IsCPUThread()) @@ -896,9 +838,7 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct return; } -#ifdef __LIBRETRO__ Common::OneShotEvent cpu_thread_job_finished; -#endif // Pause the CPU (set it to stepping mode). const bool was_running = PauseAndLock(system); @@ -907,11 +847,8 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct { // If the core hasn't been started, there is no active CPU thread we can race against. function(); -#ifdef __LIBRETRO__ wait_for_completion = false; -#endif } -#ifdef __LIBRETRO__ else if (wait_for_completion) { // Queue the job function followed by triggering the event. @@ -920,7 +857,6 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct cpu_thread_job_finished.Set(); }); } -#endif else { // Queue the job function. @@ -930,7 +866,6 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct // Release the CPU thread, and let it execute the callback. RestoreStateAndUnlock(system, was_running); -#ifdef __LIBRETRO__ // If we're waiting for completion, block until the event fires. if (wait_for_completion) { @@ -938,7 +873,6 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct while (!cpu_thread_job_finished.WaitFor(std::chrono::milliseconds(10))) Host_YieldToUI(); } -#endif } // --- Callbacks for backends / engine --- @@ -973,14 +907,12 @@ void Callback_NewField(Core::System& system) } AchievementManager::GetInstance().DoFrame(); -#ifdef __LIBRETRO__ if (system.IsDualCoreMode()) system.GetFifo().StopGpuLoop(); // frontend waiting on audio here holds the thread while // the frame is presented. if (SoundStream* sound_stream = system.GetSoundStream()) sound_stream->NewField(); -#endif } void UpdateTitle(Core::System& system) @@ -1009,13 +941,10 @@ void Shutdown(Core::System& system) // shut down. // For more info read "DirectX Graphics Infrastructure (DXGI): Best Practices" // on MSDN. - if (s_emu_thread.joinable()) - s_emu_thread.join(); -#ifdef __LIBRETRO__ + Common::JoinThread(s_emu_thread); for (auto& g : s_emu_thread_scope_guards) g.Exit(); s_emu_thread_scope_guards.clear(); -#endif // Make sure there's nothing left over in case we're about to exit. HostDispatchJobs(system); @@ -1146,7 +1075,6 @@ CPUThreadGuard::~CPUThreadGuard() RestoreStateAndUnlock(m_system, m_was_unpaused); } -#ifdef __LIBRETRO__ void SingleCorePostRunShutdown() { #ifdef USE_MEMORYWATCHER @@ -1164,6 +1092,5 @@ void SingleCorePostRunShutdown() INFO_LOG_FMT(GDB_STUB, "Killed by CPU shutdown"); } } -#endif } // namespace Core diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index da540912e3..dd59de787d 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include "Common/CommonTypes.h" #include "Common/Functional.h" @@ -198,7 +198,10 @@ void UpdateTitle(Core::System& system); WindowSystemInfo wsi); void SingleCorePostRunShutdown(); extern std::unique_ptr g_boot_params; - extern std::thread s_emu_thread; + // Starts EmuThread on its own thread (dual core); s_emu_thread is its handle. + bool StartEmuThread(Core::System& system, std::unique_ptr boot, + const WindowSystemInfo& wsi); + extern sthread_t* s_emu_thread; extern bool s_frame_step; extern std::atomic s_stop_frame_step; #endif diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index ddb7a26732..b8ce2b3f80 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index c84e082e30..9a6e51aa99 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -115,10 +115,10 @@ void CPUManager::Run() auto& power_pc = m_system.GetPowerPC(); // Start a separate time tracker thread - std::thread timing; + sthread_t* timing = nullptr; if (Config::Get(Config::MAIN_TIME_TRACKING)) { - timing = std::thread(&CPUManager::StartTimePlayedTimer, this); + timing = sthread_create(Common::ThreadEntry<&CPUManager::StartTimePlayedTimer>, this); } #ifdef __LIBRETRO__ @@ -219,10 +219,10 @@ void CPUManager::Run() } } - if (timing.joinable()) + if (timing) { m_time_played_finish_sync.Set(); - timing.join(); + Common::JoinThread(timing); } state_lock.unlock(); diff --git a/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp b/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp index 7495d20fd8..83fc7b002f 100644 --- a/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp +++ b/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp @@ -5,7 +5,6 @@ #include #include -#include #include "Common/ChunkFile.h" #include "Common/CommonPaths.h" @@ -51,8 +50,9 @@ void DSPLLE::DoState(PointerWrap& p) } // Regular thread -void DSPLLE::DSPThread(DSPLLE* dsp_lle) +void DSPLLE::DSPThread(void* data) { + DSPLLE* const dsp_lle = static_cast(data); Common::SetCurrentThreadName("DSP thread"); while (dsp_lle->m_is_running.IsSet()) @@ -155,7 +155,7 @@ bool DSPLLE::Initialize(bool wii, bool dsp_thread) if (dsp_thread) { m_is_running.Set(true); - m_dsp_thread = std::thread(DSPThread, this); + m_dsp_thread = sthread_create(DSPThread, this); } return true; @@ -169,7 +169,7 @@ void DSPLLE::DSP_StopSoundStream() m_is_running.Clear(); m_ppc_event.Set(); m_dsp_event.Set(); - m_dsp_thread.join(); + Common::JoinThread(m_dsp_thread); } void DSPLLE::Shutdown() diff --git a/Source/Core/Core/HW/DSPLLE/DSPLLE.h b/Source/Core/Core/HW/DSPLLE/DSPLLE.h index 0797308fee..8cba6adefd 100644 --- a/Source/Core/Core/HW/DSPLLE/DSPLLE.h +++ b/Source/Core/Core/HW/DSPLLE/DSPLLE.h @@ -5,7 +5,8 @@ #include #include -#include + +#include #include "Common/CommonTypes.h" #include "Common/Flag.h" @@ -40,10 +41,10 @@ public: u32 DSP_UpdateRate() override; private: - static void DSPThread(DSPLLE* dsp_lle); + static void DSPThread(void* data); DSPCore m_dsp_core; - std::thread m_dsp_thread; + sthread_t* m_dsp_thread = nullptr; std::mutex m_dsp_thread_mutex; bool m_is_dsp_on_thread = false; Common::Flag m_is_running; diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp index 3175bae7c4..a27c098ee2 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "SFML/Network/IpAddress.hpp" #include "SFML/Network/Socket.hpp" @@ -21,6 +22,7 @@ #include "Common/MsgHandler.h" #include "Common/Network.h" #include "Common/ScopeGuard.h" +#include "Common/Thread.h" #include "Core/HW/EXI/EXI_DeviceEthernet.h" namespace @@ -116,8 +118,7 @@ void CEXIETHERNET::BuiltInBBAInterface::Deactivate() m_upnp_httpd.close(); // Wait for read thread to exit. - if (m_read_thread.joinable()) - m_read_thread.join(); + Common::JoinThread(m_read_thread); } bool CEXIETHERNET::BuiltInBBAInterface::IsActivated() @@ -700,8 +701,9 @@ bool CEXIETHERNET::BuiltInBBAInterface::SendFrame(const u8* frame, u32 size) return true; } -void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(CEXIETHERNET::BuiltInBBAInterface* self) +void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(void* data) { + BuiltInBBAInterface* const self = static_cast(data); std::size_t datasize = 0; while (!self->m_read_thread_shutdown.IsSet()) { @@ -771,7 +773,7 @@ void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(CEXIETHERNET::BuiltInB bool CEXIETHERNET::BuiltInBBAInterface::RecvInit() { - m_read_thread = std::thread(ReadThreadHandler, this); + m_read_thread = sthread_create(ReadThreadHandler, this); return true; } diff --git a/Source/Core/Core/HW/EXI/BBA/IPC.cpp b/Source/Core/Core/HW/EXI/BBA/IPC.cpp index e23795e7eb..fec2550bd0 100644 --- a/Source/Core/Core/HW/EXI/BBA/IPC.cpp +++ b/Source/Core/Core/HW/EXI/BBA/IPC.cpp @@ -6,6 +6,7 @@ #include #include "Common/Logging/Log.h" +#include "Common/Thread.h" #include "Core/HW/EXI/EXI_DeviceEthernet.h" namespace ExpansionInterface @@ -33,9 +34,9 @@ void CEXIETHERNET::IPCBBAInterface::Deactivate() m_read_enabled.Clear(); m_read_thread_shutdown.Set(); - if (m_read_thread.joinable()) + if (m_read_thread) { - m_read_thread.join(); + Common::JoinThread(m_read_thread); } m_channel.disconnect(); @@ -67,7 +68,7 @@ bool CEXIETHERNET::IPCBBAInterface::SendFrame(const u8* const frame, const u32 s bool CEXIETHERNET::IPCBBAInterface::RecvInit() { - m_read_thread = std::thread(&CEXIETHERNET::IPCBBAInterface::ReadThreadHandler, this); + m_read_thread = sthread_create(Common::ThreadEntry<&CEXIETHERNET::IPCBBAInterface::ReadThreadHandler>, this); return true; } diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 3bef36ad77..7bada0a153 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -20,6 +20,7 @@ #include "Common/Logging/Log.h" #include "Common/Network.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "Core/HW/EXI/BBA/TAPServerConnection.h" #include "SFML/Network/IpAddress.hpp" @@ -142,8 +143,7 @@ void TAPServerConnection::Deactivate() { m_read_enabled.Clear(); m_read_shutdown.Set(); - if (m_read_thread.joinable()) - m_read_thread.join(); + Common::JoinThread(m_read_thread); m_read_shutdown.Clear(); if (m_fd >= 0) @@ -158,7 +158,7 @@ bool TAPServerConnection::IsActivated() bool TAPServerConnection::RecvInit() { - m_read_thread = std::thread(&TAPServerConnection::ReadThreadHandler, this); + m_read_thread = sthread_create(Common::ThreadEntry<&TAPServerConnection::ReadThreadHandler>, this); return true; } diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h index f7010142cb..f609110609 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h @@ -5,7 +5,8 @@ #include #include -#include + +#include #include "Common/CommonTypes.h" #include "Common/Flag.h" @@ -37,7 +38,7 @@ private: Common::SocketContext m_socket_context; int m_fd = -1; - std::thread m_read_thread; + sthread_t* m_read_thread = nullptr; Common::Flag m_read_enabled; Common::Flag m_read_shutdown; diff --git a/Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp b/Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp index b6aa647954..32969fb461 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp @@ -8,6 +8,7 @@ #include "Common/Logging/Log.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "Core/HW/EXI/EXI_Device.h" namespace ExpansionInterface @@ -37,8 +38,7 @@ void CEXIETHERNET::TAPNetworkInterface::Deactivate() readEnabled.Clear(); readThreadShutdown.Set(); - if (readThread.joinable()) - readThread.join(); + Common::JoinThread(readThread); } bool CEXIETHERNET::TAPNetworkInterface::IsActivated() @@ -64,8 +64,9 @@ bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size) } } -void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self) +void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(void* data) { + TAPNetworkInterface* const self = static_cast(data); while (!self->readThreadShutdown.IsSet()) { fd_set rfds; @@ -95,7 +96,7 @@ void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* s bool CEXIETHERNET::TAPNetworkInterface::RecvInit() { - readThread = std::thread(ReadThreadHandler, this); + readThread = sthread_create(ReadThreadHandler, this); return true; } diff --git a/Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp b/Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp index a147cc7073..2a70f19607 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp @@ -11,6 +11,7 @@ #include "Common/Logging/Log.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #ifdef __linux__ #include @@ -83,8 +84,7 @@ void CEXIETHERNET::TAPNetworkInterface::Deactivate() readEnabled.Clear(); readThreadShutdown.Set(); - if (readThread.joinable()) - readThread.join(); + Common::JoinThread(readThread); #else NOTIMPLEMENTED("Deactivate"); #endif @@ -123,8 +123,9 @@ bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size) } #ifdef __linux__ -void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self) +void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(void* data) { + TAPNetworkInterface* const self = static_cast(data); while (!self->readThreadShutdown.IsSet()) { fd_set rfds; @@ -156,7 +157,7 @@ void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* s bool CEXIETHERNET::TAPNetworkInterface::RecvInit() { #ifdef __linux__ - readThread = std::thread(ReadThreadHandler, this); + readThread = sthread_create(ReadThreadHandler, this); return true; #else NOTIMPLEMENTED("RecvInit"); diff --git a/Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp b/Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp index c0abd75e43..d40d0cfe1d 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp @@ -7,6 +7,7 @@ #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/EXI/EXI_DeviceEthernet.h" @@ -254,8 +255,7 @@ void CEXIETHERNET::TAPNetworkInterface::Deactivate() CancelIoEx(mHAdapter, nullptr); // Wait for read thread to exit. - if (readThread.joinable()) - readThread.join(); + Common::JoinThread(readThread); // Clean-up handles CloseHandle(mReadOverlapped.hEvent); @@ -271,8 +271,9 @@ bool CEXIETHERNET::TAPNetworkInterface::IsActivated() return mHAdapter != INVALID_HANDLE_VALUE; } -void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self) +void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(void* data) { + TAPNetworkInterface* const self = static_cast(data); while (!self->readThreadShutdown.IsSet()) { DWORD transferred; @@ -359,7 +360,7 @@ bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size) bool CEXIETHERNET::TAPNetworkInterface::RecvInit() { - readThread = std::thread(ReadThreadHandler, this); + readThread = sthread_create(ReadThreadHandler, this); return true; } diff --git a/Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp b/Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp index 07b7bad783..9e59efc082 100644 --- a/Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp +++ b/Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp @@ -3,6 +3,7 @@ #include "Common/Logging/Log.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "Core/HW/EXI/EXI_DeviceEthernet.h" #include "SFML/Network/IpAddress.hpp" @@ -85,7 +86,11 @@ void CEXIETHERNET::XLinkNetworkInterface::Deactivate() m_read_thread_shutdown.Set(); // Detach the thread and let it die on its own (leaks ~9kb of memory) - m_read_thread.detach(); + if (m_read_thread) + { + sthread_detach(m_read_thread); + m_read_thread = nullptr; + } // Close the socket m_sf_socket.unbind(); @@ -134,9 +139,9 @@ bool CEXIETHERNET::XLinkNetworkInterface::SendFrame(const u8* frame, u32 size) } } -void CEXIETHERNET::XLinkNetworkInterface::ReadThreadHandler( - CEXIETHERNET::XLinkNetworkInterface* self) +void CEXIETHERNET::XLinkNetworkInterface::ReadThreadHandler(void* data) { + XLinkNetworkInterface* const self = static_cast(data); std::optional sender; u16 port; @@ -291,7 +296,7 @@ void CEXIETHERNET::XLinkNetworkInterface::ReadThreadHandler( bool CEXIETHERNET::XLinkNetworkInterface::RecvInit() { - m_read_thread = std::thread(ReadThreadHandler, this); + m_read_thread = sthread_create(ReadThreadHandler, this); return true; } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 59fabfafd1..df3fabd586 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include "SFML/Network/IpAddress.hpp" @@ -17,6 +16,8 @@ #include #ifdef HAVE_CPPIPC #include + +#include #endif #include "Common/Flag.h" @@ -349,10 +350,10 @@ private: protected: #if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ defined(__OpenBSD__) - std::thread readThread; + sthread_t* readThread = nullptr; Common::Flag readEnabled; Common::Flag readThreadShutdown; - static void ReadThreadHandler(TAPNetworkInterface* self); + static void ReadThreadHandler(void* data); #endif #if defined(_WIN32) HANDLE mHAdapter = INVALID_HANDLE_VALUE; @@ -417,10 +418,10 @@ private: sf::IpAddress m_sf_recipient_ip = sf::IpAddress::Any; char m_in_frame[9004]{}; char m_out_frame[9004]{}; - std::thread m_read_thread; + sthread_t* m_read_thread = nullptr; Common::Flag m_read_enabled; Common::Flag m_read_thread_shutdown; - static void ReadThreadHandler(XLinkNetworkInterface* self); + static void ReadThreadHandler(void* data); #endif }; @@ -458,10 +459,10 @@ private: #if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) NetworkRef m_network_ref; - std::thread m_read_thread; + sthread_t* m_read_thread = nullptr; Common::Flag m_read_enabled; Common::Flag m_read_thread_shutdown; - static void ReadThreadHandler(BuiltInBBAInterface* self); + static void ReadThreadHandler(void* data); #endif void WriteToQueue(std::vector data); bool WillQueueOverrun() const; @@ -497,7 +498,7 @@ private: bool m_active{}; ipc::channel m_channel; - std::thread m_read_thread; + sthread_t* m_read_thread = nullptr; Common::Flag m_read_enabled; Common::Flag m_read_thread_shutdown; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceGecko.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceGecko.cpp index 06e90f83b0..d79dca9fee 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceGecko.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceGecko.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -21,35 +20,35 @@ namespace ExpansionInterface { u16 GeckoSockServer::server_port; int GeckoSockServer::client_count; -std::thread GeckoSockServer::connectionThread; +sthread_t* GeckoSockServer::connectionThread = nullptr; Common::Flag GeckoSockServer::server_running; std::mutex GeckoSockServer::connection_lock; std::queue> GeckoSockServer::waiting_socks; GeckoSockServer::GeckoSockServer() : client_running(false) { - if (!connectionThread.joinable()) - connectionThread = std::thread(GeckoConnectionWaiter); + if (!connectionThread) + connectionThread = sthread_create(GeckoConnectionWaiter, nullptr); } GeckoSockServer::~GeckoSockServer() { - if (clientThread.joinable()) + if (clientThread) { --client_count; client_running.Clear(); - clientThread.join(); + Common::JoinThread(clientThread); } - if (client_count <= 0 && connectionThread.joinable()) + if (client_count <= 0 && connectionThread) { server_running.Clear(); - connectionThread.join(); + Common::JoinThread(connectionThread); } } -void GeckoSockServer::GeckoConnectionWaiter() +void GeckoSockServer::GeckoConnectionWaiter(void*) { Common::SetCurrentThreadName("Gecko Connection Waiter"); @@ -93,15 +92,15 @@ bool GeckoSockServer::GetAvailableSock() if (!waiting_socks.empty()) { client = std::move(waiting_socks.front()); - if (clientThread.joinable()) + if (clientThread) { client_running.Clear(); - clientThread.join(); + Common::JoinThread(clientThread); recv_fifo = std::deque(); send_fifo = std::deque(); } - clientThread = std::thread(&GeckoSockServer::ClientThread, this); + clientThread = sthread_create(Common::ThreadEntry<&GeckoSockServer::ClientThread>, this); client_count++; waiting_socks.pop(); sock_filled = true; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceGecko.h b/Source/Core/Core/HW/EXI/EXI_DeviceGecko.h index 6bed93b3a4..9714ae0c01 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceGecko.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceGecko.h @@ -8,7 +8,8 @@ #include #include #include -#include + +#include #include "Common/CommonTypes.h" #include "Common/Flag.h" @@ -26,7 +27,7 @@ public: // Client for this server object std::unique_ptr client; void ClientThread(); - std::thread clientThread; + sthread_t* clientThread = nullptr; std::mutex transfer_lock; std::deque send_fifo; @@ -37,11 +38,11 @@ private: Common::Flag client_running; // Only ever one server thread - static void GeckoConnectionWaiter(); + static void GeckoConnectionWaiter(void*); static u16 server_port; static Common::Flag server_running; - static std::thread connectionThread; + static sthread_t* connectionThread; static std::mutex connection_lock; static std::queue> waiting_socks; }; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index 0ae3869998..c56d69cd25 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -204,7 +204,7 @@ GCMemcardDirectory::GCMemcardDirectory(std::string directory, ExpansionInterface return; #endif - m_flush_thread = std::thread(&GCMemcardDirectory::FlushThread, this); + m_flush_thread = sthread_create(Common::ThreadEntry<&GCMemcardDirectory::FlushThread>, this); } void GCMemcardDirectory::FlushThread() @@ -241,9 +241,9 @@ GCMemcardDirectory::~GCMemcardDirectory() m_flush_trigger.Set(); #ifdef __LIBRETRO__ - if (m_flush_thread.joinable()) + if (m_flush_thread) #endif - m_flush_thread.join(); + Common::JoinThread(m_flush_thread); FlushToFile(); } diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h index c2e8c11e4d..7012dee99b 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h @@ -5,9 +5,10 @@ #include #include -#include #include +#include + #include "Common/Event.h" #include "Common/Flag.h" #include "Core/HW/GCMemcard/GCIFile.h" @@ -63,5 +64,5 @@ private: Common::Event m_flush_trigger; std::mutex m_write_mutex; Common::Flag m_exiting; - std::thread m_flush_thread; + sthread_t* m_flush_thread = nullptr; }; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp index 1a8644b81a..4fec579204 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -78,16 +77,16 @@ MemoryCard::MemoryCard(std::string filename, ExpansionInterface::Slot card_slot, // Class members (including inherited ones) have now been initialized, so // it's safe to startup the flush thread (which reads them). m_flush_buffer = std::make_unique(m_memory_card_size); - m_flush_thread = std::thread(&MemoryCard::FlushThread, this); + m_flush_thread = sthread_create(Common::ThreadEntry<&MemoryCard::FlushThread>, this); } MemoryCard::~MemoryCard() { - if (m_flush_thread.joinable()) + if (m_flush_thread) { m_flush_trigger.Set(); - m_flush_thread.join(); + Common::JoinThread(m_flush_thread); } } diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h index 96e7a29b90..3176dc6f9c 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h @@ -6,7 +6,8 @@ #include #include #include -#include + +#include #include "Common/Event.h" #include "Common/Flag.h" #include "Core/HW/GCMemcard/GCMemcard.h" @@ -39,7 +40,7 @@ private: std::string m_filename; std::unique_ptr m_memcard_data; std::unique_ptr m_flush_buffer; - std::thread m_flush_thread; + sthread_t* m_flush_thread = nullptr; std::mutex m_flush_mutex; Common::Event m_flush_trigger; Common::Flag m_dirty; diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp b/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp index ef8b908026..38c19da4f7 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "Common/CommonTypes.h" #include "Common/Flag.h" @@ -23,7 +22,7 @@ namespace SerialInterface { namespace { -std::thread s_connection_thread; +sthread_t* s_connection_thread = nullptr; std::queue> s_waiting_socks; std::queue> s_waiting_clocks; std::mutex s_cs_gba; @@ -36,7 +35,7 @@ constexpr auto SEND_MAX_SIZE = 5, RECV_MAX_SIZE = 5; // --- GameBoy Advance "Link Cable" --- -static void GBAConnectionWaiter() +static void GBAConnectionWaiter(void*) { s_server_running.Set(); @@ -81,8 +80,7 @@ static void GBAConnectionWaiter() void GBAConnectionWaiter_Shutdown() { s_server_running.Clear(); - if (s_connection_thread.joinable()) - s_connection_thread.join(); + Common::JoinThread(s_connection_thread); } template @@ -109,8 +107,8 @@ static std::unique_ptr GetNextClock() GBASockServer::GBASockServer() { - if (!s_connection_thread.joinable()) - s_connection_thread = std::thread(GBAConnectionWaiter); + if (!s_connection_thread) + s_connection_thread = sthread_create(GBAConnectionWaiter, nullptr); s_num_connected = 0; } diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp index 18415e9b7f..9f03f3a031 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -590,7 +591,7 @@ void WiimoteScanner::StartThread() if (m_scan_thread_running.IsSet()) return; m_scan_thread_running.Set(); - m_scan_thread = std::thread(&WiimoteScanner::ThreadFunc, this); + m_scan_thread = sthread_create(Common::ThreadEntry<&WiimoteScanner::ThreadFunc>, this); } void WiimoteScanner::StopThread() @@ -605,7 +606,7 @@ void WiimoteScanner::StopThread() } m_scan_thread_running.Clear(); - m_scan_thread.join(); + Common::JoinThread(m_scan_thread); } } @@ -681,7 +682,8 @@ void WiimoteScanner::ThreadFunc() #if defined(__LIBRETRO__) && defined(SKIP_WIMOTESCANNER_THREAD) return; #endif - std::thread pool_thread(&WiimoteScanner::PoolThreadFunc, this); + sthread_t* pool_thread = + sthread_create(Common::ThreadEntry<&WiimoteScanner::PoolThreadFunc>, this); Common::SetCurrentThreadName("Wiimote Scanning Thread"); @@ -776,7 +778,7 @@ void WiimoteScanner::ThreadFunc() m_backends.clear(); } - pool_thread.join(); + Common::JoinThread(pool_thread); NOTICE_LOG_FMT(WIIMOTE, "Wiimote scanning thread has stopped."); } @@ -790,26 +792,26 @@ bool Wiimote::Connect(int index) void Wiimote::StartThread() { - if (m_write_thread.joinable()) + if (m_write_thread) return; m_run_thread.Set(); // Note that the write thread starts the read thread. - m_write_thread = std::thread(&Wiimote::WriteThreadFunc, this); + m_write_thread = sthread_create(Common::ThreadEntry<&Wiimote::WriteThreadFunc>, this); m_thread_ready_event.Wait(); } void Wiimote::StopThread() { - if (!m_write_thread.joinable()) + if (!m_write_thread) return; m_run_thread.Clear(); retro_eventcount_notify(&m_write_event); // Note that the write thread stops the read thread. - m_write_thread.join(); + Common::JoinThread(m_write_thread); } void Wiimote::ReadThreadFunc() @@ -848,7 +850,7 @@ void Wiimote::WriteThreadFunc() return; } - std::thread read_thread{&Wiimote::ReadThreadFunc, this}; + sthread_t* read_thread = sthread_create(Common::ThreadEntry<&Wiimote::ReadThreadFunc>, this); // Windows and also the DolphinBar require performing a write // to detect disconnections in a timely manner @@ -901,7 +903,7 @@ void Wiimote::WriteThreadFunc() } IOWakeup(); - read_thread.join(); + Common::JoinThread(read_thread); NOTICE_LOG_FMT(WIIMOTE, "Disconnecting Wiimote {}.", m_index + 1); DisconnectInternal(); diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h index b01c91f34c..764a24204d 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include "Common/Config/Config.h" @@ -25,6 +24,8 @@ #include #include +#include + class PointerWrap; namespace WiimoteReal @@ -172,7 +173,7 @@ private: // And we track the rumble state to drop unnecessary rumble reports. bool m_rumble_state = false; - std::thread m_write_thread; + sthread_t* m_write_thread = nullptr; // Whether to keep running the thread. Common::Flag m_run_thread; // Triggered when the thread has finished ConnectInternal. @@ -237,7 +238,7 @@ private: std::vector> m_backends; mutable std::mutex m_backends_mutex; - std::thread m_scan_thread; + sthread_t* m_scan_thread = nullptr; Common::Flag m_scan_thread_running; Common::Flag m_populate_devices; Common::Event m_scan_mode_changed_or_population_event; diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp index b0e54bd089..7d00c5e9c1 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp @@ -174,7 +174,8 @@ NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& m_handle_mail = !ios.GetIOSC().IsUsingDefaultId() && !m_send_list.IsDisabled(); m_scheduler_work_queue.Reset("WiiConnect24 Scheduler Worker"); - m_scheduler_timer_thread = std::thread([this] { SchedulerTimer(); }); + m_scheduler_timer_thread = + sthread_create(Common::ThreadEntry<&NetKDRequestDevice::SchedulerTimer>, this); } NetKDRequestDevice::~NetKDRequestDevice() @@ -185,13 +186,13 @@ NetKDRequestDevice::~NetKDRequestDevice() { std::lock_guard lg(m_scheduler_lock); - if (!m_scheduler_timer_thread.joinable()) + if (!m_scheduler_timer_thread) return; m_shutdown_event.Set(); } - m_scheduler_timer_thread.join(); + Common::JoinThread(m_scheduler_timer_thread); m_scheduler_work_queue.Shutdown(); m_work_queue.Shutdown(); } diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h index 7919bda29f..3e13eea99f 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h @@ -7,7 +7,8 @@ #include #include #include -#include + +#include #include "Common/CommonTypes.h" #include "Common/Event.h" @@ -125,6 +126,6 @@ private: bool m_handle_mail; Common::Event m_shutdown_event; std::mutex m_scheduler_lock; - std::thread m_scheduler_timer_thread; + sthread_t* m_scheduler_timer_thread = nullptr; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/USBScanner.cpp b/Source/Core/Core/IOS/USB/USBScanner.cpp index 7f12d3e37d..be817dd03a 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.cpp +++ b/Source/Core/Core/IOS/USB/USBScanner.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #ifdef __LIBUSB__ @@ -99,24 +98,27 @@ void USBScanner::StartScanning() { if (m_thread_running.TestAndSet()) { - m_thread = std::thread([this] { - Common::SetCurrentThreadName("USB Scan Thread"); - while (m_thread_running.IsSet()) - { - if (UpdateDevices()) - m_first_scan_complete_flag.Set(true); - Common::SleepCurrentThread(50); - } - m_devices.clear(); - m_first_scan_complete_flag.Set(false); - }); + m_thread = sthread_create(Common::ThreadEntry<&USBScanner::ScanThread>, this); } } +void USBScanner::ScanThread() +{ + Common::SetCurrentThreadName("USB Scan Thread"); + while (m_thread_running.IsSet()) + { + if (UpdateDevices()) + m_first_scan_complete_flag.Set(true); + Common::SleepCurrentThread(50); + } + m_devices.clear(); + m_first_scan_complete_flag.Set(false); +} + void USBScanner::StopScanning() { if (m_thread_running.TestAndClear()) - m_thread.join(); + Common::JoinThread(m_thread); } USBScanner::DeviceMap USBScanner::GetDevices() const diff --git a/Source/Core/Core/IOS/USB/USBScanner.h b/Source/Core/Core/IOS/USB/USBScanner.h index 314cc92078..c57e94e2ba 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.h +++ b/Source/Core/Core/IOS/USB/USBScanner.h @@ -7,7 +7,8 @@ #include #include #include -#include + +#include #include "Common/CommonTypes.h" #include "Common/Flag.h" @@ -44,6 +45,7 @@ private: void StopScanning(); bool UpdateDevices(); + void ScanThread(); bool AddNewDevices(DeviceMap* new_devices) const; static void WakeupSantrollerDevice(libusb_device* device); static void AddEmulatedDevices(DeviceMap* new_devices); @@ -56,7 +58,7 @@ private: std::mutex m_clients_mutex; Common::Flag m_thread_running; - std::thread m_thread; + sthread_t* m_thread = nullptr; std::mutex m_thread_start_stop_mutex; Common::WaitableFlag m_first_scan_complete_flag; diff --git a/Source/Core/Core/LibusbUtils.cpp b/Source/Core/Core/LibusbUtils.cpp index 357a621631..a077650c3a 100644 --- a/Source/Core/Core/LibusbUtils.cpp +++ b/Source/Core/Core/LibusbUtils.cpp @@ -4,7 +4,6 @@ #include "Core/LibusbUtils.h" #include -#include #if defined(__LIBUSB__) #include @@ -36,7 +35,7 @@ public: WARN_LOG_FMT(IOS_USB, "Failed to set LIBUSB_OPTION_USE_USBDK: {}", ErrorWrap(usbdk_ret)); #endif m_event_thread_running.Set(); - m_event_thread = std::thread(&Impl::EventThread, this); + m_event_thread = sthread_create(Common::ThreadEntry<&Impl::EventThread>, this); } ~Impl() @@ -46,7 +45,7 @@ public: #if defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000105 libusb_interrupt_event_handler(m_context); #endif - m_event_thread.join(); + Common::JoinThread(m_event_thread); libusb_exit(m_context); } @@ -58,7 +57,7 @@ public: #if defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000105 libusb_interrupt_event_handler(m_context); #endif - m_event_thread.join(); + Common::JoinThread(m_event_thread); libusb_exit(m_context); m_context = nullptr; // clear this so it no longer fires in deconstructor again } @@ -100,7 +99,7 @@ private: libusb_context* m_context = nullptr; mutable std::mutex m_device_list_mutex; Common::Flag m_event_thread_running; - std::thread m_event_thread; + sthread_t* m_event_thread = nullptr; }; #else class Context::Impl diff --git a/Source/Core/Core/MemTools.cpp b/Source/Core/Core/MemTools.cpp index 871bb3f615..484d3f5f15 100644 --- a/Source/Core/Core/MemTools.cpp +++ b/Source/Core/Core/MemTools.cpp @@ -131,8 +131,9 @@ static void CheckKR(const char* name, kern_return_t kr) } } -static void ExceptionThread(mach_port_t port) +static void ExceptionThread(void* data) { + const mach_port_t port = static_cast(reinterpret_cast(data)); Common::SetCurrentThreadName("Mach exception thread"); #pragma pack(4) struct alignas(8) @@ -229,8 +230,8 @@ void InstallExceptionHandler() mach_port_t port; CheckKR("mach_port_allocate", mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)); - std::thread exc_thread(ExceptionThread, port); - exc_thread.detach(); + Common::StartDetachedThread(ExceptionThread, + reinterpret_cast(static_cast(port))); // Obtain a send right for thread_set_exception_ports to copy... CheckKR("mach_port_insert_right", mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND)); diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index 6e195f3e22..439d71a116 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -28,6 +27,7 @@ #include "Common/MsgHandler.h" #include "Common/NandPaths.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "Common/Timer.h" #include "Common/VariantUtil.h" #include "Common/Version.h" @@ -199,8 +199,7 @@ void MovieManager::Init(const BootParameters& boot) if (IsPlayingInput()) { ReadHeader(); - std::thread md5thread(&MovieManager::CheckMD5, this); - md5thread.detach(); + Common::StartDetachedThread(Common::ThreadEntry<&MovieManager::CheckMD5>, this); if (strncmp(m_temp_header.gameID.data(), SConfig::GetInstance().GetGameID().c_str(), 6)) { PanicAlertFmtT("The recorded game ({0}) is not the same as the selected game ({1})", @@ -212,8 +211,7 @@ void MovieManager::Init(const BootParameters& boot) if (IsRecordingInput()) { GetSettings(); - std::thread md5thread(&MovieManager::GetMD5, this); - md5thread.detach(); + Common::StartDetachedThread(Common::ThreadEntry<&MovieManager::GetMD5>, this); m_tick_count_at_last_input = 0; } @@ -486,8 +484,7 @@ bool MovieManager::BeginRecordingInput(const ControllerTypeArray& controllers, State::SaveAs(m_system, save_path); m_recording_from_save_state = true; - std::thread md5thread(&MovieManager::GetMD5, this); - md5thread.detach(); + Common::StartDetachedThread(Common::ThreadEntry<&MovieManager::GetMD5>, this); GetSettings(); } diff --git a/Source/Core/DiscIO/CachedBlob.cpp b/Source/Core/DiscIO/CachedBlob.cpp index f1bbf055ee..9c0cd23a08 100644 --- a/Source/Core/DiscIO/CachedBlob.cpp +++ b/Source/Core/DiscIO/CachedBlob.cpp @@ -5,11 +5,13 @@ #include #include -#include + +#include #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/MemArena.h" +#include "Common/Thread.h" #include "DiscIO/DiscScrubber.h" #include "DiscIO/Volume.h" @@ -20,14 +22,15 @@ class CacheFiller final { public: explicit CacheFiller(std::unique_ptr reader, bool attempt_to_scrub) - : m_thread{&CacheFiller::ThreadFunc, this, std::move(reader), attempt_to_scrub} + : m_thread_reader{std::move(reader)}, m_attempt_to_scrub{attempt_to_scrub} { + m_thread = sthread_create(Common::ThreadEntry<&CacheFiller::ThreadFunc>, this); } ~CacheFiller() { m_stop_thread.store(true, std::memory_order_relaxed); - m_thread.join(); + Common::JoinThread(m_thread); } bool Read(u64 offset, u64 size, u8* out_ptr) @@ -81,8 +84,10 @@ private: return CacheState::Cached; } - void ThreadFunc(std::unique_ptr reader, bool attempt_to_scrub) + void ThreadFunc() { + const std::unique_ptr reader = std::move(m_thread_reader); + const bool attempt_to_scrub = m_attempt_to_scrub; static constexpr auto PERIODIC_LOG_TIME = std::chrono::seconds{1}; const auto start_time = Clock::now(); @@ -179,7 +184,9 @@ private: DiscScrubber m_scrubber; std::atomic_bool m_stop_thread{}; - std::thread m_thread; + std::unique_ptr m_thread_reader; // handed to the thread + bool m_attempt_to_scrub; + sthread_t* m_thread = nullptr; }; class CachedBlobReader final : public BlobReader diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index 3031f14a0f..a02e17a1a7 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -4,7 +4,7 @@ #include "DiscIO/VolumeVerifier.h" #include -#include +#include #include #include #include @@ -31,8 +31,10 @@ #include "Common/Logging/Log.h" #include "Common/MinizipUtil.h" #include "Common/MsgHandler.h" +#include "Common/ParallelFor.h" #include "Common/ScopeGuard.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "Common/Swap.h" #include "Common/Version.h" #include "Core/IOS/Device.h" @@ -66,47 +68,59 @@ void RedumpVerifier::Start(const Volume& volume) m_disc_number = volume.GetDiscNumber().value_or(0); m_size = volume.GetDataSize(); - const Platform platform = volume.GetVolumeType(); + m_platform = volume.GetVolumeType(); + m_thread = sthread_create(Common::ThreadEntry<&RedumpVerifier::MatchThread>, this); +} - m_future = std::async(std::launch::async, [this, platform]() -> std::vector { - std::string system; - DownloadState* download_state; - switch (platform) - { - case Platform::GameCubeDisc: - system = "gc"; - download_state = &m_gc_download_state; - break; +RedumpVerifier::~RedumpVerifier() +{ + Common::JoinThread(m_thread); +} - case Platform::WiiDisc: - system = "wii"; - download_state = &m_wii_download_state; - break; +void RedumpVerifier::MatchThread() +{ + m_potential_matches = FindPotentialMatches(m_platform); +} - default: - m_result.status = Status::Error; - return {}; - } +std::vector RedumpVerifier::FindPotentialMatches(Platform platform) +{ + std::string system; + DownloadState* download_state; + switch (platform) + { + case Platform::GameCubeDisc: + system = "gc"; + download_state = &m_gc_download_state; + break; - { - std::lock_guard lk(download_state->mutex); - download_state->status = DownloadDatfile(system, download_state->status); - } + case Platform::WiiDisc: + system = "wii"; + download_state = &m_wii_download_state; + break; - switch (download_state->status) - { - case DownloadStatus::FailButOldCacheAvailable: - ERROR_LOG_FMT(DISCIO, "Failed to fetch data from Redump.info, using old cached data instead"); - [[fallthrough]]; - case DownloadStatus::Success: - return ScanDatfile(ReadDatfile(system), system); + default: + m_result.status = Status::Error; + return {}; + } - case DownloadStatus::Fail: - default: - m_result = {Status::Error, Common::GetStringT("Failed to connect to Redump.info")}; - return {}; - } - }); + { + std::lock_guard lk(download_state->mutex); + download_state->status = DownloadDatfile(system, download_state->status); + } + + switch (download_state->status) + { + case DownloadStatus::FailButOldCacheAvailable: + ERROR_LOG_FMT(DISCIO, "Failed to fetch data from Redump.info, using old cached data instead"); + [[fallthrough]]; + case DownloadStatus::Success: + return ScanDatfile(ReadDatfile(system), system); + + case DownloadStatus::Fail: + default: + m_result = {Status::Error, Common::GetStringT("Failed to connect to Redump.info")}; + return {}; + } } static std::string GetPathForSystem(const std::string& system) @@ -329,7 +343,8 @@ RedumpVerifier::Result RedumpVerifier::Finish(const Hashes>& has if (hashes.crc32.empty() && hashes.md5.empty() && hashes.sha1.empty()) return m_result; - const std::vector potential_matches = m_future.get(); + Common::JoinThread(m_thread); + const std::vector potential_matches = std::move(m_potential_matches); for (PotentialMatch p : potential_matches) { if (HashesMatch(hashes.crc32, p.hashes.crc32) && HashesMatch(hashes.md5, p.hashes.md5) && @@ -362,6 +377,7 @@ VolumeVerifier::VolumeVerifier(const Volume& volume, bool redump_verification, { if (!m_calculating_any_hash) m_redump_verification = false; + m_async_worker.Reset("VolumeVerifier"); } VolumeVerifier::~VolumeVerifier() @@ -1097,18 +1113,14 @@ void VolumeVerifier::SetUpHashing() } } -void VolumeVerifier::WaitForAsyncOperations() const +void VolumeVerifier::WaitForAsyncOperations() { - if (m_crc32_future.valid()) - m_crc32_future.wait(); - if (m_md5_future.valid()) - m_md5_future.wait(); - if (m_sha1_future.valid()) - m_sha1_future.wait(); - if (m_content_future.valid()) - m_content_future.wait(); - if (m_group_future.valid()) - m_group_future.wait(); + m_async_worker.WaitForCompletion(); +} + +void VolumeVerifier::RunAsyncTask(void* ctx, size_t index) +{ + (*static_cast>*>(ctx))[index](); } bool VolumeVerifier::ReadChunkAndWaitForAsyncOperations(u64 bytes_to_read) @@ -1218,11 +1230,16 @@ void VolumeVerifier::Process() m_excess_bytes = excess_bytes; const u64 byte_increment = bytes_to_read - excess_bytes; + // The tasks for this chunk run on the async worker, spread over the parallel_for pool, while + // the caller reads the next chunk. The previous chunk's tasks must be done before reuse. + WaitForAsyncOperations(); + m_async_tasks.clear(); + if (m_calculating_any_hash) { if (m_hashes_to_calculate.crc32) { - m_crc32_future = std::async(std::launch::async, [this, byte_increment] { + m_async_tasks.emplace_back([this, byte_increment] { m_crc32_context = Common::UpdateCRC32(m_crc32_context, m_data.data(), static_cast(byte_increment)); }); @@ -1230,14 +1247,14 @@ void VolumeVerifier::Process() if (m_hashes_to_calculate.md5) { - m_md5_future = std::async(std::launch::async, [this, byte_increment] { + m_async_tasks.emplace_back([this, byte_increment] { mbedtls_md5_update_ret(&m_md5_context, m_data.data(), byte_increment); }); } if (m_hashes_to_calculate.sha1) { - m_sha1_future = std::async(std::launch::async, [this, byte_increment] { + m_async_tasks.emplace_back([this, byte_increment] { m_sha1_context->Update(m_data.data(), byte_increment); }); } @@ -1245,7 +1262,7 @@ void VolumeVerifier::Process() if (content_read) { - m_content_future = std::async(std::launch::async, [this, read_failed, content] { + m_async_tasks.emplace_back([this, read_failed, content] { if (read_failed || !m_volume.CheckContentIntegrity(content, m_data, m_ticket)) { AddProblem(Severity::High, Common::FmtFormatT("Content {0:08x} is corrupt.", content.id)); @@ -1257,8 +1274,7 @@ void VolumeVerifier::Process() if (group_read) { - m_group_future = std::async(std::launch::async, [this, read_failed, - group_index = m_group_index] { + m_async_tasks.emplace_back([this, read_failed, group_index = m_group_index] { const GroupToVerify& group = m_groups[group_index]; u64 offset_in_group = 0; for (u64 block_index = group.block_index_start; block_index < group.block_index_end; @@ -1291,6 +1307,12 @@ void VolumeVerifier::Process() m_group_index++; } + if (!m_async_tasks.empty()) + { + m_async_worker.Push( + [this] { parallel_for(m_async_tasks.size(), RunAsyncTask, &m_async_tasks); }); + } + m_progress += byte_increment; } diff --git a/Source/Core/DiscIO/VolumeVerifier.h b/Source/Core/DiscIO/VolumeVerifier.h index d057fc83d3..b66b7dfc93 100644 --- a/Source/Core/DiscIO/VolumeVerifier.h +++ b/Source/Core/DiscIO/VolumeVerifier.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include #include @@ -12,8 +12,11 @@ #include +#include + #include "Common/CommonTypes.h" #include "Common/Crypto/SHA1.h" +#include "Common/WorkQueueThread.h" #include "Core/IOS/ES/Formats.h" #include "DiscIO/DiscScrubber.h" #include "DiscIO/Volume.h" @@ -58,6 +61,11 @@ public: std::string message; }; + RedumpVerifier() = default; + ~RedumpVerifier(); + RedumpVerifier(const RedumpVerifier&) = delete; + RedumpVerifier& operator=(const RedumpVerifier&) = delete; + void Start(const Volume& volume); Result Finish(const Hashes>& hashes); @@ -91,7 +99,13 @@ private: u8 m_disc_number = 0; u64 m_size = 0; - std::future> m_future; + void MatchThread(); + std::vector FindPotentialMatches(Platform platform); + + // Looks the disc up in the Redump datfile while the disc is hashed. + sthread_t* m_thread = nullptr; + Platform m_platform{}; + std::vector m_potential_matches; Result m_result; static DownloadState m_gc_download_state; @@ -155,7 +169,8 @@ private: void CheckMisc(); void CheckSuperPaperMario(); void SetUpHashing(); - void WaitForAsyncOperations() const; + void WaitForAsyncOperations(); + static void RunAsyncTask(void* ctx, size_t index); bool ReadChunkAndWaitForAsyncOperations(u64 bytes_to_read); void AddProblem(Severity severity, std::string text); @@ -180,11 +195,7 @@ private: u64 m_excess_bytes = 0; std::vector m_data; - std::future m_crc32_future; - std::future m_md5_future; - std::future m_sha1_future; - std::future m_content_future; - std::future m_group_future; + std::vector> m_async_tasks; DiscScrubber m_scrubber; IOS::ES::TicketReader m_ticket; @@ -203,6 +214,9 @@ private: u64 m_progress = 0; u64 m_max_progress = 0; DataSizeType m_data_size_type; + + // Declared last so it stops before anything its tasks touch is destroyed. + Common::AsyncWorkThreadSP m_async_worker; }; } // namespace DiscIO diff --git a/Source/Core/DiscIO/VolumeWii.cpp b/Source/Core/DiscIO/VolumeWii.cpp index 920578f2b9..152fc01995 100644 --- a/Source/Core/DiscIO/VolumeWii.cpp +++ b/Source/Core/DiscIO/VolumeWii.cpp @@ -7,13 +7,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include @@ -23,6 +21,7 @@ #include "Common/Crypto/AES.h" #include "Common/Crypto/SHA1.h" #include "Common/Logging/Log.h" +#include "Common/ParallelFor.h" #include "DiscIO/Blob.h" #include "DiscIO/DiscExtractor.h" @@ -483,75 +482,82 @@ bool VolumeWii::CheckBlockIntegrity(u64 block_index, const Partition& partition) return CheckBlockIntegrity(block_index, cluster.data(), partition); } +namespace +{ +struct HashJob +{ + const std::array* in; + VolumeWii::HashBlock* out; +}; + +// H0 hashes of one block, and that block's entry in the H1 table of its subgroup. +void HashBlockJob(void* ctx, size_t i) +{ + const HashJob* const job = static_cast(ctx); + VolumeWii::HashBlock* const out = job->out; + const size_t h1_base = Common::AlignDown(i, 8); + + for (size_t j = 0; j < 31; ++j) + out[i].h0[j] = Common::SHA1::CalculateDigest(job->in[i].data() + j * 0x400, 0x400); + out[i].padding_0 = {}; + out[h1_base].h1[i - h1_base] = Common::SHA1::CalculateDigest(out[i].h0); +} + +struct EncryptJob +{ + const std::array* in; + const VolumeWii::HashBlock* hashes; + u8* out; + const Common::AES::Context* aes; +}; + +void EncryptBlockJob(void* ctx, size_t i) +{ + const EncryptJob* const job = static_cast(ctx); + u8* const out_ptr = job->out + i * VolumeWii::BLOCK_TOTAL_SIZE; + + job->aes->CryptIvZero(reinterpret_cast(&job->hashes[i]), out_ptr, + VolumeWii::BLOCK_HEADER_SIZE); + job->aes->Crypt(out_ptr + 0x3D0, job->in[i].data(), out_ptr + VolumeWii::BLOCK_HEADER_SIZE, + VolumeWii::BLOCK_DATA_SIZE); +} +} // namespace + bool VolumeWii::HashGroup(const std::array in[BLOCKS_PER_GROUP], HashBlock out[BLOCKS_PER_GROUP], const std::function& read_function) { - std::array, BLOCKS_PER_GROUP> hash_futures; - bool success = true; - - for (size_t i = 0; i < BLOCKS_PER_GROUP; ++i) + // The reads go through one blob reader, so they stay on this thread; the 64 blocks of hashing + // are spread over the shared worker pool. + if (read_function) { - if (read_function && success) - success = read_function(i); - - hash_futures[i] = std::async(std::launch::async, [&in, &out, &hash_futures, success, i] { - const size_t h1_base = Common::AlignDown(i, 8); - - if (success) - { - // H0 hashes - for (size_t j = 0; j < 31; ++j) - out[i].h0[j] = Common::SHA1::CalculateDigest(in[i].data() + j * 0x400, 0x400); - - // H0 padding - out[i].padding_0 = {}; - - // H1 hash - out[h1_base].h1[i - h1_base] = Common::SHA1::CalculateDigest(out[i].h0); - } - - if (i % 8 == 7) - { - for (size_t j = 0; j < 7; ++j) - hash_futures[h1_base + j].get(); - - if (success) - { - // H1 padding - out[h1_base].padding_1 = {}; - - // H1 copies - for (size_t j = 1; j < 8; ++j) - out[h1_base + j].h1 = out[h1_base].h1; - - // H2 hash - out[0].h2[h1_base / 8] = Common::SHA1::CalculateDigest(out[i].h1); - } - - if (i == BLOCKS_PER_GROUP - 1) - { - for (size_t j = 0; j < 7; ++j) - hash_futures[j * 8 + 7].get(); - - if (success) - { - // H2 padding - out[0].padding_2 = {}; - - // H2 copies - for (size_t j = 1; j < BLOCKS_PER_GROUP; ++j) - out[j].h2 = out[0].h2; - } - } - } - }); + for (size_t i = 0; i < BLOCKS_PER_GROUP; ++i) + { + if (!read_function(i)) + return false; + } } - // Wait for all the async tasks to finish - hash_futures.back().get(); + HashJob job{in, out}; + parallel_for(BLOCKS_PER_GROUP, HashBlockJob, &job); - return success; + for (size_t h1_base = 0; h1_base < BLOCKS_PER_GROUP; h1_base += 8) + { + // H1 padding and copies + out[h1_base].padding_1 = {}; + for (size_t j = 1; j < 8; ++j) + out[h1_base + j].h1 = out[h1_base].h1; + + // H2 hash + out[0].h2[h1_base / 8] = Common::SHA1::CalculateDigest(out[h1_base].h1); + } + + // H2 padding and copies + out[0].padding_2 = {}; + for (size_t j = 1; j < BLOCKS_PER_GROUP; ++j) + out[j].h2 = out[0].h2; + + return true; } bool VolumeWii::EncryptGroup( @@ -586,34 +592,11 @@ bool VolumeWii::EncryptGroup( if (hash_exception_callback) hash_exception_callback(unencrypted_hashes.data()); - const unsigned int threads = - std::min(BLOCKS_PER_GROUP, std::max(1, std::thread::hardware_concurrency())); - - std::vector> encryption_futures(threads); - auto aes_context = Common::AES::CreateContextEncrypt(key.data()); - for (size_t i = 0; i < threads; ++i) - { - encryption_futures[i] = std::async( - std::launch::async, - [&unencrypted_data, &unencrypted_hashes, &aes_context, &out](size_t start, size_t end) { - for (size_t j = start; j < end; ++j) - { - u8* out_ptr = out->data() + j * BLOCK_TOTAL_SIZE; - - aes_context->CryptIvZero(reinterpret_cast(&unencrypted_hashes[j]), out_ptr, - BLOCK_HEADER_SIZE); - - aes_context->Crypt(out_ptr + 0x3D0, unencrypted_data[j].data(), - out_ptr + BLOCK_HEADER_SIZE, BLOCK_DATA_SIZE); - } - }, - i * BLOCKS_PER_GROUP / threads, (i + 1) * BLOCKS_PER_GROUP / threads); - } - - for (std::future& future : encryption_futures) - future.get(); + EncryptJob job{unencrypted_data.data(), unencrypted_hashes.data(), out->data(), + aes_context.get()}; + parallel_for(BLOCKS_PER_GROUP, EncryptBlockJob, &job); return true; } diff --git a/Source/Core/DolphinLibretro/Main.cpp b/Source/Core/DolphinLibretro/Main.cpp index 97ba667a1e..87fc76341a 100644 --- a/Source/Core/DolphinLibretro/Main.cpp +++ b/Source/Core/DolphinLibretro/Main.cpp @@ -15,6 +15,7 @@ #include "Common/FileUtil.h" #include "Common/GL/GLContext.h" #include "Common/Logging/LogManager.h" +#include "Common/ParallelFor.h" #include "Common/Thread.h" #include "Common/scmrev.h" #include "Common/Version.h" @@ -162,6 +163,7 @@ void retro_init(void) void retro_deinit(void) { Libretro::g_emuthread_launched = false; + parallel_for_deinit(); #ifdef PERF_TEST perf_cb.perf_log(); #endif @@ -286,8 +288,7 @@ void retro_run(void) WindowSystemInfo wsi(WindowSystemType::Libretro, nullptr, nullptr, nullptr); if (system.IsDualCoreMode()) { - Core::s_emu_thread = std::thread(Core::EmuThread, - std::ref(system), std::move(Core::g_boot_params), wsi); + Core::StartEmuThread(system, std::move(Core::g_boot_params), wsi); // Wait until CPU thread has reached Run() auto& cpu_manager = system.GetCPU(); diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp index 9a604d5f92..3dd0ce263b 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -215,7 +214,7 @@ private: std::vector m_servers; u32 m_client_uid = 0; SteadyClock::time_point m_next_listports_time; - std::thread m_hotplug_thread; + sthread_t* m_hotplug_thread = nullptr; Common::Flag m_hotplug_thread_running; Config::ConfigChangedCallbackID m_config_change_callback_id; }; @@ -372,7 +371,7 @@ void InputBackend::StartHotplugThread() return; } - m_hotplug_thread = std::thread(&InputBackend::HotplugThreadFunc, this); + m_hotplug_thread = sthread_create(Common::ThreadEntry<&InputBackend::HotplugThreadFunc>, this); } void InputBackend::StopHotplugThread() @@ -384,7 +383,7 @@ void InputBackend::StopHotplugThread() return; } - m_hotplug_thread.join(); + Common::JoinThread(m_hotplug_thread); for (auto& server : m_servers) { diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index f86b1465f5..90007ebfa0 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -78,8 +78,8 @@ static void ResetRumbleLockNeeded(); static void Reset(); static void Setup(); static void ProcessInputPayload(const u8* data, std::size_t size); -static void ReadThreadFunc(); -static void WriteThreadFunc(); +static void ReadThreadFunc(void*); +static void WriteThreadFunc(void*); #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION enum class AdapterStatus @@ -130,9 +130,9 @@ static std::array s_port_states; static std::array s_controller_write_payload; static std::atomic s_controller_write_payload_size{0}; -static std::thread s_read_adapter_thread; +static sthread_t* s_read_adapter_thread = nullptr; static Common::Flag s_read_adapter_thread_running; -static std::thread s_write_adapter_thread; +static sthread_t* s_write_adapter_thread = nullptr; static Common::Flag s_write_adapter_thread_running; static Common::Event s_write_happened; @@ -142,7 +142,7 @@ static std::mutex s_read_mutex; static std::mutex s_write_mutex; #endif -static std::thread s_adapter_detect_thread; +static sthread_t* s_adapter_detect_thread = nullptr; static Common::Flag s_adapter_detect_thread_running; static Common::Event s_hotplug_event; @@ -178,7 +178,7 @@ static std::array s_config_r static std::atomic s_adapter_poll_rate{}; #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION || GCADAPTER_USE_ANDROID_IMPLEMENTATION -static void ReadThreadFunc() +static void ReadThreadFunc(void*) { Common::SetCurrentThreadName("GCAdapter Read Thread"); NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GCAdapter read thread started"); @@ -209,7 +209,7 @@ static void ReadThreadFunc() #endif s_write_adapter_thread_running.Set(true); - s_write_adapter_thread = std::thread(WriteThreadFunc); + s_write_adapter_thread = sthread_create(WriteThreadFunc, nullptr); std::chrono::steady_clock::time_point last_successful_read_time = std::chrono::steady_clock::now(); @@ -300,7 +300,7 @@ static void ReadThreadFunc() s_controller_write_payload_size.store(0); // Kick the waiting event s_write_happened.Set(); - s_write_adapter_thread.join(); + Common::JoinThread(s_write_adapter_thread); } #if GCADAPTER_USE_ANDROID_IMPLEMENTATION @@ -315,7 +315,7 @@ static void ReadThreadFunc() #endif #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION || GCADAPTER_USE_ANDROID_IMPLEMENTATION -static void WriteThreadFunc() +static void WriteThreadFunc(void*) { Common::SetCurrentThreadName("GCAdapter Write Thread"); NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GCAdapter write thread started"); @@ -411,7 +411,7 @@ Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterDisconnected(JNIEnv* env } #endif -static void ScanThreadFunc() +static void ScanThreadFunc(void*) { Common::SetCurrentThreadName("GC Adapter Scanning Thread"); NOTICE_LOG_FMT(CONTROLLERINTERFACE, "GC Adapter scanning thread started"); @@ -513,7 +513,7 @@ static void StartScanThread() return; #endif s_adapter_detect_thread_running.Set(true); - s_adapter_detect_thread = std::thread(ScanThreadFunc); + s_adapter_detect_thread = sthread_create(ScanThreadFunc, nullptr); } static void StopScanThread() @@ -521,7 +521,7 @@ static void StopScanThread() if (s_adapter_detect_thread_running.TestAndClear()) { s_hotplug_event.Set(); - s_adapter_detect_thread.join(); + Common::JoinThread(s_adapter_detect_thread); } } @@ -617,10 +617,10 @@ static void Setup() // Make sure the thread isn't in the middle of shutting down while starting a new one if (s_read_adapter_thread_running.TestAndClear()) - s_read_adapter_thread.join(); + Common::JoinThread(s_read_adapter_thread); s_read_adapter_thread_running.Set(true); - s_read_adapter_thread = std::thread(ReadThreadFunc); + s_read_adapter_thread = sthread_create(ReadThreadFunc, nullptr); #endif } #endif @@ -772,7 +772,7 @@ static void AddGCAdapter(libusb_device* device) } s_read_adapter_thread_running.Set(true); - s_read_adapter_thread = std::thread(ReadThreadFunc); + s_read_adapter_thread = sthread_create(ReadThreadFunc, nullptr); s_status = AdapterStatus::Detected; if (s_detect_callback != nullptr) @@ -812,7 +812,7 @@ static void Reset() #endif if (s_read_adapter_thread_running.TestAndClear()) - s_read_adapter_thread.join(); + Common::JoinThread(s_read_adapter_thread); // The read thread will close the write thread s_read_thread_has_init.Clear(); diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp index b886927a5c..d13d9adc93 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp @@ -24,9 +24,17 @@ void CustomAssetLoader::Shutdown() bool CustomAssetLoader::StartWorkerThreads(u32 num_worker_threads) { + // Each thread is handed its slot, so the vector must not move once threads are running. + m_worker_threads.reserve(num_worker_threads); for (u32 i = 0; i < num_worker_threads; i++) { - m_worker_threads.emplace_back(&CustomAssetLoader::WorkerThreadRun, this, i); + WorkerThread& worker = m_worker_threads.emplace_back(WorkerThread{this, i, nullptr}); + worker.thread = sthread_create(WorkerThreadEntry, &worker); + if (!worker.thread) + { + m_worker_threads.pop_back(); + break; + } } return HasWorkerThreads(); @@ -59,12 +67,18 @@ void CustomAssetLoader::StopWorkerThreads() } // Wait for worker threads to exit. - for (std::thread& thr : m_worker_threads) - thr.join(); + for (WorkerThread& worker : m_worker_threads) + Common::JoinThread(worker.thread); m_worker_threads.clear(); m_exit_flag.Clear(); } +void CustomAssetLoader::WorkerThreadEntry(void* data) +{ + const WorkerThread* const worker = static_cast(data); + worker->owner->WorkerThreadRun(worker->index); +} + void CustomAssetLoader::WorkerThreadRun(u32 thread_index) { #if defined(__LIBRETRO__) && defined(SKIP_ASSETLOADER_THREAD) diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h index 8b67da65a9..6d26e55a84 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h @@ -8,9 +8,10 @@ #include #include #include -#include #include +#include + #include "Common/Flag.h" #include "VideoCommon/Assets/CustomAsset.h" @@ -56,11 +57,18 @@ private: bool HasWorkerThreads() const; void StopWorkerThreads(); + struct WorkerThread + { + CustomAssetLoader* owner; + u32 index; + sthread_t* thread; + }; + static void WorkerThreadEntry(void* data); void WorkerThreadRun(u32 thread_index); Common::Flag m_exit_flag; - std::vector m_worker_threads; + std::vector m_worker_threads; std::mutex m_assets_to_load_lock; std::list m_assets_to_load; diff --git a/Source/Core/VideoCommon/AsyncShaderCompiler.cpp b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp index 83eaa2e23e..f1ba82ed97 100644 --- a/Source/Core/VideoCommon/AsyncShaderCompiler.cpp +++ b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp @@ -140,17 +140,26 @@ bool AsyncShaderCompiler::StartWorkerThreads(u32 num_worker_threads) m_worker_thread_start_result.store(false); - std::thread thr(&AsyncShaderCompiler::WorkerThreadEntryPoint, this, thread_param); + // The new thread takes its parameter before it signals m_init_event. + m_starting_thread_param = thread_param; + sthread_t* thr = sthread_create(Common::ThreadEntry<&AsyncShaderCompiler::WorkerThreadStart>, + this); + if (!thr) + { + WorkerThreadExit(thread_param); + WARN_LOG_FMT(VIDEO, "Failed to start shader compiler worker thread."); + break; + } m_init_event.Wait(); if (!m_worker_thread_start_result.load()) { WARN_LOG_FMT(VIDEO, "Failed to start shader compiler worker thread."); - thr.join(); + Common::JoinThread(thr); break; } - m_worker_threads.push_back(std::move(thr)); + m_worker_threads.push_back(thr); } return HasWorkerThreads(); @@ -183,8 +192,8 @@ void AsyncShaderCompiler::StopWorkerThreads() } // Wait for worker threads to exit. - for (std::thread& thr : m_worker_threads) - thr.join(); + for (sthread_t*& thr : m_worker_threads) + Common::JoinThread(thr); m_worker_threads.clear(); m_exit_flag.Clear(); } @@ -203,6 +212,11 @@ void AsyncShaderCompiler::WorkerThreadExit(void* param) { } +void AsyncShaderCompiler::WorkerThreadStart() +{ + WorkerThreadEntryPoint(m_starting_thread_param); +} + void AsyncShaderCompiler::WorkerThreadEntryPoint(void* param) { Common::SetCurrentThreadName("AsyncShaderCompiler Worker"); diff --git a/Source/Core/VideoCommon/AsyncShaderCompiler.h b/Source/Core/VideoCommon/AsyncShaderCompiler.h index ffcca0c4ec..e80f427a98 100644 --- a/Source/Core/VideoCommon/AsyncShaderCompiler.h +++ b/Source/Core/VideoCommon/AsyncShaderCompiler.h @@ -10,10 +10,11 @@ #include #include #include -#include #include #include +#include + #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/Flag.h" @@ -68,13 +69,15 @@ protected: virtual void WorkerThreadExit(void* param); private: + void WorkerThreadStart(); void WorkerThreadEntryPoint(void* param); void WorkerThreadRun(); Common::Flag m_exit_flag; Common::Event m_init_event; - std::vector m_worker_threads; + std::vector m_worker_threads; + void* m_starting_thread_param = nullptr; std::atomic_bool m_worker_thread_start_result{false}; // A multimap is used to store the work items. We can't use a priority_queue here, because diff --git a/Source/Core/VideoCommon/FrameDumper.cpp b/Source/Core/VideoCommon/FrameDumper.cpp index 8bb98833f2..ba745901b4 100644 --- a/Source/Core/VideoCommon/FrameDumper.cpp +++ b/Source/Core/VideoCommon/FrameDumper.cpp @@ -6,6 +6,7 @@ #include "Common/Assert.h" #include "Common/FileUtil.h" #include "Common/Image.h" +#include "Common/Thread.h" #include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" @@ -159,8 +160,7 @@ void FrameDumper::ShutdownFrameDumping() // Wake thread up, and wait for it to exit. m_frame_dump_thread_running.Clear(); m_frame_dump_start.Set(); - if (m_frame_dump_thread.joinable()) - m_frame_dump_thread.join(); + Common::JoinThread(m_frame_dump_thread); m_frame_dump_render_framebuffer.reset(); m_frame_dump_render_texture.reset(); @@ -174,10 +174,9 @@ void FrameDumper::DumpFrameData(const u8* data, int w, int h, int stride) if (!m_frame_dump_thread_running.IsSet()) { - if (m_frame_dump_thread.joinable()) - m_frame_dump_thread.join(); + Common::JoinThread(m_frame_dump_thread); m_frame_dump_thread_running.Set(); - m_frame_dump_thread = std::thread(&FrameDumper::FrameDumpThreadFunc, this); + m_frame_dump_thread = sthread_create(Common::ThreadEntry<&FrameDumper::FrameDumpThreadFunc>, this); } // Wake worker thread up. diff --git a/Source/Core/VideoCommon/FrameDumper.h b/Source/Core/VideoCommon/FrameDumper.h index 4b15489eed..0069e35818 100644 --- a/Source/Core/VideoCommon/FrameDumper.h +++ b/Source/Core/VideoCommon/FrameDumper.h @@ -3,7 +3,6 @@ #pragma once -#include #include "Common/CommonTypes.h" #include "Common/Event.h" @@ -63,7 +62,7 @@ private: // Ensures all encoded frames have been written to the output file. void FinishFrameData(); - std::thread m_frame_dump_thread; + sthread_t* m_frame_dump_thread = nullptr; Common::Flag m_frame_dump_thread_running; // Used to kick frame dump thread.