2017-12-20 22:17:14 +01:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
#include <condition_variable>
|
|
|
|
#include <mutex>
|
|
|
|
#include <queue>
|
|
|
|
|
2018-07-16 18:45:54 +02:00
|
|
|
#include "../util/thread.h"
|
2017-12-20 22:17:14 +01:00
|
|
|
#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() { }
|
|
|
|
|
2018-03-02 10:31:08 +01:00
|
|
|
/**
|
|
|
|
* \brief Retrieves next command in a command chain
|
|
|
|
*
|
|
|
|
* This can be used to quickly iterate
|
|
|
|
* over commands within a chunk.
|
|
|
|
* \returns Pointer the next command
|
|
|
|
*/
|
|
|
|
DxvkCsCmd* next() const {
|
|
|
|
return m_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Sets next command in a command chain
|
|
|
|
* \param [in] next Next command
|
|
|
|
*/
|
|
|
|
void setNext(DxvkCsCmd* next) {
|
|
|
|
m_next = next;
|
|
|
|
}
|
|
|
|
|
2017-12-20 22:17:14 +01:00
|
|
|
/**
|
|
|
|
* \brief Executes embedded commands
|
|
|
|
* \param [in] ctx The target context
|
|
|
|
*/
|
|
|
|
virtual void exec(DxvkContext* ctx) const = 0;
|
|
|
|
|
2018-03-02 10:31:08 +01:00
|
|
|
private:
|
|
|
|
|
|
|
|
DxvkCsCmd* m_next = nullptr;
|
|
|
|
|
2017-12-20 22:17:14 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \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 {
|
2018-03-09 12:31:35 +01:00
|
|
|
constexpr static size_t MaxBlockSize = 16384;
|
2017-12-20 22:17:14 +01:00
|
|
|
public:
|
|
|
|
|
|
|
|
DxvkCsChunk();
|
|
|
|
~DxvkCsChunk();
|
|
|
|
|
2018-01-20 22:28:15 +01:00
|
|
|
/**
|
|
|
|
* \brief Number of commands recorded to the chunk
|
|
|
|
*
|
|
|
|
* Can be used to check whether the chunk needs to
|
|
|
|
* be dispatched or just to keep track of statistics.
|
|
|
|
*/
|
|
|
|
size_t commandCount() const {
|
|
|
|
return m_commandCount;
|
|
|
|
}
|
|
|
|
|
2017-12-20 22:17:14 +01:00
|
|
|
/**
|
|
|
|
* \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>;
|
|
|
|
|
2018-03-02 10:31:08 +01:00
|
|
|
if (m_commandOffset + sizeof(FuncType) > MaxBlockSize)
|
2017-12-20 22:17:14 +01:00
|
|
|
return false;
|
|
|
|
|
2018-03-02 10:31:08 +01:00
|
|
|
DxvkCsCmd* tail = m_tail;
|
|
|
|
|
|
|
|
m_tail = new (m_data + m_commandOffset)
|
|
|
|
FuncType(std::move(command));
|
|
|
|
|
|
|
|
if (tail != nullptr)
|
|
|
|
tail->setNext(m_tail);
|
|
|
|
else
|
|
|
|
m_head = m_tail;
|
2017-12-20 22:17:14 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2018-03-02 10:31:08 +01:00
|
|
|
DxvkCsCmd* m_head = nullptr;
|
|
|
|
DxvkCsCmd* m_tail = nullptr;
|
2017-12-20 22:17:14 +01:00
|
|
|
|
|
|
|
alignas(64)
|
|
|
|
char m_data[MaxBlockSize];
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Command stream thread
|
|
|
|
*
|
|
|
|
* Spawns a thread that will execute
|
|
|
|
* commands on a DXVK context.
|
|
|
|
*/
|
|
|
|
class DxvkCsThread {
|
2018-04-03 20:06:25 +02:00
|
|
|
|
2017-12-20 22:17:14 +01:00
|
|
|
public:
|
|
|
|
|
|
|
|
DxvkCsThread(const Rc<DxvkContext>& context);
|
|
|
|
~DxvkCsThread();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \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
|
|
|
|
*/
|
2018-01-23 12:03:26 +01:00
|
|
|
void dispatchChunk(Rc<DxvkCsChunk>&& chunk);
|
2017-12-20 22:17:14 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* \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;
|
|
|
|
|
|
|
|
std::atomic<bool> m_stopped = { false };
|
|
|
|
std::mutex m_mutex;
|
|
|
|
std::condition_variable m_condOnAdd;
|
|
|
|
std::condition_variable m_condOnSync;
|
2018-01-21 12:59:43 +01:00
|
|
|
std::queue<Rc<DxvkCsChunk>> m_chunksQueued;
|
2018-07-16 18:45:54 +02:00
|
|
|
dxvk::thread m_thread;
|
2017-12-20 22:17:14 +01:00
|
|
|
|
2018-01-20 23:12:03 +01:00
|
|
|
uint32_t m_chunksPending = 0;
|
|
|
|
|
2017-12-20 22:17:14 +01:00
|
|
|
void threadFunc();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2018-07-16 18:45:54 +02:00
|
|
|
}
|