1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2024-12-01 16:24:12 +01:00

[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.
This commit is contained in:
Philip Rebohle 2017-12-20 22:17:14 +01:00
parent 41d660f220
commit 518b469742
4 changed files with 292 additions and 3 deletions

View File

@ -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 <dxvk_adapter.h>
#include <dxvk_device.h>
namespace dxvk {
class D3D11Device;

90
src/dxvk/dxvk_cs.cpp Normal file
View File

@ -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<DxvkContext>& 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<DxvkCsChunk>&& chunk) {
{ std::lock_guard<std::mutex> 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<std::mutex> lock(m_mutex);
m_condOnSync.wait(lock, [this] {
return m_chunks.size() == 0;
});
}
void DxvkCsThread::threadFunc() {
while (!m_stopped.load()) {
Rc<DxvkCsChunk> chunk;
{ std::unique_lock<std::mutex> 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());
}
}
}

198
src/dxvk/dxvk_cs.h Normal file
View File

@ -0,0 +1,198 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#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<typename T>
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<typename T>
bool push(T& command) {
using FuncType = DxvkCsTypedCmd<T>;
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<DxvkCsCmd*, MaxCommands> 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<DxvkContext>& 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<typename T>
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<DxvkCsChunk>&& 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<DxvkContext> m_context;
// Chunk that is being recorded
Rc<DxvkCsChunk> m_curChunk;
// Chunks that are executing
std::atomic<bool> m_stopped = { false };
std::mutex m_mutex;
std::condition_variable m_condOnAdd;
std::condition_variable m_condOnSync;
std::queue<Rc<DxvkCsChunk>> m_chunks;
std::thread m_thread;
void threadFunc();
};
}

View File

@ -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',