From 68ca71d8a437ef94ba38e9b32b972a4ce95ab447 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 11 Dec 2017 19:17:08 +0100 Subject: [PATCH] [dxvk] Implemented recycling of command buffers and staging buffers --- src/dxgi/dxgi_presenter.cpp | 4 +-- src/dxvk/dxvk_device.cpp | 39 ++++++++++++++++++----- src/dxvk/dxvk_device.h | 11 +++++-- src/dxvk/dxvk_recycler.h | 63 +++++++++++++++++++++++++++++++++++++ src/dxvk/dxvk_staging.cpp | 5 +++ src/dxvk/dxvk_staging.h | 2 ++ 6 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 src/dxvk/dxvk_recycler.h diff --git a/src/dxgi/dxgi_presenter.cpp b/src/dxgi/dxgi_presenter.cpp index 0b569ae5..c6d7988a 100644 --- a/src/dxgi/dxgi_presenter.cpp +++ b/src/dxgi/dxgi_presenter.cpp @@ -22,7 +22,7 @@ namespace dxvk { // Create swap chain for the surface DxvkSwapchainProperties swapchainProperties; swapchainProperties.preferredSurfaceFormat = this->pickFormat(bufferFormat); - swapchainProperties.preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR; + swapchainProperties.preferredPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; swapchainProperties.preferredBufferSize.width = bufferWidth; swapchainProperties.preferredBufferSize.height = bufferHeight; @@ -207,7 +207,7 @@ namespace dxvk { DXGI_FORMAT bufferFormat) { DxvkSwapchainProperties swapchainProperties; swapchainProperties.preferredSurfaceFormat = this->pickFormat(bufferFormat); - swapchainProperties.preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR; + swapchainProperties.preferredPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; swapchainProperties.preferredBufferSize.width = bufferWidth; swapchainProperties.preferredBufferSize.height = bufferHeight; diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 079ad9aa..2c476547 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -33,12 +33,20 @@ namespace dxvk { Rc DxvkDevice::allocStagingBuffer(VkDeviceSize size) { - // TODO actually recycle old buffers - const VkDeviceSize baseSize = 64 * 1024 * 1024; - const VkDeviceSize bufferSize = std::max(baseSize, size); + // In case we need a standard-size staging buffer, try + // to recycle an old one that has been returned earlier + if (size <= DefaultStagingBufferSize) { + const Rc buffer + = m_recycledStagingBuffers.retrieveObject(); + + if (buffer != nullptr) + return buffer; + } + // Staging buffers only need to be able to handle transfer + // operations, and they need to be in host-visible memory. DxvkBufferCreateInfo info; - info.size = bufferSize; + info.size = size; info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; info.stages = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT; @@ -49,18 +57,32 @@ namespace dxvk { = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + // Don't create buffers that are too small. A staging + // buffer should be able to serve multiple uploads. + if (info.size < DefaultStagingBufferSize) + info.size = DefaultStagingBufferSize; + return new DxvkStagingBuffer(this->createBuffer(info, memFlags)); } void DxvkDevice::recycleStagingBuffer(const Rc& buffer) { - // TODO implement + // Drop staging buffers that are bigger than the + // standard ones to save memory, recycle the rest + if (buffer->size() == DefaultStagingBufferSize) + m_recycledStagingBuffers.returnObject(buffer); } Rc DxvkDevice::createCommandList() { - return new DxvkCommandList(m_vkd, this, - m_adapter->graphicsQueueFamily()); + Rc cmdList = m_recycledCommandLists.retrieveObject(); + + if (cmdList == nullptr) { + cmdList = new DxvkCommandList(m_vkd, + this, m_adapter->graphicsQueueFamily()); + } + + return cmdList; } @@ -176,6 +198,9 @@ namespace dxvk { // TODO Delay synchronization by putting these into a ring buffer fence->wait(std::numeric_limits::max()); commandList->reset(); + + // FIXME this must go away once the ring buffer is implemented + m_recycledCommandLists.returnObject(commandList); return fence; } diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h index d20d2bb8..d23fdf2e 100644 --- a/src/dxvk/dxvk_device.h +++ b/src/dxvk/dxvk_device.h @@ -9,6 +9,7 @@ #include "dxvk_image.h" #include "dxvk_memory.h" #include "dxvk_pipemanager.h" +#include "dxvk_recycler.h" #include "dxvk_renderpass.h" #include "dxvk_sampler.h" #include "dxvk_shader.h" @@ -28,7 +29,7 @@ namespace dxvk { * contexts. Multiple contexts can be created for a device. */ class DxvkDevice : public RcObject { - + constexpr static VkDeviceSize DefaultStagingBufferSize = 64 * 1024 * 1024; public: DxvkDevice( @@ -266,8 +267,12 @@ namespace dxvk { Rc m_renderPassPool; Rc m_pipelineManager; - VkQueue m_graphicsQueue; - VkQueue m_presentQueue; + VkQueue m_graphicsQueue = VK_NULL_HANDLE; + VkQueue m_presentQueue = VK_NULL_HANDLE; + + // TODO fine-tune buffer sizes + DxvkRecycler m_recycledCommandLists; + DxvkRecycler m_recycledStagingBuffers; }; diff --git a/src/dxvk/dxvk_recycler.h b/src/dxvk/dxvk_recycler.h new file mode 100644 index 00000000..03e80de4 --- /dev/null +++ b/src/dxvk/dxvk_recycler.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +namespace dxvk { + + /** + * \brief Object recycler + * + * Implements a thread-safe buffer that can store up to + * a given number of objects of a certain type. This way, + * DXVK can efficiently reuse and reset objects instead + * of destroying them and creating them anew. + * \tparam T Type of the objects to store + * \tparam N Maximum number of objects to store + */ + template + class DxvkRecycler { + + public: + + /** + * \brief Retrieves an object if possible + * + * Returns an object that was returned to the recycler + * earier. In case no objects are available, this will + * return \c nullptr and a new object has to be created. + * \return An object, or \c nullptr + */ + Rc retrieveObject() { + std::lock_guard lock(m_mutex); + + if (m_objectId == 0) + return nullptr; + + return m_objects.at(--m_objectId); + } + + /** + * \brief Returns an object to the recycler + * + * If the buffer is full, the object will be destroyed + * once the last reference runs out of scope. No further + * action needs to be taken in this case. + * \param [in] object The object to return + */ + void returnObject(const Rc& object) { + std::lock_guard lock(m_mutex); + + if (m_objectId < N) + m_objects.at(m_objectId++) = object; + } + + private: + + std::mutex m_mutex; + std::array, N> m_objects; + size_t m_objectId = 0; + + }; + +} \ No newline at end of file diff --git a/src/dxvk/dxvk_staging.cpp b/src/dxvk/dxvk_staging.cpp index 6308c382..e96e0a66 100644 --- a/src/dxvk/dxvk_staging.cpp +++ b/src/dxvk/dxvk_staging.cpp @@ -15,6 +15,11 @@ namespace dxvk { } + VkDeviceSize DxvkStagingBuffer::size() const { + return m_bufferSize; + } + + VkDeviceSize DxvkStagingBuffer::freeBytes() const { return m_bufferSize - m_bufferOffset; } diff --git a/src/dxvk/dxvk_staging.h b/src/dxvk/dxvk_staging.h index 5396ee0f..dae02f4e 100644 --- a/src/dxvk/dxvk_staging.h +++ b/src/dxvk/dxvk_staging.h @@ -22,6 +22,8 @@ namespace dxvk { ~DxvkStagingBuffer(); + VkDeviceSize size() const; + VkDeviceSize freeBytes() const; bool alloc(