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 diff --git a/Source/Core/VideoCommon/AsyncShaderCompiler.h b/Source/Core/VideoCommon/AsyncShaderCompiler.h new file mode 100644 index 0000000000..88a3d116f4 --- /dev/null +++ b/Source/Core/VideoCommon/AsyncShaderCompiler.h @@ -0,0 +1,83 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/Event.h" +#include "Common/Flag.h" + +namespace VideoCommon +{ +class AsyncShaderCompiler +{ +public: + class WorkItem + { + public: + virtual ~WorkItem() = default; + virtual bool Compile() = 0; + virtual void Retrieve() = 0; + }; + + using WorkItemPtr = std::unique_ptr; + + AsyncShaderCompiler(); + virtual ~AsyncShaderCompiler(); + + template + static WorkItemPtr CreateWorkItem(Params... params) + { + return std::unique_ptr(new T(params...)); + } + + void QueueWorkItem(WorkItemPtr item); + void RetrieveWorkItems(); + bool HasPendingWork(); + + // Simpler version without progress updates. + void WaitUntilCompletion(); + + // Calls progress_callback periodically, with completed_items, and total_items. + void WaitUntilCompletion(const std::function& progress_callback); + + // Needed because of calling virtual methods in shutdown procedure. + void StartWorkerThreads(u32 num_worker_threads); + bool HasWorkerThreads() const; + void StopWorkerThreads(); + +protected: + virtual bool WorkerThreadInitMainThread(void** param); + virtual bool WorkerThreadInitWorkerThread(void* param); + virtual void WorkerThreadExit(void* param); + +private: + void WorkerThreadEntryPoint(void* param); + void WorkerThreadRun(); + + Common::Flag m_exit_flag; + Common::Event m_init_event; + + std::vector m_worker_threads; + std::atomic_bool m_worker_thread_start_result{false}; + + std::deque m_pending_work; + std::mutex m_pending_work_lock; + std::condition_variable m_worker_thread_wake; + std::atomic_size_t m_busy_workers{0}; + + std::deque m_completed_work; + std::mutex m_completed_work_lock; +}; + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 0724212bdc..782fc7af7e 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -1,6 +1,7 @@ set(SRCS AbstractTexture.cpp AsyncRequests.cpp + AsyncShaderCompiler.cpp BoundingBox.cpp BPFunctions.cpp BPMemory.cpp diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj index e33ccd9d7f..8dad6ea023 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj @@ -38,6 +38,7 @@ + @@ -94,6 +95,7 @@ + diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters index f4ec59eb1d..4d0348d7b0 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters @@ -176,6 +176,9 @@ Shader Generators + + Util + @@ -332,8 +335,11 @@ Base + + Util + - \ No newline at end of file +