[dxvk] Introduce new pipeline manager worker thread system

This commit is contained in:
Philip Rebohle 2022-07-05 18:01:50 +02:00
parent 1c573a7fd5
commit 02e6a212bb
No known key found for this signature in database
GPG Key ID: C8CC613427A31C99
2 changed files with 254 additions and 2 deletions

View File

@ -1,9 +1,166 @@
#include <optional>
#include "dxvk_device.h"
#include "dxvk_pipemanager.h"
#include "dxvk_state_cache.h"
namespace dxvk {
DxvkPipelineWorkers::DxvkPipelineWorkers(
DxvkDevice* device,
DxvkPipelineCache* cache)
: m_cache(cache) {
// Use a reasonably large number of threads for compiling, but
// leave some cores to the application to avoid excessive stutter
uint32_t numCpuCores = dxvk::thread::hardware_concurrency();
m_workerCount = ((std::max(1u, numCpuCores) - 1) * 5) / 7;
if (m_workerCount < 1) m_workerCount = 1;
if (m_workerCount > 32) m_workerCount = 32;
if (device->config().numCompilerThreads > 0)
m_workerCount = device->config().numCompilerThreads;
}
DxvkPipelineWorkers::~DxvkPipelineWorkers() {
this->stopWorkers();
}
void DxvkPipelineWorkers::compilePipelineLibrary(
DxvkShaderPipelineLibrary* library) {
std::unique_lock lock(m_queueLock);
this->startWorkers();
m_pendingTasks += 1;
PipelineLibraryEntry e = { };
e.pipelineLibrary = library;
m_queuedLibraries.push(e);
m_queueCond.notify_one();
}
void DxvkPipelineWorkers::compileComputePipeline(
DxvkComputePipeline* pipeline,
const DxvkComputePipelineStateInfo& state) {
std::unique_lock lock(m_queueLock);
this->startWorkers();
m_pendingTasks += 1;
PipelineEntry e = { };
e.computePipeline = pipeline;
e.computeState = state;
m_queuedPipelines.push(e);
m_queueCond.notify_one();
}
void DxvkPipelineWorkers::compileGraphicsPipeline(
DxvkGraphicsPipeline* pipeline,
const DxvkGraphicsPipelineStateInfo& state) {
std::unique_lock lock(m_queueLock);
this->startWorkers();
m_pendingTasks += 1;
PipelineEntry e = { };
e.graphicsPipeline = pipeline;
e.graphicsState = state;
m_queuedPipelines.push(e);
m_queueCond.notify_one();
}
bool DxvkPipelineWorkers::isBusy() const {
return m_pendingTasks.load() != 0ull;
}
void DxvkPipelineWorkers::stopWorkers() {
{ std::unique_lock lock(m_queueLock);
if (!m_workersRunning)
return;
m_workersRunning = false;
m_queueCond.notify_all();
}
for (auto& worker : m_workers)
worker.join();
m_workers.clear();
}
void DxvkPipelineWorkers::startWorkers() {
if (!m_workersRunning) {
m_workersRunning = true;
Logger::info(str::format("DXVK: Using ", m_workerCount, " compiler threads"));
m_workers.resize(m_workerCount);
for (auto& worker : m_workers) {
worker = dxvk::thread([this] { runWorker(); });
worker.set_priority(ThreadPriority::Lowest);
}
}
}
void DxvkPipelineWorkers::runWorker() {
env::setThreadName("dxvk-shader");
while (true) {
std::optional<PipelineEntry> p;
std::optional<PipelineLibraryEntry> l;
{ std::unique_lock lock(m_queueLock);
m_queueCond.wait(lock, [this] {
return !m_workersRunning
|| !m_queuedLibraries.empty()
|| !m_queuedPipelines.empty();
});
if (!m_workersRunning) {
// Skip pending work, exiting early is
// more important in this case.
break;
} else if (!m_queuedLibraries.empty()) {
l = m_queuedLibraries.front();
m_queuedLibraries.pop();
} else if (!m_queuedPipelines.empty()) {
p = m_queuedPipelines.front();
m_queuedPipelines.pop();
}
}
if (l) {
if (l->pipelineLibrary)
l->pipelineLibrary->compilePipeline(m_cache->handle());
m_pendingTasks -= 1;
}
if (p) {
if (p->computePipeline)
p->computePipeline->compilePipeline(p->computeState);
else if (p->graphicsPipeline)
p->graphicsPipeline->compilePipeline(p->graphicsState);
m_pendingTasks -= 1;
}
}
}
DxvkPipelineManager::DxvkPipelineManager(
DxvkDevice* device)
: m_device (device),

View File

@ -2,6 +2,7 @@
#pragma once
#include <mutex>
#include <queue>
#include <unordered_map>
#include "dxvk_compute.h"
@ -10,7 +11,7 @@
namespace dxvk {
class DxvkStateCache;
class DxvkDevice;
/**
* \brief Pipeline count
@ -22,7 +23,101 @@ namespace dxvk {
uint32_t numGraphicsPipelines;
uint32_t numComputePipelines;
};
/**
* \brief Pipeline manager worker threads
*
* Spawns worker threads to compile shader pipeline
* libraries and optimized pipelines asynchronously.
*/
class DxvkPipelineWorkers {
public:
DxvkPipelineWorkers(
DxvkDevice* device,
DxvkPipelineCache* cache);
~DxvkPipelineWorkers();
/**
* \brief Compiles a pipeline library
*
* Asynchronously compiles a basic variant of
* the pipeline with default compile arguments.
* Note that pipeline libraries are high priority.
* \param [in] library The pipeline library
*/
void compilePipelineLibrary(
DxvkShaderPipelineLibrary* library);
/**
* \brief Compiles an optimized compute pipeline
*
* \param [in] pipeline Compute pipeline
* \param [in] state Pipeline state
*/
void compileComputePipeline(
DxvkComputePipeline* pipeline,
const DxvkComputePipelineStateInfo& state);
/**
* \brief Compiles an optimized graphics pipeline
*
* \param [in] pipeline Compute pipeline
* \param [in] state Pipeline state
*/
void compileGraphicsPipeline(
DxvkGraphicsPipeline* pipeline,
const DxvkGraphicsPipelineStateInfo& state);
/**
* \brief Checks whether workers are busy
* \returns \c true if there is unfinished work
*/
bool isBusy() const;
/**
* \brief Stops all worker threads
*
* Stops threads and waits for their current work
* to complete. Queued work will be discarded.
*/
void stopWorkers();
private:
struct PipelineEntry {
DxvkComputePipeline* computePipeline;
DxvkGraphicsPipeline* graphicsPipeline;
DxvkComputePipelineStateInfo computeState;
DxvkGraphicsPipelineStateInfo graphicsState;
};
struct PipelineLibraryEntry {
DxvkShaderPipelineLibrary* pipelineLibrary;
};
DxvkPipelineCache* m_cache;
std::atomic<uint64_t> m_pendingTasks = { 0ull };
dxvk::mutex m_queueLock;
dxvk::condition_variable m_queueCond;
std::queue<PipelineLibraryEntry> m_queuedLibraries;
std::queue<PipelineEntry> m_queuedPipelines;
uint32_t m_workerCount = 0;
bool m_workersRunning = false;
std::vector<dxvk::thread> m_workers;
void startWorkers();
void runWorker();
};
/**
* \brief Pipeline manager