diff --git a/src/dxvk/dxvk_cmdlist.cpp b/src/dxvk/dxvk_cmdlist.cpp index 10785075..adff122d 100644 --- a/src/dxvk/dxvk_cmdlist.cpp +++ b/src/dxvk/dxvk_cmdlist.cpp @@ -219,11 +219,18 @@ namespace dxvk { m_signalTracker.reset(); m_statCounters.reset(); + // Recycle descriptor pools for (const auto& descriptorPools : m_descriptorPools) descriptorPools.second->recycleDescriptorPool(descriptorPools.first); m_descriptorPools.clear(); + // Release pipelines + for (auto pipeline : m_pipelines) + pipeline->releasePipeline(); + + m_pipelines.clear(); + m_waitSemaphores.clear(); m_signalSemaphores.clear(); } diff --git a/src/dxvk/dxvk_cmdlist.h b/src/dxvk/dxvk_cmdlist.h index 1e862539..f4492a06 100644 --- a/src/dxvk/dxvk_cmdlist.h +++ b/src/dxvk/dxvk_cmdlist.h @@ -8,6 +8,7 @@ #include "dxvk_fence.h" #include "dxvk_gpu_event.h" #include "dxvk_gpu_query.h" +#include "dxvk_graphics.h" #include "dxvk_lifetime.h" #include "dxvk_limits.h" #include "dxvk_pipelayout.h" @@ -30,7 +31,7 @@ namespace dxvk { }; using DxvkCmdBufferFlags = Flags; - + /** * \brief Queue submission info * @@ -176,6 +177,15 @@ namespace dxvk { m_gpuQueryTracker.trackQuery(handle); } + /** + * \brief Tracks a graphics pipeline + * \param [in] pipeline Pipeline + */ + void trackGraphicsPipeline(DxvkGraphicsPipeline* pipeline) { + pipeline->acquirePipeline(); + m_pipelines.push_back(pipeline); + } + /** * \brief Queues signal * @@ -822,6 +832,8 @@ namespace dxvk { Rc, Rc>> m_descriptorPools; + std::vector m_pipelines; + VkCommandBuffer getCmdBuffer(DxvkCmdBuffer cmdBuffer) const { if (cmdBuffer == DxvkCmdBuffer::ExecBuffer) return m_execBuffer; if (cmdBuffer == DxvkCmdBuffer::InitBuffer) return m_initBuffer; diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index 48eb05f5..56778c04 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -49,6 +49,11 @@ namespace dxvk { if (m_device->features().extTransformFeedback.transformFeedback) m_globalRwGraphicsBarrier.access |= VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT; + + // Store the lifetime tracking bit as a context feature so + // that we don't have to scan device features at draw time + if (m_device->mustTrackPipelineLifetime()) + m_features.set(DxvkContextFeature::TrackGraphicsPipeline); } @@ -4484,6 +4489,9 @@ namespace dxvk { return false; } + if (m_features.test(DxvkContextFeature::TrackGraphicsPipeline)) + m_cmd->trackGraphicsPipeline(newPipeline); + if (unlikely(newPipeline->getSpecConstantMask() != m_state.gp.constants.mask)) this->resetSpecConstants(newPipeline->getSpecConstantMask()); diff --git a/src/dxvk/dxvk_context_state.h b/src/dxvk/dxvk_context_state.h index d0b4d563..c13d996c 100644 --- a/src/dxvk/dxvk_context_state.h +++ b/src/dxvk/dxvk_context_state.h @@ -60,7 +60,8 @@ namespace dxvk { /** * \brief Context feature bits */ - enum class DxvkContextFeature { + enum class DxvkContextFeature : uint32_t { + TrackGraphicsPipeline, FeatureCount }; diff --git a/src/dxvk/dxvk_graphics.cpp b/src/dxvk/dxvk_graphics.cpp index dbd8c7d0..c0ecbbb4 100644 --- a/src/dxvk/dxvk_graphics.cpp +++ b/src/dxvk/dxvk_graphics.cpp @@ -880,15 +880,8 @@ namespace dxvk { DxvkGraphicsPipeline::~DxvkGraphicsPipeline() { - for (const auto& instance : m_fastPipelines) { - this->destroyPipeline(instance.second); - - m_vsLibrary->releasePipelineHandle(); - m_fsLibrary->releasePipelineHandle(); - } - - for (const auto& instance : m_basePipelines) - this->destroyPipeline(instance.second); + this->destroyBasePipelines(); + this->destroyOptimizedPipelines(); } @@ -915,7 +908,7 @@ namespace dxvk { return std::make_pair(VK_NULL_HANDLE, DxvkGraphicsPipelineType::FastPipeline); // Prevent other threads from adding new instances and check again - std::lock_guard lock(m_mutex); + std::unique_lock lock(m_mutex); instance = this->findInstance(state); if (!instance) { @@ -924,6 +917,10 @@ namespace dxvk { bool canCreateBasePipeline = this->canCreateBasePipeline(state); instance = this->createInstance(state, canCreateBasePipeline); + // Unlock here since we may dispatch the pipeline to a worker, + // which will then acquire it to increment the use counter. + lock.unlock(); + // If necessary, compile an optimized pipeline variant if (!instance->fastHandle.load()) m_workers->compileGraphicsPipeline(this, state); @@ -965,7 +962,7 @@ namespace dxvk { return; // Prevent other threads from adding new instances and check again - std::lock_guard lock(m_mutex); + std::unique_lock lock(m_mutex); instance = this->findInstance(state); if (!instance) @@ -987,6 +984,45 @@ namespace dxvk { } + void DxvkGraphicsPipeline::acquirePipeline() { + if (!m_device->mustTrackPipelineLifetime()) + return; + + // We need to lock here to make sure that any ongoing pipeline + // destruction finishes before the calling thread can access the + // pipeline, and that no pipelines get destroyed afterwards. + std::unique_lock lock(m_mutex); + m_useCount += 1; + } + + + void DxvkGraphicsPipeline::releasePipeline() { + if (!m_device->mustTrackPipelineLifetime()) + return; + + std::unique_lock lock(m_mutex); + + if (!(--m_useCount)) { + // Don't destroy base pipelines if that's all we're going to + // use, since that would pretty much ruin the experience. + if (m_device->config().enableGraphicsPipelineLibrary == Tristate::True) + return; + + // Exit early if there's nothing to do + if (m_basePipelines.empty()) + return; + + // Remove any base pipeline references, but + // keep the optimized pipelines around. + for (auto& entry : m_pipelines) + entry.baseHandle.store(VK_NULL_HANDLE); + + // Destroy the actual Vulkan pipelines + this->destroyBasePipelines(); + } + } + + DxvkGraphicsPipelineInstance* DxvkGraphicsPipeline::createInstance( const DxvkGraphicsPipelineStateInfo& state, bool doCreateBasePipeline) { @@ -1220,7 +1256,27 @@ namespace dxvk { } - void DxvkGraphicsPipeline::destroyPipeline(VkPipeline pipeline) const { + void DxvkGraphicsPipeline::destroyBasePipelines() { + for (const auto& instance : m_basePipelines) { + this->destroyVulkanPipeline(instance.second); + + m_vsLibrary->releasePipelineHandle(); + m_fsLibrary->releasePipelineHandle(); + } + + m_basePipelines.clear(); + } + + + void DxvkGraphicsPipeline::destroyOptimizedPipelines() { + for (const auto& instance : m_fastPipelines) + this->destroyVulkanPipeline(instance.second); + + m_fastPipelines.clear(); + } + + + void DxvkGraphicsPipeline::destroyVulkanPipeline(VkPipeline pipeline) const { auto vk = m_device->vkd(); vk->vkDestroyPipeline(vk->device(), pipeline, nullptr); diff --git a/src/dxvk/dxvk_graphics.h b/src/dxvk/dxvk_graphics.h index 4c234f81..cfc424cd 100644 --- a/src/dxvk/dxvk_graphics.h +++ b/src/dxvk/dxvk_graphics.h @@ -535,7 +535,24 @@ namespace dxvk { */ void compilePipeline( const DxvkGraphicsPipelineStateInfo& state); - + + /** + * \brief Acquires the pipeline + * + * Increments the use count by one and prevents any Vulkan + * pipelines from being destroyed. Must be called before + * \ref getPipelineHandle or \ref compilePipeline. + */ + void acquirePipeline(); + + /** + * \brief Releases the pipeline + * + * Decrements the use count by one. If the counter reaches + * zero, any Vulkan pipeline objects may be destroyed. + */ + void releasePipeline(); + private: DxvkDevice* m_device; @@ -560,11 +577,12 @@ namespace dxvk { alignas(CACHE_LINE_SIZE) dxvk::mutex m_mutex; sync::List m_pipelines; + uint32_t m_useCount = 0; std::unordered_map< DxvkGraphicsPipelineBaseInstanceKey, VkPipeline, DxvkHash, DxvkEq> m_basePipelines; - + alignas(CACHE_LINE_SIZE) dxvk::mutex m_fastMutex; std::unordered_map< @@ -595,7 +613,11 @@ namespace dxvk { const DxvkGraphicsPipelineFastInstanceKey& key, VkPipelineCreateFlags flags) const; - void destroyPipeline( + void destroyBasePipelines(); + + void destroyOptimizedPipelines(); + + void destroyVulkanPipeline( VkPipeline pipeline) const; SpirvCodeBuffer getShaderCode( diff --git a/src/dxvk/dxvk_pipemanager.cpp b/src/dxvk/dxvk_pipemanager.cpp index 589f095f..426443d7 100644 --- a/src/dxvk/dxvk_pipemanager.cpp +++ b/src/dxvk/dxvk_pipemanager.cpp @@ -64,6 +64,7 @@ namespace dxvk { std::unique_lock lock(m_queueLock); this->startWorkers(); + pipeline->acquirePipeline(); m_pendingTasks += 1; PipelineEntry e = { }; @@ -148,10 +149,12 @@ namespace dxvk { } if (p) { - if (p->computePipeline) + if (p->computePipeline) { p->computePipeline->compilePipeline(p->computeState); - else if (p->graphicsPipeline) + } else if (p->graphicsPipeline) { p->graphicsPipeline->compilePipeline(p->graphicsState); + p->graphicsPipeline->releasePipeline(); + } m_pendingTasks -= 1; }