diff --git a/meson.build b/meson.build index d2d1b6cd..aff9446c 100644 --- a/meson.build +++ b/meson.build @@ -90,7 +90,7 @@ else endif dxvk_version = vcs_tag( - command: ['git', 'describe', '--dirty=+'], + command: ['git', 'describe', '--dirty=-async'], input: 'version.h.in', output: 'version.h') diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index 9f9a4c25..b9e34316 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -590,7 +590,7 @@ namespace dxvk { const Rc& imageView, VkImageAspectFlags clearAspects, VkClearValue clearValue) { - this->updateFramebuffer(); + this->updateFramebuffer(false); // Prepare attachment ops DxvkColorAttachmentOps colorOp; @@ -2237,7 +2237,7 @@ namespace dxvk { VkOffset3D offset, VkExtent3D extent, VkClearValue value) { - this->updateFramebuffer(); + this->updateFramebuffer(false); // Find out if the render target view is currently bound, // so that we can avoid spilling the render pass if it is. @@ -3354,7 +3354,7 @@ namespace dxvk { // Retrieve and bind actual Vulkan pipeline handle m_gpActivePipeline = m_state.gp.pipeline != nullptr && m_state.om.framebuffer != nullptr ? m_state.gp.pipeline->getPipelineHandle(m_state.gp.state, - m_state.om.framebuffer->getRenderPass()) + m_state.om.framebuffer->getRenderPass(), this->checkAsyncCompilationCompat()) : VK_NULL_HANDLE; if (m_gpActivePipeline != VK_NULL_HANDLE) { @@ -3623,7 +3623,7 @@ namespace dxvk { } - void DxvkContext::updateFramebuffer() { + void DxvkContext::updateFramebuffer(bool isDraw) { if (m_flags.test(DxvkContextFlag::GpDirtyFramebuffer)) { m_flags.clr(DxvkContextFlag::GpDirtyFramebuffer); @@ -3642,6 +3642,11 @@ namespace dxvk { : VkComponentMapping(); } + if (isDraw) { + for (uint32_t i = 0; i < fb->numAttachments(); i++) + fb->getAttachment(i).view->setRtBindingFrameId(m_device->getCurrentFrameId()); + } + m_flags.set(DxvkContextFlag::GpDirtyPipelineState); } } @@ -3876,7 +3881,7 @@ namespace dxvk { void DxvkContext::commitGraphicsState(bool indexed) { if (m_flags.test(DxvkContextFlag::GpDirtyFramebuffer)) - this->updateFramebuffer(); + this->updateFramebuffer(true); if (!m_flags.test(DxvkContextFlag::GpRenderPassBound)) this->startRenderPass(); @@ -4124,4 +4129,13 @@ namespace dxvk { } } + + bool DxvkContext::checkAsyncCompilationCompat() { + bool fbCompat = true; + for (uint32_t i = 0; fbCompat && i < m_state.om.framebuffer->numAttachments(); i++) { + const auto& attachment = m_state.om.framebuffer->getAttachment(i); + fbCompat &= attachment.view->getRtBindingAsyncCompilationCompat(); + } + return fbCompat; + } } \ No newline at end of file diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h index 68aa5542..b264f6ee 100644 --- a/src/dxvk/dxvk_context.h +++ b/src/dxvk/dxvk_context.h @@ -1076,7 +1076,7 @@ namespace dxvk { VkDescriptorSet set, const DxvkPipelineLayout* layout); - void updateFramebuffer(); + void updateFramebuffer(bool isDraw); void updateIndexBufferBinding(); void updateVertexBufferBindings(); @@ -1102,6 +1102,8 @@ namespace dxvk { void commitGraphicsPostBarriers(); + bool checkAsyncCompilationCompat(); + void emitMemoryBarrier( VkPipelineStageFlags srcStages, VkAccessFlags srcAccess, diff --git a/src/dxvk/dxvk_framebuffer.h b/src/dxvk/dxvk_framebuffer.h index 49133845..ff80297b 100644 --- a/src/dxvk/dxvk_framebuffer.h +++ b/src/dxvk/dxvk_framebuffer.h @@ -120,8 +120,8 @@ namespace dxvk { * \brief Retrieves render pass * \returns Render pass reference */ - const DxvkRenderPass& getRenderPass() const { - return *m_renderPass; + const Rc& getRenderPass() const { + return m_renderPass; } /** @@ -221,4 +221,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/dxvk/dxvk_graphics.cpp b/src/dxvk/dxvk_graphics.cpp index d50fb209..3e2132c0 100644 --- a/src/dxvk/dxvk_graphics.cpp +++ b/src/dxvk/dxvk_graphics.cpp @@ -4,6 +4,7 @@ #include "dxvk_device.h" #include "dxvk_graphics.h" #include "dxvk_pipemanager.h" +#include "dxvk_pipecompiler.h" #include "dxvk_spec_const.h" #include "dxvk_state_cache.h" @@ -79,8 +80,6 @@ namespace dxvk { DxvkGraphicsPipeline::~DxvkGraphicsPipeline() { - for (const auto& instance : m_pipelines) - this->destroyPipeline(instance.pipeline()); } @@ -100,8 +99,9 @@ namespace dxvk { VkPipeline DxvkGraphicsPipeline::getPipelineHandle( const DxvkGraphicsPipelineStateInfo& state, - const DxvkRenderPass& renderPass) { - VkRenderPass renderPassHandle = renderPass.getDefaultHandle(); + const Rc& renderPass, + bool async) { + VkRenderPass renderPassHandle = renderPass->getDefaultHandle(); VkPipeline newPipelineHandle = VK_NULL_HANDLE; @@ -119,29 +119,49 @@ namespace dxvk { // If no pipeline instance exists with the given state // vector, create a new one and add it to the list. - newPipelineHandle = this->compilePipeline(state, renderPass, m_basePipeline); + Rc newPipeline = + new DxvkGraphicsPipelineInstance(m_pipeMgr->m_device->vkd(), + state, renderPassHandle, VK_NULL_HANDLE); + + if (async && m_pipeMgr->m_compiler != nullptr) + m_pipeMgr->m_compiler->queueCompilation(this, newPipeline, renderPass); + else + newPipelineHandle = this->compileInstance(newPipeline, renderPass); // Add new pipeline to the set - m_pipelines.emplace_back(state, renderPassHandle, newPipelineHandle); + m_pipelines.push_back(newPipeline); m_pipeMgr->m_numGraphicsPipelines += 1; - - if (!m_basePipeline && newPipelineHandle) - m_basePipeline = newPipelineHandle; } - if (newPipelineHandle != VK_NULL_HANDLE) - this->writePipelineStateToCache(state, renderPass.format()); - return newPipelineHandle; } - const DxvkGraphicsPipelineInstance* DxvkGraphicsPipeline::findInstance( + VkPipeline DxvkGraphicsPipeline::compileInstance( + const Rc& instance, + const Rc& renderPass) { + VkPipeline newPipelineHandle = this->compilePipeline( + instance->m_stateVector, *renderPass, m_basePipeline); + + if (!instance->setPipeline(newPipelineHandle)) { + m_vkd->vkDestroyPipeline(m_vkd->device(), newPipelineHandle, nullptr); + } else { + if (!m_basePipeline && newPipelineHandle) + m_basePipeline = newPipelineHandle; + if (newPipelineHandle != VK_NULL_HANDLE) + this->writePipelineStateToCache(instance->m_stateVector, renderPass->format()); + } + + return newPipelineHandle; + } + + + DxvkGraphicsPipelineInstance* DxvkGraphicsPipeline::findInstance( const DxvkGraphicsPipelineStateInfo& state, VkRenderPass renderPass) const { for (const auto& instance : m_pipelines) { - if (instance.isCompatible(state, renderPass)) - return &instance; + if (instance->isCompatible(state, renderPass)) + return instance.ptr(); } return nullptr; diff --git a/src/dxvk/dxvk_graphics.h b/src/dxvk/dxvk_graphics.h index e5686950..8811f10c 100644 --- a/src/dxvk/dxvk_graphics.h +++ b/src/dxvk/dxvk_graphics.h @@ -132,19 +132,25 @@ namespace dxvk { * Stores a state vector and the * corresponding pipeline handle. */ - class DxvkGraphicsPipelineInstance { - + class DxvkGraphicsPipelineInstance: public RcObject { + friend class DxvkGraphicsPipeline; public: DxvkGraphicsPipelineInstance() { } DxvkGraphicsPipelineInstance( + const Rc& vkd, const DxvkGraphicsPipelineStateInfo& state, VkRenderPass rp, VkPipeline pipe) - : m_stateVector (state), + : m_vkd (vkd), + m_stateVector (state), m_renderPass (rp), m_pipeline (pipe) { } + ~DxvkGraphicsPipelineInstance() { + m_vkd->vkDestroyPipeline(m_vkd->device(), m_pipeline, nullptr); + } + /** * \brief Checks for matching pipeline state * @@ -159,23 +165,38 @@ namespace dxvk { && m_renderPass == rp; } + /** + * \brief Sets the pipeline handle + * + * If a pipeline handle has already been + * set up, this method will fail and the new pipeline + * handle should be destroyed. + * \param [in] pipeline The pipeline + */ + bool setPipeline(VkPipeline pipeline) { + VkPipeline expected = VK_NULL_HANDLE; + return m_pipeline.compare_exchange_strong(expected, pipeline); + } + /** * \brief Retrieves pipeline * \returns The pipeline handle */ VkPipeline pipeline() const { - return m_pipeline; + return m_pipeline.load(); } private: + const Rc m_vkd; DxvkGraphicsPipelineStateInfo m_stateVector; VkRenderPass m_renderPass; - VkPipeline m_pipeline; + std::atomic m_pipeline; }; + /** * \brief Graphics pipeline * @@ -234,20 +255,20 @@ namespace dxvk { * state. If necessary, a new pipeline will be created. * \param [in] state Pipeline state vector * \param [in] renderPass The render pass + * \param [in] async Compile asynchronously * \returns Pipeline handle */ VkPipeline getPipelineHandle( const DxvkGraphicsPipelineStateInfo& state, - const DxvkRenderPass& renderPass); + const Rc& renderPass, + bool async); + + VkPipeline compileInstance( + const Rc& instance, + const Rc& renderPass); private: - struct PipelineStruct { - DxvkGraphicsPipelineStateInfo stateVector; - VkRenderPass renderPass; - VkPipeline pipeline; - }; - Rc m_vkd; DxvkPipelineManager* m_pipeMgr; @@ -267,13 +288,13 @@ namespace dxvk { DxvkGraphicsCommonPipelineStateInfo m_common; // List of pipeline instances, shared between threads - alignas(CACHE_LINE_SIZE) sync::Spinlock m_mutex; - std::vector m_pipelines; + alignas(CACHE_LINE_SIZE) sync::Spinlock m_mutex; + std::vector> m_pipelines; // Pipeline handles used for derivative pipelines VkPipeline m_basePipeline = VK_NULL_HANDLE; - const DxvkGraphicsPipelineInstance* findInstance( + DxvkGraphicsPipelineInstance* findInstance( const DxvkGraphicsPipelineStateInfo& state, VkRenderPass renderPass) const; @@ -302,4 +323,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/dxvk/dxvk_image.h b/src/dxvk/dxvk_image.h index f55ae42d..1bd638f0 100644 --- a/src/dxvk/dxvk_image.h +++ b/src/dxvk/dxvk_image.h @@ -435,6 +435,37 @@ namespace dxvk { return result; } + /** + * \brief Sets render target usage frame number + * + * The image view will track internally when + * it was last used as a render target. This + * info is used for async shader compilation. + * \param [in] frameId Frame number + */ + void setRtBindingFrameId(uint32_t frameId) { + if (frameId != m_rtBindingFrameId) { + if (frameId == m_rtBindingFrameId + 1) + m_rtBindingFrameCount += 1; + else + m_rtBindingFrameCount = 0; + + m_rtBindingFrameId = frameId; + } + } + + /** + * \brief Checks for async pipeline compatibility + * + * Asynchronous pipeline compilation may be enabled if the + * render target has been drawn to in the previous frames. + * \param [in] frameId Current frame ID + * \returns \c true if async compilation is supported + */ + bool getRtBindingAsyncCompilationCompat() const { + return m_rtBindingFrameCount >= 5; + } + private: Rc m_vkd; @@ -443,6 +474,9 @@ namespace dxvk { DxvkImageViewCreateInfo m_info; VkImageView m_views[ViewCount]; + uint32_t m_rtBindingFrameId = 0; + uint32_t m_rtBindingFrameCount = 0; + void createView(VkImageViewType type, uint32_t numLayers); }; diff --git a/src/dxvk/dxvk_pipecompiler.cpp b/src/dxvk/dxvk_pipecompiler.cpp new file mode 100644 index 00000000..a70c9908 --- /dev/null +++ b/src/dxvk/dxvk_pipecompiler.cpp @@ -0,0 +1,69 @@ +#include "dxvk_graphics.h" +#include "dxvk_pipecompiler.h" + +namespace dxvk { + + DxvkPipelineCompiler::DxvkPipelineCompiler() { + uint32_t sysCpuCount = dxvk::thread::hardware_concurrency(); + uint32_t threadCount = sysCpuCount > 2 ? sysCpuCount - 2 : 1; + + Logger::info(str::format( + "DxvkPipelineCompiler: Using ", + threadCount, " workers")); + + // Start the compiler threads + m_compilerThreads.resize(threadCount); + + for (uint32_t i = 0; i < threadCount; i++) { + m_compilerThreads.at(i) = dxvk::thread( + [this] { this->runCompilerThread(); }); + } + } + + + DxvkPipelineCompiler::~DxvkPipelineCompiler() { + { std::unique_lock lock(m_compilerLock); + m_compilerStop.store(true); + } + + m_compilerCond.notify_all(); + for (auto& thread : m_compilerThreads) + thread.join(); + } + + + void DxvkPipelineCompiler::queueCompilation( + const Rc& pipeline, + const Rc& instance, + const Rc& renderPass) { + std::unique_lock lock(m_compilerLock); + m_compilerQueue.push({ pipeline, instance, renderPass }); + m_compilerCond.notify_one(); + } + + + void DxvkPipelineCompiler::runCompilerThread() { + env::setThreadName("dxvk-pcompiler"); + + while (!m_compilerStop.load()) { + PipelineEntry entry; + + { std::unique_lock lock(m_compilerLock); + + m_compilerCond.wait(lock, [this] { + return m_compilerStop.load() + || m_compilerQueue.size() != 0; + }); + + if (m_compilerQueue.size() != 0) { + entry = std::move(m_compilerQueue.front()); + m_compilerQueue.pop(); + } + } + + if (entry.pipeline != nullptr && entry.instance != nullptr) + entry.pipeline->compileInstance(entry.instance, entry.renderPass); + } + } + +} diff --git a/src/dxvk/dxvk_pipecompiler.h b/src/dxvk/dxvk_pipecompiler.h new file mode 100644 index 00000000..26e983cc --- /dev/null +++ b/src/dxvk/dxvk_pipecompiler.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +#include "../util/thread.h" +#include "dxvk_include.h" + +namespace dxvk { + + class DxvkGraphicsPipeline; + class DxvkGraphicsPipelineInstance; + + /** + * \brief Pipeline compiler + * + * Asynchronous pipeline compiler + */ + class DxvkPipelineCompiler : public RcObject { + + public: + + DxvkPipelineCompiler(); + ~DxvkPipelineCompiler(); + + /** + * \brief Compiles a pipeline asynchronously + * + * This should be used to compile graphics + * pipeline instances asynchronously. + * \param [in] pipeline The pipeline object + * \param [in] instance The pipeline instance + * \param [in] renderPass + */ + void queueCompilation( + const Rc& pipeline, + const Rc& instance, + const Rc& renderPass); + + private: + + struct PipelineEntry { + Rc pipeline; + Rc instance; + Rc renderPass; + }; + + std::atomic m_compilerStop = { false }; + std::mutex m_compilerLock; + std::condition_variable m_compilerCond; + std::queue m_compilerQueue; + std::vector m_compilerThreads; + + void runCompilerThread(); + + }; + +} diff --git a/src/dxvk/dxvk_pipemanager.cpp b/src/dxvk/dxvk_pipemanager.cpp index 682db83d..2783c847 100644 --- a/src/dxvk/dxvk_pipemanager.cpp +++ b/src/dxvk/dxvk_pipemanager.cpp @@ -45,9 +45,13 @@ namespace dxvk { : m_device (device), m_cache (new DxvkPipelineCache(device->vkd())) { std::string useStateCache = env::getEnvVar("DXVK_STATE_CACHE"); + std::string useAsync = env::getEnvVar("DXVK_ASYNC"); if (useStateCache != "0" && device->config().enableStateCache) m_stateCache = new DxvkStateCache(device, this, passManager); + + if (useAsync != "0" || device->config().useAsync) + m_compiler = new DxvkPipelineCompiler(); } diff --git a/src/dxvk/dxvk_pipemanager.h b/src/dxvk/dxvk_pipemanager.h index fb62cb9a..c3b846cf 100644 --- a/src/dxvk/dxvk_pipemanager.h +++ b/src/dxvk/dxvk_pipemanager.h @@ -5,6 +5,7 @@ #include "dxvk_compute.h" #include "dxvk_graphics.h" +#include "dxvk_pipecompiler.h" namespace dxvk { @@ -139,6 +140,7 @@ namespace dxvk { const DxvkDevice* m_device; Rc m_cache; Rc m_stateCache; + Rc m_compiler; std::atomic m_numComputePipelines = { 0 }; std::atomic m_numGraphicsPipelines = { 0 }; diff --git a/src/dxvk/dxvk_state_cache.cpp b/src/dxvk/dxvk_state_cache.cpp index a9249a3a..4d296c19 100644 --- a/src/dxvk/dxvk_state_cache.cpp +++ b/src/dxvk/dxvk_state_cache.cpp @@ -263,7 +263,7 @@ namespace dxvk { const auto& entry = m_entries[e->second]; auto rp = m_passManager->getRenderPass(entry.format); - pipeline->getPipelineHandle(entry.gpState, *rp); + pipeline->getPipelineHandle(entry.gpState, rp, false); } } else { auto pipeline = m_pipeManager->createComputePipeline(item.cs); diff --git a/src/dxvk/meson.build b/src/dxvk/meson.build index 64f20998..bd11e390 100644 --- a/src/dxvk/meson.build +++ b/src/dxvk/meson.build @@ -78,6 +78,7 @@ dxvk_src = files([ 'dxvk_openvr.cpp', 'dxvk_options.cpp', 'dxvk_pipecache.cpp', + 'dxvk_pipecompiler.cpp', 'dxvk_pipelayout.cpp', 'dxvk_pipemanager.cpp', 'dxvk_queue.cpp', diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp index e1da8a48..9d6b4ee9 100644 --- a/src/dxvk/dxvk_options.cpp +++ b/src/dxvk/dxvk_options.cpp @@ -4,6 +4,7 @@ namespace dxvk { DxvkOptions::DxvkOptions(const Config& config) { enableStateCache = config.getOption ("dxvk.enableStateCache", true); + useAsync = config.getOption ("dxvk.useAsync", false); numCompilerThreads = config.getOption ("dxvk.numCompilerThreads", 0); useRawSsbo = config.getOption("dxvk.useRawSsbo", Tristate::Auto); useEarlyDiscard = config.getOption("dxvk.useEarlyDiscard", Tristate::Auto); diff --git a/src/dxvk/dxvk_options.h b/src/dxvk/dxvk_options.h index 79259875..db2f8a32 100644 --- a/src/dxvk/dxvk_options.h +++ b/src/dxvk/dxvk_options.h @@ -10,6 +10,7 @@ namespace dxvk { /// Enable state cache bool enableStateCache; + bool useAsync; /// Number of compiler threads /// when using the state cache diff --git a/dxvk.conf b/dxvk.conf index 25d27cb9..4e409075 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -159,6 +159,15 @@ # dxvk.numCompilerThreads = 0 +# Compile pipelines asynchronously if possible. This may reduce stuttering +# in some games, but may also introduce rendering issues that might become +# apparent over time. Do not report bugs with this option enabled. +# +# Supported values: True, False + +# dxvk.useAsync = False + + # Toggles raw SSBO usage. # # Uses storage buffers to implement raw and structured buffer