From 0a9574eaa10f30e63f13c8fc35d6afcbdb4b1d76 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 19 Jun 2017 14:17:18 +1000 Subject: VideoCommon: Add AsyncShaderCompiler class implementation --- Source/Core/VideoCommon/AsyncShaderCompiler.cpp | 215 ++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 Source/Core/VideoCommon/AsyncShaderCompiler.cpp (limited to 'Source/Core/VideoCommon/AsyncShaderCompiler.cpp') diff --git a/Source/Core/VideoCommon/AsyncShaderCompiler.cpp b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp new file mode 100644 index 0000000000..15eececc4c --- /dev/null +++ b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp @@ -0,0 +1,215 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/AsyncShaderCompiler.h" +#include +#include "Common/Assert.h" +#include "Common/Logging/Log.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()); + _assert_(m_completed_work.empty()); +} + +void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item) +{ + // 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.push_back(std::move(item)); + m_worker_thread_wake.notify_one(); + } +} + +void AsyncShaderCompiler::RetrieveWorkItems() +{ + std::deque 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; +} + +void AsyncShaderCompiler::WaitUntilCompletion() +{ + while (HasPendingWork()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); +} + +void AsyncShaderCompiler::WaitUntilCompletion( + const std::function& progress_callback) +{ + if (!HasPendingWork()) + return; + + // 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 = 50; + 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; + } + + // Grab the number of pending items. We use this to work out how many are left. + size_t total_items = 0; + { + // 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. + for (;;) + { + size_t remaining_items; + { + std::lock_guard pending_guard(m_pending_work_lock); + if (m_pending_work.empty() && !m_busy_workers.load()) + break; + remaining_items = m_pending_work.size(); + } + + progress_callback(total_items - remaining_items, total_items); + std::this_thread::sleep_for(CHECK_INTERVAL); + } +} + +void AsyncShaderCompiler::StartWorkerThreads(u32 num_worker_threads) +{ + for (u32 i = 0; i < num_worker_threads; i++) + { + void* thread_param = nullptr; + if (!WorkerThreadInitMainThread(&thread_param)) + { + WARN_LOG(VIDEO, "Failed to initialize shader compiler worker thread."); + break; + } + + m_worker_thread_start_result.store(false); + + std::thread thr(&AsyncShaderCompiler::WorkerThreadEntryPoint, this, thread_param); + m_init_event.Wait(); + + if (!m_worker_thread_start_result.load()) + { + WARN_LOG(VIDEO, "Failed to start shader compiler worker thread."); + thr.join(); + break; + } + + m_worker_threads.push_back(std::move(thr)); + } +} + +bool AsyncShaderCompiler::HasWorkerThreads() const +{ + return !m_worker_threads.empty(); +} + +void AsyncShaderCompiler::StopWorkerThreads() +{ + // 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 (std::thread& thr : m_worker_threads) + thr.join(); + m_worker_threads.clear(); +} + +bool AsyncShaderCompiler::WorkerThreadInitMainThread(void** param) +{ + return true; +} + +bool AsyncShaderCompiler::WorkerThreadInitWorkerThread(void* param) +{ + return true; +} + +void AsyncShaderCompiler::WorkerThreadExit(void* param) +{ +} + +void AsyncShaderCompiler::WorkerThreadEntryPoint(void* param) +{ + // Initialize worker thread with backend-specific method. + if (!WorkerThreadInitWorkerThread(param)) + { + WARN_LOG(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++; + WorkItemPtr item(std::move(m_pending_work.front())); + m_pending_work.pop_front(); + 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 -- cgit v1.2.3 From c8f31656cb1c5f5e3c83a5b977fd822e987cb596 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 27 Jul 2017 13:15:38 +1000 Subject: VideoBackends: Support a different number of threads for precompiling At runtime, we only really want a single shader compiler thread. However, for initial boots, we can use a higher number to speed things up. --- Source/Core/VideoCommon/AsyncShaderCompiler.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'Source/Core/VideoCommon/AsyncShaderCompiler.cpp') diff --git a/Source/Core/VideoCommon/AsyncShaderCompiler.cpp b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp index 15eececc4c..59ef9762f1 100644 --- a/Source/Core/VideoCommon/AsyncShaderCompiler.cpp +++ b/Source/Core/VideoCommon/AsyncShaderCompiler.cpp @@ -106,8 +106,11 @@ void AsyncShaderCompiler::WaitUntilCompletion( } } -void AsyncShaderCompiler::StartWorkerThreads(u32 num_worker_threads) +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; @@ -131,6 +134,17 @@ void AsyncShaderCompiler::StartWorkerThreads(u32 num_worker_threads) m_worker_threads.push_back(std::move(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 @@ -140,6 +154,9 @@ bool AsyncShaderCompiler::HasWorkerThreads() const void AsyncShaderCompiler::StopWorkerThreads() { + if (!HasWorkerThreads()) + return; + // Signal worker threads to stop, and wake all of them. { std::lock_guard guard(m_pending_work_lock); @@ -151,6 +168,7 @@ void AsyncShaderCompiler::StopWorkerThreads() for (std::thread& thr : m_worker_threads) thr.join(); m_worker_threads.clear(); + m_exit_flag.Clear(); } bool AsyncShaderCompiler::WorkerThreadInitMainThread(void** param) -- cgit v1.2.3