Vulkan: Distinguish RP and outside-RP command buffer types

What goes inside and outside a render pass command buffer is largely
mutually exclusive.  Moreover, the size and frequency of allocations is
different between the two.

This change distinguishes the C++ types used for inside and outside
render pass command buffers:

- The type now documents which command buffer a function is able to
  receive.
- `isRenderPass` flag passing, checking and asserting is largely
  removed.
- A follow up change experiments with using different (Vulkan vs ANGLE)
  secondary command buffers for inside and outside RP command buffers.
- A future change could specialize the pool behaviors per command buffer
  type.

Bug: angleproject:6811
Change-Id: Ia4bc669d26ac7e94e8a0dfb9b361666c82f42cc3
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3344373
Reviewed-by: Tim Van Patten <timvp@google.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/BufferVk.cpp b/src/libANGLE/renderer/vulkan/BufferVk.cpp
index 03d98f8..eb7759c 100644
--- a/src/libANGLE/renderer/vulkan/BufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/BufferVk.cpp
@@ -449,7 +449,7 @@
         access.onBufferTransferWrite(&mBuffer);
     }
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     // Enqueue a copy command on the GPU.
diff --git a/src/libANGLE/renderer/vulkan/CommandProcessor.cpp b/src/libANGLE/renderer/vulkan/CommandProcessor.cpp
index d5a29fb..5914e24 100644
--- a/src/libANGLE/renderer/vulkan/CommandProcessor.cpp
+++ b/src/libANGLE/renderer/vulkan/CommandProcessor.cpp
@@ -57,6 +57,30 @@
 
     return true;
 }
+
+template <typename SecondaryCommandBufferListT>
+void ResetSecondaryCommandBuffers(VkDevice device,
+                                  vk::CommandPool *commandPool,
+                                  SecondaryCommandBufferListT *commandBuffers)
+{
+    // Nothing to do when using ANGLE secondary command buffers.
+}
+
+template <>
+ANGLE_MAYBE_UNUSED void ResetSecondaryCommandBuffers<std::vector<VulkanSecondaryCommandBuffer>>(
+    VkDevice device,
+    vk::CommandPool *commandPool,
+    std::vector<VulkanSecondaryCommandBuffer> *commandBuffers)
+{
+    // Note: we currently free the command buffers individually, but we could potentially reset the
+    // entire command pool.  https://issuetracker.google.com/issues/166793850
+    for (VulkanSecondaryCommandBuffer &secondary : *commandBuffers)
+    {
+        commandPool->freeCommandBuffers(device, 1, secondary.ptr());
+        secondary.releaseHandle();
+    }
+    commandBuffers->clear();
+}
 }  // namespace
 
 angle::Result FenceRecycler::newSharedFence(vk::Context *context,
@@ -98,33 +122,44 @@
 // CommandProcessorTask implementation
 void CommandProcessorTask::initTask()
 {
-    mTask                         = CustomTask::Invalid;
-    mRenderPass                   = nullptr;
-    mCommandBuffer                = nullptr;
-    mSemaphore                    = nullptr;
-    mCommandPool                  = nullptr;
-    mOneOffWaitSemaphore          = nullptr;
-    mOneOffWaitSemaphoreStageMask = 0;
-    mOneOffFence                  = nullptr;
-    mPresentInfo                  = {};
-    mPresentInfo.pResults         = nullptr;
-    mPresentInfo.pSwapchains      = nullptr;
-    mPresentInfo.pImageIndices    = nullptr;
-    mPresentInfo.pNext            = nullptr;
-    mPresentInfo.pWaitSemaphores  = nullptr;
-    mOneOffCommandBufferVk        = VK_NULL_HANDLE;
-    mPriority                     = egl::ContextPriority::Medium;
-    mHasProtectedContent          = false;
+    mTask                           = CustomTask::Invalid;
+    mOutsideRenderPassCommandBuffer = nullptr;
+    mRenderPassCommandBuffer        = nullptr;
+    mRenderPass                     = nullptr;
+    mSemaphore                      = nullptr;
+    mCommandPools                   = nullptr;
+    mOneOffWaitSemaphore            = nullptr;
+    mOneOffWaitSemaphoreStageMask   = 0;
+    mOneOffFence                    = nullptr;
+    mPresentInfo                    = {};
+    mPresentInfo.pResults           = nullptr;
+    mPresentInfo.pSwapchains        = nullptr;
+    mPresentInfo.pImageIndices      = nullptr;
+    mPresentInfo.pNext              = nullptr;
+    mPresentInfo.pWaitSemaphores    = nullptr;
+    mOneOffCommandBufferVk          = VK_NULL_HANDLE;
+    mPriority                       = egl::ContextPriority::Medium;
+    mHasProtectedContent            = false;
 }
 
-void CommandProcessorTask::initProcessCommands(bool hasProtectedContent,
-                                               CommandBufferHelper *commandBuffer,
-                                               const RenderPass *renderPass)
+void CommandProcessorTask::initOutsideRenderPassProcessCommands(
+    bool hasProtectedContent,
+    OutsideRenderPassCommandBufferHelper *commandBuffer)
 {
-    mTask                = CustomTask::ProcessCommands;
-    mCommandBuffer       = commandBuffer;
-    mRenderPass          = renderPass;
-    mHasProtectedContent = hasProtectedContent;
+    mTask                           = CustomTask::ProcessOutsideRenderPassCommands;
+    mOutsideRenderPassCommandBuffer = commandBuffer;
+    mHasProtectedContent            = hasProtectedContent;
+}
+
+void CommandProcessorTask::initRenderPassProcessCommands(
+    bool hasProtectedContent,
+    RenderPassCommandBufferHelper *commandBuffer,
+    const RenderPass *renderPass)
+{
+    mTask                    = CustomTask::ProcessRenderPassCommands;
+    mRenderPassCommandBuffer = commandBuffer;
+    mRenderPass              = renderPass;
+    mHasProtectedContent     = hasProtectedContent;
 }
 
 void CommandProcessorTask::copyPresentInfo(const VkPresentInfoKHR &other)
@@ -218,16 +253,16 @@
     const Semaphore *semaphore,
     bool hasProtectedContent,
     egl::ContextPriority priority,
-    CommandPool *commandPool,
+    SecondaryCommandPools *commandPools,
     GarbageList &&currentGarbage,
-    std::vector<CommandBuffer> &&commandBuffersToReset,
+    SecondaryCommandBufferList &&commandBuffersToReset,
     Serial submitQueueSerial)
 {
     mTask                    = CustomTask::FlushAndQueueSubmit;
     mWaitSemaphores          = waitSemaphores;
     mWaitSemaphoreStageMasks = waitSemaphoreStageMasks;
     mSemaphore               = semaphore;
-    mCommandPool             = commandPool;
+    mCommandPools            = commandPools;
     mGarbage                 = std::move(currentGarbage);
     mCommandBuffersToReset   = std::move(commandBuffersToReset);
     mPriority                = priority;
@@ -261,7 +296,8 @@
     }
 
     std::swap(mRenderPass, rhs.mRenderPass);
-    std::swap(mCommandBuffer, rhs.mCommandBuffer);
+    std::swap(mOutsideRenderPassCommandBuffer, rhs.mOutsideRenderPassCommandBuffer);
+    std::swap(mRenderPassCommandBuffer, rhs.mRenderPassCommandBuffer);
     std::swap(mTask, rhs.mTask);
     std::swap(mWaitSemaphores, rhs.mWaitSemaphores);
     std::swap(mWaitSemaphoreStageMasks, rhs.mWaitSemaphoreStageMasks);
@@ -269,7 +305,7 @@
     std::swap(mOneOffWaitSemaphore, rhs.mOneOffWaitSemaphore);
     std::swap(mOneOffWaitSemaphoreStageMask, rhs.mOneOffWaitSemaphoreStageMask);
     std::swap(mOneOffFence, rhs.mOneOffFence);
-    std::swap(mCommandPool, rhs.mCommandPool);
+    std::swap(mCommandPools, rhs.mCommandPools);
     std::swap(mGarbage, rhs.mGarbage);
     std::swap(mCommandBuffersToReset, rhs.mCommandBuffersToReset);
     std::swap(mSerial, rhs.mSerial);
@@ -286,7 +322,7 @@
 }
 
 // CommandBatch implementation.
-CommandBatch::CommandBatch() : commandPool(nullptr), hasProtectedContent(false) {}
+CommandBatch::CommandBatch() : commandPools(nullptr), hasProtectedContent(false) {}
 
 CommandBatch::~CommandBatch() = default;
 
@@ -298,7 +334,7 @@
 CommandBatch &CommandBatch::operator=(CommandBatch &&other)
 {
     std::swap(primaryCommands, other.primaryCommands);
-    std::swap(commandPool, other.commandPool);
+    std::swap(commandPools, other.commandPools);
     std::swap(commandBuffersToReset, other.commandBuffersToReset);
     std::swap(fence, other.fence);
     std::swap(serial, other.serial);
@@ -315,16 +351,10 @@
 
 void CommandBatch::resetSecondaryCommandBuffers(VkDevice device)
 {
-#if !ANGLE_USE_CUSTOM_VULKAN_CMD_BUFFERS
-    for (CommandBuffer &secondary : commandBuffersToReset)
-    {
-        // Note: we currently free the command buffers individually, but we could potentially reset
-        // the entire command pool.  https://issuetracker.google.com/issues/166793850
-        commandPool->freeCommandBuffers(device, 1, secondary.ptr());
-        secondary.releaseHandle();
-    }
-    commandBuffersToReset.clear();
-#endif
+    ResetSecondaryCommandBuffers(device, &commandPools->outsideRenderPassPool,
+                                 &commandBuffersToReset.outsideRenderPassCommandBuffers);
+    ResetSecondaryCommandBuffers(device, &commandPools->renderPassPool,
+                                 &commandBuffersToReset.renderPassCommandBuffers);
 }
 
 // CommandProcessor implementation.
@@ -466,7 +496,7 @@
                 this, task->hasProtectedContent(), task->getPriority(), task->getWaitSemaphores(),
                 task->getWaitSemaphoreStageMasks(), task->getSemaphore(),
                 std::move(task->getGarbage()), std::move(task->getCommandBuffersToReset()),
-                task->getCommandPool(), task->getQueueSerial()));
+                task->getCommandPools(), task->getQueueSerial()));
 
             ASSERT(task->getGarbage().empty());
             break;
@@ -511,25 +541,29 @@
             }
             break;
         }
-        case CustomTask::ProcessCommands:
+        case CustomTask::ProcessOutsideRenderPassCommands:
         {
-            ASSERT(!task->getCommandBuffer()->empty());
+            OutsideRenderPassCommandBufferHelper *commandBuffer =
+                task->getOutsideRenderPassCommandBuffer();
+            ANGLE_TRY(mCommandQueue.flushOutsideRPCommands(this, task->hasProtectedContent(),
+                                                           &commandBuffer));
 
-            CommandBufferHelper *commandBuffer = task->getCommandBuffer();
-            if (task->getRenderPass())
-            {
-                ANGLE_TRY(mCommandQueue.flushRenderPassCommands(
-                    this, task->hasProtectedContent(), *task->getRenderPass(), &commandBuffer));
-            }
-            else
-            {
-                ANGLE_TRY(mCommandQueue.flushOutsideRPCommands(this, task->hasProtectedContent(),
-                                                               &commandBuffer));
-            }
+            OutsideRenderPassCommandBufferHelper *originalCommandBuffer =
+                task->getOutsideRenderPassCommandBuffer();
+            mRenderer->recycleOutsideRenderPassCommandBufferHelper(mRenderer->getDevice(),
+                                                                   &originalCommandBuffer);
+            break;
+        }
+        case CustomTask::ProcessRenderPassCommands:
+        {
+            RenderPassCommandBufferHelper *commandBuffer = task->getRenderPassCommandBuffer();
+            ANGLE_TRY(mCommandQueue.flushRenderPassCommands(
+                this, task->hasProtectedContent(), *task->getRenderPass(), &commandBuffer));
 
-            CommandBufferHelper *originalCommandBuffer = task->getCommandBuffer();
-            ASSERT(originalCommandBuffer->empty());
-            mRenderer->recycleCommandBufferHelper(mRenderer->getDevice(), &originalCommandBuffer);
+            RenderPassCommandBufferHelper *originalCommandBuffer =
+                task->getRenderPassCommandBuffer();
+            mRenderer->recycleRenderPassCommandBufferHelper(mRenderer->getDevice(),
+                                                            &originalCommandBuffer);
             break;
         }
         case CustomTask::CheckCompletedCommands:
@@ -689,15 +723,15 @@
     const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
     const Semaphore *signalSemaphore,
     GarbageList &&currentGarbage,
-    std::vector<CommandBuffer> &&commandBuffersToReset,
-    CommandPool *commandPool,
+    SecondaryCommandBufferList &&commandBuffersToReset,
+    SecondaryCommandPools *commandPools,
     Serial submitQueueSerial)
 {
     ANGLE_TRY(checkAndPopPendingError(context));
 
     CommandProcessorTask task;
     task.initFlushAndQueueSubmit(waitSemaphores, waitSemaphoreStageMasks, signalSemaphore,
-                                 hasProtectedContent, priority, commandPool,
+                                 hasProtectedContent, priority, commandPools,
                                  std::move(currentGarbage), std::move(commandBuffersToReset),
                                  submitQueueSerial);
 
@@ -757,33 +791,35 @@
     return finishToSerial(context, serial, mRenderer->getMaxFenceWaitTimeNs());
 }
 
-angle::Result CommandProcessor::flushOutsideRPCommands(Context *context,
-                                                       bool hasProtectedContent,
-                                                       CommandBufferHelper **outsideRPCommands)
+angle::Result CommandProcessor::flushOutsideRPCommands(
+    Context *context,
+    bool hasProtectedContent,
+    OutsideRenderPassCommandBufferHelper **outsideRPCommands)
 {
     ANGLE_TRY(checkAndPopPendingError(context));
 
     (*outsideRPCommands)->markClosed();
     CommandProcessorTask task;
-    task.initProcessCommands(hasProtectedContent, *outsideRPCommands, nullptr);
+    task.initOutsideRenderPassProcessCommands(hasProtectedContent, *outsideRPCommands);
     queueCommand(std::move(task));
-    return mRenderer->getCommandBufferHelper(context, false, (*outsideRPCommands)->getCommandPool(),
-                                             outsideRPCommands);
+    return mRenderer->getOutsideRenderPassCommandBufferHelper(
+        context, (*outsideRPCommands)->getCommandPool(), outsideRPCommands);
 }
 
-angle::Result CommandProcessor::flushRenderPassCommands(Context *context,
-                                                        bool hasProtectedContent,
-                                                        const RenderPass &renderPass,
-                                                        CommandBufferHelper **renderPassCommands)
+angle::Result CommandProcessor::flushRenderPassCommands(
+    Context *context,
+    bool hasProtectedContent,
+    const RenderPass &renderPass,
+    RenderPassCommandBufferHelper **renderPassCommands)
 {
     ANGLE_TRY(checkAndPopPendingError(context));
 
     (*renderPassCommands)->markClosed();
     CommandProcessorTask task;
-    task.initProcessCommands(hasProtectedContent, *renderPassCommands, &renderPass);
+    task.initRenderPassProcessCommands(hasProtectedContent, *renderPassCommands, &renderPass);
     queueCommand(std::move(task));
-    return mRenderer->getCommandBufferHelper(context, true, (*renderPassCommands)->getCommandPool(),
-                                             renderPassCommands);
+    return mRenderer->getRenderPassCommandBufferHelper(
+        context, (*renderPassCommands)->getCommandPool(), renderPassCommands);
 }
 
 angle::Result CommandProcessor::ensureNoPendingWork(Context *context)
@@ -920,13 +956,13 @@
 
 void CommandQueue::releaseToCommandBatch(bool hasProtectedContent,
                                          PrimaryCommandBuffer &&commandBuffer,
-                                         CommandPool *commandPool,
+                                         SecondaryCommandPools *commandPools,
                                          CommandBatch *batch)
 {
     ANGLE_TRACE_EVENT0("gpu.angle", "CommandQueue::releaseToCommandBatch");
 
     batch->primaryCommands     = std::move(commandBuffer);
-    batch->commandPool         = commandPool;
+    batch->commandPools        = commandPools;
     batch->hasProtectedContent = hasProtectedContent;
 }
 
@@ -1029,8 +1065,8 @@
     const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
     const Semaphore *signalSemaphore,
     GarbageList &&currentGarbage,
-    std::vector<CommandBuffer> &&commandBuffersToReset,
-    CommandPool *commandPool,
+    SecondaryCommandBufferList &&commandBuffersToReset,
+    SecondaryCommandPools *commandPools,
     Serial submitQueueSerial)
 {
     // Start an empty primary buffer if we have an empty submit.
@@ -1076,11 +1112,11 @@
     if (hasProtectedContent)
     {
         releaseToCommandBatch(hasProtectedContent, std::move(mProtectedPrimaryCommands),
-                              commandPool, &batch);
+                              commandPools, &batch);
     }
     else
     {
-        releaseToCommandBatch(hasProtectedContent, std::move(mPrimaryCommands), commandPool,
+        releaseToCommandBatch(hasProtectedContent, std::move(mPrimaryCommands), commandPools,
                               &batch);
     }
     mInFlightCommands.emplace_back(scopedBatch.release());
@@ -1168,19 +1204,21 @@
     return angle::Result::Continue;
 }
 
-angle::Result CommandQueue::flushOutsideRPCommands(Context *context,
-                                                   bool hasProtectedContent,
-                                                   CommandBufferHelper **outsideRPCommands)
+angle::Result CommandQueue::flushOutsideRPCommands(
+    Context *context,
+    bool hasProtectedContent,
+    OutsideRenderPassCommandBufferHelper **outsideRPCommands)
 {
     ANGLE_TRY(ensurePrimaryCommandBufferValid(context, hasProtectedContent));
     PrimaryCommandBuffer &commandBuffer = getCommandBuffer(hasProtectedContent);
-    return (*outsideRPCommands)->flushToPrimary(context, &commandBuffer, nullptr);
+    return (*outsideRPCommands)->flushToPrimary(context, &commandBuffer);
 }
 
-angle::Result CommandQueue::flushRenderPassCommands(Context *context,
-                                                    bool hasProtectedContent,
-                                                    const RenderPass &renderPass,
-                                                    CommandBufferHelper **renderPassCommands)
+angle::Result CommandQueue::flushRenderPassCommands(
+    Context *context,
+    bool hasProtectedContent,
+    const RenderPass &renderPass,
+    RenderPassCommandBufferHelper **renderPassCommands)
 {
     ANGLE_TRY(ensurePrimaryCommandBufferValid(context, hasProtectedContent));
     PrimaryCommandBuffer &commandBuffer = getCommandBuffer(hasProtectedContent);
diff --git a/src/libANGLE/renderer/vulkan/CommandProcessor.h b/src/libANGLE/renderer/vulkan/CommandProcessor.h
index 77d1ad9..4f1b3cd 100644
--- a/src/libANGLE/renderer/vulkan/CommandProcessor.h
+++ b/src/libANGLE/renderer/vulkan/CommandProcessor.h
@@ -56,7 +56,8 @@
 {
     Invalid = 0,
     // Process SecondaryCommandBuffer commands into the primary CommandBuffer.
-    ProcessCommands,
+    ProcessOutsideRenderPassCommands,
+    ProcessRenderPassCommands,
     // End the current command buffer and submit commands to the queue
     FlushAndQueueSubmit,
     // Submit custom command buffer, excludes some state management
@@ -85,9 +86,12 @@
 
     void initTask(CustomTask command) { mTask = command; }
 
-    void initProcessCommands(bool hasProtectedContent,
-                             CommandBufferHelper *commandBuffer,
-                             const RenderPass *renderPass);
+    void initOutsideRenderPassProcessCommands(bool hasProtectedContent,
+                                              OutsideRenderPassCommandBufferHelper *commandBuffer);
+
+    void initRenderPassProcessCommands(bool hasProtectedContent,
+                                       RenderPassCommandBufferHelper *commandBuffer,
+                                       const RenderPass *renderPass);
 
     void initPresent(egl::ContextPriority priority, const VkPresentInfoKHR &presentInfo);
 
@@ -100,9 +104,9 @@
                                  const Semaphore *semaphore,
                                  bool hasProtectedContent,
                                  egl::ContextPriority priority,
-                                 CommandPool *commandPool,
+                                 SecondaryCommandPools *commandPools,
                                  GarbageList &&currentGarbage,
-                                 std::vector<CommandBuffer> &&commandBuffersToReset,
+                                 SecondaryCommandBufferList &&commandBuffersToReset,
                                  Serial submitQueueSerial);
 
     void initOneOffQueueSubmit(VkCommandBuffer commandBufferHandle,
@@ -130,7 +134,10 @@
     }
     const Semaphore *getSemaphore() { return mSemaphore; }
     GarbageList &getGarbage() { return mGarbage; }
-    std::vector<CommandBuffer> &getCommandBuffersToReset() { return mCommandBuffersToReset; }
+    SecondaryCommandBufferList &&getCommandBuffersToReset()
+    {
+        return std::move(mCommandBuffersToReset);
+    }
     egl::ContextPriority getPriority() const { return mPriority; }
     bool hasProtectedContent() const { return mHasProtectedContent; }
     VkCommandBuffer getOneOffCommandBufferVk() const { return mOneOffCommandBufferVk; }
@@ -139,8 +146,15 @@
     const Fence *getOneOffFence() { return mOneOffFence; }
     const VkPresentInfoKHR &getPresentInfo() const { return mPresentInfo; }
     const RenderPass *getRenderPass() const { return mRenderPass; }
-    CommandBufferHelper *getCommandBuffer() const { return mCommandBuffer; }
-    CommandPool *getCommandPool() const { return mCommandPool; }
+    OutsideRenderPassCommandBufferHelper *getOutsideRenderPassCommandBuffer() const
+    {
+        return mOutsideRenderPassCommandBuffer;
+    }
+    RenderPassCommandBufferHelper *getRenderPassCommandBuffer() const
+    {
+        return mRenderPassCommandBuffer;
+    }
+    SecondaryCommandPools *getCommandPools() const { return mCommandPools; }
 
   private:
     void copyPresentInfo(const VkPresentInfoKHR &other);
@@ -148,16 +162,17 @@
     CustomTask mTask;
 
     // ProcessCommands
+    OutsideRenderPassCommandBufferHelper *mOutsideRenderPassCommandBuffer;
+    RenderPassCommandBufferHelper *mRenderPassCommandBuffer;
     const RenderPass *mRenderPass;
-    CommandBufferHelper *mCommandBuffer;
 
     // Flush data
     std::vector<VkSemaphore> mWaitSemaphores;
     std::vector<VkPipelineStageFlags> mWaitSemaphoreStageMasks;
     const Semaphore *mSemaphore;
-    CommandPool *mCommandPool;
+    SecondaryCommandPools *mCommandPools;
     GarbageList mGarbage;
-    std::vector<CommandBuffer> mCommandBuffersToReset;
+    SecondaryCommandBufferList mCommandBuffersToReset;
 
     // FinishToSerial & Flush command data
     Serial mSerial;
@@ -194,9 +209,9 @@
     void resetSecondaryCommandBuffers(VkDevice device);
 
     PrimaryCommandBuffer primaryCommands;
-    // commandPool is for secondary CommandBuffer allocation
-    CommandPool *commandPool;
-    std::vector<CommandBuffer> commandBuffersToReset;
+    // commandPools is for secondary CommandBuffer allocation
+    SecondaryCommandPools *commandPools;
+    SecondaryCommandBufferList commandBuffersToReset;
     Shared<Fence> fence;
     Serial serial;
     bool hasProtectedContent;
@@ -290,8 +305,8 @@
         const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
         const Semaphore *signalSemaphore,
         GarbageList &&currentGarbage,
-        std::vector<CommandBuffer> &&commandBuffersToReset,
-        CommandPool *commandPool,
+        SecondaryCommandBufferList &&commandBuffersToReset,
+        SecondaryCommandPools *commandPools,
         Serial submitQueueSerial)                                      = 0;
     virtual angle::Result queueSubmitOneOff(Context *context,
                                             bool hasProtectedContent,
@@ -315,13 +330,15 @@
     // result). It would be nice if we didn't have to expose this for QueryVk::getResult.
     virtual angle::Result checkCompletedCommands(Context *context) = 0;
 
-    virtual angle::Result flushOutsideRPCommands(Context *context,
-                                                 bool hasProtectedContent,
-                                                 CommandBufferHelper **outsideRPCommands)   = 0;
-    virtual angle::Result flushRenderPassCommands(Context *context,
-                                                  bool hasProtectedContent,
-                                                  const RenderPass &renderPass,
-                                                  CommandBufferHelper **renderPassCommands) = 0;
+    virtual angle::Result flushOutsideRPCommands(
+        Context *context,
+        bool hasProtectedContent,
+        OutsideRenderPassCommandBufferHelper **outsideRPCommands) = 0;
+    virtual angle::Result flushRenderPassCommands(
+        Context *context,
+        bool hasProtectedContent,
+        const RenderPass &renderPass,
+        RenderPassCommandBufferHelper **renderPassCommands) = 0;
 
     // For correct synchronization with external, in particular when asked to signal an external
     // semaphore, we need to ensure that there are no pending submissions.
@@ -355,8 +372,8 @@
                               const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
                               const Semaphore *signalSemaphore,
                               GarbageList &&currentGarbage,
-                              std::vector<CommandBuffer> &&commandBuffersToReset,
-                              CommandPool *commandPool,
+                              SecondaryCommandBufferList &&commandBuffersToReset,
+                              SecondaryCommandPools *commandPools,
                               Serial submitQueueSerial) override;
 
     angle::Result queueSubmitOneOff(Context *context,
@@ -379,13 +396,15 @@
 
     angle::Result checkCompletedCommands(Context *context) override;
 
-    angle::Result flushOutsideRPCommands(Context *context,
-                                         bool hasProtectedContent,
-                                         CommandBufferHelper **outsideRPCommands) override;
-    angle::Result flushRenderPassCommands(Context *context,
-                                          bool hasProtectedContent,
-                                          const RenderPass &renderPass,
-                                          CommandBufferHelper **renderPassCommands) override;
+    angle::Result flushOutsideRPCommands(
+        Context *context,
+        bool hasProtectedContent,
+        OutsideRenderPassCommandBufferHelper **outsideRPCommands) override;
+    angle::Result flushRenderPassCommands(
+        Context *context,
+        bool hasProtectedContent,
+        const RenderPass &renderPass,
+        RenderPassCommandBufferHelper **renderPassCommands) override;
 
     angle::Result ensureNoPendingWork(Context *context) override { return angle::Result::Continue; }
 
@@ -409,7 +428,7 @@
   private:
     void releaseToCommandBatch(bool hasProtectedContent,
                                PrimaryCommandBuffer &&commandBuffer,
-                               CommandPool *commandPool,
+                               SecondaryCommandPools *commandPools,
                                CommandBatch *batch);
     angle::Result retireFinishedCommands(Context *context, size_t finishedCount);
     angle::Result ensurePrimaryCommandBufferValid(Context *context, bool hasProtectedContent);
@@ -504,8 +523,8 @@
                               const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
                               const Semaphore *signalSemaphore,
                               GarbageList &&currentGarbage,
-                              std::vector<CommandBuffer> &&commandBuffersToReset,
-                              CommandPool *commandPool,
+                              SecondaryCommandBufferList &&commandBuffersToReset,
+                              SecondaryCommandPools *commandPools,
                               Serial submitQueueSerial) override;
 
     angle::Result queueSubmitOneOff(Context *context,
@@ -527,13 +546,15 @@
 
     angle::Result checkCompletedCommands(Context *context) override;
 
-    angle::Result flushOutsideRPCommands(Context *context,
-                                         bool hasProtectedContent,
-                                         CommandBufferHelper **outsideRPCommands) override;
-    angle::Result flushRenderPassCommands(Context *context,
-                                          bool hasProtectedContent,
-                                          const RenderPass &renderPass,
-                                          CommandBufferHelper **renderPassCommands) override;
+    angle::Result flushOutsideRPCommands(
+        Context *context,
+        bool hasProtectedContent,
+        OutsideRenderPassCommandBufferHelper **outsideRPCommands) override;
+    angle::Result flushRenderPassCommands(
+        Context *context,
+        bool hasProtectedContent,
+        const RenderPass &renderPass,
+        RenderPassCommandBufferHelper **renderPassCommands) override;
 
     angle::Result ensureNoPendingWork(Context *context) override;
 
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 00f0c85..1964001 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -209,10 +209,10 @@
     *maskOut &= coverageMask;
 }
 
-bool IsRenderPassStartedAndUsesImage(const vk::CommandBufferHelper &renderPassCommands,
+bool IsRenderPassStartedAndUsesImage(const vk::RenderPassCommandBufferHelper &renderPassCommands,
                                      const vk::ImageHelper &image)
 {
-    return renderPassCommands.started() && renderPassCommands.usesImageInRenderPass(image);
+    return renderPassCommands.started() && renderPassCommands.usesImage(image);
 }
 
 // When an Android surface is rotated differently than the device's native orientation, ANGLE must
@@ -365,6 +365,153 @@
     desc->append32BitValue(std::numeric_limits<uint32_t>::max());
 }
 
+vk::ImageLayout GetImageReadLayout(TextureVk *textureVk,
+                                   const gl::ProgramExecutable *executable,
+                                   size_t textureUnit,
+                                   PipelineType pipelineType)
+{
+    vk::ImageHelper &image = textureVk->getImage();
+
+    if (textureVk->hasBeenBoundAsImage())
+    {
+        return pipelineType == PipelineType::Compute ? vk::ImageLayout::ComputeShaderWrite
+                                                     : vk::ImageLayout::AllGraphicsShadersWrite;
+    }
+
+    gl::ShaderBitSet remainingShaderBits =
+        executable->getSamplerShaderBitsForTextureUnitIndex(textureUnit);
+    ASSERT(remainingShaderBits.any());
+    gl::ShaderType firstShader = remainingShaderBits.first();
+    gl::ShaderType lastShader  = remainingShaderBits.last();
+    remainingShaderBits.reset(firstShader);
+    remainingShaderBits.reset(lastShader);
+
+    if (image.hasRenderPassUsageFlag(vk::RenderPassUsage::RenderTargetAttachment))
+    {
+        // Right now we set this flag only when RenderTargetAttachment is set since we do
+        // not track all textures in the renderpass.
+        image.setRenderPassUsageFlag(vk::RenderPassUsage::TextureSampler);
+
+        if (image.isDepthOrStencil())
+        {
+            if (image.hasRenderPassUsageFlag(vk::RenderPassUsage::ReadOnlyAttachment))
+            {
+                if (firstShader == gl::ShaderType::Fragment)
+                {
+                    ASSERT(remainingShaderBits.none() && lastShader == firstShader);
+                    return vk::ImageLayout::DSAttachmentReadAndFragmentShaderRead;
+                }
+                return vk::ImageLayout::DSAttachmentReadAndAllShadersRead;
+            }
+
+            return firstShader == gl::ShaderType::Fragment
+                       ? vk::ImageLayout::DSAttachmentWriteAndFragmentShaderRead
+                       : vk::ImageLayout::DSAttachmentWriteAndAllShadersRead;
+        }
+
+        return firstShader == gl::ShaderType::Fragment
+                   ? vk::ImageLayout::ColorAttachmentAndFragmentShaderRead
+                   : vk::ImageLayout::ColorAttachmentAndAllShadersRead;
+    }
+
+    if (image.isDepthOrStencil())
+    {
+        // We always use a depth-stencil read-only layout for any depth Textures to simplify
+        // our implementation's handling of depth-stencil read-only mode. We don't have to
+        // split a RenderPass to transition a depth texture from shader-read to read-only.
+        // This improves performance in Manhattan. Future optimizations are likely possible
+        // here including using specialized barriers without breaking the RenderPass.
+        if (firstShader == gl::ShaderType::Fragment)
+        {
+            ASSERT(remainingShaderBits.none() && lastShader == firstShader);
+            return vk::ImageLayout::DSAttachmentReadAndFragmentShaderRead;
+        }
+        return vk::ImageLayout::DSAttachmentReadAndAllShadersRead;
+    }
+
+    // We barrier against either:
+    // - Vertex only
+    // - Fragment only
+    // - Pre-fragment only (vertex, geometry and tessellation together)
+    if (remainingShaderBits.any() || firstShader != lastShader)
+    {
+        return lastShader == gl::ShaderType::Fragment ? vk::ImageLayout::AllGraphicsShadersReadOnly
+                                                      : vk::ImageLayout::PreFragmentShadersReadOnly;
+    }
+
+    return kShaderReadOnlyImageLayouts[firstShader];
+}
+
+vk::ImageLayout GetImageWriteLayoutAndSubresource(const gl::ImageUnit &imageUnit,
+                                                  vk::ImageHelper &image,
+                                                  gl::ShaderBitSet shaderStages,
+                                                  gl::LevelIndex *levelOut,
+                                                  uint32_t *layerStartOut,
+                                                  uint32_t *layerCountOut)
+{
+    *levelOut = gl::LevelIndex(static_cast<uint32_t>(imageUnit.level));
+
+    *layerStartOut = 0;
+    *layerCountOut = image.getLayerCount();
+    if (imageUnit.layered)
+    {
+        *layerStartOut = imageUnit.layered;
+        *layerCountOut = 1;
+    }
+
+    gl::ShaderType firstShader = shaderStages.first();
+    gl::ShaderType lastShader  = shaderStages.last();
+    shaderStages.reset(firstShader);
+    shaderStages.reset(lastShader);
+    // We barrier against either:
+    // - Vertex only
+    // - Fragment only
+    // - Pre-fragment only (vertex, geometry and tessellation together)
+    if (shaderStages.any() || firstShader != lastShader)
+    {
+        return lastShader == gl::ShaderType::Fragment ? vk::ImageLayout::AllGraphicsShadersWrite
+                                                      : vk::ImageLayout::PreFragmentShadersWrite;
+    }
+
+    return kShaderWriteImageLayouts[firstShader];
+}
+
+void OnTextureBufferRead(ContextVk *contextVk,
+                         BufferVk *bufferVk,
+                         gl::ShaderBitSet stages,
+                         vk::CommandBufferHelperCommon *commandBufferHelper)
+{
+    vk::BufferHelper &buffer = bufferVk->getBuffer();
+
+    ASSERT(stages.any());
+
+    // TODO: accept multiple stages in bufferRead.  http://anglebug.com/3573
+    for (gl::ShaderType stage : stages)
+    {
+        // Note: if another range of the same buffer is simultaneously used for storage,
+        // such as for transform feedback output, or SSBO, unnecessary barriers can be
+        // generated.
+        commandBufferHelper->bufferRead(contextVk, VK_ACCESS_SHADER_READ_BIT,
+                                        vk::GetPipelineStage(stage), &buffer);
+    }
+}
+
+void OnImageBufferWrite(ContextVk *contextVk,
+                        BufferVk *bufferVk,
+                        gl::ShaderBitSet stages,
+                        vk::CommandBufferHelperCommon *commandBufferHelper)
+{
+    vk::BufferHelper &buffer = bufferVk->getBuffer();
+
+    // TODO: accept multiple stages in bufferWrite.  http://anglebug.com/3573
+    for (gl::ShaderType stage : stages)
+    {
+        commandBufferHelper->bufferWrite(
+            contextVk, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
+            vk::GetPipelineStage(stage), vk::AliasingMode::Disallowed, &buffer);
+    }
+}
+
 constexpr angle::PackedEnumMap<RenderPassClosureReason, const char *> kRenderPassClosureReason = {{
     {RenderPassClosureReason::AlreadySpecifiedElsewhere, nullptr},
     {RenderPassClosureReason::ContextDestruction, "Render pass closed due to context destruction"},
@@ -751,8 +898,8 @@
     }
 
     // Recycle current commands buffers.
-    mRenderer->recycleCommandBufferHelper(device, &mOutsideRenderPassCommands);
-    mRenderer->recycleCommandBufferHelper(device, &mRenderPassCommands);
+    mRenderer->recycleOutsideRenderPassCommandBufferHelper(device, &mOutsideRenderPassCommands);
+    mRenderer->recycleRenderPassCommandBufferHelper(device, &mRenderPassCommands);
 
     mRenderer->releaseSharedResources(&mResourceUseList);
 
@@ -761,7 +908,8 @@
     mRenderPassCache.destroy(mRenderer);
     mShaderLibrary.destroy(device);
     mGpuEventQueryPool.destroy(device);
-    mCommandPool.destroy(device);
+    mCommandPools.outsideRenderPassPool.destroy(device);
+    mCommandPools.renderPassPool.destroy(device);
 
     ASSERT(mCurrentGarbage.empty());
     ASSERT(mResourceUseList.empty());
@@ -869,11 +1017,16 @@
     mEmulateSeamfulCubeMapSampling = shouldEmulateSeamfulCubeMapSampling();
 
     // Assign initial command buffers from queue
-    ANGLE_TRY(vk::CommandBuffer::InitializeCommandPool(
-        this, &mCommandPool, mRenderer->getDeviceQueueIndex(), hasProtectedContent()));
-    ANGLE_TRY(
-        mRenderer->getCommandBufferHelper(this, false, &mCommandPool, &mOutsideRenderPassCommands));
-    ANGLE_TRY(mRenderer->getCommandBufferHelper(this, true, &mCommandPool, &mRenderPassCommands));
+    ANGLE_TRY(vk::OutsideRenderPassCommandBuffer::InitializeCommandPool(
+        this, &mCommandPools.outsideRenderPassPool, mRenderer->getDeviceQueueIndex(),
+        hasProtectedContent()));
+    ANGLE_TRY(vk::RenderPassCommandBuffer::InitializeCommandPool(
+        this, &mCommandPools.renderPassPool, mRenderer->getDeviceQueueIndex(),
+        hasProtectedContent()));
+    ANGLE_TRY(mRenderer->getOutsideRenderPassCommandBufferHelper(
+        this, &mCommandPools.outsideRenderPassPool, &mOutsideRenderPassCommands));
+    ANGLE_TRY(mRenderer->getRenderPassCommandBufferHelper(this, &mCommandPools.renderPassPool,
+                                                          &mRenderPassCommands));
 
     if (mGpuEventsEnabled)
     {
@@ -1383,7 +1536,8 @@
     return handleDirtyEventLogImpl(&mOutsideRenderPassCommands->getCommandBuffer());
 }
 
-angle::Result ContextVk::handleDirtyEventLogImpl(vk::CommandBuffer *commandBuffer)
+template <typename CommandBufferT>
+angle::Result ContextVk::handleDirtyEventLogImpl(CommandBufferT *commandBuffer)
 {
     // This method is called when a draw or dispatch command is being processed.  It's purpose is
     // to call the vkCmd*DebugUtilsLabelEXT functions in order to communicate to debuggers
@@ -1597,8 +1751,9 @@
     return angle::Result::Continue;
 }
 
+template <typename CommandBufferHelperT>
 ANGLE_INLINE angle::Result ContextVk::handleDirtyTexturesImpl(
-    vk::CommandBufferHelper *commandBufferHelper,
+    CommandBufferHelperT *commandBufferHelper,
     PipelineType pipelineType)
 {
     const gl::ProgramExecutable *executable = mState.getProgramExecutable();
@@ -1613,134 +1768,28 @@
         // If it's a texture buffer, get the attached buffer.
         if (textureVk->getBuffer().get() != nullptr)
         {
-            BufferVk *bufferVk       = vk::GetImpl(textureVk->getBuffer().get());
-            vk::BufferHelper &buffer = bufferVk->getBuffer();
-
-            gl::ShaderBitSet stages =
+            BufferVk *bufferVk = vk::GetImpl(textureVk->getBuffer().get());
+            const gl::ShaderBitSet stages =
                 executable->getSamplerShaderBitsForTextureUnitIndex(textureUnit);
-            ASSERT(stages.any());
 
-            // TODO: accept multiple stages in bufferRead.  http://anglebug.com/3573
-            for (gl::ShaderType stage : stages)
-            {
-                // Note: if another range of the same buffer is simultaneously used for storage,
-                // such as for transform feedback output, or SSBO, unnecessary barriers can be
-                // generated.
-                commandBufferHelper->bufferRead(this, VK_ACCESS_SHADER_READ_BIT,
-                                                vk::GetPipelineStage(stage), &buffer);
-            }
+            OnTextureBufferRead(this, bufferVk, stages, commandBufferHelper);
 
             textureVk->retainBufferViews(&mResourceUseList);
-
             continue;
         }
 
-        vk::ImageHelper &image = textureVk->getImage();
-
         // The image should be flushed and ready to use at this point. There may still be
         // lingering staged updates in its staging buffer for unused texture mip levels or
         // layers. Therefore we can't verify it has no staged updates right here.
+        vk::ImageHelper &image = textureVk->getImage();
 
         // Select the appropriate vk::ImageLayout depending on whether the texture is also bound as
         // a GL image, and whether the program is a compute or graphics shader.
-        vk::ImageLayout textureLayout;
-        if (textureVk->hasBeenBoundAsImage())
-        {
-            textureLayout = pipelineType == PipelineType::Compute
-                                ? vk::ImageLayout::ComputeShaderWrite
-                                : vk::ImageLayout::AllGraphicsShadersWrite;
-        }
-        else
-        {
-            gl::ShaderBitSet remainingShaderBits =
-                executable->getSamplerShaderBitsForTextureUnitIndex(textureUnit);
-            ASSERT(remainingShaderBits.any());
-            gl::ShaderType firstShader = remainingShaderBits.first();
-            gl::ShaderType lastShader  = remainingShaderBits.last();
-            remainingShaderBits.reset(firstShader);
-            remainingShaderBits.reset(lastShader);
+        const vk::ImageLayout imageLayout =
+            GetImageReadLayout(textureVk, executable, textureUnit, pipelineType);
 
-            if (image.hasRenderPassUsageFlag(vk::RenderPassUsage::RenderTargetAttachment))
-            {
-                // Right now we set this flag only when RenderTargetAttachment is set since we do
-                // not track all textures in the renderpass.
-                image.setRenderPassUsageFlag(vk::RenderPassUsage::TextureSampler);
-
-                if (image.isDepthOrStencil())
-                {
-                    if (image.hasRenderPassUsageFlag(vk::RenderPassUsage::ReadOnlyAttachment))
-                    {
-                        if (firstShader == gl::ShaderType::Fragment)
-                        {
-                            ASSERT(remainingShaderBits.none() && lastShader == firstShader);
-                            textureLayout = vk::ImageLayout::DSAttachmentReadAndFragmentShaderRead;
-                        }
-                        else
-                        {
-                            textureLayout = vk::ImageLayout::DSAttachmentReadAndAllShadersRead;
-                        }
-                    }
-                    else
-                    {
-                        if (firstShader == gl::ShaderType::Fragment)
-                        {
-                            textureLayout = vk::ImageLayout::DSAttachmentWriteAndFragmentShaderRead;
-                        }
-                        else
-                        {
-                            textureLayout = vk::ImageLayout::DSAttachmentWriteAndAllShadersRead;
-                        }
-                    }
-                }
-                else
-                {
-                    if (firstShader == gl::ShaderType::Fragment)
-                    {
-                        textureLayout = vk::ImageLayout::ColorAttachmentAndFragmentShaderRead;
-                    }
-                    else
-                    {
-                        textureLayout = vk::ImageLayout::ColorAttachmentAndAllShadersRead;
-                    }
-                }
-            }
-            else if (image.isDepthOrStencil())
-            {
-                // We always use a depth-stencil read-only layout for any depth Textures to simplify
-                // our implementation's handling of depth-stencil read-only mode. We don't have to
-                // split a RenderPass to transition a depth texture from shader-read to read-only.
-                // This improves performance in Manhattan. Future optimizations are likely possible
-                // here including using specialized barriers without breaking the RenderPass.
-                if (firstShader == gl::ShaderType::Fragment)
-                {
-                    ASSERT(remainingShaderBits.none() && lastShader == firstShader);
-                    textureLayout = vk::ImageLayout::DSAttachmentReadAndFragmentShaderRead;
-                }
-                else
-                {
-                    textureLayout = vk::ImageLayout::DSAttachmentReadAndAllShadersRead;
-                }
-            }
-            else
-            {
-                // We barrier against either:
-                // - Vertex only
-                // - Fragment only
-                // - Pre-fragment only (vertex, geometry and tessellation together)
-                if (remainingShaderBits.any() || firstShader != lastShader)
-                {
-                    textureLayout = lastShader == gl::ShaderType::Fragment
-                                        ? vk::ImageLayout::AllGraphicsShadersReadOnly
-                                        : vk::ImageLayout::PreFragmentShadersReadOnly;
-                }
-                else
-                {
-                    textureLayout = kShaderReadOnlyImageLayouts[firstShader];
-                }
-            }
-        }
         // Ensure the image is in the desired layout
-        commandBufferHelper->imageRead(this, image.getAspectFlags(), textureLayout, &image);
+        commandBufferHelper->imageRead(this, image.getAspectFlags(), imageLayout, &image);
 
         textureVk->retainImageViews(&mResourceUseList);
     }
@@ -1827,8 +1876,8 @@
     return angle::Result::Continue;
 }
 
-ANGLE_INLINE angle::Result ContextVk::handleDirtyShaderResourcesImpl(
-    vk::CommandBufferHelper *commandBufferHelper)
+template <typename CommandBufferHelperT>
+angle::Result ContextVk::handleDirtyShaderResourcesImpl(CommandBufferHelperT *commandBufferHelper)
 {
     const gl::ProgramExecutable *executable = mState.getProgramExecutable();
     ASSERT(executable);
@@ -1849,6 +1898,27 @@
         ANGLE_TRY(updateActiveImages(commandBufferHelper));
     }
 
+    handleDirtyShaderBufferResourcesImpl(commandBufferHelper);
+
+    ANGLE_TRY(mExecutable->updateShaderResourcesDescriptorSet(this, mDrawFramebuffer,
+                                                              mShaderBuffersDescriptorDesc));
+
+    // Record usage of storage buffers and images in the command buffer to aid handling of
+    // glMemoryBarrier.
+    if (hasImages || hasStorageBuffers)
+    {
+        commandBufferHelper->setHasShaderStorageOutput();
+    }
+
+    return angle::Result::Continue;
+}
+
+void ContextVk::handleDirtyShaderBufferResourcesImpl(
+    vk::CommandBufferHelperCommon *commandBufferHelper)
+{
+    const gl::ProgramExecutable *executable = mState.getProgramExecutable();
+    ASSERT(executable);
+
     // Process buffer barriers.
     gl::ShaderMap<const gl::ProgramState *> programStates;
     mExecutable->fillProgramStateMap(this, &programStates);
@@ -1925,18 +1995,6 @@
                 vk::GetPipelineStage(shaderType), vk::AliasingMode::Allowed, &bufferHelper);
         }
     }
-
-    ANGLE_TRY(mExecutable->updateShaderResourcesDescriptorSet(
-        this, mDrawFramebuffer, mShaderBuffersDescriptorDesc, commandBufferHelper));
-
-    // Record usage of storage buffers and images in the command buffer to aid handling of
-    // glMemoryBarrier.
-    if (hasImages || hasStorageBuffers)
-    {
-        commandBufferHelper->setHasShaderStorageOutput();
-    }
-
-    return angle::Result::Continue;
 }
 
 angle::Result ContextVk::handleDirtyGraphicsShaderResources(DirtyBits::Iterator *dirtyBitsIterator,
@@ -2122,12 +2180,13 @@
                                          PipelineType::Compute);
 }
 
-angle::Result ContextVk::handleDirtyDescriptorSetsImpl(vk::CommandBuffer *commandBuffer,
+template <typename CommandBufferT>
+angle::Result ContextVk::handleDirtyDescriptorSetsImpl(CommandBufferT *commandBuffer,
                                                        PipelineType pipelineType)
 {
     // When using Vulkan secondary command buffers, the descriptor sets need to be updated before
     // they are bound.
-    if (!vk::CommandBuffer::ExecutesInline())
+    if (!CommandBufferT::ExecutesInline())
     {
         flushDescriptorSetUpdates();
     }
@@ -2232,7 +2291,7 @@
     }
 }
 
-void ContextVk::addOverlayUsedBuffersCount(vk::CommandBufferHelper *commandBuffer)
+void ContextVk::addOverlayUsedBuffersCount(vk::CommandBufferHelperCommon *commandBuffer)
 {
     const gl::OverlayType *overlay = mState.getOverlay();
     if (!overlay->isEnabled())
@@ -2263,7 +2322,7 @@
         }
     }
 
-    if (vk::CommandBufferHelper::kEnableCommandStreamDiagnostics)
+    if (vk::CommandBufferHelperCommon::kEnableCommandStreamDiagnostics)
     {
         dumpCommandStreamDiagnostics();
     }
@@ -2273,7 +2332,7 @@
                                      std::move(mWaitSemaphores),
                                      std::move(mWaitSemaphoreStageMasks), signalSemaphore,
                                      getShareGroupVk()->releaseResourceUseLists(),
-                                     std::move(mCurrentGarbage), &mCommandPool, submitSerialOut));
+                                     std::move(mCurrentGarbage), &mCommandPools, submitSerialOut));
 
     onRenderPassFinished(RenderPassClosureReason::AlreadySpecifiedElsewhere);
     mComputeDirtyBits |= mNewComputeCommandBufferDirtyBits;
@@ -2358,9 +2417,6 @@
     //
     // Note: Once VK_EXT_calibrated_timestamps is ubiquitous, this should be redone.
 
-    // Make sure nothing is running
-    ASSERT(!hasRecordedCommands());
-
     ANGLE_TRACE_EVENT0("gpu.angle", "ContextVk::synchronizeCpuGpuTime");
 
     // Create a query used to receive the GPU timestamp
@@ -2486,7 +2542,7 @@
     return angle::Result::Continue;
 }
 
-angle::Result ContextVk::traceGpuEventImpl(vk::CommandBuffer *commandBuffer,
+angle::Result ContextVk::traceGpuEventImpl(vk::OutsideRenderPassCommandBuffer *commandBuffer,
                                            char phase,
                                            const EventName &name)
 {
@@ -3167,10 +3223,14 @@
     VkDebugUtilsLabelEXT label;
     vk::MakeDebugUtilsLabel(source, marker, &label);
 
-    vk::CommandBuffer *commandBuffer = hasStartedRenderPass()
-                                           ? mRenderPassCommandBuffer
-                                           : &mOutsideRenderPassCommands->getCommandBuffer();
-    commandBuffer->insertDebugUtilsLabelEXT(label);
+    if (hasStartedRenderPass())
+    {
+        mRenderPassCommandBuffer->insertDebugUtilsLabelEXT(label);
+    }
+    else
+    {
+        mOutsideRenderPassCommands->getCommandBuffer().insertDebugUtilsLabelEXT(label);
+    }
 }
 
 angle::Result ContextVk::pushGroupMarker(GLsizei length, const char *marker)
@@ -3206,10 +3266,14 @@
     VkDebugUtilsLabelEXT label;
     vk::MakeDebugUtilsLabel(source, message, &label);
 
-    vk::CommandBuffer *commandBuffer = hasStartedRenderPass()
-                                           ? mRenderPassCommandBuffer
-                                           : &mOutsideRenderPassCommands->getCommandBuffer();
-    commandBuffer->beginDebugUtilsLabelEXT(label);
+    if (hasStartedRenderPass())
+    {
+        mRenderPassCommandBuffer->beginDebugUtilsLabelEXT(label);
+    }
+    else
+    {
+        mOutsideRenderPassCommands->getCommandBuffer().beginDebugUtilsLabelEXT(label);
+    }
 
     return angle::Result::Continue;
 }
@@ -3221,10 +3285,14 @@
         return angle::Result::Continue;
     }
 
-    vk::CommandBuffer *commandBuffer = hasStartedRenderPass()
-                                           ? mRenderPassCommandBuffer
-                                           : &mOutsideRenderPassCommands->getCommandBuffer();
-    commandBuffer->endDebugUtilsLabelEXT();
+    if (hasStartedRenderPass())
+    {
+        mRenderPassCommandBuffer->endDebugUtilsLabelEXT();
+    }
+    else
+    {
+        mOutsideRenderPassCommands->getCommandBuffer().endDebugUtilsLabelEXT();
+    }
 
     return angle::Result::Continue;
 }
@@ -3270,16 +3338,15 @@
         return;
     }
 
-    vk::CommandBuffer *commandBuffer = nullptr;
     switch (mQueryEventType)
     {
         case GraphicsEventCmdBuf::InOutsideCmdBufQueryCmd:
             ASSERT(mOutsideRenderPassCommands);
-            commandBuffer = &mOutsideRenderPassCommands->getCommandBuffer();
+            mOutsideRenderPassCommands->getCommandBuffer().endDebugUtilsLabelEXT();
             break;
         case GraphicsEventCmdBuf::InRenderPassCmdBufQueryCmd:
             ASSERT(mRenderPassCommands);
-            commandBuffer = &mRenderPassCommands->getCommandBuffer();
+            mRenderPassCommands->getCommandBuffer().endDebugUtilsLabelEXT();
             break;
         case GraphicsEventCmdBuf::NotInQueryCmd:
             // The glClear* or gl*Query* command was noop'd or otherwise ended early.  We could
@@ -3290,7 +3357,6 @@
         default:
             UNREACHABLE();
     }
-    commandBuffer->endDebugUtilsLabelEXT();
 
     mQueryEventType = GraphicsEventCmdBuf::NotInQueryCmd;
 }
@@ -3311,21 +3377,18 @@
 
     mQueryEventType = queryEventType;
 
-    vk::CommandBuffer *commandBuffer = nullptr;
     switch (mQueryEventType)
     {
         case GraphicsEventCmdBuf::InOutsideCmdBufQueryCmd:
             ASSERT(mOutsideRenderPassCommands);
-            commandBuffer = &mOutsideRenderPassCommands->getCommandBuffer();
-            break;
+            return handleDirtyEventLogImpl(&mOutsideRenderPassCommands->getCommandBuffer());
         case GraphicsEventCmdBuf::InRenderPassCmdBufQueryCmd:
             ASSERT(mRenderPassCommands);
-            commandBuffer = &mRenderPassCommands->getCommandBuffer();
-            break;
+            return handleDirtyEventLogImpl(&mRenderPassCommands->getCommandBuffer());
         default:
             UNREACHABLE();
+            return angle::Result::Stop;
     }
-    return handleDirtyEventLogImpl(commandBuffer);
 }
 
 bool ContextVk::isViewportFlipEnabledForDrawFBO() const
@@ -5113,7 +5176,8 @@
                                              PipelineType::Compute);
 }
 
-void ContextVk::handleDirtyDriverUniformsBindingImpl(vk::CommandBuffer *commandBuffer,
+template <typename CommandBufferT>
+void ContextVk::handleDirtyDriverUniformsBindingImpl(CommandBufferT *commandBuffer,
                                                      VkPipelineBindPoint bindPoint,
                                                      DriverUniformsDescriptorSet *driverUniforms)
 {
@@ -5399,7 +5463,8 @@
     return angle::Result::Continue;
 }
 
-angle::Result ContextVk::updateActiveImages(vk::CommandBufferHelper *commandBufferHelper)
+template <typename CommandBufferHelperT>
+angle::Result ContextVk::updateActiveImages(CommandBufferHelperT *commandBufferHelper)
 {
     const gl::State &glState                = mState;
     const gl::ProgramExecutable *executable = glState.getProgramExecutable();
@@ -5440,19 +5505,11 @@
         // Special handling of texture buffers.  They have a buffer attached instead of an image.
         if (texture->getType() == gl::TextureType::Buffer)
         {
-            BufferVk *bufferVk       = vk::GetImpl(textureVk->getBuffer().get());
-            vk::BufferHelper &buffer = bufferVk->getBuffer();
+            BufferVk *bufferVk = vk::GetImpl(textureVk->getBuffer().get());
 
-            // TODO: accept multiple stages in bufferWrite.  http://anglebug.com/3573
-            for (gl::ShaderType stage : shaderStages)
-            {
-                commandBufferHelper->bufferWrite(
-                    this, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
-                    vk::GetPipelineStage(stage), vk::AliasingMode::Disallowed, &buffer);
-            }
+            OnImageBufferWrite(this, bufferVk, shaderStages, commandBufferHelper);
 
             textureVk->retainBufferViews(&mResourceUseList);
-
             continue;
         }
 
@@ -5464,50 +5521,20 @@
         }
         alreadyProcessed.insert(image);
 
-        vk::ImageLayout imageLayout;
-        gl::ShaderType firstShader = shaderStages.first();
-        gl::ShaderType lastShader  = shaderStages.last();
-        shaderStages.reset(firstShader);
-        shaderStages.reset(lastShader);
-        // We barrier against either:
-        // - Vertex only
-        // - Fragment only
-        // - Pre-fragment only (vertex, geometry and tessellation together)
-        if (shaderStages.any() || firstShader != lastShader)
-        {
-            imageLayout = lastShader == gl::ShaderType::Fragment
-                              ? vk::ImageLayout::AllGraphicsShadersWrite
-                              : vk::ImageLayout::PreFragmentShadersWrite;
-        }
-        else
-        {
-            imageLayout = kShaderWriteImageLayouts[firstShader];
-        }
+        gl::LevelIndex level;
+        uint32_t layerStart               = 0;
+        uint32_t layerCount               = 0;
+        const vk::ImageLayout imageLayout = GetImageWriteLayoutAndSubresource(
+            imageUnit, *image, shaderStages, &level, &layerStart, &layerCount);
 
-        VkImageAspectFlags aspectFlags = image->getAspectFlags();
-
-        uint32_t layerStart = 0;
-        uint32_t layerCount = image->getLayerCount();
-        if (imageUnit.layered)
-        {
-            layerStart = imageUnit.layered;
-            layerCount = 1;
-        }
-
-        commandBufferHelper->imageWrite(
-            this, gl::LevelIndex(static_cast<uint32_t>(imageUnit.level)), layerStart, layerCount,
-            aspectFlags, imageLayout, vk::AliasingMode::Allowed, image);
+        commandBufferHelper->imageWrite(this, level, layerStart, layerCount,
+                                        image->getAspectFlags(), imageLayout,
+                                        vk::AliasingMode::Allowed, image);
     }
 
     return angle::Result::Continue;
 }
 
-bool ContextVk::hasRecordedCommands()
-{
-    ASSERT(mOutsideRenderPassCommands && mRenderPassCommands);
-    return !mOutsideRenderPassCommands->empty() || mRenderPassCommands->started();
-}
-
 angle::Result ContextVk::flushImpl(const vk::Semaphore *signalSemaphore,
                                    RenderPassClosureReason renderPassClosureReason)
 {
@@ -5640,11 +5667,6 @@
     mWaitSemaphoreStageMasks.push_back(stageMask);
 }
 
-const vk::CommandPool &ContextVk::getCommandPool() const
-{
-    return mCommandPool;
-}
-
 bool ContextVk::isSerialInUse(Serial serial) const
 {
     return serial > getLastCompletedQueueSerial();
@@ -5843,7 +5865,7 @@
     const vk::PackedAttachmentCount colorAttachmentCount,
     const vk::PackedAttachmentIndex depthStencilAttachmentIndex,
     const vk::PackedClearValuesArray &clearValues,
-    vk::CommandBuffer **commandBufferOut)
+    vk::RenderPassCommandBuffer **commandBufferOut)
 {
     // Next end any currently outstanding render pass.  The render pass is normally closed before
     // reaching here for various reasons, except typically when UtilsVk needs to start one.
@@ -5856,7 +5878,7 @@
 }
 
 angle::Result ContextVk::startRenderPass(gl::Rectangle renderArea,
-                                         vk::CommandBuffer **commandBufferOut,
+                                         vk::RenderPassCommandBuffer **commandBufferOut,
                                          bool *renderPassDescChangedOut)
 {
     ASSERT(mDrawFramebuffer == vk::GetImpl(mState.getDrawFramebuffer()));
@@ -5977,7 +5999,7 @@
 
     ANGLE_TRY(mRenderPassCommands->endRenderPass(this));
 
-    if (vk::CommandBufferHelper::kEnableCommandStreamDiagnostics)
+    if (vk::CommandBufferHelperCommon::kEnableCommandStreamDiagnostics)
     {
         mRenderPassCommands->addCommandDiagnostics(this);
     }
@@ -6125,7 +6147,7 @@
 
     addOverlayUsedBuffersCount(mOutsideRenderPassCommands);
 
-    if (vk::CommandBufferHelper::kEnableCommandStreamDiagnostics)
+    if (vk::CommandBufferHelperCommon::kEnableCommandStreamDiagnostics)
     {
         mOutsideRenderPassCommands->addCommandDiagnostics(this);
     }
@@ -6452,7 +6474,8 @@
 {
     ANGLE_TRY(flushCommandBuffersIfNecessary(access));
 
-    vk::CommandBuffer *commandBuffer = &mOutsideRenderPassCommands->getCommandBuffer();
+    vk::OutsideRenderPassCommandBuffer *commandBuffer =
+        &mOutsideRenderPassCommands->getCommandBuffer();
 
     for (const vk::CommandBufferImageAccess &imageAccess : access.getReadImages())
     {
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.h b/src/libANGLE/renderer/vulkan/ContextVk.h
index 87f034f..3890ec8 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.h
+++ b/src/libANGLE/renderer/vulkan/ContextVk.h
@@ -446,8 +446,6 @@
 
     void addWaitSemaphore(VkSemaphore semaphore, VkPipelineStageFlags stageMask);
 
-    const vk::CommandPool &getCommandPool() const;
-
     Serial getLastCompletedQueueSerial() const { return mRenderer->getLastCompletedQueueSerial(); }
 
     bool isSerialInUse(Serial serial) const;
@@ -481,7 +479,7 @@
     // Create Begin/End/Instant GPU trace events, which take their timestamps from GPU queries.
     // The events are queued until the query results are available.  Possible values for `phase`
     // are TRACE_EVENT_PHASE_*
-    ANGLE_INLINE angle::Result traceGpuEvent(vk::CommandBuffer *commandBuffer,
+    ANGLE_INLINE angle::Result traceGpuEvent(vk::OutsideRenderPassCommandBuffer *commandBuffer,
                                              char phase,
                                              const EventName &name)
     {
@@ -555,10 +553,10 @@
         }
     }
 
-    angle::Result getOutsideRenderPassCommandBuffer(const vk::CommandBufferAccess &access,
-                                                    vk::CommandBuffer **commandBufferOut)
+    angle::Result getOutsideRenderPassCommandBuffer(
+        const vk::CommandBufferAccess &access,
+        vk::OutsideRenderPassCommandBuffer **commandBufferOut)
     {
-        ASSERT(!mOutsideRenderPassCommands->hasRenderPass());
         ANGLE_TRY(onResourceAccess(access));
         *commandBufferOut = &mOutsideRenderPassCommands->getCommandBuffer();
         return angle::Result::Continue;
@@ -571,7 +569,7 @@
                                      const vk::PackedAttachmentCount colorAttachmentCount,
                                      const vk::PackedAttachmentIndex depthStencilAttachmentIndex,
                                      const vk::PackedClearValuesArray &clearValues,
-                                     vk::CommandBuffer **commandBufferOut);
+                                     vk::RenderPassCommandBuffer **commandBufferOut);
 
     // Only returns true if we have a started RP and we've run setupDraw.
     bool hasStartedRenderPass() const
@@ -591,7 +589,7 @@
         return hasStartedRenderPass() && !mRenderPassCommands->getCommandBuffer().empty();
     }
 
-    vk::CommandBufferHelper &getStartedRenderPassCommands()
+    vk::RenderPassCommandBufferHelper &getStartedRenderPassCommands()
     {
         ASSERT(mRenderPassCommands->started());
         return *mRenderPassCommands;
@@ -605,7 +603,7 @@
 
     egl::ContextPriority getContextPriority() const override { return mContextPriority; }
     angle::Result startRenderPass(gl::Rectangle renderArea,
-                                  vk::CommandBuffer **commandBufferOut,
+                                  vk::RenderPassCommandBuffer **commandBufferOut,
                                   bool *renderPassDescChangedOut);
     void startNextSubpass();
     angle::Result flushCommandsAndEndRenderPass(RenderPassClosureReason reason);
@@ -644,7 +642,7 @@
 
     void syncObjectPerfCounters();
     void updateOverlayOnPresent();
-    void addOverlayUsedBuffersCount(vk::CommandBufferHelper *commandBuffer);
+    void addOverlayUsedBuffersCount(vk::CommandBufferHelperCommon *commandBuffer);
 
     // DescriptorSet writes
     VkDescriptorBufferInfo *allocDescriptorBufferInfos(size_t count);
@@ -836,7 +834,8 @@
     void updateSurfaceRotationReadFramebuffer(const gl::State &glState);
 
     angle::Result updateActiveTextures(const gl::Context *context, gl::Command command);
-    angle::Result updateActiveImages(vk::CommandBufferHelper *commandBufferHelper);
+    template <typename CommandBufferHelperT>
+    angle::Result updateActiveImages(CommandBufferHelperT *commandBufferHelper);
     angle::Result updateDefaultAttribute(size_t attribIndex);
 
     ANGLE_INLINE void invalidateCurrentGraphicsPipeline()
@@ -917,14 +916,20 @@
     // Common parts of the common dirty bit handlers.
     angle::Result handleDirtyMemoryBarrierImpl(DirtyBits::Iterator *dirtyBitsIterator,
                                                DirtyBits dirtyBitMask);
-    angle::Result handleDirtyEventLogImpl(vk::CommandBuffer *commandBuffer);
-    angle::Result handleDirtyTexturesImpl(vk::CommandBufferHelper *commandBufferHelper,
+    template <typename CommandBufferT>
+    angle::Result handleDirtyEventLogImpl(CommandBufferT *commandBuffer);
+    template <typename CommandBufferHelperT>
+    angle::Result handleDirtyTexturesImpl(CommandBufferHelperT *commandBufferHelper,
                                           PipelineType pipelineType);
-    angle::Result handleDirtyShaderResourcesImpl(vk::CommandBufferHelper *commandBufferHelper);
-    void handleDirtyDriverUniformsBindingImpl(vk::CommandBuffer *commandBuffer,
+    template <typename CommandBufferHelperT>
+    angle::Result handleDirtyShaderResourcesImpl(CommandBufferHelperT *commandBufferHelper);
+    void handleDirtyShaderBufferResourcesImpl(vk::CommandBufferHelperCommon *commandBufferHelper);
+    template <typename CommandBufferT>
+    void handleDirtyDriverUniformsBindingImpl(CommandBufferT *commandBuffer,
                                               VkPipelineBindPoint bindPoint,
                                               DriverUniformsDescriptorSet *driverUniforms);
-    angle::Result handleDirtyDescriptorSetsImpl(vk::CommandBuffer *commandBuffer,
+    template <typename CommandBufferT>
+    angle::Result handleDirtyDescriptorSetsImpl(CommandBufferT *commandBuffer,
                                                 PipelineType pipelineType);
     void handleDirtyGraphicsScissorImpl(bool isPrimitivesGeneratedQueryActive);
 
@@ -941,7 +946,7 @@
     angle::Result submitFrame(const vk::Semaphore *signalSemaphore, Serial *submitSerialOut);
 
     angle::Result synchronizeCpuGpuTime();
-    angle::Result traceGpuEventImpl(vk::CommandBuffer *commandBuffer,
+    angle::Result traceGpuEventImpl(vk::OutsideRenderPassCommandBuffer *commandBuffer,
                                     char phase,
                                     const EventName &name);
     angle::Result checkCompletedGpuEvents();
@@ -949,7 +954,6 @@
     void handleDeviceLost();
     bool shouldEmulateSeamfulCubeMapSampling() const;
     void clearAllGarbage();
-    bool hasRecordedCommands();
     void dumpCommandStreamDiagnostics();
     angle::Result flushOutsideRenderPassCommands();
     // Flush commands and end render pass without setting any dirty bits.
@@ -1018,7 +1022,7 @@
     std::array<GraphicsDirtyBitHandler, DIRTY_BIT_MAX> mGraphicsDirtyBitHandlers;
     std::array<ComputeDirtyBitHandler, DIRTY_BIT_MAX> mComputeDirtyBitHandlers;
 
-    vk::CommandBuffer *mRenderPassCommandBuffer;
+    vk::RenderPassCommandBuffer *mRenderPassCommandBuffer;
 
     vk::PipelineHelper *mCurrentGraphicsPipeline;
     vk::PipelineHelper *mCurrentComputePipeline;
@@ -1130,14 +1134,14 @@
     gl::AttribArray<vk::DynamicBuffer> mDefaultAttribBuffers;
 
     // We use a single pool for recording commands. We also keep a free list for pool recycling.
-    vk::CommandPool mCommandPool;
+    vk::SecondaryCommandPools mCommandPools;
 
     vk::GarbageList mCurrentGarbage;
 
     RenderPassCache mRenderPassCache;
 
-    vk::CommandBufferHelper *mOutsideRenderPassCommands;
-    vk::CommandBufferHelper *mRenderPassCommands;
+    vk::OutsideRenderPassCommandBufferHelper *mOutsideRenderPassCommands;
+    vk::RenderPassCommandBufferHelper *mRenderPassCommands;
 
     // The following is used when creating debug-util markers for graphics debuggers (e.g. AGI).  A
     // given gl{Begin|End}Query command may result in commands being submitted to the outside or
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index 451fafc..642f135 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -859,7 +859,7 @@
     access.onImageTransferRead(imageAspectMask, srcImage);
     access.onImageTransferWrite(drawRenderTarget->getLevelIndex(), 1,
                                 drawRenderTarget->getLayerIndex(), 1, imageAspectMask, dstImage);
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkImageBlit blit               = {};
@@ -1405,8 +1405,9 @@
     ANGLE_TRY(drawRenderTarget->getImageView(contextVk, &resolveImageView));
     vk::Framebuffer *newSrcFramebuffer = nullptr;
     ANGLE_TRY(srcFramebufferVk->getFramebuffer(contextVk, &newSrcFramebuffer, resolveImageView));
-    // 2. Update the CommandBufferHelper with the new framebuffer and render pass
-    vk::CommandBufferHelper &commandBufferHelper = contextVk->getStartedRenderPassCommands();
+    // 2. Update the RenderPassCommandBufferHelper with the new framebuffer and render pass
+    vk::RenderPassCommandBufferHelper &commandBufferHelper =
+        contextVk->getStartedRenderPassCommands();
     commandBufferHelper.updateRenderPassForResolve(contextVk, newSrcFramebuffer,
                                                    srcFramebufferVk->getRenderPassDesc());
 
@@ -1438,7 +1439,7 @@
                                     &dstImage);
     }
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkImageResolve resolveRegion                = {};
@@ -2329,7 +2330,7 @@
 }
 
 angle::Result FramebufferVk::clearWithCommand(ContextVk *contextVk,
-                                              vk::CommandBufferHelper *renderpassCommands,
+                                              vk::RenderPassCommandBufferHelper *renderpassCommands,
                                               const gl::Rectangle &scissoredRenderArea)
 {
     // Clear is not affected by viewport, so ContextVk::updateScissor may have decided on a smaller
@@ -2380,11 +2381,11 @@
         updateRenderPassReadOnlyDepthMode(contextVk, renderpassCommands);
     }
 
-    VkClearRect rect                           = {};
-    rect.rect.extent.width                     = scissoredRenderArea.width;
-    rect.rect.extent.height                    = scissoredRenderArea.height;
-    rect.layerCount                            = mCurrentFramebufferDesc.getLayerCount();
-    vk::CommandBuffer *renderPassCommandBuffer = &renderpassCommands->getCommandBuffer();
+    VkClearRect rect                                     = {};
+    rect.rect.extent.width                               = scissoredRenderArea.width;
+    rect.rect.extent.height                              = scissoredRenderArea.height;
+    rect.layerCount                                      = mCurrentFramebufferDesc.getLayerCount();
+    vk::RenderPassCommandBuffer *renderPassCommandBuffer = &renderpassCommands->getCommandBuffer();
 
     renderPassCommandBuffer->clearAttachments(static_cast<uint32_t>(attachments.size()),
                                               attachments.data(), 1, &rect);
@@ -2402,7 +2403,7 @@
 
 angle::Result FramebufferVk::startNewRenderPass(ContextVk *contextVk,
                                                 const gl::Rectangle &scissoredRenderArea,
-                                                vk::CommandBuffer **commandBufferOut,
+                                                vk::RenderPassCommandBuffer **commandBufferOut,
                                                 bool *renderPassDescChangedOut)
 {
     ANGLE_TRY(contextVk->flushCommandsAndEndRenderPass(RenderPassClosureReason::NewRenderPass));
@@ -2817,7 +2818,7 @@
 }
 
 void FramebufferVk::updateRenderPassReadOnlyDepthMode(ContextVk *contextVk,
-                                                      vk::CommandBufferHelper *renderPass)
+                                                      vk::RenderPassCommandBufferHelper *renderPass)
 {
     bool readOnlyDepthStencilMode =
         getDepthStencilRenderTarget() && !getDepthStencilRenderTarget()->hasResolveAttachment() &&
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.h b/src/libANGLE/renderer/vulkan/FramebufferVk.h
index 5731c07..135a77f 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.h
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.h
@@ -138,7 +138,7 @@
 
     angle::Result startNewRenderPass(ContextVk *contextVk,
                                      const gl::Rectangle &scissoredRenderArea,
-                                     vk::CommandBuffer **commandBufferOut,
+                                     vk::RenderPassCommandBuffer **commandBufferOut,
                                      bool *renderPassDescChangedOut);
 
     GLint getSamples() const;
@@ -157,7 +157,7 @@
     }
     bool isReadOnlyDepthFeedbackLoopMode() const { return mReadOnlyDepthFeedbackLoopMode; }
     void updateRenderPassReadOnlyDepthMode(ContextVk *contextVk,
-                                           vk::CommandBufferHelper *renderPass);
+                                           vk::RenderPassCommandBufferHelper *renderPass);
 
     void onSwitchProgramFramebufferFetch(ContextVk *contextVk, bool programUsesFramebufferFetch);
 
@@ -211,7 +211,7 @@
                                 const VkClearDepthStencilValue &clearDepthStencilValue);
     void redeferClears(ContextVk *contextVk);
     angle::Result clearWithCommand(ContextVk *contextVk,
-                                   vk::CommandBufferHelper *renderpassCommands,
+                                   vk::RenderPassCommandBufferHelper *renderpassCommands,
                                    const gl::Rectangle &scissoredRenderArea);
     void updateActiveColorMasks(size_t colorIndex, bool r, bool g, bool b, bool a);
     void updateRenderPassDesc(ContextVk *contextVk);
diff --git a/src/libANGLE/renderer/vulkan/OverlayVk.cpp b/src/libANGLE/renderer/vulkan/OverlayVk.cpp
index 4dfdeb9..a51b173 100644
--- a/src/libANGLE/renderer/vulkan/OverlayVk.cpp
+++ b/src/libANGLE/renderer/vulkan/OverlayVk.cpp
@@ -132,7 +132,7 @@
     access.onBufferTransferRead(&fontDataBuffer.get());
     access.onImageTransferWrite(gl::LevelIndex(0), 1, 0, gl::overlay::kFontCount,
                                 VK_IMAGE_ASPECT_COLOR_BIT, &mFontImage);
-    vk::CommandBuffer *fontDataUpload;
+    vk::OutsideRenderPassCommandBuffer *fontDataUpload;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &fontDataUpload));
 
     VkBufferImageCopy copy           = {};
diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
index 10f763c..b868bec 100644
--- a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
@@ -1551,8 +1551,7 @@
 angle::Result ProgramExecutableVk::updateShaderResourcesDescriptorSet(
     ContextVk *contextVk,
     FramebufferVk *framebufferVk,
-    const vk::ShaderBuffersDescriptorDesc &shaderBuffersDesc,
-    vk::CommandBufferHelper *commandBufferHelper)
+    const vk::ShaderBuffersDescriptorDesc &shaderBuffersDesc)
 {
     const gl::ProgramExecutable *executable = contextVk->getState().getProgramExecutable();
     ASSERT(executable);
@@ -1884,8 +1883,9 @@
     return angle::Result::Continue;
 }
 
+template <typename CommandBufferT>
 angle::Result ProgramExecutableVk::updateDescriptorSets(ContextVk *contextVk,
-                                                        vk::CommandBuffer *commandBuffer,
+                                                        CommandBufferT *commandBuffer,
                                                         PipelineType pipelineType)
 {
     // Can probably use better dirty bits here.
@@ -1970,6 +1970,15 @@
     return angle::Result::Continue;
 }
 
+template angle::Result ProgramExecutableVk::updateDescriptorSets<vk::priv::SecondaryCommandBuffer>(
+    ContextVk *contextVk,
+    vk::priv::SecondaryCommandBuffer *commandBuffer,
+    PipelineType pipelineType);
+template angle::Result ProgramExecutableVk::updateDescriptorSets<vk::VulkanSecondaryCommandBuffer>(
+    ContextVk *contextVk,
+    vk::VulkanSecondaryCommandBuffer *commandBuffer,
+    PipelineType pipelineType);
+
 // Requires that trace is enabled to see the output, which is supported with is_debug=true
 void ProgramExecutableVk::outputCumulativePerfCounters()
 {
diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
index 398150e..e909181 100644
--- a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
@@ -161,8 +161,7 @@
     angle::Result updateShaderResourcesDescriptorSet(
         ContextVk *contextVk,
         FramebufferVk *framebufferVk,
-        const vk::ShaderBuffersDescriptorDesc &shaderBuffersDesc,
-        vk::CommandBufferHelper *commandBufferHelper);
+        const vk::ShaderBuffersDescriptorDesc &shaderBuffersDesc);
     angle::Result updateTransformFeedbackDescriptorSet(
         const gl::ProgramState &programState,
         gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks,
@@ -174,8 +173,9 @@
                                                      ContextVk *contextVk,
                                                      FramebufferVk *framebufferVk);
 
+    template <typename CommandBufferT>
     angle::Result updateDescriptorSets(ContextVk *contextVk,
-                                       vk::CommandBuffer *commandBuffer,
+                                       CommandBufferT *commandBuffer,
                                        PipelineType pipelineType);
 
     void updateEarlyFragmentTestsOptimization(ContextVk *contextVk);
diff --git a/src/libANGLE/renderer/vulkan/README.md b/src/libANGLE/renderer/vulkan/README.md
index 684be1f..b515c01 100644
--- a/src/libANGLE/renderer/vulkan/README.md
+++ b/src/libANGLE/renderer/vulkan/README.md
@@ -63,7 +63,7 @@
     access.onBufferTransferRead(srcBuffer);
 
     // Get a pointer to a secondary command buffer for command recording.
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     // Record the copy command into the secondary buffer. We're done!
diff --git a/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp b/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
index a0fb084..840cabe 100644
--- a/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
@@ -174,7 +174,7 @@
     uint32_t rendererQueueFamilyIndex = contextVk->getRenderer()->getQueueFamilyIndex();
     if (mImage->isQueueChangeNeccesary(rendererQueueFamilyIndex))
     {
-        vk::CommandBuffer *commandBuffer;
+        vk::OutsideRenderPassCommandBuffer *commandBuffer;
         ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
         mImage->changeLayoutAndQueue(contextVk, aspect, vk::ImageLayout::ColorAttachment,
                                      rendererQueueFamilyIndex, commandBuffer);
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index 2d74730..10198e2 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -1075,7 +1075,8 @@
     mYuvConversionCache.destroy(this);
     mVkFormatDescriptorCountMap.clear();
 
-    mCommandBufferRecycler.onDestroy();
+    mOutsideRenderPassCommandBufferRecycler.onDestroy();
+    mRenderPassCommandBufferRecycler.onDestroy();
 
     mBufferMemoryAllocator.destroy(this);
     mAllocator.destroy();
@@ -2097,7 +2098,8 @@
     // Used to support EXT_blend_func_extended
     mEnabledFeatures.features.dualSrcBlend = mPhysicalDeviceFeatures.dualSrcBlend;
 
-    if (!vk::CommandBuffer::ExecutesInline())
+    if (!vk::OutsideRenderPassCommandBuffer::ExecutesInline() ||
+        !vk::RenderPassCommandBuffer::ExecutesInline())
     {
         mEnabledFeatures.features.inheritedQueries = mPhysicalDeviceFeatures.inheritedQueries;
     }
@@ -3006,11 +3008,12 @@
     ApplyFeatureOverrides(&mFeatures, displayVk->getState());
 
     // Disable async command queue when using Vulkan secondary command buffers temporarily to avoid
-    // threading hazards with ContextVk::mCommandPool.  Note that this is done even if the feature
+    // threading hazards with ContextVk::mCommandPools.  Note that this is done even if the feature
     // is enabled through an override.
     // TODO: Investigate whether async command queue is useful with Vulkan secondary command buffers
     // and enable the feature.  http://anglebug.com/6811
-    if (!vk::CommandBuffer::ExecutesInline())
+    if (!vk::OutsideRenderPassCommandBuffer::ExecutesInline() ||
+        !vk::RenderPassCommandBuffer::ExecutesInline())
     {
         mFeatures.asyncCommandQueue.enabled = false;
     }
@@ -3515,20 +3518,24 @@
                                       const vk::Semaphore *signalSemaphore,
                                       std::vector<vk::ResourceUseList> &&resourceUseLists,
                                       vk::GarbageList &&currentGarbage,
-                                      vk::CommandPool *commandPool,
+                                      vk::SecondaryCommandPools *commandPools,
                                       Serial *submitSerialOut)
 {
     std::lock_guard<std::mutex> commandQueueLock(mCommandQueueMutex);
 
+    vk::SecondaryCommandBufferList commandBuffersToReset = {
+        std::move(mOutsideRenderPassCommandBufferRecycler.releaseCommandBuffersToReset()),
+        std::move(mRenderPassCommandBufferRecycler.releaseCommandBuffersToReset()),
+    };
+
     if (isAsyncCommandQueueEnabled())
     {
         *submitSerialOut = mCommandProcessor.reserveSubmitSerial();
 
         ANGLE_TRY(mCommandProcessor.submitFrame(
             context, hasProtectedContent, contextPriority, waitSemaphores, waitSemaphoreStageMasks,
-            signalSemaphore, std::move(currentGarbage),
-            std::move(mCommandBufferRecycler.getCommandBuffersToReset()), commandPool,
-            *submitSerialOut));
+            signalSemaphore, std::move(currentGarbage), std::move(commandBuffersToReset),
+            commandPools, *submitSerialOut));
     }
     else
     {
@@ -3536,9 +3543,8 @@
 
         ANGLE_TRY(mCommandQueue.submitFrame(
             context, hasProtectedContent, contextPriority, waitSemaphores, waitSemaphoreStageMasks,
-            signalSemaphore, std::move(currentGarbage),
-            std::move(mCommandBufferRecycler.getCommandBuffersToReset()), commandPool,
-            *submitSerialOut));
+            signalSemaphore, std::move(currentGarbage), std::move(commandBuffersToReset),
+            commandPools, *submitSerialOut));
     }
 
     waitSemaphores.clear();
@@ -3636,10 +3642,11 @@
     return angle::Result::Continue;
 }
 
-angle::Result RendererVk::flushRenderPassCommands(vk::Context *context,
-                                                  bool hasProtectedContent,
-                                                  const vk::RenderPass &renderPass,
-                                                  vk::CommandBufferHelper **renderPassCommands)
+angle::Result RendererVk::flushRenderPassCommands(
+    vk::Context *context,
+    bool hasProtectedContent,
+    const vk::RenderPass &renderPass,
+    vk::RenderPassCommandBufferHelper **renderPassCommands)
 {
     ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::flushRenderPassCommands");
 
@@ -3658,9 +3665,10 @@
     return angle::Result::Continue;
 }
 
-angle::Result RendererVk::flushOutsideRPCommands(vk::Context *context,
-                                                 bool hasProtectedContent,
-                                                 vk::CommandBufferHelper **outsideRPCommands)
+angle::Result RendererVk::flushOutsideRPCommands(
+    vk::Context *context,
+    bool hasProtectedContent,
+    vk::OutsideRenderPassCommandBufferHelper **outsideRPCommands)
 {
     ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::flushOutsideRPCommands");
 
@@ -3703,23 +3711,44 @@
     return result;
 }
 
-angle::Result RendererVk::getCommandBufferHelper(vk::Context *context,
-                                                 bool hasRenderPass,
-                                                 vk::CommandPool *commandPool,
-                                                 vk::CommandBufferHelper **commandBufferHelperOut)
+angle::Result RendererVk::getOutsideRenderPassCommandBufferHelper(
+    vk::Context *context,
+    vk::CommandPool *commandPool,
+    vk::OutsideRenderPassCommandBufferHelper **commandBufferHelperOut)
 {
-    ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::getCommandBufferHelper");
+    ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::getOutsideRenderPassCommandBufferHelper");
     std::unique_lock<std::mutex> lock(mCommandBufferRecyclerMutex);
-    return mCommandBufferRecycler.getCommandBufferHelper(context, hasRenderPass, commandPool,
-                                                         commandBufferHelperOut);
+    return mOutsideRenderPassCommandBufferRecycler.getCommandBufferHelper(context, commandPool,
+                                                                          commandBufferHelperOut);
 }
 
-void RendererVk::recycleCommandBufferHelper(VkDevice device,
-                                            vk::CommandBufferHelper **commandBuffer)
+angle::Result RendererVk::getRenderPassCommandBufferHelper(
+    vk::Context *context,
+    vk::CommandPool *commandPool,
+    vk::RenderPassCommandBufferHelper **commandBufferHelperOut)
 {
-    ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::recycleCommandBufferHelper");
+    ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::getRenderPassCommandBufferHelper");
+    std::unique_lock<std::mutex> lock(mCommandBufferRecyclerMutex);
+    return mRenderPassCommandBufferRecycler.getCommandBufferHelper(context, commandPool,
+                                                                   commandBufferHelperOut);
+}
+
+void RendererVk::recycleOutsideRenderPassCommandBufferHelper(
+    VkDevice device,
+    vk::OutsideRenderPassCommandBufferHelper **commandBuffer)
+{
+    ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::recycleOutsideRenderPassCommandBufferHelper");
     std::lock_guard<std::mutex> lock(mCommandBufferRecyclerMutex);
-    mCommandBufferRecycler.recycleCommandBufferHelper(device, commandBuffer);
+    mOutsideRenderPassCommandBufferRecycler.recycleCommandBufferHelper(device, commandBuffer);
+}
+
+void RendererVk::recycleRenderPassCommandBufferHelper(
+    VkDevice device,
+    vk::RenderPassCommandBufferHelper **commandBuffer)
+{
+    ANGLE_TRACE_EVENT0("gpu.angle", "RendererVk::recycleRenderPassCommandBufferHelper");
+    std::lock_guard<std::mutex> lock(mCommandBufferRecyclerMutex);
+    mRenderPassCommandBufferRecycler.recycleCommandBufferHelper(device, commandBuffer);
 }
 
 void RendererVk::logCacheStats() const
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.h b/src/libANGLE/renderer/vulkan/RendererVk.h
index 3df8d13..2058826 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.h
+++ b/src/libANGLE/renderer/vulkan/RendererVk.h
@@ -297,9 +297,14 @@
                                          bool hasProtectedContent,
                                          vk::PrimaryCommandBuffer *commandBufferOut);
 
-    void resetSecondaryCommandBuffer(vk::CommandBuffer &&commandBuffer)
+    void resetOutsideRenderPassCommandBuffer(vk::OutsideRenderPassCommandBuffer &&commandBuffer)
     {
-        mCommandBufferRecycler.resetCommandBufferHelper(std::move(commandBuffer));
+        mOutsideRenderPassCommandBufferRecycler.resetCommandBuffer(std::move(commandBuffer));
+    }
+
+    void resetRenderPassCommandBuffer(vk::RenderPassCommandBuffer &&commandBuffer)
+    {
+        mRenderPassCommandBufferRecycler.resetCommandBuffer(std::move(commandBuffer));
     }
 
     // Fire off a single command buffer immediately with default priority.
@@ -432,7 +437,7 @@
                               const vk::Semaphore *signalSemaphore,
                               std::vector<vk::ResourceUseList> &&resourceUseLists,
                               vk::GarbageList &&currentGarbage,
-                              vk::CommandPool *commandPool,
+                              vk::SecondaryCommandPools *commandPools,
                               Serial *submitSerialOut);
 
     void handleDeviceLost();
@@ -447,20 +452,30 @@
     angle::Result flushRenderPassCommands(vk::Context *context,
                                           bool hasProtectedContent,
                                           const vk::RenderPass &renderPass,
-                                          vk::CommandBufferHelper **renderPassCommands);
-    angle::Result flushOutsideRPCommands(vk::Context *context,
-                                         bool hasProtectedContent,
-                                         vk::CommandBufferHelper **outsideRPCommands);
+                                          vk::RenderPassCommandBufferHelper **renderPassCommands);
+    angle::Result flushOutsideRPCommands(
+        vk::Context *context,
+        bool hasProtectedContent,
+        vk::OutsideRenderPassCommandBufferHelper **outsideRPCommands);
 
     VkResult queuePresent(vk::Context *context,
                           egl::ContextPriority priority,
                           const VkPresentInfoKHR &presentInfo);
 
-    angle::Result getCommandBufferHelper(vk::Context *context,
-                                         bool hasRenderPass,
-                                         vk::CommandPool *commandPool,
-                                         vk::CommandBufferHelper **commandBufferHelperOut);
-    void recycleCommandBufferHelper(VkDevice device, vk::CommandBufferHelper **commandBuffer);
+    angle::Result getOutsideRenderPassCommandBufferHelper(
+        vk::Context *context,
+        vk::CommandPool *commandPool,
+        vk::OutsideRenderPassCommandBufferHelper **commandBufferHelperOut);
+    angle::Result getRenderPassCommandBufferHelper(
+        vk::Context *context,
+        vk::CommandPool *commandPool,
+        vk::RenderPassCommandBufferHelper **commandBufferHelperOut);
+
+    void recycleOutsideRenderPassCommandBufferHelper(
+        VkDevice device,
+        vk::OutsideRenderPassCommandBufferHelper **commandBuffer);
+    void recycleRenderPassCommandBufferHelper(VkDevice device,
+                                              vk::RenderPassCommandBufferHelper **commandBuffer);
 
     // Process GPU memory reports
     void processMemoryReportCallback(const VkDeviceMemoryReportCallbackDataEXT &callbackData)
@@ -663,7 +678,11 @@
 
     // Command buffer pool management.
     std::mutex mCommandBufferRecyclerMutex;
-    vk::CommandBufferRecycler mCommandBufferRecycler;
+    vk::CommandBufferRecycler<vk::OutsideRenderPassCommandBuffer,
+                              vk::OutsideRenderPassCommandBufferHelper>
+        mOutsideRenderPassCommandBufferRecycler;
+    vk::CommandBufferRecycler<vk::RenderPassCommandBuffer, vk::RenderPassCommandBufferHelper>
+        mRenderPassCommandBufferRecycler;
 
     vk::BufferMemoryAllocator mBufferMemoryAllocator;
     vk::Allocator mAllocator;
diff --git a/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp b/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp
index b1fd734..ba83e75 100644
--- a/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp
@@ -81,7 +81,7 @@
             BufferVk *bufferVk             = vk::GetImpl(buffer);
             vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
 
-            vk::CommandBuffer *commandBuffer;
+            vk::OutsideRenderPassCommandBuffer *commandBuffer;
             ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
             // Queue ownership transfer.
@@ -101,7 +101,7 @@
             vk::ImageHelper &image = textureVk->getImage();
             vk::ImageLayout layout = vk::GetImageLayoutFromGLImageLayout(textureBarrier.layout);
 
-            vk::CommandBuffer *commandBuffer;
+            vk::OutsideRenderPassCommandBuffer *commandBuffer;
             ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
             // Image should not be accessed while unowned. Emulated formats may have staged updates
@@ -136,7 +136,7 @@
             vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
 
             ANGLE_TRY(contextVk->onBufferReleaseToExternal(bufferHelper));
-            vk::CommandBuffer *commandBuffer;
+            vk::OutsideRenderPassCommandBuffer *commandBuffer;
             ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
             // Queue ownership transfer.
@@ -166,7 +166,7 @@
             ANGLE_TRY(textureVk->ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels));
 
             ANGLE_TRY(contextVk->onImageReleaseToExternal(image));
-            vk::CommandBuffer *commandBuffer;
+            vk::OutsideRenderPassCommandBuffer *commandBuffer;
             ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
             // Queue ownership transfer and layout transition.
diff --git a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
index 464c2dc..d0168e8 100644
--- a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
@@ -1477,7 +1477,7 @@
     // ImageLayout::Present bellow.
     contextVk->finalizeImageLayout(&image.image);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
     if (mColorImageMS.valid())
diff --git a/src/libANGLE/renderer/vulkan/SyncVk.cpp b/src/libANGLE/renderer/vulkan/SyncVk.cpp
index 2045639..97f8485 100644
--- a/src/libANGLE/renderer/vulkan/SyncVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SyncVk.cpp
@@ -143,7 +143,7 @@
     // Every resource already tracks its usage and issues the appropriate barriers, so there's
     // really nothing to do here.  An execution barrier is issued to strictly satisfy what the
     // application asked for.
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
     commandBuffer->pipelineBarrier(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
                                    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 0,
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index b83ac3c..efdd645 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -1005,7 +1005,7 @@
         access.onImageTransferWrite(level, 1, baseLayer, layerCount, VK_IMAGE_ASPECT_COLOR_BIT,
                                     mImage);
 
-        vk::CommandBuffer *commandBuffer;
+        vk::OutsideRenderPassCommandBuffer *commandBuffer;
         ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
         VkImageSubresourceLayers destSubresource = srcSubresource;
@@ -1042,7 +1042,7 @@
         access.onImageTransferWrite(gl::LevelIndex(0), 1, 0, layerCount, VK_IMAGE_ASPECT_COLOR_BIT,
                                     &stagingImage->get());
 
-        vk::CommandBuffer *commandBuffer;
+        vk::OutsideRenderPassCommandBuffer *commandBuffer;
         ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
         VkImageSubresourceLayers destSubresource = srcSubresource;
@@ -1410,7 +1410,7 @@
             newLayout = vk::ImageLayout::AllGraphicsShadersReadOnly;
         }
 
-        vk::CommandBuffer *commandBuffer;
+        vk::OutsideRenderPassCommandBuffer *commandBuffer;
         ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
         mImage->changeLayoutAndQueue(contextVk, mImage->getAspectFlags(), newLayout,
                                      rendererQueueFamilyIndex, commandBuffer);
@@ -1740,7 +1740,7 @@
     access.onBufferTransferRead(srcBuffer);
     access.onImageTransferWrite(level, 1, layerIndex, layerCount, mImage->getAspectFlags(), mImage);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     commandBuffer->copyBufferToImage(srcBuffer->getBuffer().getHandle(), mImage->getImage(),
@@ -1799,7 +1799,7 @@
                                          mImage->getLayerCount(), VK_IMAGE_ASPECT_COLOR_BIT,
                                          mImage);
 
-        vk::CommandBuffer *commandBuffer;
+        vk::OutsideRenderPassCommandBuffer *commandBuffer;
         ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
         // Generate mipmaps for every layer separately.
@@ -2039,7 +2039,7 @@
                                 &stagingImage->get());
     access.onImageTransferRead(aspectFlags, srcImage);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkImageCopy copyRegion               = {};
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.cpp b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
index 94b55c1..a4c92f9 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.cpp
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
@@ -1426,82 +1426,92 @@
     return angle::Result::Continue;
 }
 
-angle::Result UtilsVk::setupProgram(ContextVk *contextVk,
-                                    Function function,
-                                    vk::RefCounted<vk::ShaderAndSerial> *fsCsShader,
-                                    vk::RefCounted<vk::ShaderAndSerial> *vsShader,
-                                    vk::ShaderProgramHelper *program,
-                                    const vk::GraphicsPipelineDesc *pipelineDesc,
-                                    const VkDescriptorSet descriptorSet,
-                                    const void *pushConstants,
-                                    size_t pushConstantsSize,
-                                    vk::CommandBuffer *commandBuffer)
+angle::Result UtilsVk::setupComputeProgram(ContextVk *contextVk,
+                                           Function function,
+                                           vk::RefCounted<vk::ShaderAndSerial> *csShader,
+                                           vk::ShaderProgramHelper *program,
+                                           const VkDescriptorSet descriptorSet,
+                                           const void *pushConstants,
+                                           size_t pushConstantsSize,
+                                           vk::OutsideRenderPassCommandBuffer *commandBuffer)
 {
-    RendererVk *renderer = contextVk->getRenderer();
-
-    const bool isCompute = function >= Function::ComputeStartIndex;
-    const VkShaderStageFlags pushConstantsShaderStage =
-        isCompute ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_FRAGMENT_BIT;
-    const VkPipelineBindPoint pipelineBindPoint =
-        isCompute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS;
-
-    // If compute, vsShader and pipelineDesc should be nullptr, and if not compute they shouldn't
-    // be.
-    ASSERT(isCompute != (vsShader && pipelineDesc));
+    ASSERT(function >= Function::ComputeStartIndex);
 
     const vk::BindingPointer<vk::PipelineLayout> &pipelineLayout = mPipelineLayouts[function];
 
-    if (isCompute)
-    {
-        vk::PipelineHelper *pipeline;
-        program->setShader(gl::ShaderType::Compute, fsCsShader);
-        ANGLE_TRY(program->getComputePipeline(contextVk, pipelineLayout.get(), &pipeline));
-        pipeline->retain(&contextVk->getResourceUseList());
-        commandBuffer->bindComputePipeline(pipeline->getPipeline());
+    vk::PipelineHelper *pipeline;
+    program->setShader(gl::ShaderType::Compute, csShader);
+    ANGLE_TRY(program->getComputePipeline(contextVk, pipelineLayout.get(), &pipeline));
+    pipeline->retain(&contextVk->getResourceUseList());
+    commandBuffer->bindComputePipeline(pipeline->getPipeline());
 
-        contextVk->invalidateComputePipelineBinding();
-    }
-    else
-    {
-        program->setShader(gl::ShaderType::Vertex, vsShader);
-        if (fsCsShader)
-        {
-            program->setShader(gl::ShaderType::Fragment, fsCsShader);
-        }
-
-        // This value is not used but is passed to getGraphicsPipeline to avoid a nullptr check.
-        const vk::GraphicsPipelineDesc *descPtr;
-        vk::PipelineHelper *helper;
-        vk::PipelineCache *pipelineCache = nullptr;
-        ANGLE_TRY(renderer->getPipelineCache(&pipelineCache));
-        ANGLE_TRY(program->getGraphicsPipeline(contextVk, &contextVk->getRenderPassCache(),
-                                               *pipelineCache, pipelineLayout.get(), *pipelineDesc,
-                                               gl::AttributesMask(), gl::ComponentTypeMask(),
-                                               gl::DrawBufferMask(), &descPtr, &helper));
-        helper->retain(&contextVk->getResourceUseList());
-        commandBuffer->bindGraphicsPipeline(helper->getPipeline());
-
-        contextVk->invalidateGraphicsPipelineBinding();
-    }
+    contextVk->invalidateComputePipelineBinding();
 
     if (descriptorSet != VK_NULL_HANDLE)
     {
-        commandBuffer->bindDescriptorSets(pipelineLayout.get(), pipelineBindPoint,
+        commandBuffer->bindDescriptorSets(pipelineLayout.get(), VK_PIPELINE_BIND_POINT_COMPUTE,
                                           DescriptorSetIndex::Internal, 1, &descriptorSet, 0,
                                           nullptr);
-        if (isCompute)
-        {
-            contextVk->invalidateComputeDescriptorSet(DescriptorSetIndex::Internal);
-        }
-        else
-        {
-            contextVk->invalidateGraphicsDescriptorSet(DescriptorSetIndex::Internal);
-        }
+        contextVk->invalidateComputeDescriptorSet(DescriptorSetIndex::Internal);
     }
 
     if (pushConstants)
     {
-        commandBuffer->pushConstants(pipelineLayout.get(), pushConstantsShaderStage, 0,
+        commandBuffer->pushConstants(pipelineLayout.get(), VK_SHADER_STAGE_COMPUTE_BIT, 0,
+                                     static_cast<uint32_t>(pushConstantsSize), pushConstants);
+    }
+
+    return angle::Result::Continue;
+}
+
+angle::Result UtilsVk::setupGraphicsProgram(ContextVk *contextVk,
+                                            Function function,
+                                            vk::RefCounted<vk::ShaderAndSerial> *vsShader,
+                                            vk::RefCounted<vk::ShaderAndSerial> *fsShader,
+                                            vk::ShaderProgramHelper *program,
+                                            const vk::GraphicsPipelineDesc *pipelineDesc,
+                                            const VkDescriptorSet descriptorSet,
+                                            const void *pushConstants,
+                                            size_t pushConstantsSize,
+                                            vk::RenderPassCommandBuffer *commandBuffer)
+{
+    RendererVk *renderer = contextVk->getRenderer();
+
+    ASSERT(function < Function::ComputeStartIndex);
+
+    const vk::BindingPointer<vk::PipelineLayout> &pipelineLayout = mPipelineLayouts[function];
+
+    program->setShader(gl::ShaderType::Vertex, vsShader);
+    if (fsShader)
+    {
+        program->setShader(gl::ShaderType::Fragment, fsShader);
+    }
+
+    // This value is not used but is passed to getGraphicsPipeline to avoid a nullptr check.
+    const vk::GraphicsPipelineDesc *descPtr;
+    vk::PipelineHelper *helper;
+    vk::PipelineCache *pipelineCache = nullptr;
+    ANGLE_TRY(renderer->getPipelineCache(&pipelineCache));
+    ANGLE_TRY(program->getGraphicsPipeline(contextVk, &contextVk->getRenderPassCache(),
+                                           *pipelineCache, pipelineLayout.get(), *pipelineDesc,
+                                           gl::AttributesMask(), gl::ComponentTypeMask(),
+                                           gl::DrawBufferMask(), &descPtr, &helper));
+    helper->retain(&contextVk->getResourceUseList());
+    commandBuffer->bindGraphicsPipeline(helper->getPipeline());
+
+    contextVk->invalidateGraphicsPipelineBinding();
+
+    if (descriptorSet != VK_NULL_HANDLE)
+    {
+        commandBuffer->bindDescriptorSets(pipelineLayout.get(), VK_PIPELINE_BIND_POINT_GRAPHICS,
+                                          DescriptorSetIndex::Internal, 1, &descriptorSet, 0,
+                                          nullptr);
+        contextVk->invalidateGraphicsDescriptorSet(DescriptorSetIndex::Internal);
+    }
+
+    if (pushConstants)
+    {
+        commandBuffer->pushConstants(pipelineLayout.get(), VK_SHADER_STAGE_FRAGMENT_BIT, 0,
                                      static_cast<uint32_t>(pushConstantsSize), pushConstants);
     }
 
@@ -1519,7 +1529,7 @@
     access.onBufferComputeShaderRead(src);
     access.onBufferComputeShaderWrite(dst);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkDescriptorSet descriptorSet;
@@ -1554,9 +1564,9 @@
     vk::RefCounted<vk::ShaderAndSerial> *shader = nullptr;
     ANGLE_TRY(contextVk->getShaderLibrary().getConvertIndex_comp(contextVk, flags, &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ConvertIndexBuffer, shader, nullptr,
-                           &mConvertIndexPrograms[flags], nullptr, descriptorSet, &shaderParams,
-                           sizeof(ConvertIndexShaderParams), commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::ConvertIndexBuffer, shader,
+                                  &mConvertIndexPrograms[flags], descriptorSet, &shaderParams,
+                                  sizeof(ConvertIndexShaderParams), commandBuffer));
 
     constexpr uint32_t kInvocationsPerGroup = 64;
     constexpr uint32_t kInvocationsPerIndex = 2;
@@ -1585,7 +1595,7 @@
     access.onBufferComputeShaderWrite(dstIndirectBuf);
     access.onBufferComputeShaderWrite(dstIndexBuf);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkDescriptorSet descriptorSet;
@@ -1625,9 +1635,9 @@
     vk::RefCounted<vk::ShaderAndSerial> *shader = nullptr;
     ANGLE_TRY(contextVk->getShaderLibrary().getConvertIndex_comp(contextVk, flags, &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ConvertIndexIndirectBuffer, shader, nullptr,
-                           &mConvertIndexPrograms[flags], nullptr, descriptorSet, &shaderParams,
-                           sizeof(ConvertIndexIndirectShaderParams), commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::ConvertIndexIndirectBuffer, shader,
+                                  &mConvertIndexPrograms[flags], descriptorSet, &shaderParams,
+                                  sizeof(ConvertIndexIndirectShaderParams), commandBuffer));
 
     constexpr uint32_t kInvocationsPerGroup = 64;
     constexpr uint32_t kInvocationsPerIndex = 2;
@@ -1657,7 +1667,7 @@
     access.onBufferComputeShaderWrite(dstIndirectBuffer);
     access.onBufferComputeShaderWrite(dstIndexBuffer);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkDescriptorSet descriptorSet;
@@ -1697,10 +1707,10 @@
     ANGLE_TRY(contextVk->getShaderLibrary().getConvertIndexIndirectLineLoop_comp(contextVk, flags,
                                                                                  &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ConvertIndexIndirectLineLoopBuffer, shader, nullptr,
-                           &mConvertIndexIndirectLineLoopPrograms[flags], nullptr, descriptorSet,
-                           &shaderParams, sizeof(ConvertIndexIndirectLineLoopShaderParams),
-                           commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::ConvertIndexIndirectLineLoopBuffer, shader,
+                                  &mConvertIndexIndirectLineLoopPrograms[flags], descriptorSet,
+                                  &shaderParams, sizeof(ConvertIndexIndirectLineLoopShaderParams),
+                                  commandBuffer));
 
     commandBuffer->dispatch(1, 1, 1);
 
@@ -1723,7 +1733,7 @@
     access.onBufferComputeShaderWrite(dstIndirectBuffer);
     access.onBufferComputeShaderWrite(dstIndexBuffer);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkDescriptorSet descriptorSet;
@@ -1760,10 +1770,10 @@
     ANGLE_TRY(
         contextVk->getShaderLibrary().getConvertIndirectLineLoop_comp(contextVk, flags, &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ConvertIndirectLineLoopBuffer, shader, nullptr,
-                           &mConvertIndirectLineLoopPrograms[flags], nullptr, descriptorSet,
-                           &shaderParams, sizeof(ConvertIndirectLineLoopShaderParams),
-                           commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::ConvertIndirectLineLoopBuffer, shader,
+                                  &mConvertIndirectLineLoopPrograms[flags], descriptorSet,
+                                  &shaderParams, sizeof(ConvertIndirectLineLoopShaderParams),
+                                  commandBuffer));
 
     commandBuffer->dispatch(1, 1, 1);
 
@@ -1781,7 +1791,7 @@
     access.onBufferComputeShaderRead(src);
     access.onBufferComputeShaderWrite(dst);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     ConvertVertexShaderParams shaderParams;
@@ -1886,7 +1896,7 @@
                                                vk::BufferHelper *dst,
                                                vk::BufferHelper *src,
                                                uint32_t flags,
-                                               vk::CommandBuffer *commandBuffer,
+                                               vk::OutsideRenderPassCommandBuffer *commandBuffer,
                                                const ConvertVertexShaderParams &shaderParams)
 {
     ANGLE_TRY(ensureConvertVertexResourcesInitialized(contextVk));
@@ -1916,9 +1926,9 @@
     vk::RefCounted<vk::ShaderAndSerial> *shader = nullptr;
     ANGLE_TRY(contextVk->getShaderLibrary().getConvertVertex_comp(contextVk, flags, &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ConvertVertexBuffer, shader, nullptr,
-                           &mConvertVertexPrograms[flags], nullptr, descriptorSet, &shaderParams,
-                           sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::ConvertVertexBuffer, shader,
+                                  &mConvertVertexPrograms[flags], descriptorSet, &shaderParams,
+                                  sizeof(shaderParams), commandBuffer));
 
     commandBuffer->dispatch(UnsignedCeilDivide(shaderParams.outputCount, 64), 1, 1);
 
@@ -1932,7 +1942,7 @@
                                        const vk::ImageView *imageView,
                                        const vk::RenderPassDesc &renderPassDesc,
                                        const gl::Rectangle &renderArea,
-                                       vk::CommandBuffer **commandBufferOut)
+                                       vk::RenderPassCommandBuffer **commandBufferOut)
 {
     vk::RenderPass *compatibleRenderPass = nullptr;
     ANGLE_TRY(contextVk->getCompatibleRenderPass(renderPassDesc, &compatibleRenderPass));
@@ -1977,7 +1987,7 @@
 
     const gl::Rectangle &scissoredRenderArea = params.clearArea;
     vk::Framebuffer *currentFramebuffer      = nullptr;
-    vk::CommandBuffer *commandBuffer;
+    vk::RenderPassCommandBuffer *commandBuffer;
 
     // Start a new render pass if not already started
     ANGLE_TRY(framebuffer->getFramebuffer(contextVk, &currentFramebuffer, nullptr));
@@ -1992,7 +2002,7 @@
 
     if (params.clearStencil || params.clearDepth)
     {
-        vk::CommandBufferHelper *renderpassCommands;
+        vk::RenderPassCommandBufferHelper *renderpassCommands;
         renderpassCommands = &contextVk->getStartedRenderPassCommands();
 
         // Because clear is not affected by depth/stencil test, we have to explicitly mark
@@ -2101,9 +2111,9 @@
         contextVk->getStartedRenderPassCommands().isTransformFeedbackActiveUnpaused();
     contextVk->pauseTransformFeedbackIfActiveUnpaused();
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ImageClear, fragmentShader, vertexShader,
-                           imageClearProgram, &pipelineDesc, VK_NULL_HANDLE, &shaderParams,
-                           sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupGraphicsProgram(contextVk, Function::ImageClear, vertexShader, fragmentShader,
+                                   imageClearProgram, &pipelineDesc, VK_NULL_HANDLE, &shaderParams,
+                                   sizeof(shaderParams), commandBuffer));
 
     // Make sure this draw call doesn't count towards occlusion query results.
     contextVk->pauseRenderPassQueriesIfActive();
@@ -2178,7 +2188,7 @@
     pipelineDesc.setRasterizationSamples(dst->getSamples());
     pipelineDesc.setRenderPassDesc(renderPassDesc);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::RenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(startRenderPass(contextVk, dst, &destView.get(), renderPassDesc, renderArea,
                               &commandBuffer));
 
@@ -2203,9 +2213,9 @@
     ANGLE_TRY(shaderLibrary.getFullScreenTri_vert(contextVk, 0, &vertexShader));
     ANGLE_TRY(shaderLibrary.getImageClear_frag(contextVk, flags, &fragmentShader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ImageClear, fragmentShader, vertexShader,
-                           &mImageClearPrograms[flags], &pipelineDesc, VK_NULL_HANDLE,
-                           &shaderParams, sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupGraphicsProgram(contextVk, Function::ImageClear, vertexShader, fragmentShader,
+                                   &mImageClearPrograms[flags], &pipelineDesc, VK_NULL_HANDLE,
+                                   &shaderParams, sizeof(shaderParams), commandBuffer));
 
     // Note: this utility creates its own framebuffer, thus bypassing ContextVk::startRenderPass.
     // As such, occlusion queries are not enabled.
@@ -2407,7 +2417,7 @@
         SetStencilForShaderExport(contextVk, &pipelineDesc);
     }
 
-    vk::CommandBuffer *commandBuffer;
+    vk::RenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(framebuffer->startNewRenderPass(contextVk, params.blitArea, &commandBuffer, nullptr));
 
     VkViewport viewport;
@@ -2424,7 +2434,8 @@
     contextVk->onImageRenderPassRead(src->getAspectFlags(), vk::ImageLayout::FragmentShaderReadOnly,
                                      src);
 
-    vk::CommandBufferHelper *renderPassCommands = &contextVk->getStartedRenderPassCommands();
+    vk::RenderPassCommandBufferHelper *renderPassCommands =
+        &contextVk->getStartedRenderPassCommands();
     if (blitDepth)
     {
         // Explicitly mark a depth write because we are modifying the depth buffer.
@@ -2500,9 +2511,9 @@
     ANGLE_TRY(shaderLibrary.getFullScreenTri_vert(contextVk, 0, &vertexShader));
     ANGLE_TRY(shaderLibrary.getBlitResolve_frag(contextVk, flags, &fragmentShader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::BlitResolve, fragmentShader, vertexShader,
-                           &mBlitResolvePrograms[flags], &pipelineDesc, descriptorSet,
-                           &shaderParams, sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupGraphicsProgram(contextVk, Function::BlitResolve, vertexShader, fragmentShader,
+                                   &mBlitResolvePrograms[flags], &pipelineDesc, descriptorSet,
+                                   &shaderParams, sizeof(shaderParams), commandBuffer));
 
     // Note: this utility starts the render pass directly, thus bypassing
     // ContextVk::startRenderPass. As such, occlusion queries are not enabled.
@@ -2634,7 +2645,7 @@
                                 depthStencilRenderTarget->getLayerIndex(), 1,
                                 depthStencilImage->getAspectFlags(), depthStencilImage);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     // Blit/resolve stencil into the buffer.
@@ -2678,9 +2689,9 @@
     ANGLE_TRY(contextVk->getShaderLibrary().getBlitResolveStencilNoExport_comp(contextVk, flags,
                                                                                &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::BlitResolveStencilNoExport, shader, nullptr,
-                           &mBlitResolveStencilNoExportPrograms[flags], nullptr, descriptorSet,
-                           &shaderParams, sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::BlitResolveStencilNoExport, shader,
+                                  &mBlitResolveStencilNoExportPrograms[flags], descriptorSet,
+                                  &shaderParams, sizeof(shaderParams), commandBuffer));
     commandBuffer->dispatch(UnsignedCeilDivide(bufferRowLengthInUints, 8),
                             UnsignedCeilDivide(params.blitArea.height, 8), 1);
     descriptorPoolBinding.reset();
@@ -2851,7 +2862,7 @@
         std::swap(renderArea.width, renderArea.height);
     }
 
-    vk::CommandBuffer *commandBuffer;
+    vk::RenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(
         startRenderPass(contextVk, dst, destView, renderPassDesc, renderArea, &commandBuffer));
 
@@ -2890,9 +2901,9 @@
     ANGLE_TRY(shaderLibrary.getFullScreenTri_vert(contextVk, 0, &vertexShader));
     ANGLE_TRY(shaderLibrary.getImageCopy_frag(contextVk, flags, &fragmentShader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::ImageCopy, fragmentShader, vertexShader,
-                           &mImageCopyPrograms[flags], &pipelineDesc, descriptorSet, &shaderParams,
-                           sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupGraphicsProgram(contextVk, Function::ImageCopy, vertexShader, fragmentShader,
+                                   &mImageCopyPrograms[flags], &pipelineDesc, descriptorSet,
+                                   &shaderParams, sizeof(shaderParams), commandBuffer));
 
     // Note: this utility creates its own framebuffer, thus bypassing ContextVk::startRenderPass.
     // As such, occlusion queries are not enabled.
@@ -2984,7 +2995,7 @@
                                 isDst3D ? 1 : params.copyExtents[2], VK_IMAGE_ASPECT_COLOR_BIT,
                                 dst);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     // Copy src into buffer, completely packed.
@@ -3195,12 +3206,12 @@
 
     // Note: onImageRead/onImageWrite is expected to be called by the caller.  This avoids inserting
     // barriers between calls for each layer of the image.
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::GenerateMipmap, shader, nullptr,
-                           &mGenerateMipmapPrograms[flags], nullptr, descriptorSet, &shaderParams,
-                           sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::GenerateMipmap, shader,
+                                  &mGenerateMipmapPrograms[flags], descriptorSet, &shaderParams,
+                                  sizeof(shaderParams), commandBuffer));
 
     commandBuffer->dispatch(workGroupX, workGroupY, 1);
     descriptorPoolBinding.reset();
@@ -3301,7 +3312,7 @@
         SetStencilForShaderExport(contextVk, &pipelineDesc);
     }
 
-    vk::CommandBuffer *commandBuffer =
+    vk::RenderPassCommandBuffer *commandBuffer =
         &contextVk->getStartedRenderPassCommands().getCommandBuffer();
 
     VkViewport viewport;
@@ -3365,9 +3376,9 @@
     ANGLE_TRY(GetUnresolveFrag(contextVk, colorAttachmentCount, colorAttachmentTypes,
                                params.unresolveDepth, params.unresolveStencil, fragmentShader));
 
-    ANGLE_TRY(setupProgram(contextVk, function, fragmentShader, vertexShader,
-                           &mUnresolvePrograms[flags], &pipelineDesc, descriptorSet, nullptr, 0,
-                           commandBuffer));
+    ANGLE_TRY(setupGraphicsProgram(contextVk, function, vertexShader, fragmentShader,
+                                   &mUnresolvePrograms[flags], &pipelineDesc, descriptorSet,
+                                   nullptr, 0, commandBuffer));
     // This draw call is made before ContextVk gets a chance to start the occlusion query.  As such,
     // occlusion queries are not enabled.
     commandBuffer->draw(3, 0);
@@ -3419,7 +3430,7 @@
     access.onBufferComputeShaderRead(enabledWidgetsBuffer);
     access.onImageComputeShaderWrite(gl::LevelIndex(0), 1, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, dst);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkDescriptorImageInfo imageInfo = {};
@@ -3451,9 +3462,9 @@
     vk::RefCounted<vk::ShaderAndSerial> *shader = nullptr;
     ANGLE_TRY(contextVk->getShaderLibrary().getOverlayCull_comp(contextVk, flags, &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::OverlayCull, shader, nullptr,
-                           &mOverlayCullPrograms[flags], nullptr, descriptorSet, nullptr, 0,
-                           commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::OverlayCull, shader,
+                                  &mOverlayCullPrograms[flags], descriptorSet, nullptr, 0,
+                                  commandBuffer));
 
     commandBuffer->dispatch(dst->getExtents().width, dst->getExtents().height, 1);
     descriptorPoolBinding.reset();
@@ -3499,7 +3510,7 @@
     access.onBufferComputeShaderRead(textWidgetsBuffer);
     access.onBufferComputeShaderRead(graphWidgetsBuffer);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     VkDescriptorImageInfo imageInfos[3] = {};
@@ -3559,9 +3570,9 @@
     vk::RefCounted<vk::ShaderAndSerial> *shader = nullptr;
     ANGLE_TRY(contextVk->getShaderLibrary().getOverlayDraw_comp(contextVk, flags, &shader));
 
-    ANGLE_TRY(setupProgram(contextVk, Function::OverlayDraw, shader, nullptr,
-                           &mOverlayDrawPrograms[flags], nullptr, descriptorSet, &shaderParams,
-                           sizeof(shaderParams), commandBuffer));
+    ANGLE_TRY(setupComputeProgram(contextVk, Function::OverlayDraw, shader,
+                                  &mOverlayDrawPrograms[flags], descriptorSet, &shaderParams,
+                                  sizeof(shaderParams), commandBuffer));
 
     // Every pixel of culledWidgets corresponds to one workgroup, so we can use that as dispatch
     // size.
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.h b/src/libANGLE/renderer/vulkan/UtilsVk.h
index dc80611..fbc8c61 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.h
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.h
@@ -481,21 +481,26 @@
         EnumCount   = 22,
     };
 
-    // Common function that creates the pipeline for the specified function, binds it and prepares
-    // the draw/dispatch call.  If function >= ComputeStartIndex, fsCsShader is expected to be a
-    // compute shader, vsShader and pipelineDesc should be nullptr, and this will set up a dispatch
-    // call. Otherwise fsCsShader is expected to be a fragment shader and this will set up a draw
-    // call.
-    angle::Result setupProgram(ContextVk *contextVk,
-                               Function function,
-                               vk::RefCounted<vk::ShaderAndSerial> *fsCsShader,
-                               vk::RefCounted<vk::ShaderAndSerial> *vsShader,
-                               vk::ShaderProgramHelper *program,
-                               const vk::GraphicsPipelineDesc *pipelineDesc,
-                               const VkDescriptorSet descriptorSet,
-                               const void *pushConstants,
-                               size_t pushConstantsSize,
-                               vk::CommandBuffer *commandBuffer);
+    // Common functions that create the pipeline for the specified function, binds it and prepares
+    // the draw/dispatch call.
+    angle::Result setupComputeProgram(ContextVk *contextVk,
+                                      Function function,
+                                      vk::RefCounted<vk::ShaderAndSerial> *csShader,
+                                      vk::ShaderProgramHelper *program,
+                                      const VkDescriptorSet descriptorSet,
+                                      const void *pushConstants,
+                                      size_t pushConstantsSize,
+                                      vk::OutsideRenderPassCommandBuffer *commandBuffer);
+    angle::Result setupGraphicsProgram(ContextVk *contextVk,
+                                       Function function,
+                                       vk::RefCounted<vk::ShaderAndSerial> *vsShader,
+                                       vk::RefCounted<vk::ShaderAndSerial> *fsShader,
+                                       vk::ShaderProgramHelper *program,
+                                       const vk::GraphicsPipelineDesc *pipelineDesc,
+                                       const VkDescriptorSet descriptorSet,
+                                       const void *pushConstants,
+                                       size_t pushConstantsSize,
+                                       vk::RenderPassCommandBuffer *commandBuffer);
 
     // Initializes descriptor set layout, pipeline layout and descriptor pool corresponding to given
     // function, if not already initialized.  Uses setSizes to create the layout.  For example, if
@@ -533,14 +538,14 @@
                                   const vk::ImageView *imageView,
                                   const vk::RenderPassDesc &renderPassDesc,
                                   const gl::Rectangle &renderArea,
-                                  vk::CommandBuffer **commandBufferOut);
+                                  vk::RenderPassCommandBuffer **commandBufferOut);
 
     // Set up descriptor set and call dispatch.
     angle::Result convertVertexBufferImpl(ContextVk *contextVk,
                                           vk::BufferHelper *dst,
                                           vk::BufferHelper *src,
                                           uint32_t flags,
-                                          vk::CommandBuffer *commandBuffer,
+                                          vk::OutsideRenderPassCommandBuffer *commandBuffer,
                                           const ConvertVertexShaderParams &shaderParams);
 
     // Blits or resolves either color or depth/stencil, based on which view is given.
diff --git a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
index 7f4f8f6..85307b5 100644
--- a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
@@ -441,7 +441,8 @@
     // We use secondary command buffers almost everywhere and they require a feature to be
     // able to execute in the presence of queries.  As a result, we won't support timestamp queries
     // unless that feature is available.
-    if (vk::CommandBuffer::SupportsQueries(mPhysicalDeviceFeatures))
+    if (vk::OutsideRenderPassCommandBuffer::SupportsQueries(mPhysicalDeviceFeatures) &&
+        vk::RenderPassCommandBuffer::SupportsQueries(mPhysicalDeviceFeatures))
     {
         mNativeExtensions.disjointTimerQueryEXT = queueFamilyProperties.timestampValidBits > 0;
         mNativeCaps.queryCounterBitsTimeElapsed = queueFamilyProperties.timestampValidBits;
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index 4d27fba..b865f06 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -85,7 +85,7 @@
     VkAccessFlags srcAccessMask;
     // Read or write.
     ResourceAccess type;
-    // CommandBufferHelper tracks an array of PipelineBarriers. This indicates which array element
+    // *CommandBufferHelper track an array of PipelineBarriers. This indicates which array element
     // this should be merged into. Right now we track individual barrier for every PipelineStage. If
     // layout has a single stage mask bit, we use that stage as index. If layout has multiple stage
     // mask bits, we pick the lowest stage as the index since it is the first stage that needs
@@ -811,6 +811,71 @@
 
     return sizeMismatch || releaseByPolicy;
 }
+
+// Helper functions used below
+char GetLoadOpShorthand(RenderPassLoadOp loadOp)
+{
+    switch (loadOp)
+    {
+        case RenderPassLoadOp::Clear:
+            return 'C';
+        case RenderPassLoadOp::Load:
+            return 'L';
+        case RenderPassLoadOp::None:
+            return 'N';
+        default:
+            return 'D';
+    }
+}
+
+char GetStoreOpShorthand(RenderPassStoreOp storeOp)
+{
+    switch (storeOp)
+    {
+        case RenderPassStoreOp::Store:
+            return 'S';
+        case RenderPassStoreOp::None:
+            return 'N';
+        default:
+            return 'D';
+    }
+}
+
+template <typename CommandBufferHelperT>
+void RecycleCommandBufferHelper(VkDevice device,
+                                std::vector<CommandBufferHelperT *> *freeList,
+                                CommandBufferHelperT **commandBufferHelper,
+                                priv::SecondaryCommandBuffer *commandBuffer)
+{
+    freeList->push_back(*commandBufferHelper);
+}
+
+template <typename CommandBufferHelperT>
+void RecycleCommandBufferHelper(VkDevice device,
+                                std::vector<CommandBufferHelperT *> *freeList,
+                                CommandBufferHelperT **commandBufferHelper,
+                                VulkanSecondaryCommandBuffer *commandBuffer)
+{
+    CommandPool *pool = (*commandBufferHelper)->getCommandPool();
+
+    pool->freeCommandBuffers(device, 1, commandBuffer->ptr());
+    commandBuffer->releaseHandle();
+    SafeDelete(*commandBufferHelper);
+}
+
+ANGLE_MAYBE_UNUSED void ResetSecondaryCommandBuffer(
+    std::vector<priv::SecondaryCommandBuffer> *resetList,
+    priv::SecondaryCommandBuffer &&commandBuffer)
+{
+    commandBuffer.reset();
+}
+
+ANGLE_MAYBE_UNUSED void ResetSecondaryCommandBuffer(
+    std::vector<VulkanSecondaryCommandBuffer> *resetList,
+    VulkanSecondaryCommandBuffer &&commandBuffer)
+{
+    resetList->push_back(std::move(commandBuffer));
+}
 }  // anonymous namespace
 
 // This is an arbitrary max. We can change this later if necessary.
@@ -953,45 +1018,18 @@
     mValues[index.get()] = clearValue;
 }
 
-// CommandBufferHelper implementation.
-CommandBufferHelper::CommandBufferHelper()
+// CommandBufferHelperCommon implementation.
+CommandBufferHelperCommon::CommandBufferHelperCommon()
     : mPipelineBarriers(),
       mPipelineBarrierMask(),
       mCommandPool(nullptr),
-      mCounter(0),
-      mClearValues{},
-      mRenderPassStarted(false),
-      mTransformFeedbackCounterBuffers{},
-      mValidTransformFeedbackBufferCount(0),
-      mRebindTransformFeedbackBuffers(false),
-      mIsTransformFeedbackActiveUnpaused(false),
-      mIsRenderPassCommandBuffer(false),
       mHasShaderStorageOutput(false),
-      mHasGLMemoryBarrierIssued(false),
-      mDepthAccess(ResourceAccess::Unused),
-      mStencilAccess(ResourceAccess::Unused),
-      mDepthCmdCountInvalidated(kInfiniteCmdCount),
-      mDepthCmdCountDisabled(kInfiniteCmdCount),
-      mStencilCmdCountInvalidated(kInfiniteCmdCount),
-      mStencilCmdCountDisabled(kInfiniteCmdCount),
-      mDepthStencilAttachmentIndex(kAttachmentIndexInvalid),
-      mDepthStencilImage(nullptr),
-      mDepthStencilResolveImage(nullptr),
-      mDepthStencilLevelIndex(0),
-      mDepthStencilLayerIndex(0),
-      mDepthStencilLayerCount(0),
-      mColorImagesCount(0),
-      mImageOptimizeForPresent(nullptr)
+      mHasGLMemoryBarrierIssued(false)
 {}
 
-CommandBufferHelper::~CommandBufferHelper()
-{
-    mFramebuffer.setHandle(VK_NULL_HANDLE);
-}
+CommandBufferHelperCommon::~CommandBufferHelperCommon() {}
 
-angle::Result CommandBufferHelper::initialize(Context *context,
-                                              bool isRenderPassCommandBuffer,
-                                              CommandPool *commandPool)
+void CommandBufferHelperCommon::initializeImpl(Context *context, CommandPool *commandPool)
 {
     ASSERT(mUsedBuffers.empty());
     constexpr size_t kInitialBufferCount = 128;
@@ -1001,82 +1039,21 @@
     // Push a scope into the pool allocator so we can easily free and re-init on reset()
     mAllocator.push();
 
-    mIsRenderPassCommandBuffer = isRenderPassCommandBuffer;
-    mCommandPool               = commandPool;
-
-    return initializeCommandBuffer(context);
+    mCommandPool = commandPool;
 }
 
-angle::Result CommandBufferHelper::initializeCommandBuffer(Context *context)
-{
-    return mCommandBuffer.initialize(context, mCommandPool, mIsRenderPassCommandBuffer,
-                                     &mAllocator);
-}
-
-angle::Result CommandBufferHelper::reset(Context *context)
+void CommandBufferHelperCommon::resetImpl()
 {
     mAllocator.pop();
     mAllocator.push();
 
-    // Reset and re-initialize the command buffer
-    context->getRenderer()->resetSecondaryCommandBuffer(std::move(mCommandBuffer));
-    ANGLE_TRY(initializeCommandBuffer(context));
-
     mUsedBuffers.clear();
-
-    if (mIsRenderPassCommandBuffer)
-    {
-        mRenderPassStarted                 = false;
-        mValidTransformFeedbackBufferCount = 0;
-        mRebindTransformFeedbackBuffers    = false;
-        mHasShaderStorageOutput            = false;
-        mHasGLMemoryBarrierIssued          = false;
-        mDepthAccess                       = ResourceAccess::Unused;
-        mStencilAccess                     = ResourceAccess::Unused;
-        mDepthCmdCountInvalidated          = kInfiniteCmdCount;
-        mDepthCmdCountDisabled             = kInfiniteCmdCount;
-        mStencilCmdCountInvalidated        = kInfiniteCmdCount;
-        mStencilCmdCountDisabled           = kInfiniteCmdCount;
-        mColorImagesCount                  = PackedAttachmentCount(0);
-        mDepthStencilAttachmentIndex       = kAttachmentIndexInvalid;
-        mDepthInvalidateArea               = gl::Rectangle();
-        mStencilInvalidateArea             = gl::Rectangle();
-        mRenderPassUsedImages.clear();
-        mDepthStencilImage        = nullptr;
-        mDepthStencilResolveImage = nullptr;
-        mColorImages.reset();
-        mColorResolveImages.reset();
-        mImageOptimizeForPresent = nullptr;
-    }
-    // This state should never change for non-renderPass command buffer
-    ASSERT(mRenderPassStarted == false);
-    ASSERT(mValidTransformFeedbackBufferCount == 0);
-    ASSERT(!mRebindTransformFeedbackBuffers);
-    ASSERT(!mIsTransformFeedbackActiveUnpaused);
-    ASSERT(mRenderPassUsedImages.empty());
-
-    return angle::Result::Continue;
 }
 
-bool CommandBufferHelper::usesBuffer(const BufferHelper &buffer) const
-{
-    return mUsedBuffers.contains(buffer.getBufferSerial().getValue());
-}
-
-bool CommandBufferHelper::usesBufferForWrite(const BufferHelper &buffer) const
-{
-    BufferAccess access;
-    if (!mUsedBuffers.get(buffer.getBufferSerial().getValue(), &access))
-    {
-        return false;
-    }
-    return access == BufferAccess::Write;
-}
-
-void CommandBufferHelper::bufferRead(ContextVk *contextVk,
-                                     VkAccessFlags readAccessType,
-                                     PipelineStage readStage,
-                                     BufferHelper *buffer)
+void CommandBufferHelperCommon::bufferRead(ContextVk *contextVk,
+                                           VkAccessFlags readAccessType,
+                                           PipelineStage readStage,
+                                           BufferHelper *buffer)
 {
     VkPipelineStageFlagBits stageBits = kPipelineStageFlagBitMap[readStage];
     if (buffer->recordReadBarrier(readAccessType, stageBits, &mPipelineBarriers[readStage]))
@@ -1092,11 +1069,11 @@
     }
 }
 
-void CommandBufferHelper::bufferWrite(ContextVk *contextVk,
-                                      VkAccessFlags writeAccessType,
-                                      PipelineStage writeStage,
-                                      AliasingMode aliasingMode,
-                                      BufferHelper *buffer)
+void CommandBufferHelperCommon::bufferWrite(ContextVk *contextVk,
+                                            VkAccessFlags writeAccessType,
+                                            PipelineStage writeStage,
+                                            AliasingMode aliasingMode,
+                                            BufferHelper *buffer)
 {
     buffer->retainReadWrite(&contextVk->getResourceUseList());
     VkPipelineStageFlagBits stageBits = kPipelineStageFlagBitMap[writeStage];
@@ -1124,70 +1101,298 @@
     }
 }
 
-void CommandBufferHelper::imageRead(ContextVk *contextVk,
-                                    VkImageAspectFlags aspectFlags,
-                                    ImageLayout imageLayout,
-                                    ImageHelper *image)
+bool CommandBufferHelperCommon::usesBuffer(const BufferHelper &buffer) const
+{
+    return mUsedBuffers.contains(buffer.getBufferSerial().getValue());
+}
+
+bool CommandBufferHelperCommon::usesBufferForWrite(const BufferHelper &buffer) const
+{
+    BufferAccess access;
+    if (!mUsedBuffers.get(buffer.getBufferSerial().getValue(), &access))
+    {
+        return false;
+    }
+    return access == BufferAccess::Write;
+}
+
+void CommandBufferHelperCommon::executeBarriers(const angle::FeaturesVk &features,
+                                                PrimaryCommandBuffer *primary)
+{
+    // make a local copy for faster access
+    PipelineStagesMask mask = mPipelineBarrierMask;
+    if (mask.none())
+    {
+        return;
+    }
+
+    if (features.preferAggregateBarrierCalls.enabled)
+    {
+        PipelineStagesMask::Iterator iter = mask.begin();
+        PipelineBarrier &barrier          = mPipelineBarriers[*iter];
+        for (++iter; iter != mask.end(); ++iter)
+        {
+            barrier.merge(&mPipelineBarriers[*iter]);
+        }
+        barrier.execute(primary);
+    }
+    else
+    {
+        for (PipelineStage pipelineStage : mask)
+        {
+            PipelineBarrier &barrier = mPipelineBarriers[pipelineStage];
+            barrier.execute(primary);
+        }
+    }
+    mPipelineBarrierMask.reset();
+}
+
+void CommandBufferHelperCommon::imageReadImpl(ContextVk *contextVk,
+                                              VkImageAspectFlags aspectFlags,
+                                              ImageLayout imageLayout,
+                                              ImageHelper *image)
 {
     if (image->isReadBarrierNecessary(imageLayout))
     {
         updateImageLayoutAndBarrier(contextVk, image, aspectFlags, imageLayout);
     }
-
-    if (mIsRenderPassCommandBuffer)
-    {
-        // As noted in the header we don't support multiple read layouts for Images.
-        // We allow duplicate uses in the RP to accommodate for normal GL sampler usage.
-        if (!usesImageInRenderPass(*image))
-        {
-            mRenderPassUsedImages.insert(image->getImageSerial().getValue());
-            image->retain(&contextVk->getResourceUseList());
-        }
-    }
-    else
-    {
-        image->retain(&contextVk->getResourceUseList());
-    }
 }
 
-void CommandBufferHelper::imageWrite(ContextVk *contextVk,
-                                     gl::LevelIndex level,
-                                     uint32_t layerStart,
-                                     uint32_t layerCount,
-                                     VkImageAspectFlags aspectFlags,
-                                     ImageLayout imageLayout,
-                                     AliasingMode aliasingMode,
-                                     ImageHelper *image)
+void CommandBufferHelperCommon::imageWriteImpl(ContextVk *contextVk,
+                                               gl::LevelIndex level,
+                                               uint32_t layerStart,
+                                               uint32_t layerCount,
+                                               VkImageAspectFlags aspectFlags,
+                                               ImageLayout imageLayout,
+                                               AliasingMode aliasingMode,
+                                               ImageHelper *image)
 {
     image->retain(&contextVk->getResourceUseList());
     image->onWrite(level, 1, layerStart, layerCount, aspectFlags);
     // Write always requires a barrier
     updateImageLayoutAndBarrier(contextVk, image, aspectFlags, imageLayout);
+}
 
-    if (mIsRenderPassCommandBuffer)
+void CommandBufferHelperCommon::updateImageLayoutAndBarrier(Context *context,
+                                                            ImageHelper *image,
+                                                            VkImageAspectFlags aspectFlags,
+                                                            ImageLayout imageLayout)
+{
+    PipelineStage barrierIndex = kImageMemoryBarrierData[imageLayout].barrierIndex;
+    ASSERT(barrierIndex != PipelineStage::InvalidEnum);
+    PipelineBarrier *barrier = &mPipelineBarriers[barrierIndex];
+    if (image->updateLayoutAndBarrier(context, aspectFlags, imageLayout, barrier))
     {
-        // When used as a storage image we allow for aliased writes.
-        if (aliasingMode == AliasingMode::Disallowed)
-        {
-            ASSERT(!usesImageInRenderPass(*image));
-        }
-        if (!usesImageInRenderPass(*image))
-        {
-            mRenderPassUsedImages.insert(image->getImageSerial().getValue());
-        }
+        mPipelineBarrierMask.set(barrierIndex);
     }
 }
 
-void CommandBufferHelper::colorImagesDraw(ResourceUseList *resourceUseList,
-                                          ImageHelper *image,
-                                          ImageHelper *resolveImage,
-                                          PackedAttachmentIndex packedAttachmentIndex)
+void CommandBufferHelperCommon::addCommandDiagnosticsCommon(std::ostringstream *out)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
+    *out << "Memory Barrier: ";
+    for (PipelineBarrier &barrier : mPipelineBarriers)
+    {
+        if (!barrier.isEmpty())
+        {
+            barrier.addDiagnosticsString(*out);
+        }
+    }
+    *out << "\\l";
+}
+
+// OutsideRenderPassCommandBufferHelper implementation.
+OutsideRenderPassCommandBufferHelper::OutsideRenderPassCommandBufferHelper() {}
+
+OutsideRenderPassCommandBufferHelper::~OutsideRenderPassCommandBufferHelper() {}
+
+angle::Result OutsideRenderPassCommandBufferHelper::initialize(Context *context,
+                                                               CommandPool *commandPool)
+{
+    initializeImpl(context, commandPool);
+    return initializeCommandBuffer(context);
+}
+
+angle::Result OutsideRenderPassCommandBufferHelper::initializeCommandBuffer(Context *context)
+{
+    return mCommandBuffer.initialize(context, mCommandPool, false, &mAllocator);
+}
+
+angle::Result OutsideRenderPassCommandBufferHelper::reset(Context *context)
+{
+    resetImpl();
+
+    // Reset and re-initialize the command buffer
+    context->getRenderer()->resetOutsideRenderPassCommandBuffer(std::move(mCommandBuffer));
+    return initializeCommandBuffer(context);
+}
+
+void OutsideRenderPassCommandBufferHelper::imageRead(ContextVk *contextVk,
+                                                     VkImageAspectFlags aspectFlags,
+                                                     ImageLayout imageLayout,
+                                                     ImageHelper *image)
+{
+    imageReadImpl(contextVk, aspectFlags, imageLayout, image);
+    image->retain(&contextVk->getResourceUseList());
+}
+
+void OutsideRenderPassCommandBufferHelper::imageWrite(ContextVk *contextVk,
+                                                      gl::LevelIndex level,
+                                                      uint32_t layerStart,
+                                                      uint32_t layerCount,
+                                                      VkImageAspectFlags aspectFlags,
+                                                      ImageLayout imageLayout,
+                                                      AliasingMode aliasingMode,
+                                                      ImageHelper *image)
+{
+    imageWriteImpl(contextVk, level, layerStart, layerCount, aspectFlags, imageLayout, aliasingMode,
+                   image);
+}
+
+angle::Result OutsideRenderPassCommandBufferHelper::flushToPrimary(Context *context,
+                                                                   PrimaryCommandBuffer *primary)
+{
+    ANGLE_TRACE_EVENT0("gpu.angle", "OutsideRenderPassCommandBufferHelper::flushToPrimary");
+    ASSERT(!empty());
+
+    // Commands that are added to primary before beginRenderPass command
+    executeBarriers(context->getRenderer()->getFeatures(), primary);
+
+    ANGLE_TRY(mCommandBuffer.end(context));
+    mCommandBuffer.executeCommands(primary);
+
+    // Restart the command buffer.
+    return reset(context);
+}
+
+void OutsideRenderPassCommandBufferHelper::addCommandDiagnostics(ContextVk *contextVk)
+{
+    std::ostringstream out;
+    addCommandDiagnosticsCommon(&out);
+
+    out << mCommandBuffer.dumpCommands("\\l");
+    contextVk->addCommandBufferDiagnostics(out.str());
+}
+
+// RenderPassCommandBufferHelper implementation.
+RenderPassCommandBufferHelper::RenderPassCommandBufferHelper()
+    : mCounter(0),
+      mClearValues{},
+      mRenderPassStarted(false),
+      mTransformFeedbackCounterBuffers{},
+      mValidTransformFeedbackBufferCount(0),
+      mRebindTransformFeedbackBuffers(false),
+      mIsTransformFeedbackActiveUnpaused(false),
+      mDepthAccess(ResourceAccess::Unused),
+      mStencilAccess(ResourceAccess::Unused),
+      mDepthCmdCountInvalidated(kInfiniteCmdCount),
+      mDepthCmdCountDisabled(kInfiniteCmdCount),
+      mStencilCmdCountInvalidated(kInfiniteCmdCount),
+      mStencilCmdCountDisabled(kInfiniteCmdCount),
+      mDepthStencilAttachmentIndex(kAttachmentIndexInvalid),
+      mDepthStencilImage(nullptr),
+      mDepthStencilResolveImage(nullptr),
+      mDepthStencilLevelIndex(0),
+      mDepthStencilLayerIndex(0),
+      mDepthStencilLayerCount(0),
+      mColorImagesCount(0),
+      mImageOptimizeForPresent(nullptr)
+{}
+
+RenderPassCommandBufferHelper::~RenderPassCommandBufferHelper()
+{
+    mFramebuffer.setHandle(VK_NULL_HANDLE);
+}
+
+angle::Result RenderPassCommandBufferHelper::initialize(Context *context, CommandPool *commandPool)
+{
+    initializeImpl(context, commandPool);
+    return initializeCommandBuffer(context);
+}
+
+angle::Result RenderPassCommandBufferHelper::initializeCommandBuffer(Context *context)
+{
+    return mCommandBuffer.initialize(context, mCommandPool, true, &mAllocator);
+}
+
+angle::Result RenderPassCommandBufferHelper::reset(Context *context)
+{
+    resetImpl();
+
+    mRenderPassStarted                 = false;
+    mValidTransformFeedbackBufferCount = 0;
+    mRebindTransformFeedbackBuffers    = false;
+    mHasShaderStorageOutput            = false;
+    mHasGLMemoryBarrierIssued          = false;
+    mDepthAccess                       = ResourceAccess::Unused;
+    mStencilAccess                     = ResourceAccess::Unused;
+    mDepthCmdCountInvalidated          = kInfiniteCmdCount;
+    mDepthCmdCountDisabled             = kInfiniteCmdCount;
+    mStencilCmdCountInvalidated        = kInfiniteCmdCount;
+    mStencilCmdCountDisabled           = kInfiniteCmdCount;
+    mColorImagesCount                  = PackedAttachmentCount(0);
+    mDepthStencilAttachmentIndex       = kAttachmentIndexInvalid;
+    mDepthInvalidateArea               = gl::Rectangle();
+    mStencilInvalidateArea             = gl::Rectangle();
+    mRenderPassUsedImages.clear();
+    mDepthStencilImage        = nullptr;
+    mDepthStencilResolveImage = nullptr;
+    mColorImages.reset();
+    mColorResolveImages.reset();
+    mImageOptimizeForPresent = nullptr;
+
+    // Reset and re-initialize the command buffer
+    context->getRenderer()->resetRenderPassCommandBuffer(std::move(mCommandBuffer));
+    return initializeCommandBuffer(context);
+}
+
+void RenderPassCommandBufferHelper::imageRead(ContextVk *contextVk,
+                                              VkImageAspectFlags aspectFlags,
+                                              ImageLayout imageLayout,
+                                              ImageHelper *image)
+{
+    imageReadImpl(contextVk, aspectFlags, imageLayout, image);
+
+    // As noted in the header we don't support multiple read layouts for Images.
+    // We allow duplicate uses in the RP to accommodate for normal GL sampler usage.
+    if (!usesImage(*image))
+    {
+        mRenderPassUsedImages.insert(image->getImageSerial().getValue());
+        image->retain(&contextVk->getResourceUseList());
+    }
+}
+
+void RenderPassCommandBufferHelper::imageWrite(ContextVk *contextVk,
+                                               gl::LevelIndex level,
+                                               uint32_t layerStart,
+                                               uint32_t layerCount,
+                                               VkImageAspectFlags aspectFlags,
+                                               ImageLayout imageLayout,
+                                               AliasingMode aliasingMode,
+                                               ImageHelper *image)
+{
+    imageWriteImpl(contextVk, level, layerStart, layerCount, aspectFlags, imageLayout, aliasingMode,
+                   image);
+
+    // When used as a storage image we allow for aliased writes.
+    if (aliasingMode == AliasingMode::Disallowed)
+    {
+        ASSERT(!usesImage(*image));
+    }
+    if (!usesImage(*image))
+    {
+        mRenderPassUsedImages.insert(image->getImageSerial().getValue());
+    }
+}
+
+void RenderPassCommandBufferHelper::colorImagesDraw(ResourceUseList *resourceUseList,
+                                                    ImageHelper *image,
+                                                    ImageHelper *resolveImage,
+                                                    PackedAttachmentIndex packedAttachmentIndex)
+{
     ASSERT(packedAttachmentIndex < mColorImagesCount);
 
     image->retain(resourceUseList);
-    if (!usesImageInRenderPass(*image))
+    if (!usesImage(*image))
     {
         // This is possible due to different layers of the same texture being attached to different
         // attachments
@@ -1200,7 +1405,7 @@
     if (resolveImage)
     {
         resolveImage->retain(resourceUseList);
-        if (!usesImageInRenderPass(*resolveImage))
+        if (!usesImage(*resolveImage))
         {
             mRenderPassUsedImages.insert(resolveImage->getImageSerial().getValue());
         }
@@ -1210,16 +1415,15 @@
     }
 }
 
-void CommandBufferHelper::depthStencilImagesDraw(ResourceUseList *resourceUseList,
-                                                 gl::LevelIndex level,
-                                                 uint32_t layerStart,
-                                                 uint32_t layerCount,
-                                                 ImageHelper *image,
-                                                 ImageHelper *resolveImage)
+void RenderPassCommandBufferHelper::depthStencilImagesDraw(ResourceUseList *resourceUseList,
+                                                           gl::LevelIndex level,
+                                                           uint32_t layerStart,
+                                                           uint32_t layerCount,
+                                                           ImageHelper *image,
+                                                           ImageHelper *resolveImage)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
-    ASSERT(!usesImageInRenderPass(*image));
-    ASSERT(!resolveImage || !usesImageInRenderPass(*resolveImage));
+    ASSERT(!usesImage(*image));
+    ASSERT(!resolveImage || !usesImage(*resolveImage));
 
     // Because depthStencil buffer's read/write property can change while we build renderpass, we
     // defer the image layout changes until endRenderPass time or when images going away so that we
@@ -1244,7 +1448,7 @@
     }
 }
 
-void CommandBufferHelper::onDepthAccess(ResourceAccess access)
+void RenderPassCommandBufferHelper::onDepthAccess(ResourceAccess access)
 {
     // Update the access for optimizing this render pass's loadOp
     UpdateAccess(&mDepthAccess, access);
@@ -1257,7 +1461,7 @@
     }
 }
 
-void CommandBufferHelper::onStencilAccess(ResourceAccess access)
+void RenderPassCommandBufferHelper::onStencilAccess(ResourceAccess access)
 {
     // Update the access for optimizing this render pass's loadOp
     UpdateAccess(&mStencilAccess, access);
@@ -1270,9 +1474,9 @@
     }
 }
 
-bool CommandBufferHelper::onDepthStencilAccess(ResourceAccess access,
-                                               uint32_t *cmdCountInvalidated,
-                                               uint32_t *cmdCountDisabled)
+bool RenderPassCommandBufferHelper::onDepthStencilAccess(ResourceAccess access,
+                                                         uint32_t *cmdCountInvalidated,
+                                                         uint32_t *cmdCountDisabled)
 {
     if (*cmdCountInvalidated == kInfiniteCmdCount)
     {
@@ -1313,9 +1517,9 @@
     }
 }
 
-void CommandBufferHelper::updateStartedRenderPassWithDepthMode(bool readOnlyDepthStencilMode)
+void RenderPassCommandBufferHelper::updateStartedRenderPassWithDepthMode(
+    bool readOnlyDepthStencilMode)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     ASSERT(mRenderPassStarted);
 
     if (mDepthStencilImage)
@@ -1344,7 +1548,7 @@
     }
 }
 
-void CommandBufferHelper::restoreDepthContent()
+void RenderPassCommandBufferHelper::restoreDepthContent()
 {
     // Note that the image may have been deleted since the render pass has started.
     if (mDepthStencilImage)
@@ -1356,7 +1560,7 @@
     }
 }
 
-void CommandBufferHelper::restoreStencilContent()
+void RenderPassCommandBufferHelper::restoreStencilContent()
 {
     // Note that the image may have been deleted since the render pass has started.
     if (mDepthStencilImage)
@@ -1368,57 +1572,12 @@
     }
 }
 
-void CommandBufferHelper::executeBarriers(const angle::FeaturesVk &features,
-                                          PrimaryCommandBuffer *primary)
+void RenderPassCommandBufferHelper::finalizeColorImageLayout(
+    Context *context,
+    ImageHelper *image,
+    PackedAttachmentIndex packedAttachmentIndex,
+    bool isResolveImage)
 {
-    // make a local copy for faster access
-    PipelineStagesMask mask = mPipelineBarrierMask;
-    if (mask.none())
-    {
-        return;
-    }
-
-    if (features.preferAggregateBarrierCalls.enabled)
-    {
-        PipelineStagesMask::Iterator iter = mask.begin();
-        PipelineBarrier &barrier          = mPipelineBarriers[*iter];
-        for (++iter; iter != mask.end(); ++iter)
-        {
-            barrier.merge(&mPipelineBarriers[*iter]);
-        }
-        barrier.execute(primary);
-    }
-    else
-    {
-        for (PipelineStage pipelineStage : mask)
-        {
-            PipelineBarrier &barrier = mPipelineBarriers[pipelineStage];
-            barrier.execute(primary);
-        }
-    }
-    mPipelineBarrierMask.reset();
-}
-
-void CommandBufferHelper::updateImageLayoutAndBarrier(Context *context,
-                                                      ImageHelper *image,
-                                                      VkImageAspectFlags aspectFlags,
-                                                      ImageLayout imageLayout)
-{
-    PipelineStage barrierIndex = kImageMemoryBarrierData[imageLayout].barrierIndex;
-    ASSERT(barrierIndex != PipelineStage::InvalidEnum);
-    PipelineBarrier *barrier = &mPipelineBarriers[barrierIndex];
-    if (image->updateLayoutAndBarrier(context, aspectFlags, imageLayout, barrier))
-    {
-        mPipelineBarrierMask.set(barrierIndex);
-    }
-}
-
-void CommandBufferHelper::finalizeColorImageLayout(Context *context,
-                                                   ImageHelper *image,
-                                                   PackedAttachmentIndex packedAttachmentIndex,
-                                                   bool isResolveImage)
-{
-    ASSERT(mIsRenderPassCommandBuffer);
     ASSERT(packedAttachmentIndex < mColorImagesCount);
     ASSERT(image != nullptr);
 
@@ -1446,7 +1605,7 @@
     {
         ASSERT(packedAttachmentIndex == kAttachmentIndexZero);
         // Use finalLayout instead of extra barrier for layout change to present
-        mImageOptimizeForPresent->setCurrentImageLayout(vk::ImageLayout::Present);
+        mImageOptimizeForPresent->setCurrentImageLayout(ImageLayout::Present);
         // TODO(syoussefi):  We currently don't store the layout of the resolve attachments, so once
         // multisampled backbuffers are optimized to use resolve attachments, this information needs
         // to be stored somewhere.  http://anglebug.com/4836
@@ -1458,9 +1617,8 @@
     image->resetRenderPassUsageFlags();
 }
 
-void CommandBufferHelper::finalizeDepthStencilImageLayout(Context *context)
+void RenderPassCommandBufferHelper::finalizeDepthStencilImageLayout(Context *context)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     ASSERT(mDepthStencilImage);
 
     // Do depth stencil layout change.
@@ -1507,9 +1665,8 @@
     }
 }
 
-void CommandBufferHelper::finalizeDepthStencilResolveImageLayout(Context *context)
+void RenderPassCommandBufferHelper::finalizeDepthStencilResolveImageLayout(Context *context)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     ASSERT(mDepthStencilImage);
     ASSERT(!mDepthStencilResolveImage->hasRenderPassUsageFlag(RenderPassUsage::ReadOnlyAttachment));
 
@@ -1545,10 +1702,8 @@
     mDepthStencilResolveImage->resetRenderPassUsageFlags();
 }
 
-void CommandBufferHelper::finalizeImageLayout(Context *context, const ImageHelper *image)
+void RenderPassCommandBufferHelper::finalizeImageLayout(Context *context, const ImageHelper *image)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
-
     if (image->hasRenderPassUsageFlag(RenderPassUsage::RenderTargetAttachment))
     {
         for (PackedAttachmentIndex index = kAttachmentIndexZero; index < mColorImagesCount; ++index)
@@ -1579,7 +1734,7 @@
     }
 }
 
-void CommandBufferHelper::finalizeDepthStencilLoadStore(Context *context)
+void RenderPassCommandBufferHelper::finalizeDepthStencilLoadStore(Context *context)
 {
     ASSERT(mDepthStencilAttachmentIndex != kAttachmentIndexInvalid);
 
@@ -1656,10 +1811,10 @@
     SetBitField(dsOps.stencilStoreOp, stencilStoreOp);
 }
 
-void CommandBufferHelper::finalizeDepthStencilLoadStoreOps(Context *context,
-                                                           ResourceAccess access,
-                                                           RenderPassLoadOp *loadOp,
-                                                           RenderPassStoreOp *storeOp)
+void RenderPassCommandBufferHelper::finalizeDepthStencilLoadStoreOps(Context *context,
+                                                                     ResourceAccess access,
+                                                                     RenderPassLoadOp *loadOp,
+                                                                     RenderPassStoreOp *storeOp)
 {
     // For read only depth stencil, we can use StoreOpNone if available.  DontCare is still
     // preferred, so do this after handling DontCare.
@@ -1696,29 +1851,28 @@
     }
 }
 
-void CommandBufferHelper::finalizeDepthStencilImageLayoutAndLoadStore(Context *context)
+void RenderPassCommandBufferHelper::finalizeDepthStencilImageLayoutAndLoadStore(Context *context)
 {
     finalizeDepthStencilImageLayout(context);
     finalizeDepthStencilLoadStore(context);
     mDepthStencilImage->resetRenderPassUsageFlags();
 }
 
-angle::Result CommandBufferHelper::beginRenderPass(
+angle::Result RenderPassCommandBufferHelper::beginRenderPass(
     ContextVk *contextVk,
     const Framebuffer &framebuffer,
     const gl::Rectangle &renderArea,
     const RenderPassDesc &renderPassDesc,
     const AttachmentOpsArray &renderPassAttachmentOps,
-    const vk::PackedAttachmentCount colorAttachmentCount,
+    const PackedAttachmentCount colorAttachmentCount,
     const PackedAttachmentIndex depthStencilAttachmentIndex,
     const PackedClearValuesArray &clearValues,
-    CommandBuffer **commandBufferOut)
+    RenderPassCommandBuffer **commandBufferOut)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
-    ASSERT(empty());
+    ASSERT(!mRenderPassStarted);
 
     VkCommandBufferInheritanceInfo inheritanceInfo = {};
-    ANGLE_TRY(vk::CommandBuffer::InitializeRenderPassInheritanceInfo(
+    ANGLE_TRY(RenderPassCommandBuffer::InitializeRenderPassInheritanceInfo(
         contextVk, framebuffer, renderPassDesc, &inheritanceInfo));
     ANGLE_TRY(mCommandBuffer.begin(contextVk, inheritanceInfo));
 
@@ -1737,7 +1891,7 @@
     return angle::Result::Continue;
 }
 
-angle::Result CommandBufferHelper::endRenderPass(ContextVk *contextVk)
+angle::Result RenderPassCommandBufferHelper::endRenderPass(ContextVk *contextVk)
 {
     ANGLE_TRY(mCommandBuffer.end(contextVk));
 
@@ -1771,11 +1925,10 @@
     return angle::Result::Continue;
 }
 
-void CommandBufferHelper::beginTransformFeedback(size_t validBufferCount,
-                                                 const VkBuffer *counterBuffers,
-                                                 bool rebindBuffers)
+void RenderPassCommandBufferHelper::beginTransformFeedback(size_t validBufferCount,
+                                                           const VkBuffer *counterBuffers,
+                                                           bool rebindBuffers)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     mValidTransformFeedbackBufferCount = static_cast<uint32_t>(validBufferCount);
     mRebindTransformFeedbackBuffers    = rebindBuffers;
 
@@ -1785,24 +1938,23 @@
     }
 }
 
-void CommandBufferHelper::endTransformFeedback()
+void RenderPassCommandBufferHelper::endTransformFeedback()
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     pauseTransformFeedback();
     mValidTransformFeedbackBufferCount = 0;
 }
 
-void CommandBufferHelper::invalidateRenderPassColorAttachment(PackedAttachmentIndex attachmentIndex)
+void RenderPassCommandBufferHelper::invalidateRenderPassColorAttachment(
+    PackedAttachmentIndex attachmentIndex)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     SetBitField(mAttachmentOps[attachmentIndex].storeOp, RenderPassStoreOp::DontCare);
     mAttachmentOps[attachmentIndex].isInvalidated = true;
 }
 
-void CommandBufferHelper::invalidateRenderPassDepthAttachment(const gl::DepthStencilState &dsState,
-                                                              const gl::Rectangle &invalidateArea)
+void RenderPassCommandBufferHelper::invalidateRenderPassDepthAttachment(
+    const gl::DepthStencilState &dsState,
+    const gl::Rectangle &invalidateArea)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     // Keep track of the size of commands in the command buffer.  If the size grows in the
     // future, that implies that drawing occured since invalidated.
     mDepthCmdCountInvalidated = mCommandBuffer.getRenderPassWriteCommandCount();
@@ -1815,11 +1967,10 @@
     ExtendRenderPassInvalidateArea(invalidateArea, &mDepthInvalidateArea);
 }
 
-void CommandBufferHelper::invalidateRenderPassStencilAttachment(
+void RenderPassCommandBufferHelper::invalidateRenderPassStencilAttachment(
     const gl::DepthStencilState &dsState,
     const gl::Rectangle &invalidateArea)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     // Keep track of the size of commands in the command buffer.  If the size grows in the
     // future, that implies that drawing occured since invalidated.
     mStencilCmdCountInvalidated = mCommandBuffer.getRenderPassWriteCommandCount();
@@ -1834,157 +1985,53 @@
     ExtendRenderPassInvalidateArea(invalidateArea, &mStencilInvalidateArea);
 }
 
-angle::Result CommandBufferHelper::flushToPrimary(Context *context,
-                                                  PrimaryCommandBuffer *primary,
-                                                  const RenderPass *renderPass)
+angle::Result RenderPassCommandBufferHelper::flushToPrimary(Context *context,
+                                                            PrimaryCommandBuffer *primary,
+                                                            const RenderPass *renderPass)
 {
-    ANGLE_TRACE_EVENT0("gpu.angle", "CommandBufferHelper::flushToPrimary");
-    ASSERT(!empty());
+    ANGLE_TRACE_EVENT0("gpu.angle", "RenderPassCommandBufferHelper::flushToPrimary");
+    ASSERT(mRenderPassStarted);
 
     // Commands that are added to primary before beginRenderPass command
     executeBarriers(context->getRenderer()->getFeatures(), primary);
 
-    if (mIsRenderPassCommandBuffer)
-    {
-        ASSERT(renderPass != nullptr);
+    ASSERT(renderPass != nullptr);
 
-        VkRenderPassBeginInfo beginInfo    = {};
-        beginInfo.sType                    = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
-        beginInfo.renderPass               = renderPass->getHandle();
-        beginInfo.framebuffer              = mFramebuffer.getHandle();
-        beginInfo.renderArea.offset.x      = static_cast<uint32_t>(mRenderArea.x);
-        beginInfo.renderArea.offset.y      = static_cast<uint32_t>(mRenderArea.y);
-        beginInfo.renderArea.extent.width  = static_cast<uint32_t>(mRenderArea.width);
-        beginInfo.renderArea.extent.height = static_cast<uint32_t>(mRenderArea.height);
-        beginInfo.clearValueCount = static_cast<uint32_t>(mRenderPassDesc.attachmentCount());
-        beginInfo.pClearValues    = mClearValues.data();
+    VkRenderPassBeginInfo beginInfo    = {};
+    beginInfo.sType                    = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+    beginInfo.renderPass               = renderPass->getHandle();
+    beginInfo.framebuffer              = mFramebuffer.getHandle();
+    beginInfo.renderArea.offset.x      = static_cast<uint32_t>(mRenderArea.x);
+    beginInfo.renderArea.offset.y      = static_cast<uint32_t>(mRenderArea.y);
+    beginInfo.renderArea.extent.width  = static_cast<uint32_t>(mRenderArea.width);
+    beginInfo.renderArea.extent.height = static_cast<uint32_t>(mRenderArea.height);
+    beginInfo.clearValueCount          = static_cast<uint32_t>(mRenderPassDesc.attachmentCount());
+    beginInfo.pClearValues             = mClearValues.data();
 
-        // Run commands inside the RenderPass.
-        constexpr VkSubpassContents kSubpassContents =
-            vk::CommandBuffer::ExecutesInline() ? VK_SUBPASS_CONTENTS_INLINE
-                                                : VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS;
+    // Run commands inside the RenderPass.
+    constexpr VkSubpassContents kSubpassContents =
+        RenderPassCommandBuffer::ExecutesInline() ? VK_SUBPASS_CONTENTS_INLINE
+                                                  : VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS;
 
-        primary->beginRenderPass(beginInfo, kSubpassContents);
-        mCommandBuffer.executeCommands(primary);
-        primary->endRenderPass();
-    }
-    else
-    {
-        ANGLE_TRY(mCommandBuffer.end(context));
-        mCommandBuffer.executeCommands(primary);
-    }
+    primary->beginRenderPass(beginInfo, kSubpassContents);
+    mCommandBuffer.executeCommands(primary);
+    primary->endRenderPass();
 
     // Restart the command buffer.
     return reset(context);
 }
 
-void CommandBufferHelper::updateRenderPassForResolve(ContextVk *contextVk,
-                                                     Framebuffer *newFramebuffer,
-                                                     const RenderPassDesc &renderPassDesc)
+void RenderPassCommandBufferHelper::updateRenderPassForResolve(ContextVk *contextVk,
+                                                               Framebuffer *newFramebuffer,
+                                                               const RenderPassDesc &renderPassDesc)
 {
     ASSERT(newFramebuffer);
     mFramebuffer.setHandle(newFramebuffer->getHandle());
     mRenderPassDesc = renderPassDesc;
 }
 
-// Helper functions used below
-char GetLoadOpShorthand(RenderPassLoadOp loadOp)
+void RenderPassCommandBufferHelper::resumeTransformFeedback()
 {
-    switch (loadOp)
-    {
-        case RenderPassLoadOp::Clear:
-            return 'C';
-        case RenderPassLoadOp::Load:
-            return 'L';
-        case RenderPassLoadOp::None:
-            return 'N';
-        default:
-            return 'D';
-    }
-}
-
-char GetStoreOpShorthand(RenderPassStoreOp storeOp)
-{
-    switch (storeOp)
-    {
-        case RenderPassStoreOp::Store:
-            return 'S';
-        case RenderPassStoreOp::None:
-            return 'N';
-        default:
-            return 'D';
-    }
-}
-
-void CommandBufferHelper::addCommandDiagnostics(ContextVk *contextVk)
-{
-    std::ostringstream out;
-
-    out << "Memory Barrier: ";
-    for (PipelineBarrier &barrier : mPipelineBarriers)
-    {
-        if (!barrier.isEmpty())
-        {
-            barrier.addDiagnosticsString(out);
-        }
-    }
-    out << "\\l";
-
-    if (mIsRenderPassCommandBuffer)
-    {
-        size_t attachmentCount             = mRenderPassDesc.attachmentCount();
-        size_t depthStencilAttachmentCount = mRenderPassDesc.hasDepthStencilAttachment() ? 1 : 0;
-        size_t colorAttachmentCount        = attachmentCount - depthStencilAttachmentCount;
-
-        PackedAttachmentIndex attachmentIndexVk(0);
-        std::string loadOps, storeOps;
-
-        if (colorAttachmentCount > 0)
-        {
-            loadOps += " Color: ";
-            storeOps += " Color: ";
-
-            for (size_t i = 0; i < colorAttachmentCount; ++i)
-            {
-                loadOps += GetLoadOpShorthand(
-                    static_cast<RenderPassLoadOp>(mAttachmentOps[attachmentIndexVk].loadOp));
-                storeOps += GetStoreOpShorthand(
-                    static_cast<RenderPassStoreOp>(mAttachmentOps[attachmentIndexVk].storeOp));
-                ++attachmentIndexVk;
-            }
-        }
-
-        if (depthStencilAttachmentCount > 0)
-        {
-            ASSERT(depthStencilAttachmentCount == 1);
-
-            loadOps += " Depth/Stencil: ";
-            storeOps += " Depth/Stencil: ";
-
-            loadOps += GetLoadOpShorthand(
-                static_cast<RenderPassLoadOp>(mAttachmentOps[attachmentIndexVk].loadOp));
-            loadOps += GetLoadOpShorthand(
-                static_cast<RenderPassLoadOp>(mAttachmentOps[attachmentIndexVk].stencilLoadOp));
-
-            storeOps += GetStoreOpShorthand(
-                static_cast<RenderPassStoreOp>(mAttachmentOps[attachmentIndexVk].storeOp));
-            storeOps += GetStoreOpShorthand(
-                static_cast<RenderPassStoreOp>(mAttachmentOps[attachmentIndexVk].stencilStoreOp));
-        }
-
-        if (attachmentCount > 0)
-        {
-            out << "LoadOp:  " << loadOps << "\\l";
-            out << "StoreOp: " << storeOps << "\\l";
-        }
-    }
-    out << mCommandBuffer.dumpCommands("\\l");
-    contextVk->addCommandBufferDiagnostics(out.str());
-}
-
-void CommandBufferHelper::resumeTransformFeedback()
-{
-    ASSERT(mIsRenderPassCommandBuffer);
     ASSERT(isTransformFeedbackStarted());
 
     uint32_t numCounterBuffers =
@@ -1997,24 +2044,24 @@
                                           mTransformFeedbackCounterBuffers.data(), nullptr);
 }
 
-void CommandBufferHelper::pauseTransformFeedback()
+void RenderPassCommandBufferHelper::pauseTransformFeedback()
 {
-    ASSERT(mIsRenderPassCommandBuffer);
     ASSERT(isTransformFeedbackStarted() && isTransformFeedbackActiveUnpaused());
     mIsTransformFeedbackActiveUnpaused = false;
     mCommandBuffer.endTransformFeedback(0, mValidTransformFeedbackBufferCount,
                                         mTransformFeedbackCounterBuffers.data(), nullptr);
 }
 
-void CommandBufferHelper::updateRenderPassColorClear(PackedAttachmentIndex colorIndexVk,
-                                                     const VkClearValue &clearValue)
+void RenderPassCommandBufferHelper::updateRenderPassColorClear(PackedAttachmentIndex colorIndexVk,
+                                                               const VkClearValue &clearValue)
 {
     mAttachmentOps.setClearOp(colorIndexVk);
     mClearValues.store(colorIndexVk, VK_IMAGE_ASPECT_COLOR_BIT, clearValue);
 }
 
-void CommandBufferHelper::updateRenderPassDepthStencilClear(VkImageAspectFlags aspectFlags,
-                                                            const VkClearValue &clearValue)
+void RenderPassCommandBufferHelper::updateRenderPassDepthStencilClear(
+    VkImageAspectFlags aspectFlags,
+    const VkClearValue &clearValue)
 {
     // Don't overwrite prior clear values for individual aspects.
     VkClearValue combinedClearValue = mClearValues[mDepthStencilAttachmentIndex];
@@ -2035,10 +2082,9 @@
     mClearValues.storeNoDepthStencil(mDepthStencilAttachmentIndex, combinedClearValue);
 }
 
-void CommandBufferHelper::growRenderArea(ContextVk *contextVk, const gl::Rectangle &newRenderArea)
+void RenderPassCommandBufferHelper::growRenderArea(ContextVk *contextVk,
+                                                   const gl::Rectangle &newRenderArea)
 {
-    ASSERT(mIsRenderPassCommandBuffer);
-
     // The render area is grown such that it covers both the previous and the new render areas.
     gl::GetEnclosingRectangle(mRenderArea, newRenderArea, &mRenderArea);
 
@@ -2061,13 +2107,66 @@
     }
 }
 
-// CommandBufferRecycler implementation.
-CommandBufferRecycler::CommandBufferRecycler()  = default;
-CommandBufferRecycler::~CommandBufferRecycler() = default;
-
-void CommandBufferRecycler::onDestroy()
+void RenderPassCommandBufferHelper::addCommandDiagnostics(ContextVk *contextVk)
 {
-    for (vk::CommandBufferHelper *commandBufferHelper : mCommandBufferHelperFreeList)
+    std::ostringstream out;
+    addCommandDiagnosticsCommon(&out);
+
+    size_t attachmentCount             = mRenderPassDesc.attachmentCount();
+    size_t depthStencilAttachmentCount = mRenderPassDesc.hasDepthStencilAttachment() ? 1 : 0;
+    size_t colorAttachmentCount        = attachmentCount - depthStencilAttachmentCount;
+
+    PackedAttachmentIndex attachmentIndexVk(0);
+    std::string loadOps, storeOps;
+
+    if (colorAttachmentCount > 0)
+    {
+        loadOps += " Color: ";
+        storeOps += " Color: ";
+
+        for (size_t i = 0; i < colorAttachmentCount; ++i)
+        {
+            loadOps += GetLoadOpShorthand(
+                static_cast<RenderPassLoadOp>(mAttachmentOps[attachmentIndexVk].loadOp));
+            storeOps += GetStoreOpShorthand(
+                static_cast<RenderPassStoreOp>(mAttachmentOps[attachmentIndexVk].storeOp));
+            ++attachmentIndexVk;
+        }
+    }
+
+    if (depthStencilAttachmentCount > 0)
+    {
+        ASSERT(depthStencilAttachmentCount == 1);
+
+        loadOps += " Depth/Stencil: ";
+        storeOps += " Depth/Stencil: ";
+
+        loadOps += GetLoadOpShorthand(
+            static_cast<RenderPassLoadOp>(mAttachmentOps[attachmentIndexVk].loadOp));
+        loadOps += GetLoadOpShorthand(
+            static_cast<RenderPassLoadOp>(mAttachmentOps[attachmentIndexVk].stencilLoadOp));
+
+        storeOps += GetStoreOpShorthand(
+            static_cast<RenderPassStoreOp>(mAttachmentOps[attachmentIndexVk].storeOp));
+        storeOps += GetStoreOpShorthand(
+            static_cast<RenderPassStoreOp>(mAttachmentOps[attachmentIndexVk].stencilStoreOp));
+    }
+
+    if (attachmentCount > 0)
+    {
+        out << "LoadOp:  " << loadOps << "\\l";
+        out << "StoreOp: " << storeOps << "\\l";
+    }
+
+    out << mCommandBuffer.dumpCommands("\\l");
+    contextVk->addCommandBufferDiagnostics(out.str());
+}
+
+// CommandBufferRecycler implementation.
+template <typename CommandBufferT, typename CommandBufferHelperT>
+void CommandBufferRecycler<CommandBufferT, CommandBufferHelperT>::onDestroy()
+{
+    for (CommandBufferHelperT *commandBufferHelper : mCommandBufferHelperFreeList)
     {
         SafeDelete(commandBufferHelper);
     }
@@ -2076,60 +2175,68 @@
     ASSERT(mSecondaryCommandBuffersToReset.empty());
 }
 
-angle::Result CommandBufferRecycler::getCommandBufferHelper(
+template void CommandBufferRecycler<OutsideRenderPassCommandBuffer,
+                                    OutsideRenderPassCommandBufferHelper>::onDestroy();
+template void
+CommandBufferRecycler<RenderPassCommandBuffer, RenderPassCommandBufferHelper>::onDestroy();
+
+template <typename CommandBufferT, typename CommandBufferHelperT>
+angle::Result CommandBufferRecycler<CommandBufferT, CommandBufferHelperT>::getCommandBufferHelper(
     Context *context,
-    bool hasRenderPass,
     CommandPool *commandPool,
-    CommandBufferHelper **commandBufferHelperOut)
+    CommandBufferHelperT **commandBufferHelperOut)
 {
     if (mCommandBufferHelperFreeList.empty())
     {
-        vk::CommandBufferHelper *commandBuffer = new vk::CommandBufferHelper();
-        *commandBufferHelperOut                = commandBuffer;
+        CommandBufferHelperT *commandBuffer = new CommandBufferHelperT();
+        *commandBufferHelperOut             = commandBuffer;
 
-        return commandBuffer->initialize(context, hasRenderPass, commandPool);
+        return commandBuffer->initialize(context, commandPool);
     }
     else
     {
-        vk::CommandBufferHelper *commandBuffer = mCommandBufferHelperFreeList.back();
+        CommandBufferHelperT *commandBuffer = mCommandBufferHelperFreeList.back();
         mCommandBufferHelperFreeList.pop_back();
-        commandBuffer->setHasRenderPass(hasRenderPass);
         *commandBufferHelperOut = commandBuffer;
         return angle::Result::Continue;
     }
 }
 
-void CommandBufferRecycler::recycleCommandBufferHelper(VkDevice device,
-                                                       vk::CommandBufferHelper **commandBuffer)
+template angle::Result
+CommandBufferRecycler<OutsideRenderPassCommandBuffer, OutsideRenderPassCommandBufferHelper>::
+    getCommandBufferHelper(Context *context,
+                           CommandPool *commandPool,
+                           OutsideRenderPassCommandBufferHelper **commandBufferHelperOut);
+template angle::Result
+CommandBufferRecycler<RenderPassCommandBuffer, RenderPassCommandBufferHelper>::
+    getCommandBufferHelper(Context *context,
+                           CommandPool *commandPool,
+                           RenderPassCommandBufferHelper **commandBufferHelperOut);
+
+template <typename CommandBufferT, typename CommandBufferHelperT>
+void CommandBufferRecycler<CommandBufferT, CommandBufferHelperT>::recycleCommandBufferHelper(
+    VkDevice device,
+    CommandBufferHelperT **commandBuffer)
 {
     ASSERT((*commandBuffer)->empty());
     (*commandBuffer)->markOpen();
-    recycleImpl(device, commandBuffer);
+    RecycleCommandBufferHelper(device, &mCommandBufferHelperFreeList, commandBuffer,
+                               &(*commandBuffer)->getCommandBuffer());
 }
 
-#if ANGLE_USE_CUSTOM_VULKAN_CMD_BUFFERS
-void CommandBufferRecycler::recycleImpl(VkDevice device, CommandBufferHelper **commandBuffer)
-{
-    mCommandBufferHelperFreeList.push_back(*commandBuffer);
-}
-void CommandBufferRecycler::resetCommandBufferHelper(CommandBuffer &&commandBuffer)
-{
-    commandBuffer.reset();
-}
-#else   // ANGLE_USE_CUSTOM_VULKAN_CMD_BUFFERS
-void CommandBufferRecycler::recycleImpl(VkDevice device, CommandBufferHelper **commandBuffer)
-{
-    CommandPool *pool = (*commandBuffer)->getCommandPool();
+template void
+CommandBufferRecycler<OutsideRenderPassCommandBuffer, OutsideRenderPassCommandBufferHelper>::
+    recycleCommandBufferHelper(VkDevice device,
+                               OutsideRenderPassCommandBufferHelper **commandBuffer);
+template void CommandBufferRecycler<RenderPassCommandBuffer, RenderPassCommandBufferHelper>::
+    recycleCommandBufferHelper(VkDevice device, RenderPassCommandBufferHelper **commandBuffer);
 
-    pool->freeCommandBuffers(device, 1, (*commandBuffer)->getCommandBuffer().ptr());
-    (*commandBuffer)->getCommandBuffer().releaseHandle();
-    SafeDelete(*commandBuffer);
-}
-void CommandBufferRecycler::resetCommandBufferHelper(CommandBuffer &&commandBuffer)
+template <typename CommandBufferT, typename CommandBufferHelperT>
+void CommandBufferRecycler<CommandBufferT, CommandBufferHelperT>::resetCommandBuffer(
+    CommandBufferT &&commandBuffer)
 {
-    mSecondaryCommandBuffersToReset.push_back(std::move(commandBuffer));
+    ResetSecondaryCommandBuffer(&mSecondaryCommandBuffersToReset, std::move(commandBuffer));
 }
-#endif  // ANGLE_USE_CUSTOM_VULKAN_CMD_BUFFERS
 
 // DynamicBuffer implementation.
 DynamicBuffer::DynamicBuffer()
@@ -3152,9 +3259,10 @@
     mStatus = QueryStatus::Inactive;
 }
 
+template <typename CommandBufferT>
 void QueryHelper::beginQueryImpl(ContextVk *contextVk,
-                                 CommandBuffer *resetCommandBuffer,
-                                 CommandBuffer *commandBuffer)
+                                 OutsideRenderPassCommandBuffer *resetCommandBuffer,
+                                 CommandBufferT *commandBuffer)
 {
     ASSERT(mStatus != QueryStatus::Active);
     const QueryPool &queryPool = getQueryPool();
@@ -3163,7 +3271,8 @@
     mStatus = QueryStatus::Active;
 }
 
-void QueryHelper::endQueryImpl(ContextVk *contextVk, CommandBuffer *commandBuffer)
+template <typename CommandBufferT>
+void QueryHelper::endQueryImpl(ContextVk *contextVk, CommandBufferT *commandBuffer)
 {
     ASSERT(mStatus != QueryStatus::Ended);
     commandBuffer->endQuery(getQueryPool(), mQuery);
@@ -3181,7 +3290,7 @@
             RenderPassClosureReason::BeginNonRenderPassQuery));
     }
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
     ANGLE_TRY(contextVk->handleGraphicsEventLog(rx::GraphicsEventCmdBuf::InOutsideCmdBufQueryCmd));
@@ -3199,7 +3308,7 @@
             RenderPassClosureReason::EndNonRenderPassQuery));
     }
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
 
     ANGLE_TRY(contextVk->handleGraphicsEventLog(rx::GraphicsEventCmdBuf::InOutsideCmdBufQueryCmd));
@@ -3227,10 +3336,10 @@
 
 angle::Result QueryHelper::beginRenderPassQuery(ContextVk *contextVk)
 {
-    CommandBuffer *outsideRenderPassCommandBuffer;
+    OutsideRenderPassCommandBuffer *outsideRenderPassCommandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &outsideRenderPassCommandBuffer));
 
-    CommandBuffer *renderPassCommandBuffer =
+    RenderPassCommandBuffer *renderPassCommandBuffer =
         &contextVk->getStartedRenderPassCommands().getCommandBuffer();
 
     beginQueryImpl(contextVk, outsideRenderPassCommandBuffer, renderPassCommandBuffer);
@@ -3254,7 +3363,7 @@
             contextVk->flushCommandsAndEndRenderPass(RenderPassClosureReason::TimestampQuery));
     }
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
     writeTimestamp(contextVk, commandBuffer);
     return angle::Result::Continue;
@@ -3269,7 +3378,8 @@
     primary->writeTimestamp(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, queryPool, mQuery);
 }
 
-void QueryHelper::writeTimestamp(ContextVk *contextVk, CommandBuffer *commandBuffer)
+void QueryHelper::writeTimestamp(ContextVk *contextVk,
+                                 OutsideRenderPassCommandBuffer *commandBuffer)
 {
     const QueryPool &queryPool = getQueryPool();
     resetQueryPoolImpl(contextVk, queryPool, commandBuffer);
@@ -3515,7 +3625,7 @@
     access.onBufferTransferWrite(&mDynamicIndexBuffer);
     access.onBufferTransferRead(sourceBuffer);
 
-    vk::CommandBuffer *commandBuffer;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     commandBuffer->copyBuffer(sourceBuffer->getBuffer(), mDynamicIndexBuffer.getBuffer(),
@@ -3675,7 +3785,9 @@
 }
 
 // static
-void LineLoopHelper::Draw(uint32_t count, uint32_t baseVertex, CommandBuffer *commandBuffer)
+void LineLoopHelper::Draw(uint32_t count,
+                          uint32_t baseVertex,
+                          RenderPassCommandBuffer *commandBuffer)
 {
     // Our first index is always 0 because that's how we set it up in createIndexBuffer*.
     commandBuffer->drawIndexedBaseVertex(count, baseVertex);
@@ -4048,7 +4160,7 @@
         access.onBufferTransferWrite(this);
     }
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     commandBuffer->copyBuffer(srcBuffer->getBuffer(), getBuffer(), regionCount, copyRegions);
@@ -4129,7 +4241,8 @@
     return invalidate(renderer, 0, getSize());
 }
 
-void BufferHelper::changeQueue(uint32_t newQueueFamilyIndex, CommandBuffer *commandBuffer)
+void BufferHelper::changeQueue(uint32_t newQueueFamilyIndex,
+                               OutsideRenderPassCommandBuffer *commandBuffer)
 {
     VkBufferMemoryBarrier bufferMemoryBarrier = {};
     bufferMemoryBarrier.sType                 = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
@@ -4150,7 +4263,7 @@
 void BufferHelper::acquireFromExternal(ContextVk *contextVk,
                                        uint32_t externalQueueFamilyIndex,
                                        uint32_t rendererQueueFamilyIndex,
-                                       CommandBuffer *commandBuffer)
+                                       OutsideRenderPassCommandBuffer *commandBuffer)
 {
     mCurrentQueueFamilyIndex = externalQueueFamilyIndex;
 
@@ -4161,7 +4274,7 @@
 void BufferHelper::releaseToExternal(ContextVk *contextVk,
                                      uint32_t rendererQueueFamilyIndex,
                                      uint32_t externalQueueFamilyIndex,
-                                     CommandBuffer *commandBuffer)
+                                     OutsideRenderPassCommandBuffer *commandBuffer)
 {
     ASSERT(mCurrentQueueFamilyIndex == rendererQueueFamilyIndex);
 
@@ -5280,7 +5393,7 @@
                                        VkImageAspectFlags aspectMask,
                                        ImageLayout newLayout,
                                        uint32_t newQueueFamilyIndex,
-                                       CommandBuffer *commandBuffer)
+                                       OutsideRenderPassCommandBuffer *commandBuffer)
 {
     ASSERT(isQueueChangeNeccesary(newQueueFamilyIndex));
     barrierImpl(context, aspectMask, newLayout, newQueueFamilyIndex, commandBuffer);
@@ -5290,7 +5403,7 @@
                                       uint32_t externalQueueFamilyIndex,
                                       uint32_t rendererQueueFamilyIndex,
                                       ImageLayout currentLayout,
-                                      CommandBuffer *commandBuffer)
+                                      OutsideRenderPassCommandBuffer *commandBuffer)
 {
     // The image must be newly allocated or have been released to the external
     // queue. If this is not the case, it's an application bug, so ASSERT might
@@ -5321,7 +5434,7 @@
                                     uint32_t rendererQueueFamilyIndex,
                                     uint32_t externalQueueFamilyIndex,
                                     ImageLayout desiredLayout,
-                                    CommandBuffer *commandBuffer)
+                                    OutsideRenderPassCommandBuffer *commandBuffer)
 {
     ASSERT(mCurrentQueueFamilyIndex == rendererQueueFamilyIndex);
 
@@ -5422,12 +5535,11 @@
     mCurrentQueueFamilyIndex = newQueueFamilyIndex;
 }
 
-template void ImageHelper::barrierImpl<rx::vk::priv::CommandBuffer>(
-    Context *context,
-    VkImageAspectFlags aspectMask,
-    ImageLayout newLayout,
-    uint32_t newQueueFamilyIndex,
-    rx::vk::priv::CommandBuffer *commandBuffer);
+template void ImageHelper::barrierImpl<priv::CommandBuffer>(Context *context,
+                                                            VkImageAspectFlags aspectMask,
+                                                            ImageLayout newLayout,
+                                                            uint32_t newQueueFamilyIndex,
+                                                            priv::CommandBuffer *commandBuffer);
 
 bool ImageHelper::updateLayoutAndBarrier(Context *context,
                                          VkImageAspectFlags aspectMask,
@@ -5514,7 +5626,7 @@
                              uint32_t levelCount,
                              uint32_t baseArrayLayer,
                              uint32_t layerCount,
-                             CommandBuffer *commandBuffer)
+                             OutsideRenderPassCommandBuffer *commandBuffer)
 {
     ASSERT(valid());
 
@@ -5545,7 +5657,7 @@
                                     uint32_t levelCount,
                                     uint32_t baseArrayLayer,
                                     uint32_t layerCount,
-                                    CommandBuffer *commandBuffer)
+                                    OutsideRenderPassCommandBuffer *commandBuffer)
 {
     ASSERT(valid());
 
@@ -5574,7 +5686,7 @@
                         LevelIndex mipLevel,
                         uint32_t baseArrayLayer,
                         uint32_t layerCount,
-                        CommandBuffer *commandBuffer)
+                        OutsideRenderPassCommandBuffer *commandBuffer)
 {
     const angle::Format &angleFormat = getActualFormat();
     bool isDepthStencil              = angleFormat.depthBits > 0 || angleFormat.stencilBits > 0;
@@ -5632,7 +5744,7 @@
                        const gl::Extents &copySize,
                        const VkImageSubresourceLayers &srcSubresource,
                        const VkImageSubresourceLayers &dstSubresource,
-                       CommandBuffer *commandBuffer)
+                       OutsideRenderPassCommandBuffer *commandBuffer)
 {
     ASSERT(commandBuffer->valid() && srcImage->valid() && dstImage->valid());
 
@@ -5717,7 +5829,7 @@
                                     region.dstSubresource.layerCount, VK_IMAGE_ASPECT_COLOR_BIT,
                                     dstImage);
 
-        CommandBuffer *commandBuffer;
+        OutsideRenderPassCommandBuffer *commandBuffer;
         ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
         ASSERT(srcImage->valid() && dstImage->valid());
@@ -5767,7 +5879,7 @@
     access.onImageTransferWrite(baseLevelGL + 1, maxLevel.get(), 0, mLayerCount,
                                 VK_IMAGE_ASPECT_COLOR_BIT, this);
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     // We are able to use blitImage since the image format we are using supports it.
@@ -5870,7 +5982,7 @@
 
 void ImageHelper::resolve(ImageHelper *dst,
                           const VkImageResolve &region,
-                          CommandBuffer *commandBuffer)
+                          OutsideRenderPassCommandBuffer *commandBuffer)
 {
     ASSERT(mCurrentLayout == ImageLayout::TransferSrc ||
            mCurrentLayout == ImageLayout::SharedPresent);
@@ -6991,7 +7103,7 @@
     access.onImageTransferWrite(levelGLStart, 1, kMaxContentDefinedLayerCount, 0, aspectFlags,
                                 this);
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     for (gl::LevelIndex updateMipLevelGL = levelGLStart; updateMipLevelGL < levelGLEnd;
@@ -7556,7 +7668,7 @@
     access.onBufferTransferWrite(dstBuffer);
     access.onImageTransferRead(aspectFlags, this);
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     commandBuffer->copyImageToBuffer(mImage, getCurrentLayout(), bufferHandle, regionCount,
@@ -7729,7 +7841,7 @@
                                     &resolvedImage.get());
     }
 
-    CommandBuffer *commandBuffer;
+    OutsideRenderPassCommandBuffer *commandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(access, &commandBuffer));
 
     const angle::Format *readFormat = &getActualFormat();
@@ -7797,7 +7909,7 @@
         copyAccess.onBufferTransferWrite(&packBuffer);
         copyAccess.onImageTransferRead(copyAspectFlags, src);
 
-        CommandBuffer *copyCommandBuffer;
+        OutsideRenderPassCommandBuffer *copyCommandBuffer;
         ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(copyAccess, &copyCommandBuffer));
 
         ASSERT(packPixelsParams.outputPitch % readFormat->pixelBytes == 0);
@@ -7838,7 +7950,7 @@
     CommandBufferAccess readbackAccess;
     readbackAccess.onBufferTransferWrite(stagingBuffer);
 
-    CommandBuffer *readbackCommandBuffer;
+    OutsideRenderPassCommandBuffer *readbackCommandBuffer;
     ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(readbackAccess, &readbackCommandBuffer));
 
     readbackCommandBuffer->copyImageToBuffer(src->getImage(), src->getCurrentLayout(), bufferHandle,
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index 733877d..fd10664 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -450,7 +450,8 @@
     // When syncing gpu/cpu time, main thread accesses primary directly
     void writeTimestampToPrimary(ContextVk *contextVk, PrimaryCommandBuffer *primary);
     // All other timestamp accesses should be made on outsideRenderPassCommandBuffer
-    void writeTimestamp(ContextVk *contextVk, CommandBuffer *outsideRenderPassCommandBuffer);
+    void writeTimestamp(ContextVk *contextVk,
+                        OutsideRenderPassCommandBuffer *outsideRenderPassCommandBuffer);
 
     // Whether this query helper has generated and submitted any commands.
     bool hasSubmittedCommands() const;
@@ -470,10 +471,12 @@
 
     // Reset needs to always be done outside a render pass, which may be different from the
     // passed-in command buffer (which could be the render pass').
+    template <typename CommandBufferT>
     void beginQueryImpl(ContextVk *contextVk,
-                        CommandBuffer *resetCommandBuffer,
-                        CommandBuffer *commandBuffer);
-    void endQueryImpl(ContextVk *contextVk, CommandBuffer *commandBuffer);
+                        OutsideRenderPassCommandBuffer *resetCommandBuffer,
+                        CommandBufferT *commandBuffer);
+    template <typename CommandBufferT>
+    void endQueryImpl(ContextVk *contextVk, CommandBufferT *commandBuffer);
     template <typename CommandBufferT>
     void resetQueryPoolImpl(ContextVk *contextVk,
                             const QueryPool &queryPool,
@@ -792,19 +795,19 @@
     angle::Result invalidate(RendererVk *renderer);
     angle::Result invalidate(RendererVk *renderer, VkDeviceSize offset, VkDeviceSize size);
 
-    void changeQueue(uint32_t newQueueFamilyIndex, CommandBuffer *commandBuffer);
+    void changeQueue(uint32_t newQueueFamilyIndex, OutsideRenderPassCommandBuffer *commandBuffer);
 
     // Performs an ownership transfer from an external instance or API.
     void acquireFromExternal(ContextVk *contextVk,
                              uint32_t externalQueueFamilyIndex,
                              uint32_t rendererQueueFamilyIndex,
-                             CommandBuffer *commandBuffer);
+                             OutsideRenderPassCommandBuffer *commandBuffer);
 
     // Performs an ownership transfer to an external instance or API.
     void releaseToExternal(ContextVk *contextVk,
                            uint32_t rendererQueueFamilyIndex,
                            uint32_t externalQueueFamilyIndex,
-                           CommandBuffer *commandBuffer);
+                           OutsideRenderPassCommandBuffer *commandBuffer);
 
     // Returns true if the image is owned by an external API or instance.
     bool isReleasedToExternal() const;
@@ -936,24 +939,15 @@
 // This value indicates an "infinite" CmdCount that is not valid for comparing
 constexpr uint32_t kInfiniteCmdCount = 0xFFFFFFFF;
 
-// CommandBufferHelper (CBH) class wraps ANGLE's custom command buffer
-//  class, SecondaryCommandBuffer. This provides a way to temporarily
-//  store Vulkan commands that be can submitted in-line to a primary
-//  command buffer at a later time.
-// The current plan is for the main ANGLE thread to record commands
-//  into the CBH and then pass the CBH off to a worker thread that will
-//  process the commands into a primary command buffer and then submit
-//  those commands to the queue.
-class CommandBufferHelper : angle::NonCopyable
+// CommandBufferHelperCommon and derivatives OutsideRenderPassCommandBufferHelper and
+// RenderPassCommandBufferHelper wrap the outside/inside render pass secondary command buffers,
+// together with other information such as barriers to issue before the command buffer, tracking of
+// resource usages, etc.  When the asyncCommandQueue feature is enabled, objects of these classes
+// are handed off to the worker thread to be executed on the primary command buffer.
+class CommandBufferHelperCommon : angle::NonCopyable
 {
   public:
-    CommandBufferHelper();
-    ~CommandBufferHelper();
-
-    // General Functions (non-renderPass specific)
-    angle::Result initialize(Context *context,
-                             bool isRenderPassCommandBuffer,
-                             CommandPool *commandPool);
+    CommandPool *getCommandPool() { return mCommandPool; }
 
     void bufferRead(ContextVk *contextVk,
                     VkAccessFlags readAccessType,
@@ -965,6 +959,151 @@
                      AliasingMode aliasingMode,
                      BufferHelper *buffer);
 
+    bool usesBuffer(const BufferHelper &buffer) const;
+    bool usesBufferForWrite(const BufferHelper &buffer) const;
+    size_t getUsedBuffersCount() const { return mUsedBuffers.size(); }
+
+    void executeBarriers(const angle::FeaturesVk &features, PrimaryCommandBuffer *primary);
+
+    // The markOpen and markClosed functions are to aid in proper use of the *CommandBufferHelper.
+    // saw invalid use due to threading issues that can be easily caught by marking when it's safe
+    // (open) to write to the commandbuffer.
+#if !defined(ANGLE_ENABLE_ASSERTS)
+    void markOpen() {}
+    void markClosed() {}
+#endif
+
+    void setHasShaderStorageOutput() { mHasShaderStorageOutput = true; }
+    bool hasShaderStorageOutput() const { return mHasShaderStorageOutput; }
+
+    bool hasGLMemoryBarrierIssued() const { return mHasGLMemoryBarrierIssued; }
+
+    // Dumping the command stream is disabled by default.
+    static constexpr bool kEnableCommandStreamDiagnostics = false;
+
+  protected:
+    CommandBufferHelperCommon();
+    ~CommandBufferHelperCommon();
+
+    void initializeImpl(Context *context, CommandPool *commandPool);
+
+    void resetImpl();
+
+    void imageReadImpl(ContextVk *contextVk,
+                       VkImageAspectFlags aspectFlags,
+                       ImageLayout imageLayout,
+                       ImageHelper *image);
+    void imageWriteImpl(ContextVk *contextVk,
+                        gl::LevelIndex level,
+                        uint32_t layerStart,
+                        uint32_t layerCount,
+                        VkImageAspectFlags aspectFlags,
+                        ImageLayout imageLayout,
+                        AliasingMode aliasingMode,
+                        ImageHelper *image);
+
+    void updateImageLayoutAndBarrier(Context *context,
+                                     ImageHelper *image,
+                                     VkImageAspectFlags aspectFlags,
+                                     ImageLayout imageLayout);
+
+    void addCommandDiagnosticsCommon(std::ostringstream *out);
+
+    // Allocator used by this class. Using a pool allocator per CBH to avoid threading issues
+    //  that occur w/ shared allocator between multiple CBHs.
+    angle::PoolAllocator mAllocator;
+
+    // Barriers to be executed before the command buffer.
+    PipelineBarrierArray mPipelineBarriers;
+    PipelineStagesMask mPipelineBarrierMask;
+
+    // The command pool *CommandBufferHelper::mCommandBuffer is allocated from.  Only used with
+    // Vulkan secondary command buffers (as opposed to ANGLE's SecondaryCommandBuffer).
+    CommandPool *mCommandPool;
+
+    // Whether the command buffers contains any draw/dispatch calls that possibly output data
+    // through storage buffers and images.  This is used to determine whether glMemoryBarrier*
+    // should flush the command buffer.
+    bool mHasShaderStorageOutput;
+    // Whether glMemoryBarrier has been called while commands are recorded in this command buffer.
+    // This is used to know when to check and potentially flush the command buffer if storage
+    // buffers and images are used in it.
+    bool mHasGLMemoryBarrierIssued;
+
+    // Tracks resources used in the command buffer.
+    // For Buffers, we track the read/write access type so we can enable simultaneous reads.
+    angle::FastIntegerMap<BufferAccess> mUsedBuffers;
+};
+
+class OutsideRenderPassCommandBufferHelper final : public CommandBufferHelperCommon
+{
+  public:
+    OutsideRenderPassCommandBufferHelper();
+    ~OutsideRenderPassCommandBufferHelper();
+
+    angle::Result initialize(Context *context, CommandPool *commandPool);
+
+    angle::Result reset(Context *context);
+
+    OutsideRenderPassCommandBuffer &getCommandBuffer() { return mCommandBuffer; }
+
+    bool empty() const { return mCommandBuffer.empty(); }
+
+#if defined(ANGLE_ENABLE_ASSERTS)
+    void markOpen() { mCommandBuffer.open(); }
+    void markClosed() { mCommandBuffer.close(); }
+#endif
+
+    void imageRead(ContextVk *contextVk,
+                   VkImageAspectFlags aspectFlags,
+                   ImageLayout imageLayout,
+                   ImageHelper *image);
+    void imageWrite(ContextVk *contextVk,
+                    gl::LevelIndex level,
+                    uint32_t layerStart,
+                    uint32_t layerCount,
+                    VkImageAspectFlags aspectFlags,
+                    ImageLayout imageLayout,
+                    AliasingMode aliasingMode,
+                    ImageHelper *image);
+
+    angle::Result flushToPrimary(Context *context, PrimaryCommandBuffer *primary);
+
+    void setGLMemoryBarrierIssued()
+    {
+        if (!mCommandBuffer.empty())
+        {
+            mHasGLMemoryBarrierIssued = true;
+        }
+    }
+
+    void addCommandDiagnostics(ContextVk *contextVk);
+
+  private:
+    angle::Result initializeCommandBuffer(Context *context);
+
+    OutsideRenderPassCommandBuffer mCommandBuffer;
+};
+
+class RenderPassCommandBufferHelper final : public CommandBufferHelperCommon
+{
+  public:
+    RenderPassCommandBufferHelper();
+    ~RenderPassCommandBufferHelper();
+
+    angle::Result initialize(Context *context, CommandPool *commandPool);
+
+    angle::Result reset(Context *context);
+
+    RenderPassCommandBuffer &getCommandBuffer() { return mCommandBuffer; }
+
+    bool empty() const { return !started(); }
+
+#if defined(ANGLE_ENABLE_ASSERTS)
+    void markOpen() { mCommandBuffer.open(); }
+    void markClosed() { mCommandBuffer.close(); }
+#endif
+
     void imageRead(ContextVk *contextVk,
                    VkImageAspectFlags aspectFlags,
                    ImageLayout imageLayout,
@@ -989,44 +1128,13 @@
                                 ImageHelper *image,
                                 ImageHelper *resolveImage);
 
-    CommandBuffer &getCommandBuffer() { return mCommandBuffer; }
-    CommandPool *getCommandPool() { return mCommandPool; }
+    bool usesImage(const ImageHelper &image) const;
 
     angle::Result flushToPrimary(Context *context,
                                  PrimaryCommandBuffer *primary,
                                  const RenderPass *renderPass);
 
-    void executeBarriers(const angle::FeaturesVk &features, PrimaryCommandBuffer *primary);
-
-    void setHasRenderPass(bool hasRenderPass) { mIsRenderPassCommandBuffer = hasRenderPass; }
-
-    // The markOpen and markClosed functions are to aid in proper use of the CommandBufferHelper.
-    // saw invalid use due to threading issues that can be easily caught by marking when it's safe
-    // (open) to write to the commandbuffer.
-#if defined(ANGLE_ENABLE_ASSERTS)
-    void markOpen() { mCommandBuffer.open(); }
-    void markClosed() { mCommandBuffer.close(); }
-#else
-    void markOpen() {}
-    void markClosed() {}
-#endif
-
-    angle::Result reset(Context *context);
-
-    // Returns true if we have no work to execute. For renderpass command buffer, even if the
-    // underlying command buffer is empty, we may still have a renderpass with an empty command
-    // buffer just to do the clear.
-    bool empty() const
-    {
-        return mIsRenderPassCommandBuffer ? !mRenderPassStarted : mCommandBuffer.empty();
-    }
-    // RenderPass related functions. This is equivalent to !empty(), but only when you know this is
-    // a RenderPass command buffer
-    bool started() const
-    {
-        ASSERT(mIsRenderPassCommandBuffer);
-        return mRenderPassStarted;
-    }
+    bool started() const { return mRenderPassStarted; }
 
     // Finalize the layout if image has any deferred layout transition.
     void finalizeImageLayout(Context *context, const ImageHelper *image);
@@ -1039,7 +1147,7 @@
                                   const vk::PackedAttachmentCount colorAttachmentCount,
                                   const PackedAttachmentIndex depthStencilAttachmentIndex,
                                   const PackedClearValuesArray &clearValues,
-                                  CommandBuffer **commandBufferOut);
+                                  RenderPassCommandBuffer **commandBufferOut);
 
     angle::Result endRenderPass(ContextVk *contextVk);
 
@@ -1059,7 +1167,6 @@
 
     bool hasWriteAfterInvalidate(uint32_t cmdCountInvalidated, uint32_t cmdCountDisabled)
     {
-        ASSERT(mIsRenderPassCommandBuffer);
         return (cmdCountInvalidated != kInfiniteCmdCount &&
                 std::min(cmdCountDisabled, mCommandBuffer.getRenderPassWriteCommandCount()) !=
                     cmdCountInvalidated);
@@ -1067,7 +1174,6 @@
 
     bool isInvalidated(uint32_t cmdCountInvalidated, uint32_t cmdCountDisabled)
     {
-        ASSERT(mIsRenderPassCommandBuffer);
         return cmdCountInvalidated != kInfiniteCmdCount &&
                std::min(cmdCountDisabled, mCommandBuffer.getRenderPassWriteCommandCount()) ==
                    cmdCountInvalidated;
@@ -1078,11 +1184,7 @@
     void updateRenderPassDepthStencilClear(VkImageAspectFlags aspectFlags,
                                            const VkClearValue &clearValue);
 
-    const gl::Rectangle &getRenderArea() const
-    {
-        ASSERT(mIsRenderPassCommandBuffer);
-        return mRenderArea;
-    }
+    const gl::Rectangle &getRenderArea() const { return mRenderArea; }
 
     // If render pass is started with a small render area due to a small scissor, and if a new
     // larger scissor is specified, grow the render area to accomodate it.
@@ -1095,25 +1197,12 @@
 
     uint32_t getAndResetCounter()
     {
-        ASSERT(mIsRenderPassCommandBuffer);
         uint32_t count = mCounter;
         mCounter       = 0;
         return count;
     }
 
-    VkFramebuffer getFramebufferHandle() const
-    {
-        ASSERT(mIsRenderPassCommandBuffer);
-        return mFramebuffer.getHandle();
-    }
-
-    bool usesBuffer(const BufferHelper &buffer) const;
-    bool usesBufferForWrite(const BufferHelper &buffer) const;
-    bool usesImageInRenderPass(const ImageHelper &image) const;
-    size_t getUsedBuffersCount() const { return mUsedBuffers.size(); }
-
-    // Dumping the command stream is disabled by default.
-    static constexpr bool kEnableCommandStreamDiagnostics = false;
+    VkFramebuffer getFramebufferHandle() const { return mFramebuffer.getHandle(); }
 
     void onDepthAccess(ResourceAccess access);
     void onStencilAccess(ResourceAccess access);
@@ -1130,25 +1219,19 @@
                    VK_ATTACHMENT_LOAD_OP_CLEAR;
     }
 
-    void addCommandDiagnostics(ContextVk *contextVk);
-
     const RenderPassDesc &getRenderPassDesc() const { return mRenderPassDesc; }
     const AttachmentOpsArray &getAttachmentOps() const { return mAttachmentOps; }
 
-    bool hasRenderPass() const { return mIsRenderPassCommandBuffer; }
-
-    void setHasShaderStorageOutput() { mHasShaderStorageOutput = true; }
-    bool hasShaderStorageOutput() const { return mHasShaderStorageOutput; }
+    void setImageOptimizeForPresent(ImageHelper *image) { mImageOptimizeForPresent = image; }
 
     void setGLMemoryBarrierIssued()
     {
-        if (!empty())
+        if (mRenderPassStarted)
         {
             mHasGLMemoryBarrierIssued = true;
         }
     }
-    bool hasGLMemoryBarrierIssued() const { return mHasGLMemoryBarrierIssued; }
-    void setImageOptimizeForPresent(ImageHelper *image) { mImageOptimizeForPresent = image; }
+    void addCommandDiagnostics(ContextVk *contextVk);
 
   private:
     angle::Result initializeCommandBuffer(Context *context);
@@ -1175,22 +1258,7 @@
                                           RenderPassStoreOp *storeOp);
     void finalizeDepthStencilImageLayoutAndLoadStore(Context *context);
 
-    void updateImageLayoutAndBarrier(Context *context,
-                                     ImageHelper *image,
-                                     VkImageAspectFlags aspectFlags,
-                                     ImageLayout imageLayout);
-
-    // Allocator used by this class. Using a pool allocator per CBH to avoid threading issues
-    //  that occur w/ shared allocator between multiple CBHs.
-    angle::PoolAllocator mAllocator;
-
-    // General state (non-renderPass related)
-    PipelineBarrierArray mPipelineBarriers;
-    PipelineStagesMask mPipelineBarrierMask;
-    CommandBuffer mCommandBuffer;
-    // The command pool mCommandBuffer is allocated from.  Only used with Vulkan secondary command
-    // buffers (as opposed to ANGLE's SecondaryCommandBuffer).
-    CommandPool *mCommandPool;
+    RenderPassCommandBuffer mCommandBuffer;
 
     // RenderPass state
     uint32_t mCounter;
@@ -1207,17 +1275,6 @@
     bool mRebindTransformFeedbackBuffers;
     bool mIsTransformFeedbackActiveUnpaused;
 
-    bool mIsRenderPassCommandBuffer;
-
-    // Whether the command buffers contains any draw/dispatch calls that possibly output data
-    // through storage buffers and images.  This is used to determine whether glMemoryBarrier*
-    // should flush the command buffer.
-    bool mHasShaderStorageOutput;
-    // Whether glMemoryBarrier has been called while commands are recorded in this command buffer.
-    // This is used to know when to check and potentially flush the command buffer if storage
-    // buffers and images are used in it.
-    bool mHasGLMemoryBarrierIssued;
-
     // State tracking for the maximum (Write been the highest) depth access during the entire
     // renderpass. Note that this does not include VK_ATTACHMENT_LOAD_OP_CLEAR which is tracked
     // separately. This is done this way to allow clear op to being optimized out when we find out
@@ -1239,9 +1296,8 @@
     PackedAttachmentIndex mDepthStencilAttachmentIndex;
 
     // Tracks resources used in the command buffer.
-    // For Buffers, we track the read/write access type so we can enable simultaneous reads.
-    // Images have unique layouts unlike buffers therefore we don't support multi-read.
-    angle::FastIntegerMap<BufferAccess> mUsedBuffers;
+    // Images have unique layouts unlike buffers therefore we can't support simultaneous reads with
+    // different layout.
     angle::FastIntegerSet mRenderPassUsedImages;
 
     ImageHelper *mDepthStencilImage;
@@ -1262,33 +1318,31 @@
 
 // The following class helps support both Vulkan and ANGLE secondary command buffers by
 // encapsulating their differences.
+template <typename CommandBufferT, typename CommandBufferHelperT>
 class CommandBufferRecycler
 {
   public:
-    CommandBufferRecycler();
-    ~CommandBufferRecycler();
+    CommandBufferRecycler()  = default;
+    ~CommandBufferRecycler() = default;
 
     void onDestroy();
 
     angle::Result getCommandBufferHelper(Context *context,
-                                         bool hasRenderPass,
                                          CommandPool *commandPool,
-                                         CommandBufferHelper **commandBufferHelperOut);
+                                         CommandBufferHelperT **commandBufferHelperOut);
 
-    void recycleCommandBufferHelper(VkDevice device, CommandBufferHelper **commandBuffer);
+    void recycleCommandBufferHelper(VkDevice device, CommandBufferHelperT **commandBuffer);
 
-    void resetCommandBufferHelper(CommandBuffer &&commandBuffer);
+    void resetCommandBuffer(CommandBufferT &&commandBuffer);
 
-    SecondaryCommandBufferList &&getCommandBuffersToReset()
+    std::vector<CommandBufferT> &&releaseCommandBuffersToReset()
     {
         return std::move(mSecondaryCommandBuffersToReset);
     }
 
   private:
-    void recycleImpl(VkDevice device, CommandBufferHelper **commandBuffer);
-
-    std::vector<vk::CommandBufferHelper *> mCommandBufferHelperFreeList;
-    SecondaryCommandBufferList mSecondaryCommandBuffersToReset;
+    std::vector<CommandBufferHelperT *> mCommandBufferHelperFreeList;
+    std::vector<CommandBufferT> mSecondaryCommandBuffersToReset;
 };
 
 // Imagine an image going through a few layout transitions:
@@ -1657,7 +1711,7 @@
                      const gl::Extents &copySize,
                      const VkImageSubresourceLayers &srcSubresources,
                      const VkImageSubresourceLayers &dstSubresources,
-                     CommandBuffer *commandBuffer);
+                     OutsideRenderPassCommandBuffer *commandBuffer);
 
     static angle::Result CopyImageSubData(const gl::Context *context,
                                           ImageHelper *srcImage,
@@ -1681,7 +1735,9 @@
 
     // Resolve this image into a destination image.  This image should be in the TransferSrc layout.
     // The destination image is automatically transitioned into TransferDst.
-    void resolve(ImageHelper *dst, const VkImageResolve &region, CommandBuffer *commandBuffer);
+    void resolve(ImageHelper *dst,
+                 const VkImageResolve &region,
+                 OutsideRenderPassCommandBuffer *commandBuffer);
 
     // Data staging
     void removeSingleSubresourceStagedUpdates(ContextVk *contextVk,
@@ -1800,7 +1856,7 @@
     void recordWriteBarrier(Context *context,
                             VkImageAspectFlags aspectMask,
                             ImageLayout newLayout,
-                            CommandBuffer *commandBuffer)
+                            OutsideRenderPassCommandBuffer *commandBuffer)
     {
         barrierImpl(context, aspectMask, newLayout, mCurrentQueueFamilyIndex, commandBuffer);
     }
@@ -1818,7 +1874,7 @@
     void recordReadBarrier(Context *context,
                            VkImageAspectFlags aspectMask,
                            ImageLayout newLayout,
-                           CommandBuffer *commandBuffer)
+                           OutsideRenderPassCommandBuffer *commandBuffer)
     {
         if (!isReadBarrierNecessary(newLayout))
         {
@@ -1837,7 +1893,7 @@
                               VkImageAspectFlags aspectMask,
                               ImageLayout newLayout,
                               uint32_t newQueueFamilyIndex,
-                              CommandBuffer *commandBuffer);
+                              OutsideRenderPassCommandBuffer *commandBuffer);
 
     // Returns true if barrier has been generated
     bool updateLayoutAndBarrier(Context *context,
@@ -1850,14 +1906,14 @@
                              uint32_t externalQueueFamilyIndex,
                              uint32_t rendererQueueFamilyIndex,
                              ImageLayout currentLayout,
-                             CommandBuffer *commandBuffer);
+                             OutsideRenderPassCommandBuffer *commandBuffer);
 
     // Performs an ownership transfer to an external instance or API.
     void releaseToExternal(ContextVk *contextVk,
                            uint32_t rendererQueueFamilyIndex,
                            uint32_t externalQueueFamilyIndex,
                            ImageLayout desiredLayout,
-                           CommandBuffer *commandBuffer);
+                           OutsideRenderPassCommandBuffer *commandBuffer);
 
     // Returns true if the image is owned by an external API or instance.
     bool isReleasedToExternal() const;
@@ -2093,14 +2149,14 @@
                LevelIndex mipLevel,
                uint32_t baseArrayLayer,
                uint32_t layerCount,
-               CommandBuffer *commandBuffer);
+               OutsideRenderPassCommandBuffer *commandBuffer);
 
     void clearColor(const VkClearColorValue &color,
                     LevelIndex baseMipLevelVk,
                     uint32_t levelCount,
                     uint32_t baseArrayLayer,
                     uint32_t layerCount,
-                    CommandBuffer *commandBuffer);
+                    OutsideRenderPassCommandBuffer *commandBuffer);
 
     void clearDepthStencil(VkImageAspectFlags clearAspectFlags,
                            const VkClearDepthStencilValue &depthStencil,
@@ -2108,7 +2164,7 @@
                            uint32_t levelCount,
                            uint32_t baseArrayLayer,
                            uint32_t layerCount,
-                           CommandBuffer *commandBuffer);
+                           OutsideRenderPassCommandBuffer *commandBuffer);
 
     angle::Result clearEmulatedChannels(ContextVk *contextVk,
                                         VkColorComponentFlags colorMaskFlags,
@@ -2235,6 +2291,11 @@
     gl::TexLevelArray<LevelContentDefinedMask> mStencilContentDefined;
 };
 
+ANGLE_INLINE bool RenderPassCommandBufferHelper::usesImage(const ImageHelper &image) const
+{
+    return mRenderPassUsedImages.contains(image.getImageSerial().getValue());
+}
+
 // A vector of image views, such as one per level or one per layer.
 using ImageViewVector = std::vector<ImageView>;
 
@@ -2668,12 +2729,6 @@
     angle::PackedEnumMap<HandleType, uint32_t> mAllocatedCounts;
 };
 
-ANGLE_INLINE bool CommandBufferHelper::usesImageInRenderPass(const ImageHelper &image) const
-{
-    ASSERT(mIsRenderPassCommandBuffer);
-    return mRenderPassUsedImages.contains(image.getImageSerial().getValue());
-}
-
 // Sometimes ANGLE issues a command internally, such as copies, draws and dispatches that do not
 // directly correspond to the application draw/dispatch call.  Before the command is recorded in the
 // command buffer, the render pass may need to be broken and/or appropriate barriers may need to be
@@ -2839,7 +2894,7 @@
     void release(ContextVk *contextVk);
     void destroy(RendererVk *renderer);
 
-    static void Draw(uint32_t count, uint32_t baseVertex, CommandBuffer *commandBuffer);
+    static void Draw(uint32_t count, uint32_t baseVertex, RenderPassCommandBuffer *commandBuffer);
 
   private:
     BufferHelper mDynamicIndexBuffer;
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.h b/src/libANGLE/renderer/vulkan/vk_utils.h
index eabc62e..559fe33 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_utils.h
@@ -202,12 +202,24 @@
 class RenderPassDesc;
 
 #if ANGLE_USE_CUSTOM_VULKAN_CMD_BUFFERS
-using CommandBuffer = priv::SecondaryCommandBuffer;
+using OutsideRenderPassCommandBuffer = priv::SecondaryCommandBuffer;
+using RenderPassCommandBuffer        = priv::SecondaryCommandBuffer;
 #else
-using CommandBuffer                          = VulkanSecondaryCommandBuffer;
+using OutsideRenderPassCommandBuffer         = VulkanSecondaryCommandBuffer;
+using RenderPassCommandBuffer                = VulkanSecondaryCommandBuffer;
 #endif
 
-using SecondaryCommandBufferList = std::vector<CommandBuffer>;
+struct SecondaryCommandBufferList
+{
+    std::vector<OutsideRenderPassCommandBuffer> outsideRenderPassCommandBuffers;
+    std::vector<RenderPassCommandBuffer> renderPassCommandBuffers;
+};
+
+struct SecondaryCommandPools
+{
+    CommandPool outsideRenderPassPool;
+    CommandPool renderPassPool;
+};
 
 VkImageAspectFlags GetDepthStencilAspectFlags(const angle::Format &format);
 VkImageAspectFlags GetFormatAspectFlags(const angle::Format &format);