diff options
| author | Stenzek <stenzek@gmail.com> | 2017-06-19 14:17:18 +1000 |
|---|---|---|
| committer | Stenzek <stenzek@gmail.com> | 2017-07-30 17:43:59 +1000 |
| commit | 0a9574eaa10f30e63f13c8fc35d6afcbdb4b1d76 (patch) | |
| tree | 9e16ba3e243688638c9c4767fbf77bf883ba4a4d /Source/Core | |
| parent | 5bad2ee4a4129e734ff4f4d861cedc4cd573df55 (diff) | |
VideoCommon: Add AsyncShaderCompiler class implementation
Diffstat (limited to 'Source/Core')
| -rw-r--r-- | Source/Core/VideoCommon/AsyncShaderCompiler.cpp | 215 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/AsyncShaderCompiler.h | 83 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/VideoCommon.vcxproj | 2 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/VideoCommon.vcxproj.filters | 8 |
5 files changed, 308 insertions, 1 deletions
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 <thread> +#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<std::mutex> guard(m_pending_work_lock); + m_pending_work.push_back(std::move(item)); + m_worker_thread_wake.notify_one(); + } +} + +void AsyncShaderCompiler::RetrieveWorkItems() +{ + std::deque<WorkItemPtr> completed_work; + { + std::lock_guard<std::mutex> 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<std::mutex> 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<void(size_t, size_t)>& 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<std::mutex> pending_guard(m_pending_work_lock); + std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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 <atomic> +#include <condition_variable> +#include <deque> +#include <functional> +#include <memory> +#include <mutex> +#include <thread> +#include <vector> + +#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<WorkItem>; + + AsyncShaderCompiler(); + virtual ~AsyncShaderCompiler(); + + template <typename T, typename... Params> + static WorkItemPtr CreateWorkItem(Params... params) + { + return std::unique_ptr<WorkItem>(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<void(size_t, size_t)>& 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<std::thread> m_worker_threads; + std::atomic_bool m_worker_thread_start_result{false}; + + std::deque<WorkItemPtr> 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<WorkItemPtr> 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 @@ <ItemGroup> <ClCompile Include="AbstractTexture.cpp" /> <ClCompile Include="AsyncRequests.cpp" /> + <ClCompile Include="AsyncShaderCompiler.cpp" /> <ClCompile Include="AVIDump.cpp" /> <ClCompile Include="BoundingBox.cpp" /> <ClCompile Include="BPFunctions.cpp" /> @@ -94,6 +95,7 @@ <ItemGroup> <ClInclude Include="AbstractTexture.h" /> <ClInclude Include="AsyncRequests.h" /> + <ClInclude Include="AsyncShaderCompiler.h" /> <ClInclude Include="AVIDump.h" /> <ClInclude Include="BoundingBox.h" /> <ClInclude Include="BPFunctions.h" /> 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 @@ <ClCompile Include="ShaderGenCommon.cpp"> <Filter>Shader Generators</Filter> </ClCompile> + <ClCompile Include="AsyncShaderCompiler.cpp"> + <Filter>Util</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="CommandProcessor.h" /> @@ -332,8 +335,11 @@ <ClInclude Include="AbstractTexture.h"> <Filter>Base</Filter> </ClInclude> + <ClInclude Include="AsyncShaderCompiler.h"> + <Filter>Util</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Text Include="CMakeLists.txt" /> </ItemGroup> -</Project>
\ No newline at end of file +</Project> |
