[Vulkan] Managed command buffer pool
authoradam.b <jsr184@gmail.com>
Thu, 22 Mar 2018 17:50:49 +0000 (17:50 +0000)
committerFrancisco Santos <eggzcape@gmail.com>
Thu, 29 Mar 2018 16:37:10 +0000 (12:37 -0400)
- fixed leaking command buffers
- command buffers are lazily preallocated on demand
- releasing command buffer doesn't destroy it but returns to the internal pool

Change-Id: I6532af408d6961f5a4be34de19fc50f949386919

dali/graphics/vulkan/vulkan-command-buffer.cpp
dali/graphics/vulkan/vulkan-command-buffer.h
dali/graphics/vulkan/vulkan-command-pool.cpp
dali/graphics/vulkan/vulkan-command-pool.h
dali/graphics/vulkan/vulkan-types.h

index d7ef0d1..db38136 100644 (file)
@@ -36,9 +36,11 @@ namespace Vulkan
 {
 struct CommandBuffer::Impl
 {
-  Impl( CommandPool& commandPool, const vk::CommandBufferAllocateInfo& allocateInfo, vk::CommandBuffer commandBuffer )
-  : mGraphics( commandPool.GetGraphics() ),
+  Impl( CommandBuffer& owner, CommandPool& commandPool, uint32_t poolIndex, const vk::CommandBufferAllocateInfo& allocateInfo, vk::CommandBuffer commandBuffer )
+  : mOwner( owner ),
+    mGraphics( commandPool.GetGraphics() ),
     mOwnerCommandPool( commandPool ),
+    mPoolAllocationIndex( poolIndex ),
     mAllocateInfo( allocateInfo ),
     mCommandBuffer( commandBuffer )
   {
@@ -46,11 +48,18 @@ struct CommandBuffer::Impl
 
   ~Impl()
   {
-    mResources.clear();
     mGraphics.GetDevice().freeCommandBuffers( mOwnerCommandPool.GetPool(),
                                               1, &mCommandBuffer );
   }
 
+  void ReleaseCommandBuffer()
+  {
+    mResources.clear();
+
+    // tell pool the buffer is not in use anymore
+    mOwnerCommandPool.ReleaseCommandBuffer(mOwner, false);
+  }
+
   bool Initialise()
   {
     return true;
@@ -320,9 +329,10 @@ struct CommandBuffer::Impl
          .setSubresourceRange( vk::ImageSubresourceRange{ aspectMask, 0, image->GetLevelCount(), 0, image->GetLayerCount() } );
   }
 
-
+  CommandBuffer&                mOwner;
   Graphics&                     mGraphics;
   CommandPool&                  mOwnerCommandPool;
+  uint32_t                      mPoolAllocationIndex;
   vk::CommandBufferAllocateInfo mAllocateInfo{};
 
   vk::CommandBuffer mCommandBuffer{};
@@ -350,10 +360,11 @@ struct CommandBuffer::Impl
  */
 
 CommandBuffer::CommandBuffer( CommandPool&                         commandPool,
+                              uint32_t                             poolIndex,
                               const vk::CommandBufferAllocateInfo& allocateInfo,
                               vk::CommandBuffer                    vkCommandBuffer )
 {
-  mImpl = MakeUnique<Impl>( commandPool, allocateInfo, vkCommandBuffer );
+  mImpl = MakeUnique<Impl>( *this, commandPool, poolIndex, allocateInfo, vkCommandBuffer );
 }
 
 CommandBuffer::~CommandBuffer()
@@ -384,11 +395,6 @@ void CommandBuffer::Free()
   mImpl->Free();
 }
 
-void CommandBuffer::OnRelease( uint32_t refcount )
-{
-  VkManaged::OnRelease( refcount );
-}
-
 /** Push wait semaphores */
 void CommandBuffer::PushWaitSemaphores( const std::vector<vk::Semaphore>&          semaphores,
                                         const std::vector<vk::PipelineStageFlags>& stages )
@@ -621,6 +627,17 @@ vk::ImageMemoryBarrier CommandBuffer::ImageLayoutTransitionBarrier( ImageRef ima
                                               aspectMask  );
 }
 
+uint32_t CommandBuffer::GetPoolAllocationIndex() const
+{
+  return mImpl->mPoolAllocationIndex;
+}
+
+bool CommandBuffer::OnDestroy()
+{
+  mImpl->ReleaseCommandBuffer();
+  return true;
+}
+
 } // namespace Vulkan
 } // namespace Graphics
 } // namespace Dali
index 3efd080..9deb97a 100644 (file)
@@ -35,6 +35,7 @@ class DescriptorSet;
 class CommandBuffer : public VkManaged
 {
   friend class CommandPool;
+  friend class CommandBufferPool;
 
 public:
 
@@ -215,8 +216,6 @@ public:
   void CopyBufferToImage( BufferRef srcBuffer, ImageRef dstImage, vk::ImageLayout dstLayout,
                           std::vector<vk::BufferImageCopy> regions );
 
-  void OnRelease( uint32_t refcount ) override;
-
   /**
    * Creates layout transition barrier
    * @return
@@ -241,11 +240,24 @@ public:
                                                        vk::ImageAspectFlags   aspectMask
   ) const;
 
+  /**
+   * Implements VkManaged::OnDestroy
+   * @return
+   */
+  bool OnDestroy() override;
+
 private:
 
-  // Constructor called by the CommandPool only
-  CommandBuffer( CommandPool& commandPool, const vk::CommandBufferAllocateInfo& allocateInfo, vk::CommandBuffer vkCommandBuffer );
+  /**
+   * Returns allocation index
+   * @return
+   */
+  uint32_t GetPoolAllocationIndex() const;
 
+private:
+
+  // Constructor called by the CommandPool only
+  CommandBuffer( CommandPool& commandPool, uint32_t poolIndex, const vk::CommandBufferAllocateInfo& allocateInfo, vk::CommandBuffer vkCommandBuffer );
   class Impl;
   std::unique_ptr<Impl> mImpl;
 
index 77a7ddc..a4e0cbe 100644 (file)
@@ -28,10 +28,162 @@ namespace Vulkan
 {
 
 /**
+ * CommandBufferPool contains preallocated command buffers that are
+ * reusable.
+ */
+struct CommandBufferPool
+{
+  static constexpr uint32_t INVALID_NODE_INDEX{ 0xffffffffu };
+  struct Node
+  {
+    Node( uint32_t _nextFreeIndex, CommandBuffer* _commandBuffer ) :
+      nextFreeIndex( _nextFreeIndex ), commandBuffer( _commandBuffer )
+    {
+
+    }
+    uint32_t          nextFreeIndex;
+    CommandBuffer*    commandBuffer;
+  };
+
+  CommandBufferPool( CommandPool& owner, Graphics& graphics, uint32_t initialCapacity, uint32_t defaultIncrease, bool isPrimary )
+  : mOwner( owner ),
+    mGraphics( graphics ),
+    mPoolData{},
+    mFirstFree(INVALID_NODE_INDEX),
+    mCapacity( initialCapacity ),
+    mAllocationCount( 0u ),
+    mDefaultIncrease( defaultIncrease ),
+    mIsPrimary( isPrimary )
+  {
+    // don't allocate anything if initial capacity is 0
+    if(initialCapacity)
+    {
+      Resize(initialCapacity);
+    }
+  }
+
+  ~CommandBufferPool()
+  {
+    // free all buffers here
+    for( auto&& cmd : mPoolData )
+    {
+      delete cmd.commandBuffer;
+    }
+  }
+
+  /**
+   * Creates new batch of command buffers
+   * @param allocateInfo
+   * @return
+   */
+  std::vector<vk::CommandBuffer> AllocateVkCommandBuffers( vk::CommandBufferAllocateInfo allocateInfo )
+  {
+    return VkAssert( mGraphics.GetDevice().allocateCommandBuffers( allocateInfo ) );
+  }
+
+  /**
+   * Resizes command pool to the new capacity. Pool may only grow
+   * @param newCapacity
+   */
+  void Resize( uint32_t newCapacity )
+  {
+    if( newCapacity <= mPoolData.size() )
+    {
+      return;
+    }
+
+    auto diff = newCapacity - mPoolData.size();
+
+    auto allocateInfo = vk::CommandBufferAllocateInfo{}
+      .setCommandBufferCount( U32(diff) )
+      .setCommandPool( mOwner.GetPool() )
+      .setLevel( mIsPrimary ? vk::CommandBufferLevel::ePrimary : vk::CommandBufferLevel::eSecondary );
+    auto newBuffers = AllocateVkCommandBuffers( allocateInfo );
+
+    uint32_t i = U32(mPoolData.size() + 1);
+
+    mFirstFree = U32(mPoolData.size());
+    if(!mPoolData.empty())
+    {
+      mPoolData.back()
+               .nextFreeIndex = U32(mPoolData.size());
+    }
+    for( auto&& cmdbuf : newBuffers )
+    {
+      auto commandBuffer = new CommandBuffer( mOwner, i-1, allocateInfo, cmdbuf);
+      mPoolData.emplace_back( i, commandBuffer );
+      ++i;
+    }
+    mPoolData.back().nextFreeIndex = INVALID_NODE_INDEX;
+    mCapacity = U32(mPoolData.size());
+  }
+
+  /**
+   * Allocates new command buffer
+   * @return
+   */
+  CommandBufferRef AllocateCommandBuffer( bool reset )
+  {
+    // resize if no more nodes
+    if( mFirstFree == INVALID_NODE_INDEX )
+    {
+      Resize( U32(mPoolData.size() + mDefaultIncrease) );
+    }
+
+    auto& node = mPoolData[mFirstFree];
+    mFirstFree = node.nextFreeIndex;
+
+    if( reset )
+    {
+      node.commandBuffer->Reset();
+    }
+
+    ++mAllocationCount;
+    return CommandBufferRef(node.commandBuffer);
+  }
+
+  /**
+   * Releases command buffer back to the pool
+   * @param reset if true, Resets command buffer
+   * @param ref
+   */
+  void ReleaseCommandBuffer( CommandBuffer& buffer, bool reset = false )
+  {
+    auto indexInPool = buffer.GetPoolAllocationIndex();
+    mPoolData[indexInPool].nextFreeIndex = mFirstFree;
+    mFirstFree = indexInPool;
+
+    if( reset )
+    {
+      buffer.Reset();
+    }
+    --mAllocationCount;
+  }
+
+  uint32_t GetCapacity() const
+  {
+    return mCapacity;
+  }
+
+  uint32_t GetAllocationCount() const
+  {
+    return mAllocationCount;
+  }
+
+  CommandPool&                  mOwner;
+  Graphics&                     mGraphics;
+  std::vector<Node>             mPoolData;
+  uint32_t                      mFirstFree;
+  uint32_t                      mCapacity;
+  uint32_t                      mAllocationCount;
+  uint32_t                      mDefaultIncrease;
+  bool                          mIsPrimary;
+};
+
+/**
  *
  * Class: CommandPool::Impl
  */
-
 struct CommandPool::Impl
 {
   Impl( Graphics& graphics, CommandPool& interface, const vk::CommandPoolCreateInfo& createInfo )
@@ -53,40 +205,33 @@ struct CommandPool::Impl
   bool Initialise()
   {
     mCommandPool = VkAssert(mGraphics.GetDevice().createCommandPool(mCreateInfo, mGraphics.GetAllocator()));
+    mInternalPoolPrimary = std::make_unique<CommandBufferPool>( mInterface, mGraphics, 0, 32, true );
+    mInternalPoolSecondary = std::make_unique<CommandBufferPool>( mInterface, mGraphics, 0, 32, false );
     return true;
   }
 
-
   void Reset( bool releaseResources )
   {
     mGraphics.GetDevice().resetCommandPool( mCommandPool, releaseResources ? vk::CommandPoolResetFlagBits::eReleaseResources : vk::CommandPoolResetFlags{} );
-    mAllocatedCommandBuffers.clear();
   }
 
   CommandBufferRef NewCommandBuffer( const vk::CommandBufferAllocateInfo& allocateInfo )
   {
     vk::CommandBufferAllocateInfo info( allocateInfo );
-    info.setCommandPool( mCommandPool );
-    info.setCommandBufferCount(1);
-    auto retval = VkAssert( mGraphics.GetDevice().allocateCommandBuffers( info ) );
-    mAllocatedCommandBuffers.emplace_back( new CommandBuffer( mInterface, info, retval[0]) );
-    return mAllocatedCommandBuffers.back();
+    auto& usedPool = allocateInfo.level == vk::CommandBufferLevel::ePrimary ? *mInternalPoolPrimary.get() : *mInternalPoolSecondary.get();
+    auto retval = usedPool.AllocateCommandBuffer( false );
+    return retval;
   }
 
-  bool ReleaseCommandBuffer( const CommandBufferRef& buffer, bool forceRelease )
+  bool ReleaseCommandBuffer( CommandBuffer& buffer, bool forceRelease )
   {
-    if( buffer.GetRefCount() == 2 )
+    if(buffer.IsPrimary())
+    {
+      mInternalPoolPrimary->ReleaseCommandBuffer( buffer );
+    }
+    else
     {
-      for(auto&& cmdBuf : mAllocatedCommandBuffers )
-      {
-        if(cmdBuf == buffer )
-        {
-          // fixme: should remove from list but in future the cache of command buffer will work
-          // different
-          cmdBuf.Reset();
-          return true;
-        }
-      }
+      mInternalPoolSecondary->ReleaseCommandBuffer( buffer );
     }
     return false;
   }
@@ -96,7 +241,9 @@ struct CommandPool::Impl
   vk::CommandPoolCreateInfo mCreateInfo;
   vk::CommandPool mCommandPool;
 
-  std::vector<CommandBufferRef> mAllocatedCommandBuffers;
+  // Pools are lazily allocated, depends on the requested command buffers
+  std::unique_ptr<CommandBufferPool> mInternalPoolPrimary;
+  std::unique_ptr<CommandBufferPool> mInternalPoolSecondary;
 };
 
 /**
@@ -161,11 +308,30 @@ void CommandPool::Reset( bool releaseResources )
   mImpl->Reset( releaseResources );
 }
 
-bool CommandPool::ReleaseCommandBuffer( CommandBufferRef buffer, bool forceRelease )
+bool CommandPool::ReleaseCommandBuffer( CommandBuffer& buffer, bool forceRelease )
 {
   return mImpl->ReleaseCommandBuffer( buffer, forceRelease );
 }
 
+uint32_t CommandPool::GetCapacity() const
+{
+  return mImpl->mInternalPoolPrimary->GetCapacity()+
+         mImpl->mInternalPoolSecondary->GetCapacity();
+}
+
+uint32_t CommandPool::GetAllocationCount() const
+{
+  return mImpl->mInternalPoolPrimary->GetAllocationCount()+
+         mImpl->mInternalPoolSecondary->GetAllocationCount();
+}
+
+uint32_t CommandPool::GetAllocationCount( vk::CommandBufferLevel level ) const
+{
+  return level == vk::CommandBufferLevel::ePrimary ?
+         mImpl->mInternalPoolPrimary->GetAllocationCount() :
+         mImpl->mInternalPoolSecondary->GetAllocationCount();
+}
+
 } // namespace Vulkan
 } // namespace Graphics
 } // namespace Dali
index 787bc77..dab0419 100644 (file)
@@ -91,7 +91,26 @@ public:
    * @param buffer
    * @return
    */
-  bool ReleaseCommandBuffer( CommandBufferRef buffer, bool forceRelease );
+  bool ReleaseCommandBuffer( CommandBuffer& buffer, bool forceRelease );
+
+  /**
+   * Returns current pool capacity ( 0 if nothing allocated )
+   * @return
+   */
+  uint32_t GetCapacity() const;
+
+  /**
+   * Returns number of allocated command buffers
+   * @return
+   */
+  uint32_t GetAllocationCount() const;
+
+  /**
+   * Returns number of allocated command buffers by level
+   * @param level
+   * @return
+   */
+  uint32_t GetAllocationCount( vk::CommandBufferLevel level ) const;
 
 public:
 
index ed085fa..6e7c1a4 100644 (file)
@@ -81,32 +81,6 @@ inline uint32_t U32(T value)
   return static_cast< uint32_t >(value);
 }
 
-class Resource
-{
-public:
-  Resource() : mUserCount{0u} {}
-  virtual ~Resource() = default;
-
-  void IncreaseUserCount()
-  {
-    ++mUserCount;
-  }
-
-  void DecreaseUserCount()
-  {
-    --mUserCount;
-  }
-
-  uint32_t GetUserCount() const
-  {
-    return mUserCount;
-  }
-
-private:
-
-  std::atomic<uint32_t> mUserCount;
-};
-
 /**
  * Vulkan object handle
  * @tparam T