From d3b21741804a7747fc32a462c12bd9709424dd02 Mon Sep 17 00:00:00 2001
From: Philip Rebohle <philip.rebohle@tu-dortmund.de>
Date: Sat, 16 Dec 2017 13:21:11 +0100
Subject: [PATCH] [dxvk] Implemented buffer renaming

---
 src/d3d11/d3d11_buffer.cpp |  52 ++++++-------------
 src/dxvk/dxvk_buffer.cpp   |  23 ++++++--
 src/dxvk/dxvk_buffer.h     | 104 ++++++++++++++++++++++++++++++-------
 src/dxvk/dxvk_context.cpp  |  36 ++++++++++---
 src/dxvk/dxvk_context.h    |  19 ++++++-
 src/dxvk/dxvk_device.cpp   |  16 +++---
 src/dxvk/dxvk_device.h     |  11 ++++
 7 files changed, 189 insertions(+), 72 deletions(-)

diff --git a/src/d3d11/d3d11_buffer.cpp b/src/d3d11/d3d11_buffer.cpp
index 755a72ce..6ccfe851 100644
--- a/src/d3d11/d3d11_buffer.cpp
+++ b/src/d3d11/d3d11_buffer.cpp
@@ -78,47 +78,27 @@ namespace dxvk {
     if (pMappedSubresource == nullptr)
       return S_OK;
     
-    if (!buffer->isInUse()) {
-      // Simple case: The buffer is currently not being
-      // used by the device, we can return the pointer.
-      pMappedSubresource->pData      = buffer->mapPtr(0);
-      pMappedSubresource->RowPitch   = buffer->info().size;
-      pMappedSubresource->DepthPitch = buffer->info().size;
-      return S_OK;
-    } else {
+    if (buffer->isInUse()) {
       // Don't wait if the application tells us not to
       if (MapFlags & D3D11_MAP_FLAG_DO_NOT_WAIT)
         return DXGI_ERROR_WAS_STILL_DRAWING;
       
-      // TODO optimize this. In order to properly cover common use cases
-      // like frequent constant buffer updates, we must implement buffer
-      // renaming techniques. The current approach is inefficient as it
-      // leads to a lot of Flush() and Synchronize() calls.
-      // 
-      // Possible solution:
-      //  (1) Create buffers with a significantly larger size if they
-      //      can be mapped by the host for writing. If mapping the
-      //      buffer would stall on D3D11_MAP_WRITE_DISCARD, map the
-      //      next slice. on D3D11_MAP_WRITE_NO_OVERWRITE, return the
-      //      current slice. If the buffer is bound, update bindings.
-      //  (2) If no more slices are available, create a new buffer.
-      //      Limit the number of buffers to a small, fixed number.
-      //  (3) If no more buffers are available, flush and synchronize.
-      //  (4) When renaming the buffer internally, all active bindings
-      //      need to be updated internally as well.
-      // 
-      // In order to support deferred contexts, the immediate context
-      // must commit all changes to the initial buffer slice prior to
-      // executing a command list. When mapping on deferred contexts,
-      // the deferred context shall create local buffer objects.
-      pContext->Flush();
-      pContext->Synchronize();
-      
-      pMappedSubresource->pData      = buffer->mapPtr(0);
-      pMappedSubresource->RowPitch   = buffer->info().size;
-      pMappedSubresource->DepthPitch = buffer->info().size;
-      return S_OK;
+      // Invalidate the buffer in order to avoid synchronization
+      // if the application does not need the buffer contents to
+      // be preserved. The No Overwrite mode does not require any
+      // sort of synchronization, but should be used with care.
+      if (MapType == D3D11_MAP_WRITE_DISCARD) {
+        pContext->GetDXVKContext()->invalidateBuffer(m_buffer);
+      } else if (MapType != D3D11_MAP_WRITE_NO_OVERWRITE) {
+        pContext->Flush();
+        pContext->Synchronize();
+      }
     }
+    
+    pMappedSubresource->pData      = buffer->mapPtr(0);
+    pMappedSubresource->RowPitch   = buffer->info().size;
+    pMappedSubresource->DepthPitch = buffer->info().size;
+    return S_OK;
   }
   
   
diff --git a/src/dxvk/dxvk_buffer.cpp b/src/dxvk/dxvk_buffer.cpp
index 7736182f..9940f358 100644
--- a/src/dxvk/dxvk_buffer.cpp
+++ b/src/dxvk/dxvk_buffer.cpp
@@ -1,13 +1,14 @@
 #include "dxvk_buffer.h"
+#include "dxvk_device.h"
 
 namespace dxvk {
   
-  DxvkBuffer::DxvkBuffer(
+  DxvkBufferResource::DxvkBufferResource(
     const Rc<vk::DeviceFn>&     vkd,
     const DxvkBufferCreateInfo& createInfo,
           DxvkMemoryAllocator&  memAlloc,
           VkMemoryPropertyFlags memFlags)
-  : m_vkd(vkd), m_info(createInfo) {
+  : m_vkd(vkd) {
     
     VkBufferCreateInfo info;
     info.sType                 = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@@ -34,12 +35,28 @@ namespace dxvk {
   }
   
   
-  DxvkBuffer::~DxvkBuffer() {
+  DxvkBufferResource::~DxvkBufferResource() {
     if (m_buffer != VK_NULL_HANDLE)
       m_vkd->vkDestroyBuffer(m_vkd->device(), m_buffer, nullptr);
   }
   
   
+  DxvkBuffer::DxvkBuffer(
+          DxvkDevice*           device,
+    const DxvkBufferCreateInfo& createInfo,
+          VkMemoryPropertyFlags memoryType)
+  : m_device  (device),
+    m_info    (createInfo),
+    m_memFlags(memoryType) {
+    this->allocateResource();
+  }
+  
+  
+  void DxvkBuffer::allocateResource() {
+    m_resource = m_device->allocBufferResource(m_info, m_memFlags);
+  }
+  
+  
   DxvkBufferView::DxvkBufferView(
     const Rc<vk::DeviceFn>&         vkd,
     const Rc<DxvkBuffer>&           buffer,
diff --git a/src/dxvk/dxvk_buffer.h b/src/dxvk/dxvk_buffer.h
index 403a5fd1..0fcaafa2 100644
--- a/src/dxvk/dxvk_buffer.h
+++ b/src/dxvk/dxvk_buffer.h
@@ -47,30 +47,54 @@ namespace dxvk {
   
   
   /**
-   * \brief Buffer resource
+   * \brief Physical buffer resource
+   * 
+   * 
+   */
+  class DxvkBufferResource : public DxvkResource {
+    
+  public:
+    
+    DxvkBufferResource(
+      const Rc<vk::DeviceFn>&     vkd,
+      const DxvkBufferCreateInfo& createInfo,
+            DxvkMemoryAllocator&  memAlloc,
+            VkMemoryPropertyFlags memFlags);
+    
+    ~DxvkBufferResource();
+    
+    VkBuffer handle() const {
+      return m_buffer;
+    }
+    
+    void* mapPtr(VkDeviceSize offset) const {
+      return m_memory.mapPtr(offset);
+    }
+    
+  private:
+    
+    Rc<vk::DeviceFn>        m_vkd;
+    DxvkMemory              m_memory;
+    VkBuffer                m_buffer;
+    
+  };
+  
+  
+  /**
+   * \brief Virtual buffer resource
    * 
    * A simple buffer resource that stores linear,
    * unformatted data. Can be accessed by the host
    * if allocated on an appropriate memory type.
    */
-  class DxvkBuffer : public DxvkResource {
+  class DxvkBuffer : public RcObject {
     
   public:
     
     DxvkBuffer(
-      const Rc<vk::DeviceFn>&     vkd,
+            DxvkDevice*           device,
       const DxvkBufferCreateInfo& createInfo,
-            DxvkMemoryAllocator&  memAlloc,
-            VkMemoryPropertyFlags memFlags);
-    ~DxvkBuffer();
-    
-    /**
-     * \brief Buffer handle
-     * \returns Buffer handle
-     */
-    VkBuffer handle() const {
-      return m_buffer;
-    }
+            VkMemoryPropertyFlags memoryType);
     
     /**
      * \brief Buffer properties
@@ -80,6 +104,14 @@ namespace dxvk {
       return m_info;
     }
     
+    /**
+     * \brief Buffer handle
+     * \returns Buffer handle
+     */
+    VkBuffer handle() const {
+      return m_resource->handle();;
+    }
+    
     /**
      * \brief Map pointer
      * 
@@ -90,15 +122,49 @@ namespace dxvk {
      * \returns Pointer to mapped memory region
      */
     void* mapPtr(VkDeviceSize offset) const {
-      return m_memory.mapPtr(offset);
+      return m_resource->mapPtr(offset);
     }
     
+    /**
+     * \brief Checks whether the buffer is in use
+     * 
+     * Returns \c true if the underlying buffer resource
+     * is in use. If it is, it should not be accessed by
+     * the host for reading or writing, but reallocating
+     * the buffer is a valid strategy to overcome this.
+     * \returns \c true if the buffer is in use
+     */
+    bool isInUse() const {
+      return m_resource->isInUse();
+    }
+    
+    /**
+     * \brief Underlying buffer resource
+     * 
+     * Use this for lifetime tracking.
+     * \returns The resource object
+     */
+    Rc<DxvkResource> resource() const {
+      return m_resource;
+    }
+    
+    /**
+     * \brief Allocates new backing resource
+     * 
+     * Replaces the underlying buffer and implicitly marks
+     * any buffer views using this resource as dirty. Do
+     * not call this directly as this is called implicitly
+     * by the context's \c invalidateBuffer method.
+     */
+    void allocateResource();
+    
   private:
     
-    Rc<vk::DeviceFn>      m_vkd;
-    DxvkBufferCreateInfo  m_info;
-    DxvkMemory            m_memory;
-    VkBuffer              m_buffer = VK_NULL_HANDLE;
+    DxvkDevice*             m_device;
+    DxvkBufferCreateInfo    m_info;
+    VkMemoryPropertyFlags   m_memFlags;
+    
+    Rc<DxvkBufferResource>  m_resource;
     
   };
   
diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp
index 8ec67663..d3875feb 100644
--- a/src/dxvk/dxvk_context.cpp
+++ b/src/dxvk/dxvk_context.cpp
@@ -310,8 +310,8 @@ namespace dxvk {
       
       m_barriers.recordCommands(m_cmd);
       
-      m_cmd->trackResource(dstBuffer);
-      m_cmd->trackResource(srcBuffer);
+      m_cmd->trackResource(dstBuffer->resource());
+      m_cmd->trackResource(srcBuffer->resource());
     }
   }
   
@@ -368,6 +368,29 @@ namespace dxvk {
   }
   
   
+  void DxvkContext::invalidateBuffer(const Rc<DxvkBuffer>& buffer) {
+    // Allocate new backing resource
+    buffer->allocateResource();
+    
+    // We also need to update all bindings that the buffer
+    // may be bound to either directly or through views.
+    const VkBufferUsageFlags usage = buffer->info().usage;
+    
+    if (usage & VK_BUFFER_USAGE_INDEX_BUFFER_BIT)
+      m_flags.set(DxvkContextFlag::GpDirtyIndexBuffer);
+    
+    if (usage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
+      m_flags.set(DxvkContextFlag::GpDirtyVertexBuffers);
+    
+    if (usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
+               | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
+               | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT
+               | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT))
+      m_flags.set(DxvkContextFlag::GpDirtyResources,
+                  DxvkContextFlag::CpDirtyResources);
+  }
+  
+  
   void DxvkContext::resolveImage(
     const Rc<DxvkImage>&            dstImage,
     const VkImageSubresourceLayers& dstSubresources,
@@ -471,7 +494,7 @@ namespace dxvk {
         buffer->info().access);
       m_barriers.recordCommands(m_cmd);
       
-      m_cmd->trackResource(buffer);
+      m_cmd->trackResource(buffer->resource());
     }
   }
   
@@ -785,7 +808,6 @@ namespace dxvk {
       gpState.rsDepthBiasSlope         = m_state.rs.depthBiasSlope;
       gpState.rsViewportCount          = m_state.vp.viewportCount;
       
-      // TODO implement multisampling support properly
       gpState.msSampleCount            = m_state.om.framebuffer->sampleCount();
       gpState.msSampleMask             = m_state.ms.sampleMask;
       gpState.msEnableAlphaToCoverage  = m_state.ms.enableAlphaToCoverage;
@@ -837,6 +859,8 @@ namespace dxvk {
       
       auto layout = m_state.cp.pipeline->layout();
       
+      // TODO refcount used resources
+      
       m_cmd->bindResourceDescriptors(
         VK_PIPELINE_BIND_POINT_COMPUTE,
         layout->pipelineLayout(),
@@ -904,7 +928,7 @@ namespace dxvk {
           m_state.vi.indexBuffer.offset(),
           m_state.vi.indexType);
         m_cmd->trackResource(
-          m_state.vi.indexBuffer.buffer());
+          m_state.vi.indexBuffer.buffer()->resource());
       }
     }
   }
@@ -922,7 +946,7 @@ namespace dxvk {
         
         if (handle != VK_NULL_HANDLE) {
           m_cmd->cmdBindVertexBuffers(i, 1, &handle, &offset);
-          m_cmd->trackResource(vbo.buffer());
+          m_cmd->trackResource(vbo.buffer()->resource());
         }
       }
     }
diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h
index a66adbf4..601b1ac7 100644
--- a/src/dxvk/dxvk_context.h
+++ b/src/dxvk/dxvk_context.h
@@ -244,8 +244,23 @@ namespace dxvk {
      * \param [in] subresources Image subresources
      */
     void initImage(
-      const Rc<DxvkImage>&           image,
-      const VkImageSubresourceRange& subresources);
+      const Rc<DxvkImage>&            image,
+      const VkImageSubresourceRange&  subresources);
+    
+    /**
+     * \brief Invalidates a buffer's contents
+     * 
+     * Discards a buffer's contents by allocating a new
+     * backing resource. This allows the host to access
+     * the buffer while the GPU is still accessing the
+     * original backing resource.
+     * 
+     * \warning If the buffer is used by another context,
+     * invalidating it will result in undefined behaviour.
+     * \param [in] buffer The buffer to invalidate
+     */
+    void invalidateBuffer(
+      const Rc<DxvkBuffer>&           buffer);
     
     /**
      * \brief Resolves a multisampled image resource
diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp
index 12453412..72a69eb0 100644
--- a/src/dxvk/dxvk_device.cpp
+++ b/src/dxvk/dxvk_device.cpp
@@ -29,6 +29,14 @@ namespace dxvk {
   }
   
   
+  Rc<DxvkBufferResource> DxvkDevice::allocBufferResource(
+    const DxvkBufferCreateInfo& createInfo,
+          VkMemoryPropertyFlags memoryType) {
+    return new DxvkBufferResource(m_vkd,
+      createInfo, *m_memory, memoryType);
+  }
+  
+  
   Rc<DxvkStagingBuffer> DxvkDevice::allocStagingBuffer(VkDeviceSize size) {
     // In case we need a standard-size staging buffer, try
     // to recycle an old one that has been returned earlier
@@ -102,9 +110,7 @@ namespace dxvk {
     const DxvkBufferCreateInfo& createInfo,
           VkMemoryPropertyFlags memoryType) {
     m_statCounters.increment(DxvkStat::ResBufferCreations, 1);
-    
-    return new DxvkBuffer(m_vkd,
-      createInfo, *m_memory, memoryType);
+    return new DxvkBuffer(this, createInfo, memoryType);
   }
   
   
@@ -119,9 +125,7 @@ namespace dxvk {
     const DxvkImageCreateInfo&  createInfo,
           VkMemoryPropertyFlags memoryType) {
     m_statCounters.increment(DxvkStat::ResImageCreations, 1);
-    
-    return new DxvkImage(m_vkd,
-      createInfo, *m_memory, memoryType);
+    return new DxvkImage(m_vkd, createInfo, *m_memory, memoryType);
   }
   
   
diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h
index 27babc6e..c85c9c2b 100644
--- a/src/dxvk/dxvk_device.h
+++ b/src/dxvk/dxvk_device.h
@@ -75,6 +75,17 @@ namespace dxvk {
       return m_features;
     }
     
+    /**
+     * \brief Allocates buffer resource
+     * 
+     * \param [in] createInfo Buffer create info
+     * \param [in] memoryType Memory property flags
+     * \returns The buffer resource object
+     */
+    Rc<DxvkBufferResource> allocBufferResource(
+      const DxvkBufferCreateInfo& createInfo,
+            VkMemoryPropertyFlags memoryType);
+    
     /**
      * \brief Allocates a staging buffer
      *