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

[dxvk] Implement lifetime tracking for graphics pipelines

This commit is contained in:
Philip Rebohle 2022-08-08 16:37:47 +02:00 committed by Philip Rebohle
parent 764de6ff82
commit ab1d629961
7 changed files with 128 additions and 19 deletions

View File

@ -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();
}

View File

@ -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<DxvkCmdBuffer>;
/**
* \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<DxvkDescriptorPool>,
Rc<DxvkDescriptorManager>>> m_descriptorPools;
std::vector<DxvkGraphicsPipeline*> m_pipelines;
VkCommandBuffer getCmdBuffer(DxvkCmdBuffer cmdBuffer) const {
if (cmdBuffer == DxvkCmdBuffer::ExecBuffer) return m_execBuffer;
if (cmdBuffer == DxvkCmdBuffer::InitBuffer) return m_initBuffer;

View File

@ -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<VK_PIPELINE_BIND_POINT_GRAPHICS>(newPipeline->getSpecConstantMask());

View File

@ -60,7 +60,8 @@ namespace dxvk {
/**
* \brief Context feature bits
*/
enum class DxvkContextFeature {
enum class DxvkContextFeature : uint32_t {
TrackGraphicsPipeline,
FeatureCount
};

View File

@ -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<dxvk::mutex> lock(m_mutex);
std::unique_lock<dxvk::mutex> 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<dxvk::mutex> lock(m_mutex);
std::unique_lock<dxvk::mutex> 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<dxvk::mutex> lock(m_mutex);
m_useCount += 1;
}
void DxvkGraphicsPipeline::releasePipeline() {
if (!m_device->mustTrackPipelineLifetime())
return;
std::unique_lock<dxvk::mutex> 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);

View File

@ -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<DxvkGraphicsPipelineInstance> 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(

View File

@ -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;
}