summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorStenzek <stenzek@gmail.com>2017-06-19 14:17:18 +1000
committerStenzek <stenzek@gmail.com>2017-07-30 17:43:59 +1000
commit0a9574eaa10f30e63f13c8fc35d6afcbdb4b1d76 (patch)
tree9e16ba3e243688638c9c4767fbf77bf883ba4a4d /Source/Core
parent5bad2ee4a4129e734ff4f4d861cedc4cd573df55 (diff)
VideoCommon: Add AsyncShaderCompiler class implementation
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/VideoCommon/AsyncShaderCompiler.cpp215
-rw-r--r--Source/Core/VideoCommon/AsyncShaderCompiler.h83
-rw-r--r--Source/Core/VideoCommon/CMakeLists.txt1
-rw-r--r--Source/Core/VideoCommon/VideoCommon.vcxproj2
-rw-r--r--Source/Core/VideoCommon/VideoCommon.vcxproj.filters8
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>