From 518b469742ac3686828411a46c57c1eb591fda77 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Wed, 20 Dec 2017 22:17:14 +0100 Subject: [PATCH] [dxvk] Added command stream classes While these are not being used as of yet, these classes can be used to implement command stream multithreading in the future. They are also useful to implement command lists for deferred contexts, which are a core feature of D3D11. --- src/d3d11/d3d11_context.h | 6 +- src/dxvk/dxvk_cs.cpp | 90 +++++++++++++++++ src/dxvk/dxvk_cs.h | 198 ++++++++++++++++++++++++++++++++++++++ src/dxvk/meson.build | 1 + 4 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 src/dxvk/dxvk_cs.cpp create mode 100644 src/dxvk/dxvk_cs.h diff --git a/src/d3d11/d3d11_context.h b/src/d3d11/d3d11_context.h index 38037d9d..e228641b 100644 --- a/src/d3d11/d3d11_context.h +++ b/src/d3d11/d3d11_context.h @@ -1,12 +1,12 @@ #pragma once +#include "../dxvk/dxvk_adapter.h" +#include "../dxvk/dxvk_device.h" + #include "d3d11_context_state.h" #include "d3d11_device_child.h" #include "d3d11_view.h" -#include -#include - namespace dxvk { class D3D11Device; diff --git a/src/dxvk/dxvk_cs.cpp b/src/dxvk/dxvk_cs.cpp new file mode 100644 index 00000000..cefae9c6 --- /dev/null +++ b/src/dxvk/dxvk_cs.cpp @@ -0,0 +1,90 @@ +#include "dxvk_cs.h" + +namespace dxvk { + + DxvkCsChunk::DxvkCsChunk() { + + } + + + DxvkCsChunk::~DxvkCsChunk() { + for (size_t i = 0; i < m_commandCount; i++) + m_commandList[i]->~DxvkCsCmd(); + } + + + void DxvkCsChunk::executeAll(DxvkContext* ctx) { + for (size_t i = 0; i < m_commandCount; i++) { + m_commandList[i]->exec(ctx); + m_commandList[i]->~DxvkCsCmd(); + } + + m_commandCount = 0; + m_commandOffset = 0; + } + + + DxvkCsThread::DxvkCsThread(const Rc& context) + : m_context(context), + m_curChunk(new DxvkCsChunk()), + m_thread([this] { threadFunc(); }) { + + } + + + DxvkCsThread::~DxvkCsThread() { + m_stopped.store(true); + m_condOnAdd.notify_one(); + m_thread.join(); + } + + + void DxvkCsThread::dispatchChunk(Rc&& chunk) { + { std::lock_guard lock(m_mutex); + m_chunks.push(std::move(m_curChunk)); + } + + m_condOnAdd.notify_one(); + } + + + void DxvkCsThread::flush() { + dispatchChunk(std::move(m_curChunk)); + m_curChunk = new DxvkCsChunk(); + } + + + void DxvkCsThread::synchronize() { + std::unique_lock lock(m_mutex); + + m_condOnSync.wait(lock, [this] { + return m_chunks.size() == 0; + }); + } + + + void DxvkCsThread::threadFunc() { + while (!m_stopped.load()) { + Rc chunk; + + { std::unique_lock lock(m_mutex); + + m_condOnAdd.wait(lock, [this] { + return m_stopped.load() || (m_chunks.size() != 0); + }); + + if (m_chunks.size() != 0) { + chunk = std::move(m_chunks.front()); + m_chunks.pop(); + + if (m_chunks.size() == 0) + m_condOnSync.notify_one(); + } + } + + if (chunk != nullptr) + chunk->executeAll(m_context.ptr()); + } + } + +} \ No newline at end of file diff --git a/src/dxvk/dxvk_cs.h b/src/dxvk/dxvk_cs.h new file mode 100644 index 00000000..e74ddb4e --- /dev/null +++ b/src/dxvk/dxvk_cs.h @@ -0,0 +1,198 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "dxvk_context.h" + +namespace dxvk { + + /** + * \brief Command stream operation + * + * An abstract representation of an operation + * that can be recorded into a command list. + */ + class DxvkCsCmd { + + public: + + virtual ~DxvkCsCmd() { } + + /** + * \brief Executes embedded commands + * \param [in] ctx The target context + */ + virtual void exec(DxvkContext* ctx) const = 0; + + }; + + + /** + * \brief Typed command + * + * Stores a function object which is + * used to execute an embedded command. + */ + template + class alignas(16) DxvkCsTypedCmd : public DxvkCsCmd { + + public: + + DxvkCsTypedCmd(T&& cmd) + : m_command(std::move(cmd)) { } + + DxvkCsTypedCmd (DxvkCsTypedCmd&&) = delete; + DxvkCsTypedCmd& operator = (DxvkCsTypedCmd&&) = delete; + + void exec(DxvkContext* ctx) const { + m_command(ctx); + } + + private: + + T m_command; + + }; + + + /** + * \brief Command chunk + * + * Stores a list of commands. + */ + class DxvkCsChunk : public RcObject { + constexpr static size_t MaxCommands = 64; + constexpr static size_t MaxBlockSize = 64 * MaxCommands; + public: + + DxvkCsChunk(); + ~DxvkCsChunk(); + + /** + * \brief Tries to add a command to the chunk + * + * If the given command can be added to the chunk, it + * will be consumed. Otherwise, a new chunk must be + * created which is large enough to hold the command. + * \param [in] command The command to add + * \returns \c true on success, \c false if + * a new chunk needs to be allocated + */ + template + bool push(T& command) { + using FuncType = DxvkCsTypedCmd; + + if (m_commandCount >= MaxCommands + || m_commandOffset + sizeof(FuncType) > MaxBlockSize) + return false; + + m_commandList[m_commandCount] = + new (m_data + m_commandOffset) + FuncType(std::move(command)); + + m_commandCount += 1; + m_commandOffset += sizeof(FuncType); + return true; + } + + /** + * \brief Executes all commands + * + * This will also reset the chunk + * so that it can be reused. + * \param [in] ctx The context + */ + void executeAll(DxvkContext* ctx); + + private: + + size_t m_commandCount = 0; + size_t m_commandOffset = 0; + + std::array m_commandList; + + alignas(64) + char m_data[MaxBlockSize]; + + }; + + + /** + * \brief Command stream thread + * + * Spawns a thread that will execute + * commands on a DXVK context. + */ + class DxvkCsThread { + + public: + + DxvkCsThread(const Rc& context); + ~DxvkCsThread(); + + /** + * \brief Dispatches a new command + * + * Adds the command to the current chunk and + * dispatches the chunk in case it is full. + * \param [in] command The command + */ + template + void dispatch(T&& command) { + while (!m_curChunk->push(command)) + this->flush(); + } + + /** + * \brief Dispatches an entire chunk + * + * Can be used to efficiently play back large + * command lists recorded on another thread. + * \param [in] chunk The chunk to dispatch + */ + void dispatchChunk(Rc&& chunk); + + /** + * \brief Dispatches current chunk + * + * Adds the current chunk to the dispatch + * queue and makes an empty chunk current. + * Call this before \ref synchronize. + */ + void flush(); + + /** + * \brief Synchronizes with the thread + * + * This waits for all chunks in the dispatch + * queue to be processed by the thread. Note + * that this does \e not implicitly call + * \ref flush. + */ + void synchronize(); + + private: + + const Rc m_context; + + // Chunk that is being recorded + Rc m_curChunk; + + // Chunks that are executing + std::atomic m_stopped = { false }; + std::mutex m_mutex; + std::condition_variable m_condOnAdd; + std::condition_variable m_condOnSync; + std::queue> m_chunks; + + std::thread m_thread; + + void threadFunc(); + + }; + +} \ No newline at end of file diff --git a/src/dxvk/meson.build b/src/dxvk/meson.build index d91c9916..fa0c7209 100644 --- a/src/dxvk/meson.build +++ b/src/dxvk/meson.build @@ -5,6 +5,7 @@ dxvk_src = files([ 'dxvk_cmdlist.cpp', 'dxvk_compute.cpp', 'dxvk_context.cpp', + 'dxvk_cs.cpp', 'dxvk_data.cpp', 'dxvk_descriptor.cpp', 'dxvk_device.cpp',