From e433c01ad4db60da965d0300a4158b4d0124809e Mon Sep 17 00:00:00 2001
From: Philip Rebohle <philip.rebohle@tu-dortmund.de>
Date: Sun, 15 Oct 2017 17:56:06 +0200
Subject: [PATCH] [dxvk] Some more work on shader resources and resource
 bindings

---
 src/dxvk/dxvk_barrier.cpp                 |  70 +++++++++++
 src/dxvk/dxvk_barrier.h                   |  54 +++++++++
 src/dxvk/dxvk_buffer.h                    |  60 ++++++++++
 src/dxvk/dxvk_cmdlist.cpp                 |  18 +++
 src/dxvk/dxvk_cmdlist.h                   |  11 ++
 src/dxvk/dxvk_compute.cpp                 |   2 +
 src/dxvk/dxvk_context.cpp                 | 135 +++++++++++++++-------
 src/dxvk/dxvk_context.h                   |  75 ++++++++----
 src/dxvk/dxvk_context_state.h             |   7 +-
 src/dxvk/dxvk_device.cpp                  |  19 +--
 src/dxvk/dxvk_device.h                    |   7 +-
 src/dxvk/dxvk_graphics.cpp                |  10 +-
 src/dxvk/dxvk_graphics.h                  |  12 +-
 src/dxvk/dxvk_memory.h                    |   2 +-
 src/dxvk/dxvk_pipemgr.cpp                 |   7 +-
 src/dxvk/dxvk_pipemgr.h                   |   1 +
 src/dxvk/dxvk_recorder.h                  |  11 ++
 src/dxvk/dxvk_renderpass.h                |   2 +-
 src/dxvk/dxvk_shader.h                    |  14 +++
 src/dxvk/dxvk_util.cpp                    |  23 ++++
 src/dxvk/dxvk_util.h                      |  16 +++
 src/dxvk/meson.build                      |   2 +
 src/dxvk/spirv/dxvk_spirv_code_buffer.cpp |  25 ++--
 src/dxvk/spirv/dxvk_spirv_code_buffer.h   |   7 +-
 tests/dxvk/test_dxvk_triangle.cpp         |  47 +++++++-
 25 files changed, 532 insertions(+), 105 deletions(-)
 create mode 100644 src/dxvk/dxvk_barrier.cpp
 create mode 100644 src/dxvk/dxvk_barrier.h
 create mode 100644 src/dxvk/dxvk_util.cpp
 create mode 100644 src/dxvk/dxvk_util.h

diff --git a/src/dxvk/dxvk_barrier.cpp b/src/dxvk/dxvk_barrier.cpp
new file mode 100644
index 000000000..2977bc4bf
--- /dev/null
+++ b/src/dxvk/dxvk_barrier.cpp
@@ -0,0 +1,70 @@
+#include "dxvk_barrier.h"
+
+namespace dxvk {
+  
+  DxvkBarrierSet:: DxvkBarrierSet() { }
+  DxvkBarrierSet::~DxvkBarrierSet() { }
+  
+  bool DxvkBarrierSet::hasBarriers() const {
+    return (m_srcFlags | m_dstFlags) != 0;
+  }
+  
+  
+  void DxvkBarrierSet::addMemoryBarrier(
+          VkPipelineStageFlags    srcFlags,
+          VkPipelineStageFlags    dstFlags,
+    const VkMemoryBarrier&        barrier) {
+    m_srcFlags |= srcFlags;
+    m_dstFlags |= dstFlags;
+    m_memory.push_back(barrier);
+  }
+  
+  
+  void DxvkBarrierSet::addBufferBarrier(
+          VkPipelineStageFlags    srcFlags,
+          VkPipelineStageFlags    dstFlags,
+    const VkBufferMemoryBarrier&  barrier) {
+    m_srcFlags |= srcFlags;
+    m_dstFlags |= dstFlags;
+    m_buffer.push_back(barrier);
+  }
+  
+  
+  void DxvkBarrierSet::addImageBarrier(
+          VkPipelineStageFlags    srcFlags,
+          VkPipelineStageFlags    dstFlags,
+    const VkImageMemoryBarrier&   barrier) {
+    m_srcFlags |= srcFlags;
+    m_dstFlags |= dstFlags;
+    m_image.push_back(barrier);
+  }
+  
+  
+  void DxvkBarrierSet::recordCommands(
+          DxvkRecorder&           recorder) {
+    VkPipelineStageFlags srcFlags = m_srcFlags;
+    VkPipelineStageFlags dstFlags = m_dstFlags;
+    
+    if (srcFlags == 0) srcFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+    if (dstFlags == 0) dstFlags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    
+    recorder.cmdPipelineBarrier(
+      srcFlags, dstFlags, 0,
+      m_memory.size(), m_memory.data(),
+      m_buffer.size(), m_buffer.data(),
+      m_image.size(),  m_image.data());
+    
+    this->reset();
+  }
+  
+  
+  void DxvkBarrierSet::reset() {
+    m_srcFlags = 0;
+    m_dstFlags = 0;
+    
+    m_memory.resize(0);
+    m_buffer.resize(0);
+    m_image .resize(0);
+  }
+  
+}
\ No newline at end of file
diff --git a/src/dxvk/dxvk_barrier.h b/src/dxvk/dxvk_barrier.h
new file mode 100644
index 000000000..5604a5e73
--- /dev/null
+++ b/src/dxvk/dxvk_barrier.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "dxvk_recorder.h"
+
+namespace dxvk {
+  
+  /**
+   * \brief Barrier set
+   * 
+   * Accumulates memory barriers and provides a
+   * method to record all those barriers into a
+   * command buffer at once.
+   */
+  class DxvkBarrierSet {
+    
+  public:
+    
+    DxvkBarrierSet();
+    ~DxvkBarrierSet();
+    
+    bool hasBarriers() const;
+    
+    void addMemoryBarrier(
+            VkPipelineStageFlags    srcFlags,
+            VkPipelineStageFlags    dstFlags,
+      const VkMemoryBarrier&        barrier);
+    
+    void addBufferBarrier(
+            VkPipelineStageFlags    srcFlags,
+            VkPipelineStageFlags    dstFlags,
+      const VkBufferMemoryBarrier&  barrier);
+    
+    void addImageBarrier(
+            VkPipelineStageFlags    srcFlags,
+            VkPipelineStageFlags    dstFlags,
+      const VkImageMemoryBarrier&   barrier);
+    
+    void recordCommands(
+            DxvkRecorder&           recorder);
+    
+    void reset();
+    
+  private:
+    
+    VkPipelineStageFlags m_srcFlags = 0;
+    VkPipelineStageFlags m_dstFlags = 0;
+    
+    std::vector<VkMemoryBarrier>        m_memory;
+    std::vector<VkBufferMemoryBarrier>  m_buffer;
+    std::vector<VkImageMemoryBarrier>   m_image;
+    
+  };
+  
+}
\ No newline at end of file
diff --git a/src/dxvk/dxvk_buffer.h b/src/dxvk/dxvk_buffer.h
index 6c9936105..0705d9050 100644
--- a/src/dxvk/dxvk_buffer.h
+++ b/src/dxvk/dxvk_buffer.h
@@ -19,6 +19,13 @@ namespace dxvk {
     /// Buffer usage flags
     VkBufferUsageFlags usage;
     
+    /// Pipeline stages that can access
+    /// the contents of the buffer.
+    VkPipelineStageFlags stages;
+    
+    /// Allowed access patterns
+    VkAccessFlags access;
+    
   };
   
   
@@ -77,4 +84,57 @@ namespace dxvk {
     
   };
   
+  
+  /**
+   * \brief Buffer binding
+   * 
+   * Stores the buffer and the sub-range of the buffer
+   * to bind. Bindings are considered equal if all three
+   * parameters are the same.
+   */
+  class DxvkBufferBinding {
+    
+  public:
+    
+    DxvkBufferBinding() { }
+    DxvkBufferBinding(
+      const Rc<DxvkBuffer>& buffer,
+            VkDeviceSize    rangeOffset,
+            VkDeviceSize    rangeLength)
+    : m_buffer(buffer),
+      m_offset(rangeOffset),
+      m_length(rangeLength) { }
+    
+    Rc<DxvkResource> resource() const {
+      return m_buffer;
+    }
+    
+    VkDescriptorBufferInfo descriptorInfo() const {
+      VkDescriptorBufferInfo info;
+      info.buffer = m_buffer->handle();
+      info.offset = m_offset;
+      info.range  = m_length;
+      return info;
+    }
+    
+    bool operator == (const DxvkBufferBinding& other) const {
+      return this->m_buffer == other.m_buffer
+          && this->m_offset == other.m_offset
+          && this->m_length == other.m_length;
+    }
+    
+    bool operator != (const DxvkBufferBinding& other) const {
+      return this->m_buffer != other.m_buffer
+          || this->m_offset != other.m_offset
+          || this->m_length != other.m_length;
+    }
+    
+  private:
+    
+    Rc<DxvkBuffer> m_buffer = nullptr;
+    VkDeviceSize   m_offset = 0;
+    VkDeviceSize   m_length = 0;
+    
+  };
+  
 }
\ No newline at end of file
diff --git a/src/dxvk/dxvk_cmdlist.cpp b/src/dxvk/dxvk_cmdlist.cpp
index 389e2c1e2..34bf1d9a1 100644
--- a/src/dxvk/dxvk_cmdlist.cpp
+++ b/src/dxvk/dxvk_cmdlist.cpp
@@ -138,4 +138,22 @@ namespace dxvk {
     m_vkd->vkCmdEndRenderPass(m_buffer);
   }
   
+  
+  void DxvkCommandList::cmdPipelineBarrier(
+          VkPipelineStageFlags    srcStageMask,
+          VkPipelineStageFlags    dstStageMask,
+          VkDependencyFlags       dependencyFlags,
+          uint32_t                memoryBarrierCount,
+    const VkMemoryBarrier*        pMemoryBarriers,
+          uint32_t                bufferMemoryBarrierCount,
+    const VkBufferMemoryBarrier*  pBufferMemoryBarriers,
+          uint32_t                imageMemoryBarrierCount,
+    const VkImageMemoryBarrier*   pImageMemoryBarriers) {
+    m_vkd->vkCmdPipelineBarrier(m_buffer,
+      dstStageMask, srcStageMask, dependencyFlags,
+      memoryBarrierCount, pMemoryBarriers,
+      bufferMemoryBarrierCount, pBufferMemoryBarriers,
+      imageMemoryBarrierCount, pImageMemoryBarriers);
+  }
+  
 }
\ No newline at end of file
diff --git a/src/dxvk/dxvk_cmdlist.h b/src/dxvk/dxvk_cmdlist.h
index c11b439c4..782d0a887 100644
--- a/src/dxvk/dxvk_cmdlist.h
+++ b/src/dxvk/dxvk_cmdlist.h
@@ -104,6 +104,17 @@ namespace dxvk {
     
     void cmdEndRenderPass() final;
     
+    void cmdPipelineBarrier(
+            VkPipelineStageFlags    srcStageMask,
+            VkPipelineStageFlags    dstStageMask,
+            VkDependencyFlags       dependencyFlags,
+            uint32_t                memoryBarrierCount,
+      const VkMemoryBarrier*        pMemoryBarriers,
+            uint32_t                bufferMemoryBarrierCount,
+      const VkBufferMemoryBarrier*  pBufferMemoryBarriers,
+            uint32_t                imageMemoryBarrierCount,
+      const VkImageMemoryBarrier*   pImageMemoryBarriers) final;
+    
   private:
     
     Rc<vk::DeviceFn>    m_vkd;
diff --git a/src/dxvk/dxvk_compute.cpp b/src/dxvk/dxvk_compute.cpp
index c9808b8f1..b0bdb93fa 100644
--- a/src/dxvk/dxvk_compute.cpp
+++ b/src/dxvk/dxvk_compute.cpp
@@ -6,6 +6,7 @@ namespace dxvk {
     const Rc<vk::DeviceFn>& vkd,
     const Rc<DxvkShader>&   shader)
   : m_vkd(vkd) {
+    TRACE(this, shader);
     
     std::vector<VkDescriptorSetLayoutBinding> bindings;
     
@@ -80,6 +81,7 @@ namespace dxvk {
   
   
   DxvkComputePipeline::~DxvkComputePipeline() {
+    TRACE(this);
     this->destroyObjects();
   }
   
diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp
index 93ed7f91b..0cf9562f4 100644
--- a/src/dxvk/dxvk_context.cpp
+++ b/src/dxvk/dxvk_context.cpp
@@ -4,8 +4,11 @@
 
 namespace dxvk {
   
-  DxvkContext::DxvkContext(const Rc<DxvkDevice>& device)
-  : m_device(device) {
+  DxvkContext::DxvkContext(
+    const Rc<DxvkDevice>&           device,
+    const Rc<DxvkPipelineManager>&  pipeMgr)
+  : m_device  (device),
+    m_pipeMgr (pipeMgr) {
     TRACE(this, device);
   }
   
@@ -53,6 +56,57 @@ namespace dxvk {
   }
   
   
+  void DxvkContext::bindFramebuffer(
+    const Rc<DxvkFramebuffer>& fb) {
+    TRACE(this, fb);
+    
+    if (m_state.g.fb != fb) {
+      m_state.g.fb = fb;
+      
+      if (m_state.g.flags.test(
+          DxvkGraphicsPipelineBit::RenderPassBound))
+        this->endRenderPass();
+    }
+  }
+  
+  
+  void DxvkContext::bindShader(
+          VkShaderStageFlagBits stage,
+    const Rc<DxvkShader>&       shader) {
+    TRACE(this, stage, shader);
+    
+    DxvkShaderState* state = this->getShaderState(stage);
+    
+    if (state->shader != shader) {
+      state->shader = shader;
+      this->setPipelineDirty(stage);
+    }
+  }
+  
+  
+  void DxvkContext::bindStorageBuffer(
+          VkShaderStageFlagBits stage,
+          uint32_t              slot,
+    const Rc<DxvkBuffer>&       buffer,
+          VkDeviceSize          offset,
+          VkDeviceSize          length) {
+    TRACE(this, stage, slot);
+    
+    DxvkBufferBinding binding(buffer, offset, length);
+    DxvkShaderState* state = this->getShaderState(stage);
+    
+    // TODO investigate whether it is worth checking whether
+    // the shader actually uses the resource. However, if the
+    // application is not completely retarded, always setting
+    // the 'resources dirty' flag should be the best option.
+    if (state->boundStorageBuffers.at(slot) != binding) {
+      state->boundStorageBuffers.at(slot) = binding;
+      this->setResourcesDirty(stage);
+      m_cmd->trackResource(binding.resource());
+    }
+  }
+  
+  
   void DxvkContext::clearRenderTarget(
     const VkClearAttachment&  attachment,
     const VkClearRect&        clearArea) {
@@ -67,6 +121,8 @@ namespace dxvk {
           uint32_t wgCountX,
           uint32_t wgCountY,
           uint32_t wgCountZ) {
+    TRACE(this, wgCountX, wgCountY, wgCountZ);
+    this->endRenderPass();
     this->flushComputeState();
     
     m_cmd->cmdDispatch(
@@ -79,6 +135,8 @@ namespace dxvk {
           uint32_t instanceCount,
           uint32_t firstVertex,
           uint32_t firstInstance) {
+    TRACE(this, vertexCount, instanceCount,
+      firstVertex, firstInstance);
     this->flushGraphicsState();
     
     m_cmd->cmdDraw(
@@ -93,6 +151,8 @@ namespace dxvk {
           uint32_t firstIndex,
           uint32_t vertexOffset,
           uint32_t firstInstance) {
+    TRACE(this, indexCount, instanceCount,
+      firstIndex, vertexOffset, firstInstance);
     this->flushGraphicsState();
     
     m_cmd->cmdDrawIndexed(
@@ -102,49 +162,15 @@ namespace dxvk {
   }
   
   
-  void DxvkContext::setFramebuffer(
-    const Rc<DxvkFramebuffer>& fb) {
-    TRACE(this, fb);
-    
-    if (m_state.g.fb != fb) {
-      m_state.g.fb = fb;
-      
-      if (m_state.g.flags.test(
-          DxvkGraphicsPipelineBit::RenderPassBound))
-        this->endRenderPass();
-    }
-  }
-  
-  
-  void DxvkContext::setShader(
-          VkShaderStageFlagBits stage,
-    const Rc<DxvkShader>&       shader) {
-    TRACE(this, stage, shader);
-    
-    DxvkShaderState* state = this->getShaderState(stage);
-    
-    if (state->shader != shader) {
-      state->shader = shader;
-      
-      if (stage == VK_SHADER_STAGE_COMPUTE_BIT) {
-        m_state.c.flags.set(
-          DxvkComputePipelineBit::PipelineDirty,
-          DxvkComputePipelineBit::DirtyResources);
-      } else {
-        m_state.g.flags.set(
-          DxvkGraphicsPipelineBit::PipelineDirty,
-          DxvkGraphicsPipelineBit::DirtyResources);
-      }
-    }
-  }
-  
-  
   void DxvkContext::flushComputeState() {
-    if (m_state.c.flags.test(DxvkComputePipelineBit::PipelineDirty)
-     && m_state.c.pipeline != nullptr) {
-      m_cmd->cmdBindPipeline(
-        VK_PIPELINE_BIND_POINT_COMPUTE,
-        m_state.c.pipeline->getPipelineHandle());
+    if (m_state.c.flags.test(DxvkComputePipelineBit::PipelineDirty)) {
+      m_state.c.pipeline = m_pipeMgr->getComputePipeline(m_state.c.cs.shader);
+      
+      if (m_state.c.pipeline != nullptr) {
+        m_cmd->cmdBindPipeline(
+          VK_PIPELINE_BIND_POINT_COMPUTE,
+          m_state.c.pipeline->getPipelineHandle());
+      }
     }
     
     m_state.c.flags.clr(
@@ -187,6 +213,27 @@ namespace dxvk {
   }
   
   
+  void DxvkContext::setPipelineDirty(VkShaderStageFlagBits stage) {
+    if (stage == VK_SHADER_STAGE_COMPUTE_BIT) {
+      m_state.c.flags.set(
+        DxvkComputePipelineBit::PipelineDirty,
+        DxvkComputePipelineBit::DirtyResources);
+    } else {
+      m_state.g.flags.set(
+        DxvkGraphicsPipelineBit::PipelineDirty,
+        DxvkGraphicsPipelineBit::DirtyResources);
+    }
+  }
+  
+  
+  void DxvkContext::setResourcesDirty(VkShaderStageFlagBits stage) {
+    if (stage == VK_SHADER_STAGE_COMPUTE_BIT)
+      m_state.c.flags.set(DxvkComputePipelineBit::DirtyResources);
+    else
+      m_state.g.flags.set(DxvkGraphicsPipelineBit::DirtyResources);
+  }
+  
+  
   DxvkShaderState* DxvkContext::getShaderState(VkShaderStageFlagBits stage) {
     switch (stage) {
       case VK_SHADER_STAGE_VERTEX_BIT:
diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h
index be27bce9f..1a6cabba3 100644
--- a/src/dxvk/dxvk_context.h
+++ b/src/dxvk/dxvk_context.h
@@ -1,8 +1,11 @@
 #pragma once
 
+#include "dxvk_barrier.h"
 #include "dxvk_cmdlist.h"
 #include "dxvk_context_state.h"
 #include "dxvk_deferred.h"
+#include "dxvk_pipemgr.h"
+#include "dxvk_util.h"
 
 namespace dxvk {
   
@@ -18,7 +21,8 @@ namespace dxvk {
   public:
     
     DxvkContext(
-      const Rc<DxvkDevice>& device);
+      const Rc<DxvkDevice>&           device,
+      const Rc<DxvkPipelineManager>&  pipeMgr);
     ~DxvkContext();
     
     /**
@@ -51,6 +55,41 @@ namespace dxvk {
      */
     bool endRecording();
     
+    /**
+     * \brief Sets framebuffer
+     * \param [in] fb Framebuffer
+     */
+    void bindFramebuffer(
+      const Rc<DxvkFramebuffer>& fb);
+    
+    /**
+     * \brief Sets shader for a given shader stage
+     * 
+     * Binds a shader to a given stage, while unbinding the
+     * existing one. If \c nullptr is passed as the shader
+     * to bind, the given shader stage will be disabled.
+     * When drawing, at least a vertex shader must be bound.
+     * \param [in] stage The shader stage
+     * \param [in] shader The shader to set
+     */
+    void bindShader(
+            VkShaderStageFlagBits stage,
+      const Rc<DxvkShader>&       shader);
+    
+    /**
+     * \brief Binds a storage buffer
+     * 
+     * \param [in] stage Shader stage for this binding
+     * \param [in] slot Binding slot index
+     * \param [in] buffer Buffer binding info
+     */
+    void bindStorageBuffer(
+            VkShaderStageFlagBits stage,
+            uint32_t              slot,
+      const Rc<DxvkBuffer>&       buffer,
+            VkDeviceSize          offset,
+            VkDeviceSize          length);
+    
     /**
      * \brief Clears an active render target
      * 
@@ -103,30 +142,10 @@ namespace dxvk {
             uint32_t vertexOffset,
             uint32_t firstInstance);
     
-    /**
-     * \brief Sets framebuffer
-     * \param [in] fb Framebuffer
-     */
-    void setFramebuffer(
-      const Rc<DxvkFramebuffer>& fb);
-    
-    /**
-     * \brief Sets shader for a given shader stage
-     * 
-     * Binds a shader to a given stage, while unbinding the
-     * existing one. If \c nullptr is passed as the shader
-     * to bind, the given shader stage will be disabled.
-     * When drawing, at least a vertex shader must be bound.
-     * \param [in] stage The shader stage
-     * \param [in] shader The shader to set
-     */
-    void setShader(
-            VkShaderStageFlagBits stage,
-      const Rc<DxvkShader>&       shader);
-    
   private:
     
-    const Rc<DxvkDevice> m_device;
+    const Rc<DxvkDevice>          m_device;
+    const Rc<DxvkPipelineManager> m_pipeMgr;
     
     Rc<DxvkRecorder> m_cmd;
     DxvkContextState m_state;
@@ -137,9 +156,19 @@ namespace dxvk {
     void beginRenderPass();
     void endRenderPass();
     
+    void setPipelineDirty(VkShaderStageFlagBits stage);
+    void setResourcesDirty(VkShaderStageFlagBits stage);
+    
+    void shaderResourceBarriers(
+      DxvkBarrierSet&       barriers,
+      VkShaderStageFlagBits stage);
+    
     DxvkShaderState* getShaderState(
       VkShaderStageFlagBits stage);
     
+    VkPipelineStageFlags pipelineStage(
+      VkShaderStageFlags shaderStage) const;
+    
   };
   
 }
\ No newline at end of file
diff --git a/src/dxvk/dxvk_context_state.h b/src/dxvk/dxvk_context_state.h
index 8d4f63632..c2b4a5c10 100644
--- a/src/dxvk/dxvk_context_state.h
+++ b/src/dxvk/dxvk_context_state.h
@@ -1,7 +1,9 @@
 #pragma once
 
+#include "dxvk_buffer.h"
 #include "dxvk_compute.h"
 #include "dxvk_framebuffer.h"
+#include "dxvk_image.h"
 #include "dxvk_limits.h"
 #include "dxvk_shader.h"
 
@@ -48,7 +50,10 @@ namespace dxvk {
    * buffers, storage buffers and storage images.
    */
   struct DxvkShaderState {
-    Rc<DxvkShader>  shader;
+    Rc<DxvkShader> shader;
+    
+    std::array<DxvkBufferBinding, MaxNumStorageBuffers> boundStorageBuffers;
+    std::array<DxvkBufferBinding, MaxNumUniformBuffers> boundUniformBuffers;
   };
   
   
diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp
index 7bc3109da..cce5a601d 100644
--- a/src/dxvk/dxvk_device.cpp
+++ b/src/dxvk/dxvk_device.cpp
@@ -6,10 +6,11 @@ namespace dxvk {
   DxvkDevice::DxvkDevice(
     const Rc<DxvkAdapter>&  adapter,
     const Rc<vk::DeviceFn>& vkd)
-  : m_adapter       (adapter),
-    m_vkd           (vkd),
-    m_memory        (adapter, vkd),
-    m_renderPassPool(vkd) {
+  : m_adapter         (adapter),
+    m_vkd             (vkd),
+    m_memory          (new DxvkMemoryAllocator(adapter, vkd)),
+    m_renderPassPool  (new DxvkRenderPassPool (vkd)),
+    m_pipelineManager (new DxvkPipelineManager(vkd)) {
     TRACE(this, adapter);
     
     m_vkd->vkGetDeviceQueue(m_vkd->device(),
@@ -23,6 +24,10 @@ namespace dxvk {
   
   DxvkDevice::~DxvkDevice() {
     TRACE(this);
+    m_pipelineManager = nullptr;
+    m_renderPassPool  = nullptr;
+    m_memory          = nullptr;
+    
     m_vkd->vkDeviceWaitIdle(m_vkd->device());
     m_vkd->vkDestroyDevice(m_vkd->device(), nullptr);
   }
@@ -35,14 +40,14 @@ namespace dxvk {
   
   
   Rc<DxvkContext> DxvkDevice::createContext() {
-    return new DxvkContext(this);
+    return new DxvkContext(this, m_pipelineManager);
   }
   
   
   Rc<DxvkFramebuffer> DxvkDevice::createFramebuffer(
     const DxvkRenderTargets& renderTargets) {
     auto format = renderTargets.renderPassFormat();
-    auto renderPass = m_renderPassPool.getRenderPass(format);
+    auto renderPass = m_renderPassPool->getRenderPass(format);
     return new DxvkFramebuffer(m_vkd, renderPass, renderTargets);
   }
   
@@ -51,7 +56,7 @@ namespace dxvk {
     const DxvkBufferCreateInfo& createInfo,
           VkMemoryPropertyFlags memoryType) {
     return new DxvkBuffer(m_vkd,
-      createInfo, m_memory, memoryType);
+      createInfo, *m_memory, memoryType);
   }
   
   
diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h
index e8a73f506..e6b37969c 100644
--- a/src/dxvk/dxvk_device.h
+++ b/src/dxvk/dxvk_device.h
@@ -6,6 +6,7 @@
 #include "dxvk_context.h"
 #include "dxvk_framebuffer.h"
 #include "dxvk_memory.h"
+#include "dxvk_pipemgr.h"
 #include "dxvk_renderpass.h"
 #include "dxvk_shader.h"
 #include "dxvk_swapchain.h"
@@ -179,13 +180,13 @@ namespace dxvk {
     Rc<DxvkAdapter>   m_adapter;
     Rc<vk::DeviceFn>  m_vkd;
     
-    DxvkMemoryAllocator m_memory;
-    DxvkRenderPassPool  m_renderPassPool;
+    Rc<DxvkMemoryAllocator> m_memory;
+    Rc<DxvkRenderPassPool>  m_renderPassPool;
+    Rc<DxvkPipelineManager> m_pipelineManager;
     
     VkQueue m_graphicsQueue;
     VkQueue m_presentQueue;
     
-    
   };
   
 }
\ No newline at end of file
diff --git a/src/dxvk/dxvk_graphics.cpp b/src/dxvk/dxvk_graphics.cpp
index f85261300..21311899b 100644
--- a/src/dxvk/dxvk_graphics.cpp
+++ b/src/dxvk/dxvk_graphics.cpp
@@ -2,17 +2,17 @@
 
 namespace dxvk {
   
-  size_t DxvkGraphicsPipelineState::hash() const {
+  size_t DxvkGraphicsPipelineStateInfo::hash() const {
     // TODO implement
   }
   
   
-  bool DxvkGraphicsPipelineState::operator == (const DxvkGraphicsPipelineState& other) const {
+  bool DxvkGraphicsPipelineStateInfo::operator == (const DxvkGraphicsPipelineStateInfo& other) const {
     // TODO implement
   }
   
   
-  bool DxvkGraphicsPipelineState::operator != (const DxvkGraphicsPipelineState& other) const {
+  bool DxvkGraphicsPipelineStateInfo::operator != (const DxvkGraphicsPipelineStateInfo& other) const {
     return !this->operator == (other);
   }
   
@@ -36,7 +36,7 @@ namespace dxvk {
   
   
   VkPipeline DxvkGraphicsPipeline::getPipelineHandle(
-    const DxvkGraphicsPipelineState& state) {
+    const DxvkGraphicsPipelineStateInfo& state) {
     std::lock_guard<std::mutex> lock(m_mutex);
     
     auto pair = m_pipelines.find(state);
@@ -50,7 +50,7 @@ namespace dxvk {
   
   
   VkPipeline DxvkGraphicsPipeline::compilePipeline(
-    const DxvkGraphicsPipelineState& state) const {
+    const DxvkGraphicsPipelineStateInfo& state) const {
     
   }
   
diff --git a/src/dxvk/dxvk_graphics.h b/src/dxvk/dxvk_graphics.h
index 7cf001f21..d00249676 100644
--- a/src/dxvk/dxvk_graphics.h
+++ b/src/dxvk/dxvk_graphics.h
@@ -9,13 +9,13 @@
 
 namespace dxvk {
   
-  struct DxvkGraphicsPipelineState {
+  struct DxvkGraphicsPipelineStateInfo {
     VkRenderPass renderPass;
     
     size_t hash() const;
     
-    bool operator == (const DxvkGraphicsPipelineState& other) const;
-    bool operator != (const DxvkGraphicsPipelineState& other) const;
+    bool operator == (const DxvkGraphicsPipelineStateInfo& other) const;
+    bool operator != (const DxvkGraphicsPipelineStateInfo& other) const;
   };
   
   /**
@@ -43,7 +43,7 @@ namespace dxvk {
     }
     
     VkPipeline getPipelineHandle(
-      const DxvkGraphicsPipelineState& state);
+      const DxvkGraphicsPipelineStateInfo& state);
     
   private:
     
@@ -60,11 +60,11 @@ namespace dxvk {
     std::mutex m_mutex;
     
     std::unordered_map<
-      DxvkGraphicsPipelineState,
+      DxvkGraphicsPipelineStateInfo,
       VkPipeline, DxvkHash> m_pipelines;
     
     VkPipeline compilePipeline(
-      const DxvkGraphicsPipelineState& state) const;
+      const DxvkGraphicsPipelineStateInfo& state) const;
     
   };
   
diff --git a/src/dxvk/dxvk_memory.h b/src/dxvk/dxvk_memory.h
index c44f4ea3f..d7ffb72a4 100644
--- a/src/dxvk/dxvk_memory.h
+++ b/src/dxvk/dxvk_memory.h
@@ -68,7 +68,7 @@ namespace dxvk {
    * Allocates device memory for Vulkan resources.
    * Memory objects will be destroyed automatically.
    */
-  class DxvkMemoryAllocator {
+  class DxvkMemoryAllocator : public RcObject {
     friend class DxvkMemory;
   public:
     
diff --git a/src/dxvk/dxvk_pipemgr.cpp b/src/dxvk/dxvk_pipemgr.cpp
index 1b7df0d50..6f6410dc3 100644
--- a/src/dxvk/dxvk_pipemgr.cpp
+++ b/src/dxvk/dxvk_pipemgr.cpp
@@ -14,8 +14,9 @@ namespace dxvk {
   }
   
   
-  Rc<DxvkComputePipeline> DxvkPipelineManager::getComputePipeline(
-    const Rc<DxvkShader>& cs) {
+  Rc<DxvkComputePipeline> DxvkPipelineManager::getComputePipeline(const Rc<DxvkShader>& cs) {
+    if (cs == nullptr)
+      return nullptr;
     
     DxvkPipelineKey<1> key;
     key.setShader(0, cs);
@@ -38,6 +39,8 @@ namespace dxvk {
     const Rc<DxvkShader>& tes,
     const Rc<DxvkShader>& gs,
     const Rc<DxvkShader>& fs) {
+    if (vs == nullptr)
+      return nullptr;
     
     DxvkPipelineKey<5> key;
     key.setShader(0, vs);
diff --git a/src/dxvk/dxvk_pipemgr.h b/src/dxvk/dxvk_pipemgr.h
index c625bba80..f4f973055 100644
--- a/src/dxvk/dxvk_pipemgr.h
+++ b/src/dxvk/dxvk_pipemgr.h
@@ -73,6 +73,7 @@ namespace dxvk {
      * shader. If no such pipeline object exists, a new
      * one will be created.
      * \param [in] cs Compute shader
+     * \returns Compute pipeline
      */
     Rc<DxvkComputePipeline> getComputePipeline(
       const Rc<DxvkShader>& cs);
diff --git a/src/dxvk/dxvk_recorder.h b/src/dxvk/dxvk_recorder.h
index b92723e5d..73cd272cc 100644
--- a/src/dxvk/dxvk_recorder.h
+++ b/src/dxvk/dxvk_recorder.h
@@ -61,6 +61,17 @@ namespace dxvk {
     
     virtual void cmdEndRenderPass() = 0;
     
+    virtual void cmdPipelineBarrier(
+            VkPipelineStageFlags    srcStageMask,
+            VkPipelineStageFlags    dstStageMask,
+            VkDependencyFlags       dependencyFlags,
+            uint32_t                memoryBarrierCount,
+      const VkMemoryBarrier*        pMemoryBarriers,
+            uint32_t                bufferMemoryBarrierCount,
+      const VkBufferMemoryBarrier*  pBufferMemoryBarriers,
+            uint32_t                imageMemoryBarrierCount,
+      const VkImageMemoryBarrier*   pImageMemoryBarriers) = 0;
+    
   };
   
 }
\ No newline at end of file
diff --git a/src/dxvk/dxvk_renderpass.h b/src/dxvk/dxvk_renderpass.h
index e5be00464..4ced82b86 100644
--- a/src/dxvk/dxvk_renderpass.h
+++ b/src/dxvk/dxvk_renderpass.h
@@ -140,7 +140,7 @@ namespace dxvk {
    * Thread-safe class that manages the render pass
    * objects that are used within an application.
    */
-  class DxvkRenderPassPool {
+  class DxvkRenderPassPool : public RcObject {
     
   public:
     
diff --git a/src/dxvk/dxvk_shader.h b/src/dxvk/dxvk_shader.h
index cebea04b8..63ad49961 100644
--- a/src/dxvk/dxvk_shader.h
+++ b/src/dxvk/dxvk_shader.h
@@ -8,6 +8,19 @@
 
 namespace dxvk {
   
+  /**
+   * \brief Resource access mode
+   * 
+   * Defines whether a resource will be
+   * used for reading, writing, or both.
+   */
+  enum class DxvkResourceModeBit : uint32_t {
+    Read  = 0,
+    Write = 1,
+  };
+  
+  using DxvkResourceMode = Flags<DxvkResourceModeBit>;
+  
   /**
    * \brief Shader resource type
    * 
@@ -27,6 +40,7 @@ namespace dxvk {
    * \brief Resource slot
    */
   struct DxvkResourceSlot{
+    DxvkResourceMode mode;
     DxvkResourceType type;
     uint32_t         slot;
   };
diff --git a/src/dxvk/dxvk_util.cpp b/src/dxvk/dxvk_util.cpp
new file mode 100644
index 000000000..88f4b22d0
--- /dev/null
+++ b/src/dxvk/dxvk_util.cpp
@@ -0,0 +1,23 @@
+#include "dxvk_util.h"
+
+namespace dxvk::util {
+  
+  VkPipelineStageFlags pipelineStages(
+          VkShaderStageFlags shaderStages) {
+    VkPipelineStageFlags result = 0;
+    if (shaderStages & VK_SHADER_STAGE_COMPUTE_BIT)
+      result |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+    if (shaderStages & VK_SHADER_STAGE_VERTEX_BIT)
+      result |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
+    if (shaderStages & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT)
+      result |= VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT;
+    if (shaderStages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)
+      result |= VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
+    if (shaderStages & VK_SHADER_STAGE_GEOMETRY_BIT)
+      result |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
+    if (shaderStages & VK_SHADER_STAGE_FRAGMENT_BIT)
+      result |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+    return result;
+  }
+  
+}
\ No newline at end of file
diff --git a/src/dxvk/dxvk_util.h b/src/dxvk/dxvk_util.h
new file mode 100644
index 000000000..50195d7f3
--- /dev/null
+++ b/src/dxvk/dxvk_util.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "dxvk_include.h"
+
+namespace dxvk::util {
+  
+  /**
+   * \brief Gets pipeline stage flags for shader stages
+   * 
+   * \param [in] shaderStages Shader stage flags
+   * \returns Corresponding pipeline stage flags
+   */
+  VkPipelineStageFlags pipelineStages(
+          VkShaderStageFlags shaderStages);
+  
+}
\ No newline at end of file
diff --git a/src/dxvk/meson.build b/src/dxvk/meson.build
index f0a49921e..1ede34bb2 100644
--- a/src/dxvk/meson.build
+++ b/src/dxvk/meson.build
@@ -1,5 +1,6 @@
 dxvk_src = files([
   'dxvk_adapter.cpp',
+  'dxvk_barrier.cpp',
   'dxvk_buffer.cpp',
   'dxvk_cmdlist.cpp',
   'dxvk_compute.cpp',
@@ -21,6 +22,7 @@ dxvk_src = files([
   'dxvk_surface.cpp',
   'dxvk_swapchain.cpp',
   'dxvk_sync.cpp',
+  'dxvk_util.cpp',
   
   'spirv/dxvk_spirv_code_buffer.cpp',
   
diff --git a/src/dxvk/spirv/dxvk_spirv_code_buffer.cpp b/src/dxvk/spirv/dxvk_spirv_code_buffer.cpp
index 9aeae2b81..11c6e194b 100644
--- a/src/dxvk/spirv/dxvk_spirv_code_buffer.cpp
+++ b/src/dxvk/spirv/dxvk_spirv_code_buffer.cpp
@@ -9,11 +9,20 @@ namespace dxvk {
   DxvkSpirvCodeBuffer::~DxvkSpirvCodeBuffer() { }
   
   
-  DxvkSpirvCodeBuffer::DxvkSpirvCodeBuffer(
-    std::basic_istream<uint32_t>& stream)
-  : m_code(
-    std::istreambuf_iterator<uint32_t>(stream),
-    std::istreambuf_iterator<uint32_t>()) { }
+  DxvkSpirvCodeBuffer::DxvkSpirvCodeBuffer(std::istream&& stream) {
+    stream.ignore(std::numeric_limits<std::streamsize>::max());
+    std::streamsize length = stream.gcount();
+    stream.clear();
+    stream.seekg(0, std::ios_base::beg);
+    
+    std::vector<char> buffer(length);
+    stream.read(buffer.data(), length);
+    buffer.resize(stream.gcount());
+    
+    m_code.resize(buffer.size() / sizeof(uint32_t));
+    std::memcpy(reinterpret_cast<char*>(m_code.data()),
+      buffer.data(), m_code.size() * sizeof(uint32_t));
+  }
   
   
   void DxvkSpirvCodeBuffer::append(const DxvkSpirvCodeBuffer& other) {
@@ -100,8 +109,10 @@ namespace dxvk {
   }
   
   
-  void DxvkSpirvCodeBuffer::store(std::basic_ostream<uint32_t>& stream) const {
-    stream.write(m_code.data(), m_code.size());
+  void DxvkSpirvCodeBuffer::store(std::ostream&& stream) const {
+    stream.write(
+      reinterpret_cast<const char*>(m_code.data()),
+      sizeof(uint32_t) * m_code.size());
   }
   
 }
\ No newline at end of file
diff --git a/src/dxvk/spirv/dxvk_spirv_code_buffer.h b/src/dxvk/spirv/dxvk_spirv_code_buffer.h
index e9907a237..a6e0d4b53 100644
--- a/src/dxvk/spirv/dxvk_spirv_code_buffer.h
+++ b/src/dxvk/spirv/dxvk_spirv_code_buffer.h
@@ -21,8 +21,7 @@ namespace dxvk {
   public:
     
     DxvkSpirvCodeBuffer();
-    DxvkSpirvCodeBuffer(
-      std::basic_istream<uint32_t>& stream);
+    DxvkSpirvCodeBuffer(std::istream&& stream);
     ~DxvkSpirvCodeBuffer();
     
     /**
@@ -38,7 +37,7 @@ namespace dxvk {
      * \returns Code size, in bytes
      */
     size_t size() const {
-      return m_code.size();
+      return m_code.size() * sizeof(uint32_t);
     }
     
     /**
@@ -120,7 +119,7 @@ namespace dxvk {
      * exists mostly for debugging purposes.
      * \param [in] stream Output stream
      */
-    void store(std::basic_ostream<uint32_t>& stream) const;
+    void store(std::ostream&& stream) const;
     
   private:
     
diff --git a/tests/dxvk/test_dxvk_triangle.cpp b/tests/dxvk/test_dxvk_triangle.cpp
index 1c8f84037..9e94134bf 100644
--- a/tests/dxvk/test_dxvk_triangle.cpp
+++ b/tests/dxvk/test_dxvk_triangle.cpp
@@ -3,6 +3,9 @@
 #include <dxvk_main.h>
 #include <dxvk_surface.h>
 
+#include <cstring>
+#include <fstream>
+
 #include <windows.h>
 #include <windowsx.h>
 
@@ -26,6 +29,37 @@ public:
     m_dxvkContext     (m_dxvkDevice->createContext()),
     m_dxvkCommandList (m_dxvkDevice->createCommandList()) {
     
+    DxvkBufferCreateInfo bufferInfo;
+    bufferInfo.size   = sizeof(m_testData);
+    bufferInfo.usage  = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+    bufferInfo.stages = VK_PIPELINE_STAGE_HOST_BIT
+                      | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+    bufferInfo.access = VK_ACCESS_HOST_WRITE_BIT
+                      | VK_ACCESS_HOST_READ_BIT
+                      | VK_ACCESS_SHADER_WRITE_BIT
+                      | VK_ACCESS_SHADER_READ_BIT;
+    
+    m_testBuffer = m_dxvkDevice->createBuffer(bufferInfo,
+      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+      VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+    for (size_t i = 0; i < 64; i++)
+      m_testData[i] = static_cast<int>(i);
+    std::memcpy(m_testBuffer->mapPtr(),
+      m_testData, sizeof(m_testData));
+    
+    DxvkResourceSlot computeBufferSlot;
+    computeBufferSlot.mode.set(
+      DxvkResourceModeBit::Read,
+      DxvkResourceModeBit::Write);
+    computeBufferSlot.type = DxvkResourceType::StorageBuffer;
+    computeBufferSlot.slot = 0;
+    
+    DxvkSpirvCodeBuffer code(std::ifstream("comp.spv", std::ios::binary));
+    code.store(std::ofstream("comp.2.spv", std::ios::binary));
+    
+    m_compShader = m_dxvkDevice->createShader(
+      VK_SHADER_STAGE_COMPUTE_BIT, std::move(code),
+      1, &computeBufferSlot);
   }
   
   ~TriangleApp() {
@@ -40,7 +74,7 @@ public:
     auto fbSize = fb->size();
     
     m_dxvkContext->beginRecording(m_dxvkCommandList);
-    m_dxvkContext->setFramebuffer(fb);
+    m_dxvkContext->bindFramebuffer(fb);
     
     VkClearAttachment clearAttachment;
     clearAttachment.aspectMask      = VK_IMAGE_ASPECT_COLOR_BIT;
@@ -58,6 +92,13 @@ public:
     m_dxvkContext->clearRenderTarget(
       clearAttachment,
       clearArea);
+    m_dxvkContext->bindShader(
+      VK_SHADER_STAGE_COMPUTE_BIT,
+      m_compShader);
+    m_dxvkContext->bindStorageBuffer(
+      VK_SHADER_STAGE_COMPUTE_BIT, 0,
+      m_testBuffer, 0, sizeof(m_testData));
+    m_dxvkContext->dispatch(1, 1, 1);
     m_dxvkContext->endRecording();
     
     auto fence = m_dxvkDevice->submitCommandList(
@@ -76,9 +117,13 @@ private:
   Rc<DxvkContext>     m_dxvkContext;
   Rc<DxvkCommandList> m_dxvkCommandList;
   
+  Rc<DxvkBuffer>      m_testBuffer;
+  Rc<DxvkShader>      m_compShader;
   Rc<DxvkShader>      m_vertShader;
   Rc<DxvkShader>      m_fragShader;
   
+  int m_testData[64];
+  
 };
 
 LRESULT CALLBACK WindowProc(HWND hWnd,