From 02e6a212bbb85825afe1ce9c2c3e9c5a4554954b Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 5 Jul 2022 18:01:50 +0200 Subject: [PATCH] [dxvk] Introduce new pipeline manager worker thread system --- src/dxvk/dxvk_pipemanager.cpp | 157 ++++++++++++++++++++++++++++++++++ src/dxvk/dxvk_pipemanager.h | 99 ++++++++++++++++++++- 2 files changed, 254 insertions(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_pipemanager.cpp b/src/dxvk/dxvk_pipemanager.cpp index 680c45728..9c2d96ba2 100644 --- a/src/dxvk/dxvk_pipemanager.cpp +++ b/src/dxvk/dxvk_pipemanager.cpp @@ -1,9 +1,166 @@ +#include + #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 p; + std::optional 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), diff --git a/src/dxvk/dxvk_pipemanager.h b/src/dxvk/dxvk_pipemanager.h index ba18f50ed..959259765 100644 --- a/src/dxvk/dxvk_pipemanager.h +++ b/src/dxvk/dxvk_pipemanager.h @@ -2,6 +2,7 @@ #pragma once #include +#include #include #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 m_pendingTasks = { 0ull }; + + dxvk::mutex m_queueLock; + dxvk::condition_variable m_queueCond; + + std::queue m_queuedLibraries; + std::queue m_queuedPipelines; + + uint32_t m_workerCount = 0; + bool m_workersRunning = false; + std::vector m_workers; + + void startWorkers(); + + void runWorker(); + + }; + /** * \brief Pipeline manager