mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 12:37:59 -10:00
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().
269 lines
6.5 KiB
C++
269 lines
6.5 KiB
C++
// Copyright 2017 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "VideoCommon/AsyncShaderCompiler.h"
|
|
|
|
#include <thread>
|
|
|
|
#include "Common/Assert.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/Thread.h"
|
|
|
|
#include "Core/Core.h"
|
|
#include "Core/System.h"
|
|
|
|
namespace VideoCommon
|
|
{
|
|
AsyncShaderCompiler::AsyncShaderCompiler()
|
|
{
|
|
}
|
|
|
|
AsyncShaderCompiler::~AsyncShaderCompiler()
|
|
{
|
|
// Pending work can be left at shutdown.
|
|
// The work item classes are expected to clean up after themselves.
|
|
ASSERT(!HasWorkerThreads());
|
|
}
|
|
|
|
void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item, u32 priority)
|
|
{
|
|
// If no worker threads are available, compile synchronously.
|
|
if (!HasWorkerThreads())
|
|
{
|
|
item->Compile();
|
|
m_completed_work.push_back(std::move(item));
|
|
}
|
|
else
|
|
{
|
|
std::lock_guard guard(m_pending_work_lock);
|
|
m_pending_work.emplace(priority, std::move(item));
|
|
m_worker_thread_wake.notify_one();
|
|
}
|
|
}
|
|
|
|
void AsyncShaderCompiler::RetrieveWorkItems()
|
|
{
|
|
std::deque<WorkItemPtr> completed_work;
|
|
{
|
|
std::lock_guard guard(m_completed_work_lock);
|
|
m_completed_work.swap(completed_work);
|
|
}
|
|
|
|
while (!completed_work.empty())
|
|
{
|
|
completed_work.front()->Retrieve();
|
|
completed_work.pop_front();
|
|
}
|
|
}
|
|
|
|
bool AsyncShaderCompiler::HasPendingWork()
|
|
{
|
|
std::lock_guard guard(m_pending_work_lock);
|
|
return !m_pending_work.empty() || m_busy_workers.load() != 0;
|
|
}
|
|
|
|
bool AsyncShaderCompiler::HasCompletedWork()
|
|
{
|
|
std::lock_guard guard(m_completed_work_lock);
|
|
return !m_completed_work.empty();
|
|
}
|
|
|
|
void AsyncShaderCompiler::ClearAllWork()
|
|
{
|
|
{
|
|
std::lock_guard guard(m_pending_work_lock);
|
|
m_pending_work.clear();
|
|
}
|
|
|
|
{
|
|
std::lock_guard guard(m_completed_work_lock);
|
|
m_completed_work.clear();
|
|
}
|
|
}
|
|
|
|
bool AsyncShaderCompiler::WaitUntilCompletion(
|
|
const std::function<void(size_t, size_t)>& progress_callback)
|
|
{
|
|
if (!HasPendingWork())
|
|
return true;
|
|
|
|
// Wait a second before opening a progress dialog.
|
|
// 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++)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(CHECK_INTERVAL));
|
|
if (!HasPendingWork())
|
|
return true;
|
|
}
|
|
|
|
// Grab the number of pending items. We use this to work out how many are left.
|
|
size_t total_items;
|
|
{
|
|
// Safe to hold both locks here, since nowhere else does.
|
|
std::lock_guard pending_guard(m_pending_work_lock);
|
|
std::lock_guard completed_guard(m_completed_work_lock);
|
|
total_items = m_completed_work.size() + m_pending_work.size() + m_busy_workers.load() + 1;
|
|
}
|
|
|
|
// Update progress while the compiles complete.
|
|
while (Core::GetState(Core::System::GetInstance()) != Core::State::Stopping)
|
|
{
|
|
size_t remaining_items;
|
|
{
|
|
std::lock_guard pending_guard(m_pending_work_lock);
|
|
if (m_pending_work.empty() && !m_busy_workers.load())
|
|
return true;
|
|
remaining_items = m_pending_work.size();
|
|
}
|
|
|
|
progress_callback(total_items - remaining_items, total_items);
|
|
std::this_thread::sleep_for(CHECK_INTERVAL);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AsyncShaderCompiler::StartWorkerThreads(u32 num_worker_threads)
|
|
{
|
|
if (num_worker_threads == 0)
|
|
return true;
|
|
|
|
for (u32 i = 0; i < num_worker_threads; i++)
|
|
{
|
|
void* thread_param = nullptr;
|
|
if (!WorkerThreadInitMainThread(&thread_param))
|
|
{
|
|
WARN_LOG_FMT(VIDEO, "Failed to initialize shader compiler worker thread.");
|
|
break;
|
|
}
|
|
|
|
m_worker_thread_start_result.store(false);
|
|
|
|
// 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.");
|
|
Common::JoinThread(thr);
|
|
break;
|
|
}
|
|
|
|
m_worker_threads.push_back(thr);
|
|
}
|
|
|
|
return HasWorkerThreads();
|
|
}
|
|
|
|
bool AsyncShaderCompiler::ResizeWorkerThreads(u32 num_worker_threads)
|
|
{
|
|
if (m_worker_threads.size() == num_worker_threads)
|
|
return true;
|
|
|
|
StopWorkerThreads();
|
|
return StartWorkerThreads(num_worker_threads);
|
|
}
|
|
|
|
bool AsyncShaderCompiler::HasWorkerThreads() const
|
|
{
|
|
return !m_worker_threads.empty();
|
|
}
|
|
|
|
void AsyncShaderCompiler::StopWorkerThreads()
|
|
{
|
|
if (!HasWorkerThreads())
|
|
return;
|
|
|
|
// Signal worker threads to stop, and wake all of them.
|
|
{
|
|
std::lock_guard guard(m_pending_work_lock);
|
|
m_exit_flag.Set();
|
|
m_worker_thread_wake.notify_all();
|
|
}
|
|
|
|
// Wait for worker threads to exit.
|
|
for (sthread_t*& thr : m_worker_threads)
|
|
Common::JoinThread(thr);
|
|
m_worker_threads.clear();
|
|
m_exit_flag.Clear();
|
|
}
|
|
|
|
bool AsyncShaderCompiler::WorkerThreadInitMainThread(void** param)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool AsyncShaderCompiler::WorkerThreadInitWorkerThread(void* param)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void AsyncShaderCompiler::WorkerThreadExit(void* param)
|
|
{
|
|
}
|
|
|
|
void AsyncShaderCompiler::WorkerThreadStart()
|
|
{
|
|
WorkerThreadEntryPoint(m_starting_thread_param);
|
|
}
|
|
|
|
void AsyncShaderCompiler::WorkerThreadEntryPoint(void* param)
|
|
{
|
|
Common::SetCurrentThreadName("AsyncShaderCompiler Worker");
|
|
|
|
// Initialize worker thread with backend-specific method.
|
|
if (!WorkerThreadInitWorkerThread(param))
|
|
{
|
|
WARN_LOG_FMT(VIDEO, "Failed to initialize shader compiler worker.");
|
|
m_worker_thread_start_result.store(false);
|
|
m_init_event.Set();
|
|
return;
|
|
}
|
|
|
|
m_worker_thread_start_result.store(true);
|
|
m_init_event.Set();
|
|
|
|
WorkerThreadRun();
|
|
|
|
WorkerThreadExit(param);
|
|
}
|
|
|
|
void AsyncShaderCompiler::WorkerThreadRun()
|
|
{
|
|
std::unique_lock pending_lock(m_pending_work_lock);
|
|
while (!m_exit_flag.IsSet())
|
|
{
|
|
m_worker_thread_wake.wait(pending_lock);
|
|
|
|
while (!m_pending_work.empty() && !m_exit_flag.IsSet())
|
|
{
|
|
m_busy_workers++;
|
|
auto iter = m_pending_work.begin();
|
|
WorkItemPtr item(std::move(iter->second));
|
|
m_pending_work.erase(iter);
|
|
pending_lock.unlock();
|
|
|
|
if (item->Compile())
|
|
{
|
|
std::lock_guard completed_guard(m_completed_work_lock);
|
|
m_completed_work.push_back(std::move(item));
|
|
}
|
|
|
|
pending_lock.lock();
|
|
m_busy_workers--;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace VideoCommon
|