Vulkan: Mega-refactor to VertexArrayVk.

This moves a lot of the code in VertexArrayVk into ContextVk. Having
the code in a centralized place makes the code a bit more organized
since the Context is reponsible for binding state to the command
buffers. It also makes it easier to use dirty bits to track the command
buffer state.

Bug: angleproject:2786
Change-Id: I5cefbb14028e8f3fe651f26e997ca88f8f1c7628
Reviewed-on: https://chromium-review.googlesource.com/1188953
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index e40e9a0..e21c081 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -98,6 +98,7 @@
     AttributesMask getActiveClientAttribsMask() const { return mCachedActiveClientAttribsMask; }
     AttributesMask getActiveDefaultAttribsMask() const { return mCachedActiveDefaultAttribsMask; }
     bool hasAnyEnabledClientAttrib() const { return mCachedHasAnyEnabledClientAttrib; }
+    bool hasAnyActiveClientAttrib() const { return mCachedActiveClientAttribsMask.any(); }
 
     // Places that can trigger updateVertexElementLimits:
     // 1. onVertexArrayBindingChange.
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 890184d..4b7885c 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -50,6 +50,45 @@
     }
 }
 
+void BindNonNullVertexBufferRanges(vk::CommandBuffer *commandBuffer,
+                                   const gl::AttributesMask &nonNullAttribMask,
+                                   uint32_t maxAttrib,
+                                   const gl::AttribArray<VkBuffer> &arrayBufferHandles,
+                                   const gl::AttribArray<VkDeviceSize> &arrayBufferOffsets)
+{
+    // Vulkan does not allow binding a null vertex buffer but the default state of null buffers is
+    // valid.
+
+    // We can detect if there are no gaps in active attributes by using the mask of the program
+    // attribs and the max enabled attrib.
+    ASSERT(maxAttrib > 0);
+    if (nonNullAttribMask.to_ulong() == (maxAttrib - 1))
+    {
+        commandBuffer->bindVertexBuffers(0, maxAttrib, arrayBufferHandles.data(),
+                                         arrayBufferOffsets.data());
+        return;
+    }
+
+    // Find ranges of non-null buffers and bind them all together.
+    for (uint32_t attribIdx = 0; attribIdx < maxAttrib; attribIdx++)
+    {
+        if (arrayBufferHandles[attribIdx] != VK_NULL_HANDLE)
+        {
+            // Find the end of this range of non-null handles
+            uint32_t rangeCount = 1;
+            while (attribIdx + rangeCount < maxAttrib &&
+                   arrayBufferHandles[attribIdx + rangeCount] != VK_NULL_HANDLE)
+            {
+                rangeCount++;
+            }
+
+            commandBuffer->bindVertexBuffers(attribIdx, rangeCount, &arrayBufferHandles[attribIdx],
+                                             &arrayBufferOffsets[attribIdx]);
+            attribIdx += rangeCount;
+        }
+    }
+}
+
 constexpr gl::Rectangle kMaxSizedScissor(0,
                                          0,
                                          std::numeric_limits<int>::max(),
@@ -74,8 +113,14 @@
     : ContextImpl(state),
       vk::Context(renderer),
       mCurrentDrawMode(gl::PrimitiveMode::InvalidEnum),
+      mDirtyDefaultAttribs(false),
+      mPipelineDirty(false),
       mTexturesDirty(false),
-      mVertexArrayBindingHasChanged(false),
+      mVertexBuffersDirty(false),
+      mIndexBufferDirty(false),
+      mDescriptorSetsDirty(false),
+      mLastIndexBufferOffset(0),
+      mCurrentDrawElementsType(GL_NONE),
       mClearColorMask(kAllColorChannelsMask),
       mFlipYForCurrentSurface(false),
       mDriverUniformsBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(DriverUniforms) * 16),
@@ -166,7 +211,7 @@
     return mRenderer->finish(this);
 }
 
-gl::Error ContextVk::initPipeline(const gl::DrawCallParams &drawCallParams)
+angle::Result ContextVk::initPipeline(const gl::DrawCallParams &drawCallParams)
 {
     ASSERT(!mCurrentPipeline);
 
@@ -200,13 +245,13 @@
                                      *pipelineLayout, *mPipelineDesc, activeAttribLocationsMask,
                                      &mCurrentPipeline));
 
-    return gl::NoError();
+    return angle::Result::Continue();
 }
 
-gl::Error ContextVk::setupDraw(const gl::Context *context,
-                               const gl::DrawCallParams &drawCallParams,
-                               vk::CommandBuffer **commandBufferOut,
-                               bool *shouldApplyVertexArrayOut)
+angle::Result ContextVk::setupDraw(const gl::Context *context,
+                                   const gl::DrawCallParams &drawCallParams,
+                                   bool useIndexBuffer,
+                                   vk::CommandBuffer **commandBufferOut)
 {
     if (drawCallParams.mode() != mCurrentDrawMode)
     {
@@ -214,41 +259,65 @@
         mCurrentDrawMode = drawCallParams.mode();
     }
 
-    if (mDirtyDefaultAttribs.any())
-    {
-        ANGLE_TRY(updateDefaultAttributes());
-    }
+    const gl::State &state       = mState.getState();
+    const gl::Program *programGL = state.getProgram();
+    ProgramVk *programVk         = vk::GetImpl(programGL);
+    FramebufferVk *framebufferVk = vk::GetImpl(state.getDrawFramebuffer());
+    VertexArrayVk *vertexArrayVk = vk::GetImpl(state.getVertexArray());
 
-    if (!mCurrentPipeline)
-    {
-        ANGLE_TRY(initPipeline(drawCallParams));
-    }
-
-    const auto &state                  = mState.getState();
-    const gl::Program *programGL       = state.getProgram();
-    ProgramVk *programVk               = vk::GetImpl(programGL);
-    const gl::Framebuffer *framebuffer = state.getDrawFramebuffer();
-    FramebufferVk *framebufferVk       = vk::GetImpl(framebuffer);
-    Serial queueSerial                 = mRenderer->getCurrentQueueSerial();
-
-    vk::RecordingMode mode = vk::RecordingMode::Start;
+    vk::RecordingMode mode;
     ANGLE_TRY(framebufferVk->getCommandBufferForDraw(this, commandBufferOut, &mode));
 
+    // Set any dirty bits that depend on draw call parameters or other objects.
     if (mode == vk::RecordingMode::Start)
     {
-        mTexturesDirty             = true;
-        *shouldApplyVertexArrayOut = true;
+        mPipelineDirty       = true;
+        mTexturesDirty       = true;
+        mVertexBuffersDirty  = true;
+        mIndexBufferDirty    = true;
+        mDescriptorSetsDirty = true;
     }
-    else
+
+    if (context->getStateCache().hasAnyActiveClientAttrib())
     {
-        *shouldApplyVertexArrayOut    = mVertexArrayBindingHasChanged;
-        mVertexArrayBindingHasChanged = false;
+        ANGLE_TRY(vertexArrayVk->updateClientAttribs(context, drawCallParams));
+        mVertexBuffersDirty = true;
+    }
+
+    if (programVk->dirtyUniforms())
+    {
+        ANGLE_TRY(programVk->updateUniforms(this));
+        mDescriptorSetsDirty = true;
+    }
+
+    // Flush any relevant dirty bits.
+    if (mDirtyDefaultAttribs)
+    {
+        ANGLE_TRY(updateDefaultAttributes());
+        mDirtyDefaultAttribs = false;
+    }
+
+    if (mPipelineDirty)
+    {
+        if (!mCurrentPipeline)
+        {
+            ANGLE_TRY(initPipeline(drawCallParams));
+        }
+
+        (*commandBufferOut)->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, mCurrentPipeline->get());
+
+        // Update the queue serial for the pipeline object.
+        ASSERT(mCurrentPipeline && mCurrentPipeline->valid());
+        mCurrentPipeline->updateSerial(mRenderer->getCurrentQueueSerial());
+        mPipelineDirty      = false;
+        mVertexBuffersDirty = true;
+        mIndexBufferDirty   = true;
     }
 
     // Ensure any writes to the textures are flushed before we read from them.
     if (mTexturesDirty)
     {
-        mTexturesDirty = false;
+        ANGLE_TRY(updateActiveTextures(context));
 
         // TODO(jmadill): Should probably merge this for loop with programVk's descriptor update.
         for (size_t textureIndex : programGL->getActiveSamplersMask())
@@ -257,18 +326,113 @@
             ANGLE_TRY(textureVk->ensureImageInitialized(this));
             textureVk->addReadDependency(framebufferVk);
         }
+
+        if (programVk->hasTextures())
+        {
+            ANGLE_TRY(programVk->updateTexturesDescriptorSet(this));
+        }
+
+        mDescriptorSetsDirty = true;
+        mTexturesDirty       = false;
     }
 
-    (*commandBufferOut)->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, mCurrentPipeline->get());
+    if (mDescriptorSetsDirty)
+    {
+        ANGLE_TRY(programVk->updateDescriptorSets(this, drawCallParams, *commandBufferOut));
 
-    // Update the queue serial for the pipeline object.
-    ASSERT(mCurrentPipeline && mCurrentPipeline->valid());
-    mCurrentPipeline->updateSerial(queueSerial);
+        // Bind the graphics descriptor sets.
+        (*commandBufferOut)
+            ->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, programVk->getPipelineLayout(),
+                                 kDriverUniformsDescriptorSetIndex, 1,
+                                 &mDriverUniformsDescriptorSet, 0, nullptr);
+        mDescriptorSetsDirty = false;
+    }
 
-    // Bind the graphics descriptor sets.
-    ANGLE_TRY(programVk->updateDescriptorSets(this, drawCallParams, mDriverUniformsDescriptorSet,
-                                              *commandBufferOut));
-    return gl::NoError();
+    uint32_t maxAttrib = programGL->getState().getMaxActiveAttribLocation();
+    if (mVertexBuffersDirty && maxAttrib > 0)
+    {
+        const gl::AttributesMask &programAttribsMask = programGL->getActiveAttribLocationsMask();
+        BindNonNullVertexBufferRanges(*commandBufferOut, programAttribsMask, maxAttrib,
+                                      vertexArrayVk->getCurrentArrayBufferHandles(),
+                                      vertexArrayVk->getCurrentArrayBufferOffsets());
+
+        const auto &arrayBufferResources = vertexArrayVk->getCurrentArrayBufferResources();
+
+        for (size_t attribIndex : context->getStateCache().getActiveBufferedAttribsMask())
+        {
+            if (arrayBufferResources[attribIndex])
+                arrayBufferResources[attribIndex]->addReadDependency(framebufferVk);
+        }
+
+        mVertexBuffersDirty = false;
+    }
+
+    if (useIndexBuffer && mIndexBufferDirty)
+    {
+        (*commandBufferOut)
+            ->bindIndexBuffer(vertexArrayVk->getCurrentElementArrayBufferHandle(),
+                              vertexArrayVk->getCurrentElementArrayBufferOffset(),
+                              gl_vk::GetIndexType(mCurrentDrawElementsType));
+
+        vk::CommandGraphResource *elementArrayBufferResource =
+            vertexArrayVk->getCurrentElementArrayBufferResource();
+        if (elementArrayBufferResource)
+        {
+            elementArrayBufferResource->addReadDependency(framebufferVk);
+        }
+
+        mIndexBufferDirty = false;
+    }
+
+    return angle::Result::Continue();
+}
+
+angle::Result ContextVk::setupIndexedDraw(const gl::Context *context,
+                                          const gl::DrawCallParams &drawCallParams,
+                                          vk::CommandBuffer **commandBufferOut)
+{
+    VertexArrayVk *vertexArrayVk = vk::GetImpl(mState.getState().getVertexArray());
+
+    if (drawCallParams.type() != mCurrentDrawElementsType)
+    {
+        mIndexBufferDirty        = true;
+        mCurrentDrawElementsType = drawCallParams.type();
+    }
+
+    const gl::Buffer *elementArrayBuffer = vertexArrayVk->getState().getElementArrayBuffer().get();
+    if (!elementArrayBuffer)
+    {
+        mIndexBufferDirty = true;
+        ANGLE_TRY(vertexArrayVk->updateIndexTranslation(this, drawCallParams));
+    }
+    else
+    {
+        if (drawCallParams.indices() != mLastIndexBufferOffset)
+        {
+            mIndexBufferDirty      = true;
+            mLastIndexBufferOffset = drawCallParams.indices();
+            vertexArrayVk->updateCurrentElementArrayBufferOffset(mLastIndexBufferOffset);
+        }
+
+        if (drawCallParams.type() == GL_UNSIGNED_BYTE && mIndexBufferDirty)
+        {
+            ANGLE_TRY(vertexArrayVk->updateIndexTranslation(this, drawCallParams));
+        }
+    }
+
+    return setupDraw(context, drawCallParams, true, commandBufferOut);
+}
+
+angle::Result ContextVk::setupLineLoopDraw(const gl::Context *context,
+                                           const gl::DrawCallParams &drawCallParams,
+                                           vk::CommandBuffer **commandBufferOut)
+{
+    VertexArrayVk *vertexArrayVk = vk::GetImpl(mState.getState().getVertexArray());
+    ANGLE_TRY(vertexArrayVk->handleLineLoop(this, drawCallParams));
+    mIndexBufferDirty = true;
+    mCurrentDrawElementsType =
+        drawCallParams.isDrawElements() ? drawCallParams.type() : GL_UNSIGNED_INT;
+    return setupDraw(context, drawCallParams, true, commandBufferOut);
 }
 
 gl::Error ContextVk::drawArrays(const gl::Context *context,
@@ -279,13 +443,18 @@
     const gl::DrawCallParams &drawCallParams = context->getParams<gl::DrawCallParams>();
 
     vk::CommandBuffer *commandBuffer = nullptr;
-    bool shouldApplyVertexArray      = false;
-    ANGLE_TRY(setupDraw(context, drawCallParams, &commandBuffer, &shouldApplyVertexArray));
+    uint32_t clampedVertexCount      = drawCallParams.getClampedVertexCount<uint32_t>();
 
-    const gl::VertexArray *vertexArray = context->getGLState().getVertexArray();
-    VertexArrayVk *vertexArrayVk       = vk::GetImpl(vertexArray);
-    ANGLE_TRY(
-        vertexArrayVk->drawArrays(context, drawCallParams, commandBuffer, shouldApplyVertexArray));
+    if (mode == gl::PrimitiveMode::LineLoop)
+    {
+        ANGLE_TRY(setupLineLoopDraw(context, drawCallParams, &commandBuffer));
+        vk::LineLoopHelper::Draw(clampedVertexCount, commandBuffer);
+    }
+    else
+    {
+        ANGLE_TRY(setupDraw(context, drawCallParams, false, &commandBuffer));
+        commandBuffer->draw(clampedVertexCount, 1, drawCallParams.firstVertex(), 0);
+    }
 
     return gl::NoError();
 }
@@ -309,13 +478,16 @@
     const gl::DrawCallParams &drawCallParams = context->getParams<gl::DrawCallParams>();
 
     vk::CommandBuffer *commandBuffer = nullptr;
-    bool shouldApplyVertexArray      = false;
-    ANGLE_TRY(setupDraw(context, drawCallParams, &commandBuffer, &shouldApplyVertexArray));
-
-    gl::VertexArray *vao         = mState.getState().getVertexArray();
-    VertexArrayVk *vertexArrayVk = vk::GetImpl(vao);
-    ANGLE_TRY(vertexArrayVk->drawElements(context, drawCallParams, commandBuffer,
-                                          shouldApplyVertexArray));
+    if (mode == gl::PrimitiveMode::LineLoop)
+    {
+        ANGLE_TRY(setupLineLoopDraw(context, drawCallParams, &commandBuffer));
+        vk::LineLoopHelper::Draw(count, commandBuffer);
+    }
+    else
+    {
+        ANGLE_TRY(setupIndexedDraw(context, drawCallParams, &commandBuffer));
+        commandBuffer->drawIndexed(count, 1, 0, 0, 0);
+    }
 
     return gl::NoError();
 }
@@ -456,12 +628,9 @@
         invalidateCurrentPipeline();
     }
 
-    const auto &glState = context->getGLState();
+    const gl::State &glState = context->getGLState();
 
-    // TODO(jmadill): Full dirty bits implementation.
-    bool dirtyTextures = false;
-
-    for (auto dirtyBit : dirtyBits)
+    for (size_t dirtyBit : dirtyBits)
     {
         switch (dirtyBit)
         {
@@ -626,8 +795,7 @@
                 break;
             case gl::State::DIRTY_BIT_VERTEX_ARRAY_BINDING:
             {
-                mVertexArrayBindingHasChanged = true;
-                mDirtyDefaultAttribs = context->getStateCache().getActiveDefaultAttribsMask();
+                invalidateDefaultAttributes(context->getStateCache().getActiveDefaultAttribsMask());
                 break;
             }
             case gl::State::DIRTY_BIT_DRAW_INDIRECT_BUFFER_BINDING:
@@ -638,17 +806,16 @@
                 break;
             case gl::State::DIRTY_BIT_PROGRAM_EXECUTABLE:
             {
-                dirtyTextures = true;
-
+                mTexturesDirty = true;
                 // No additional work is needed here. We will update the pipeline desc later.
-                mDirtyDefaultAttribs = context->getStateCache().getActiveDefaultAttribsMask();
+                invalidateDefaultAttributes(context->getStateCache().getActiveDefaultAttribsMask());
                 break;
             }
             case gl::State::DIRTY_BIT_TEXTURE_BINDINGS:
-                dirtyTextures = true;
+                mTexturesDirty = true;
                 break;
             case gl::State::DIRTY_BIT_SAMPLER_BINDINGS:
-                dirtyTextures = true;
+                mTexturesDirty = true;
                 break;
             case gl::State::DIRTY_BIT_TRANSFORM_FEEDBACK_BINDING:
                 break;
@@ -672,10 +839,7 @@
                 break;
             case gl::State::DIRTY_BIT_CURRENT_VALUES:
             {
-                for (size_t attribIndex : glState.getAndResetDirtyCurrentValues())
-                {
-                    invalidateDefaultAttribute(attribIndex);
-                }
+                invalidateDefaultAttributes(glState.getAndResetDirtyCurrentValues());
                 break;
             }
             break;
@@ -685,15 +849,6 @@
         }
     }
 
-    if (dirtyTextures)
-    {
-        ANGLE_TRY(updateActiveTextures(context));
-
-        ProgramVk *programVk = vk::GetImpl(glState.getProgram());
-        programVk->invalidateTextures();
-        mTexturesDirty = true;
-    }
-
     return gl::NoError();
 }
 
@@ -836,6 +991,7 @@
 
 void ContextVk::invalidateCurrentPipeline()
 {
+    mPipelineDirty   = true;
     mCurrentPipeline = nullptr;
 }
 
@@ -968,7 +1124,7 @@
     mErrors->handleError(gl::Error(glErrorCode, glErrorCode, errorStream.str()));
 }
 
-gl::Error ContextVk::updateActiveTextures(const gl::Context *context)
+angle::Result ContextVk::updateActiveTextures(const gl::Context *context)
 {
     const gl::State &glState   = mState.getState();
     const gl::Program *program = glState.getProgram();
@@ -987,13 +1143,13 @@
         // Null textures represent incomplete textures.
         if (texture == nullptr)
         {
-            ANGLE_TRY(getIncompleteTexture(context, textureType, &texture));
+            ANGLE_TRY_HANDLE(context, getIncompleteTexture(context, textureType, &texture));
         }
 
         mActiveTextures[textureUnit] = vk::GetImpl(texture);
     }
 
-    return gl::NoError();
+    return angle::Result::Continue();
 }
 
 const gl::ActiveTextureArray<TextureVk *> &ContextVk::getActiveTextures() const
@@ -1003,19 +1159,29 @@
 
 void ContextVk::invalidateDefaultAttribute(size_t attribIndex)
 {
-    mDirtyDefaultAttribs.set(attribIndex);
+    mDirtyDefaultAttribsMask.set(attribIndex);
+    mDirtyDefaultAttribs = true;
+}
+
+void ContextVk::invalidateDefaultAttributes(const gl::AttributesMask &dirtyMask)
+{
+    if (dirtyMask.any())
+    {
+        mDirtyDefaultAttribsMask = dirtyMask;
+        mDirtyDefaultAttribs     = true;
+    }
 }
 
 angle::Result ContextVk::updateDefaultAttributes()
 {
-    ASSERT(mDirtyDefaultAttribs.any());
+    ASSERT(mDirtyDefaultAttribsMask.any());
 
-    for (size_t attribIndex : mDirtyDefaultAttribs)
+    for (size_t attribIndex : mDirtyDefaultAttribsMask)
     {
         ANGLE_TRY(updateDefaultAttribute(attribIndex))
     }
 
-    mDirtyDefaultAttribs.reset();
+    mDirtyDefaultAttribsMask.reset();
     return angle::Result::Continue();
 }
 
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.h b/src/libANGLE/renderer/vulkan/ContextVk.h
index f5aec19..18c7e3f 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.h
+++ b/src/libANGLE/renderer/vulkan/ContextVk.h
@@ -158,6 +158,7 @@
 
     void invalidateCurrentPipeline();
     void invalidateDefaultAttribute(size_t attribIndex);
+    void invalidateDefaultAttributes(const gl::AttributesMask &dirtyMask);
 
     vk::DynamicDescriptorPool *getDynamicDescriptorPool(uint32_t descriptorSetIndex);
 
@@ -173,19 +174,27 @@
     void handleError(VkResult errorCode, const char *file, unsigned int line) override;
     const gl::ActiveTextureArray<TextureVk *> &getActiveTextures() const;
 
+    void setIndexBufferDirty() { mIndexBufferDirty = true; }
+
   private:
-    gl::Error initPipeline(const gl::DrawCallParams &drawCallParams);
-    gl::Error setupDraw(const gl::Context *context,
-                        const gl::DrawCallParams &drawCallParams,
-                        vk::CommandBuffer **commandBufferOut,
-                        bool *shouldApplyVertexArrayOut);
+    angle::Result initPipeline(const gl::DrawCallParams &drawCallParams);
+    angle::Result setupDraw(const gl::Context *context,
+                            const gl::DrawCallParams &drawCallParams,
+                            bool useIndexBuffer,
+                            vk::CommandBuffer **commandBufferOut);
+    angle::Result setupIndexedDraw(const gl::Context *context,
+                                   const gl::DrawCallParams &drawCallParams,
+                                   vk::CommandBuffer **commandBufferOut);
+    angle::Result setupLineLoopDraw(const gl::Context *context,
+                                    const gl::DrawCallParams &drawCallParams,
+                                    vk::CommandBuffer **commandBufferOut);
 
     void updateScissor(const gl::State &glState) const;
     void updateFlipViewportDrawFramebuffer(const gl::State &glState);
     void updateFlipViewportReadFramebuffer(const gl::State &glState);
 
     angle::Result updateDriverUniforms(const gl::State &glState);
-    gl::Error updateActiveTextures(const gl::Context *context);
+    angle::Result updateActiveTextures(const gl::Context *context);
     angle::Result updateDefaultAttributes();
     angle::Result updateDefaultAttribute(size_t attribIndex);
 
@@ -200,9 +209,18 @@
     // threads simultaneously. Hence, we keep them in the ContextVk instead of the RendererVk.
     vk::DescriptorSetLayoutArray<vk::DynamicDescriptorPool> mDynamicDescriptorPools;
 
-    // Triggers adding dependencies to the command graph.
+    // Dirty bits.
+    // TODO(jmadill): Make this into a dirty bit set. http://anglebug.com/2786
+    bool mDirtyDefaultAttribs;
+    bool mPipelineDirty;
     bool mTexturesDirty;
-    bool mVertexArrayBindingHasChanged;
+    bool mVertexBuffersDirty;
+    bool mIndexBufferDirty;
+    bool mDescriptorSetsDirty;
+
+    // The offset we had the last time we bound the index buffer.
+    const GLvoid *mLastIndexBufferOffset;
+    GLenum mCurrentDrawElementsType;
 
     // Cached clear value/mask for color and depth/stencil.
     VkClearValue mClearColorValue;
@@ -235,7 +253,7 @@
     gl::ActiveTextureArray<TextureVk *> mActiveTextures;
 
     // "Current Value" aka default vertex attribute state.
-    gl::AttributesMask mDirtyDefaultAttribs;
+    gl::AttributesMask mDirtyDefaultAttribsMask;
     gl::AttribArray<vk::DynamicBuffer> mDefaultAttribBuffers;
 };
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index 64a3508..64efdd9 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -183,8 +183,7 @@
 
 ProgramVk::DefaultUniformBlock::~DefaultUniformBlock() = default;
 
-ProgramVk::ProgramVk(const gl::ProgramState &state)
-    : ProgramImpl(state), mUniformBlocksOffsets{}, mDirtyTextures(false)
+ProgramVk::ProgramVk(const gl::ProgramState &state) : ProgramImpl(state), mUniformBlocksOffsets{}
 {
     mUsedDescriptorSetRange.invalidate();
 }
@@ -222,7 +221,6 @@
 
     mDescriptorSets.clear();
     mUsedDescriptorSetRange.invalidate();
-    mDirtyTextures = false;
 
     return angle::Result::Continue();
 }
@@ -276,7 +274,6 @@
     {
         // Ensure the descriptor set range includes the textures at position 1.
         mUsedDescriptorSetRange.extend(kTextureDescriptorSetIndex);
-        mDirtyTextures = true;
     }
 
     // Store a reference to the pipeline and descriptor set layouts. This will create them if they
@@ -334,7 +331,6 @@
         {
             // Ensure the descriptor set range includes the textures at position 1.
             mUsedDescriptorSetRange.extend(kTextureDescriptorSetIndex);
-            mDirtyTextures = true;
         }
     }
 
@@ -783,11 +779,7 @@
 
 angle::Result ProgramVk::updateUniforms(ContextVk *contextVk)
 {
-    if (!mDefaultUniformBlocks[vk::ShaderType::VertexShader].uniformsDirty &&
-        !mDefaultUniformBlocks[vk::ShaderType::FragmentShader].uniformsDirty)
-    {
-        return angle::Result::Continue();
-    }
+    ASSERT(dirtyUniforms());
 
     // Update buffer memory by immediate mapping. This immediate update only works once.
     bool anyNewBufferAllocated = false;
@@ -865,11 +857,7 @@
 
 angle::Result ProgramVk::updateTexturesDescriptorSet(ContextVk *contextVk)
 {
-    if (mState.getSamplerBindings().empty() || !mDirtyTextures)
-    {
-        return angle::Result::Continue();
-    }
-
+    ASSERT(hasTextures());
     ANGLE_TRY(allocateDescriptorSet(contextVk, kTextureDescriptorSetIndex));
 
     ASSERT(mUsedDescriptorSetRange.contains(1));
@@ -923,15 +911,9 @@
     ASSERT(writeCount > 0);
     vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
 
-    mDirtyTextures = false;
     return angle::Result::Continue();
 }
 
-void ProgramVk::invalidateTextures()
-{
-    mDirtyTextures = true;
-}
-
 void ProgramVk::setDefaultUniformBlocksMinSizeForTesting(size_t minSize)
 {
     for (DefaultUniformBlock &block : mDefaultUniformBlocks)
@@ -942,17 +924,10 @@
 
 angle::Result ProgramVk::updateDescriptorSets(ContextVk *contextVk,
                                               const gl::DrawCallParams &drawCallParams,
-                                              VkDescriptorSet driverUniformsDescriptorSet,
                                               vk::CommandBuffer *commandBuffer)
 {
     // TODO(jmadill): Line rasterization emulation shaders. http://anglebug.com/2598
     // Can probably use better dirty bits here.
-    ANGLE_TRY(updateUniforms(contextVk));
-    ANGLE_TRY(updateTexturesDescriptorSet(contextVk));
-
-    commandBuffer->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, mPipelineLayout.get(),
-                                      kDriverUniformsDescriptorSetIndex, 1,
-                                      &driverUniformsDescriptorSet, 0, nullptr);
 
     if (mUsedDescriptorSetRange.empty())
         return angle::Result::Continue();
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h
index 63985aa..54fbddb 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.h
@@ -103,17 +103,25 @@
                               const vk::PipelineLayout **pipelineLayoutOut);
 
     angle::Result updateUniforms(ContextVk *contextVk);
-
-    void invalidateTextures();
+    angle::Result updateTexturesDescriptorSet(ContextVk *contextVk);
 
     angle::Result updateDescriptorSets(ContextVk *contextVk,
                                        const gl::DrawCallParams &drawCallParams,
-                                       VkDescriptorSet driverUniformsDescriptorSet,
                                        vk::CommandBuffer *commandBuffer);
 
     // For testing only.
     void setDefaultUniformBlocksMinSizeForTesting(size_t minSize);
 
+    const vk::PipelineLayout &getPipelineLayout() const { return mPipelineLayout.get(); }
+
+    bool hasTextures() const { return !mState.getSamplerBindings().empty(); }
+
+    bool dirtyUniforms() const
+    {
+        return (mDefaultUniformBlocks[vk::ShaderType::VertexShader].uniformsDirty ||
+                mDefaultUniformBlocks[vk::ShaderType::FragmentShader].uniformsDirty);
+    }
+
   private:
     template <int cols, int rows>
     void setUniformMatrixfv(GLint location,
@@ -126,7 +134,6 @@
     angle::Result initDefaultUniformBlocks(const gl::Context *glContext);
 
     angle::Result updateDefaultUniformsDescriptorSet(ContextVk *contextVk);
-    angle::Result updateTexturesDescriptorSet(ContextVk *contextVk);
 
     template <class T>
     void getUniformImpl(GLint location, T *v, GLenum entryPointType) const;
@@ -165,7 +172,6 @@
     // Descriptor sets for uniform blocks and textures for this program.
     std::vector<VkDescriptorSet> mDescriptorSets;
     gl::RangeUI mUsedDescriptorSetRange;
-    bool mDirtyTextures;
 
     // We keep a reference to the pipeline and descriptor set layouts. This ensures they don't get
     // deleted while this program is in use.
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
index ed51a86..b346238 100644
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
@@ -51,46 +51,6 @@
     ANGLE_TRY(dynamicBuffer->flush(contextVk));
     return angle::Result::Continue();
 }
-
-void BindNonNullVertexBufferRanges(vk::CommandBuffer *commandBuffer,
-                                   const gl::AttributesMask &nonNullAttribMask,
-                                   uint32_t maxAttrib,
-                                   const gl::AttribArray<VkBuffer> &arrayBufferHandles,
-                                   const gl::AttribArray<VkDeviceSize> &arrayBufferOffsets)
-{
-    // Vulkan does not allow binding a null vertex buffer but the default state of null buffers is
-    // valid.
-
-    // We can detect if there are no gaps in active attributes by using the mask of the program
-    // attribs and the max enabled attrib.
-    ASSERT(maxAttrib > 0);
-    if (nonNullAttribMask.to_ulong() == (maxAttrib - 1))
-    {
-        commandBuffer->bindVertexBuffers(0, maxAttrib, arrayBufferHandles.data(),
-                                         arrayBufferOffsets.data());
-        return;
-    }
-
-    // Find ranges of non-null buffers and bind them all together.
-    for (uint32_t attribIdx = 0; attribIdx < maxAttrib; attribIdx++)
-    {
-        if (arrayBufferHandles[attribIdx] != VK_NULL_HANDLE)
-        {
-            // Find the end of this range of non-null handles
-            uint32_t rangeCount = 1;
-            while (attribIdx + rangeCount < maxAttrib &&
-                   arrayBufferHandles[attribIdx + rangeCount] != VK_NULL_HANDLE)
-            {
-                rangeCount++;
-            }
-
-            commandBuffer->bindVertexBuffers(attribIdx, rangeCount, &arrayBufferHandles[attribIdx],
-                                             &arrayBufferOffsets[attribIdx]);
-            attribIdx += rangeCount;
-        }
-    }
-}
-
 }  // anonymous namespace
 
 #define INIT                                        \
@@ -117,10 +77,7 @@
       mDynamicIndexData(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, kDynamicIndexDataSize),
       mTranslatedByteIndexData(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, kDynamicIndexDataSize),
       mLineLoopHelper(renderer),
-      mDirtyLineLoopTranslation(true),
-      mVertexBuffersDirty(false),
-      mIndexBufferDirty(false),
-      mLastIndexBufferOffset(0)
+      mDirtyLineLoopTranslation(true)
 {
     mCurrentArrayBufferHandles.fill(VK_NULL_HANDLE);
     mCurrentArrayBufferOffsets.fill(0);
@@ -303,7 +260,7 @@
                 mCurrentElementArrayBufferOffset = 0;
                 mLineLoopBufferFirstIndex.reset();
                 mLineLoopBufferLastIndex.reset();
-                mIndexBufferDirty         = true;
+                contextVk->setIndexBufferDirty();
                 mDirtyLineLoopTranslation = true;
                 break;
             }
@@ -326,7 +283,6 @@
 
     if (invalidatePipeline)
     {
-        mVertexBuffersDirty = true;
         contextVk->invalidateCurrentPipeline();
     }
 
@@ -402,29 +358,6 @@
     return angle::Result::Continue();
 }
 
-void VertexArrayVk::updateArrayBufferReadDependencies(vk::CommandGraphResource *drawFramebuffer,
-                                                      const gl::AttributesMask &activeAttribsMask,
-                                                      Serial serial)
-{
-    // Handle the bound array buffers.
-    for (size_t attribIndex : activeAttribsMask)
-    {
-        if (mCurrentArrayBufferResources[attribIndex])
-            mCurrentArrayBufferResources[attribIndex]->addReadDependency(drawFramebuffer);
-    }
-}
-
-void VertexArrayVk::updateElementArrayBufferReadDependency(
-    vk::CommandGraphResource *drawFramebuffer,
-    Serial serial)
-{
-    // Handle the bound element array buffer.
-    if (mCurrentElementArrayBufferResource)
-    {
-        mCurrentElementArrayBufferResource->addReadDependency(drawFramebuffer);
-    }
-}
-
 void VertexArrayVk::getPackedInputDescriptions(vk::PipelineDesc *pipelineDesc)
 {
     updatePackedInputDescriptions();
@@ -489,26 +422,90 @@
     attribDesc.offset                              = static_cast<uint32_t>(attrib.relativeOffset);
 }
 
-gl::Error VertexArrayVk::drawArrays(const gl::Context *context,
-                                    const gl::DrawCallParams &drawCallParams,
-                                    vk::CommandBuffer *commandBuffer,
-                                    bool newCommandBuffer)
+angle::Result VertexArrayVk::updateClientAttribs(const gl::Context *context,
+                                                 const gl::DrawCallParams &drawCallParams)
 {
-    ASSERT(commandBuffer->valid());
-
     ContextVk *contextVk = vk::GetImpl(context);
+    const gl::AttributesMask &clientAttribs = context->getStateCache().getActiveClientAttribsMask();
 
-    ANGLE_TRY(onDraw(context, drawCallParams, commandBuffer, newCommandBuffer));
+    ASSERT(clientAttribs.any());
+    ANGLE_TRY_HANDLE(context, drawCallParams.ensureIndexRangeResolved(context));
+
+    mDynamicVertexData.releaseRetainedBuffers(contextVk->getRenderer());
+
+    const auto &attribs  = mState.getVertexAttributes();
+    const auto &bindings = mState.getVertexBindings();
+
+    // TODO(fjhenigman): When we have a bunch of interleaved attributes, they end up
+    // un-interleaved, wasting space and copying time.  Consider improving on that.
+    for (size_t attribIndex : clientAttribs)
+    {
+        const gl::VertexAttribute &attrib = attribs[attribIndex];
+        const gl::VertexBinding &binding  = bindings[attrib.bindingIndex];
+        ASSERT(attrib.enabled && binding.getBuffer().get() == nullptr);
+
+        const size_t bytesToAllocate =
+            (drawCallParams.firstVertex() + drawCallParams.vertexCount()) *
+            mCurrentArrayBufferStrides[attribIndex];
+        const uint8_t *src = static_cast<const uint8_t *>(attrib.pointer) +
+                             drawCallParams.firstVertex() * binding.getStride();
+
+        size_t destOffset = drawCallParams.firstVertex() * mCurrentArrayBufferStrides[attribIndex];
+
+        // Only vertexCount() vertices will be used by the upcoming draw. so that is all we copy.
+        // We allocate space for firstVertex() + vertexCount() so indexing will work.  If we
+        // don't start at zero all the indices will be off.
+        // TODO(fjhenigman): See if we can account for indices being off by adjusting the
+        // offset, thus avoiding wasted memory.
+        ANGLE_TRY(StreamVertexData(contextVk, &mDynamicVertexData, src, bytesToAllocate, destOffset,
+                                   drawCallParams.vertexCount(), binding.getStride(),
+                                   mCurrentArrayBufferFormats[attribIndex]->vertexLoadFunction,
+                                   &mCurrentArrayBufferHandles[attribIndex],
+                                   &mCurrentArrayBufferOffsets[attribIndex]));
+    }
+
+    return angle::Result::Continue();
+}
+
+angle::Result VertexArrayVk::handleLineLoop(ContextVk *contextVk,
+                                            const gl::DrawCallParams &drawCallParams)
+{
+    if (drawCallParams.isDrawElements())
+    {
+        // Handle GL_LINE_LOOP drawElements.
+        if (mDirtyLineLoopTranslation)
+        {
+            gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer().get();
+            VkIndexType indexType          = gl_vk::GetIndexType(drawCallParams.type());
+
+            if (!elementArrayBuffer)
+            {
+                ANGLE_TRY(mLineLoopHelper.getIndexBufferForClientElementArray(
+                    contextVk, drawCallParams, &mCurrentElementArrayBufferHandle,
+                    &mCurrentElementArrayBufferOffset));
+            }
+            else
+            {
+                // When using an element array buffer, 'indices' is an offset to the first element.
+                intptr_t offset = reinterpret_cast<intptr_t>(drawCallParams.indices());
+                BufferVk *elementArrayBufferVk = vk::GetImpl(elementArrayBuffer);
+                ANGLE_TRY(mLineLoopHelper.getIndexBufferForElementArrayBuffer(
+                    contextVk, elementArrayBufferVk, indexType, drawCallParams.indexCount(), offset,
+                    &mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
+            }
+        }
+
+        // If we've had a drawArrays call with a line loop before, we want to make sure this is
+        // invalidated the next time drawArrays is called since we use the same index buffer for
+        // both calls.
+        mLineLoopBufferFirstIndex.reset();
+        mLineLoopBufferLastIndex.reset();
+        return angle::Result::Continue();
+    }
 
     // Note: Vertex indexes can be arbitrarily large.
     uint32_t clampedVertexCount = drawCallParams.getClampedVertexCount<uint32_t>();
 
-    if (drawCallParams.mode() != gl::PrimitiveMode::LineLoop)
-    {
-        commandBuffer->draw(clampedVertexCount, 1, drawCallParams.firstVertex(), 0);
-        return gl::NoError();
-    }
-
     // Handle GL_LINE_LOOP drawArrays.
     size_t lastVertex = static_cast<size_t>(drawCallParams.firstVertex() + clampedVertexCount);
     if (!mLineLoopBufferFirstIndex.valid() || !mLineLoopBufferLastIndex.valid() ||
@@ -523,207 +520,43 @@
         mLineLoopBufferLastIndex  = lastVertex;
     }
 
-    commandBuffer->bindIndexBuffer(mCurrentElementArrayBufferHandle,
-                                   mCurrentElementArrayBufferOffset, VK_INDEX_TYPE_UINT32);
-
-    vk::LineLoopHelper::Draw(clampedVertexCount, commandBuffer);
-
-    return gl::NoError();
+    return angle::Result::Continue();
 }
 
-gl::Error VertexArrayVk::drawElements(const gl::Context *context,
-                                      const gl::DrawCallParams &drawCallParams,
-                                      vk::CommandBuffer *commandBuffer,
-                                      bool newCommandBuffer)
+angle::Result VertexArrayVk::updateIndexTranslation(ContextVk *contextVk,
+                                                    const gl::DrawCallParams &drawCallParams)
 {
-    ASSERT(commandBuffer->valid());
+    ASSERT(drawCallParams.isDrawElements());
+    ASSERT(drawCallParams.mode() != gl::PrimitiveMode::LineLoop);
 
-    ContextVk *contextVk = vk::GetImpl(context);
-
-    if (drawCallParams.mode() != gl::PrimitiveMode::LineLoop)
-    {
-        ANGLE_TRY(onIndexedDraw(context, drawCallParams, commandBuffer, newCommandBuffer));
-        commandBuffer->drawIndexed(drawCallParams.indexCount(), 1, 0, 0, 0);
-        return gl::NoError();
-    }
-
-    // Handle GL_LINE_LOOP drawElements.
-    if (mDirtyLineLoopTranslation)
-    {
-        gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer().get();
-        VkIndexType indexType          = gl_vk::GetIndexType(drawCallParams.type());
-
-        if (!elementArrayBuffer)
-        {
-            ANGLE_TRY(mLineLoopHelper.getIndexBufferForClientElementArray(
-                contextVk, drawCallParams, &mCurrentElementArrayBufferHandle,
-                &mCurrentElementArrayBufferOffset));
-        }
-        else
-        {
-            // When using an element array buffer, 'indices' is an offset to the first element.
-            intptr_t offset                = reinterpret_cast<intptr_t>(drawCallParams.indices());
-            BufferVk *elementArrayBufferVk = vk::GetImpl(elementArrayBuffer);
-            ANGLE_TRY(mLineLoopHelper.getIndexBufferForElementArrayBuffer(
-                contextVk, elementArrayBufferVk, indexType, drawCallParams.indexCount(), offset,
-                &mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
-        }
-    }
-
-    ANGLE_TRY(onIndexedDraw(context, drawCallParams, commandBuffer, newCommandBuffer));
-    vk::LineLoopHelper::Draw(drawCallParams.indexCount(), commandBuffer);
-
-    return gl::NoError();
-}
-
-gl::Error VertexArrayVk::onDraw(const gl::Context *context,
-                                const gl::DrawCallParams &drawCallParams,
-                                vk::CommandBuffer *commandBuffer,
-                                bool newCommandBuffer)
-{
-    ContextVk *contextVk                    = vk::GetImpl(context);
-    const gl::State &state                  = context->getGLState();
-    const gl::Program *programGL            = state.getProgram();
-    const gl::AttributesMask &programAttribsMask = programGL->getActiveAttribLocationsMask();
-    const gl::AttributesMask &clientAttribs = context->getStateCache().getActiveClientAttribsMask();
-    uint32_t maxAttrib                      = programGL->getState().getMaxActiveAttribLocation();
-
-    if (clientAttribs.any())
-    {
-        ANGLE_TRY(drawCallParams.ensureIndexRangeResolved(context));
-
-        mDynamicVertexData.releaseRetainedBuffers(contextVk->getRenderer());
-
-        const auto &attribs  = mState.getVertexAttributes();
-        const auto &bindings = mState.getVertexBindings();
-
-        // TODO(fjhenigman): When we have a bunch of interleaved attributes, they end up
-        // un-interleaved, wasting space and copying time.  Consider improving on that.
-        for (size_t attribIndex : clientAttribs)
-        {
-            const gl::VertexAttribute &attrib = attribs[attribIndex];
-            const gl::VertexBinding &binding  = bindings[attrib.bindingIndex];
-            ASSERT(attrib.enabled && binding.getBuffer().get() == nullptr);
-
-            const size_t bytesToAllocate =
-                (drawCallParams.firstVertex() + drawCallParams.vertexCount()) *
-                mCurrentArrayBufferStrides[attribIndex];
-            const uint8_t *src = static_cast<const uint8_t *>(attrib.pointer) +
-                                 drawCallParams.firstVertex() * binding.getStride();
-
-            size_t destOffset =
-                drawCallParams.firstVertex() * mCurrentArrayBufferStrides[attribIndex];
-
-            // Only vertexCount() vertices will be used by the upcoming draw so that is all we copy,
-            // but we allocate space firstVertex() + vertexCount() so indexing will work.  If we
-            // don't start at zero all the indices will be off.
-            // TODO(fjhenigman): See if we can account for indices being off by adjusting the
-            // offset, thus avoiding wasted memory.
-            ANGLE_TRY(StreamVertexData(contextVk, &mDynamicVertexData, src, bytesToAllocate,
-                                       destOffset, drawCallParams.vertexCount(),
-                                       binding.getStride(),
-                                       mCurrentArrayBufferFormats[attribIndex]->vertexLoadFunction,
-                                       &mCurrentArrayBufferHandles[attribIndex],
-                                       &mCurrentArrayBufferOffsets[attribIndex]));
-        }
-
-        BindNonNullVertexBufferRanges(commandBuffer, programAttribsMask, maxAttrib,
-                                      mCurrentArrayBufferHandles, mCurrentArrayBufferOffsets);
-    }
-    else if (mVertexBuffersDirty || newCommandBuffer)
-    {
-        if (maxAttrib > 0)
-        {
-            BindNonNullVertexBufferRanges(commandBuffer, programAttribsMask, maxAttrib,
-                                          mCurrentArrayBufferHandles, mCurrentArrayBufferOffsets);
-
-            const gl::AttributesMask &bufferedAttribs =
-                context->getStateCache().getActiveBufferedAttribsMask();
-
-            vk::CommandGraphResource *drawFramebuffer = vk::GetImpl(state.getDrawFramebuffer());
-            updateArrayBufferReadDependencies(drawFramebuffer, bufferedAttribs,
-                                              contextVk->getRenderer()->getCurrentQueueSerial());
-        }
-
-        mVertexBuffersDirty = false;
-
-        // This forces the binding to happen if we follow a drawElement call from a drawArrays call.
-        mIndexBufferDirty = true;
-
-        // If we've had a drawElements call with a line loop before, we want to make sure this is
-        // invalidated the next time drawElements is called since we use the same index buffer for
-        // both calls.
-        mDirtyLineLoopTranslation = true;
-    }
-
-    return gl::NoError();
-}
-
-gl::Error VertexArrayVk::onIndexedDraw(const gl::Context *context,
-                                       const gl::DrawCallParams &drawCallParams,
-                                       vk::CommandBuffer *commandBuffer,
-                                       bool newCommandBuffer)
-{
-    ContextVk *contextVk = vk::GetImpl(context);
-    ANGLE_TRY(onDraw(context, drawCallParams, commandBuffer, newCommandBuffer));
-    bool isLineLoop      = drawCallParams.mode() == gl::PrimitiveMode::LineLoop;
     gl::Buffer *glBuffer = mState.getElementArrayBuffer().get();
-    uintptr_t offset =
-        glBuffer && !isLineLoop ? reinterpret_cast<uintptr_t>(drawCallParams.indices()) : 0;
 
-    if (!glBuffer && !isLineLoop)
+    if (!glBuffer)
     {
         ANGLE_TRY(streamIndexData(contextVk, drawCallParams.type(), drawCallParams.indexCount(),
                                   drawCallParams.indices(), &mDynamicIndexData));
-        commandBuffer->bindIndexBuffer(mCurrentElementArrayBufferHandle,
-                                       mCurrentElementArrayBufferOffset,
-                                       gl_vk::GetIndexType(drawCallParams.type()));
     }
-    else if (mIndexBufferDirty || newCommandBuffer || offset != mLastIndexBufferOffset)
+    else
     {
-        mLastIndexBufferOffset = offset;
+        ASSERT(drawCallParams.type() == GL_UNSIGNED_BYTE);
+        // Unsigned bytes don't have direct support in Vulkan so we have to expand the
+        // memory to a GLushort.
+        BufferVk *bufferVk   = vk::GetImpl(glBuffer);
+        void *srcDataMapping = nullptr;
+        ASSERT(!glBuffer->isMapped());
+        ANGLE_TRY(bufferVk->mapImpl(contextVk, &srcDataMapping));
+        uint8_t *srcData           = static_cast<uint8_t *>(srcDataMapping);
+        intptr_t offsetIntoSrcData = reinterpret_cast<intptr_t>(drawCallParams.indices());
+        srcData += offsetIntoSrcData;
 
-        if (drawCallParams.type() == GL_UNSIGNED_BYTE && !isLineLoop)
-        {
-            // Unsigned bytes don't have direct support in Vulkan so we have to expand the
-            // memory to a GLushort.
-            BufferVk *bufferVk   = vk::GetImpl(glBuffer);
-            void *srcDataMapping = nullptr;
-            ASSERT(!glBuffer->isMapped());
-            ANGLE_TRY(bufferVk->mapImpl(contextVk, &srcDataMapping));
-            uint8_t *srcData           = static_cast<uint8_t *>(srcDataMapping);
-            intptr_t offsetIntoSrcData = reinterpret_cast<intptr_t>(drawCallParams.indices());
-            srcData += offsetIntoSrcData;
+        ANGLE_TRY(streamIndexData(contextVk, drawCallParams.type(),
+                                  static_cast<size_t>(bufferVk->getSize()) - offsetIntoSrcData,
+                                  srcData, &mTranslatedByteIndexData));
 
-            ANGLE_TRY(streamIndexData(contextVk, drawCallParams.type(),
-                                      static_cast<size_t>(bufferVk->getSize()) - offsetIntoSrcData,
-                                      srcData, &mTranslatedByteIndexData));
-
-            ANGLE_TRY(bufferVk->unmapImpl(contextVk));
-
-            // We do not add the offset from the drawCallParams here because we've already copied
-            // the source starting at the offset requested.
-            offset = 0;
-        }
-
-        commandBuffer->bindIndexBuffer(mCurrentElementArrayBufferHandle,
-                                       mCurrentElementArrayBufferOffset + offset,
-                                       gl_vk::GetIndexType(drawCallParams.type()));
-
-        const gl::State &glState                  = context->getGLState();
-        vk::CommandGraphResource *drawFramebuffer = vk::GetImpl(glState.getDrawFramebuffer());
-        updateElementArrayBufferReadDependency(drawFramebuffer,
-                                               contextVk->getRenderer()->getCurrentQueueSerial());
-        mIndexBufferDirty = false;
-
-        // If we've had a drawArrays call with a line loop before, we want to make sure this is
-        // invalidated the next time drawArrays is called since we use the same index buffer for
-        // both calls.
-        mLineLoopBufferFirstIndex.reset();
-        mLineLoopBufferLastIndex.reset();
+        ANGLE_TRY(bufferVk->unmapImpl(contextVk));
     }
 
-    return gl::NoError();
+    return angle::Result::Continue();
 }
 
 void VertexArrayVk::updateDefaultAttrib(RendererVk *renderer,
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.h b/src/libANGLE/renderer/vulkan/VertexArrayVk.h
index f59e9a1..75bc43e 100644
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.h
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.h
@@ -41,19 +41,8 @@
                         const gl::VertexArray::DirtyAttribBitsArray &attribBits,
                         const gl::VertexArray::DirtyBindingBitsArray &bindingBits) override;
 
-    void updateDrawDependencies(vk::CommandGraphResource *drawFramebuffer,
-                                const gl::AttributesMask &activeAttribsMask,
-                                vk::CommandGraphResource *elementArrayBufferOverride,
-                                Serial serial,
-                                bool isDrawElements);
-
     void getPackedInputDescriptions(vk::PipelineDesc *pipelineDesc);
 
-    // Draw call handling.
-    gl::Error drawArrays(const gl::Context *context,
-                         const gl::DrawCallParams &drawCallParams,
-                         vk::CommandBuffer *commandBuffer,
-                         bool shouldApplyVertexArray);
     gl::Error drawElements(const gl::Context *context,
                            const gl::DrawCallParams &drawCallParams,
                            vk::CommandBuffer *commandBuffer,
@@ -64,6 +53,46 @@
                              VkBuffer bufferHandle,
                              uint32_t offset);
 
+    angle::Result updateClientAttribs(const gl::Context *context,
+                                      const gl::DrawCallParams &drawCallParams);
+
+    angle::Result handleLineLoop(ContextVk *contextVk, const gl::DrawCallParams &drawCallParams);
+
+    const gl::AttribArray<VkBuffer> &getCurrentArrayBufferHandles() const
+    {
+        return mCurrentArrayBufferHandles;
+    }
+
+    const gl::AttribArray<VkDeviceSize> &getCurrentArrayBufferOffsets() const
+    {
+        return mCurrentArrayBufferOffsets;
+    }
+
+    const gl::AttribArray<vk::CommandGraphResource *> &getCurrentArrayBufferResources() const
+    {
+        return mCurrentArrayBufferResources;
+    }
+
+    VkBuffer getCurrentElementArrayBufferHandle() const { return mCurrentElementArrayBufferHandle; }
+
+    VkDeviceSize getCurrentElementArrayBufferOffset() const
+    {
+        return mCurrentElementArrayBufferOffset;
+    }
+
+    void updateCurrentElementArrayBufferOffset(const GLvoid *offset)
+    {
+        mCurrentElementArrayBufferOffset = reinterpret_cast<VkDeviceSize>(offset);
+    }
+
+    vk::CommandGraphResource *getCurrentElementArrayBufferResource() const
+    {
+        return mCurrentElementArrayBufferResource;
+    }
+
+    angle::Result updateIndexTranslation(ContextVk *contextVk,
+                                         const gl::DrawCallParams &drawCallParams);
+
   private:
     // This will update any dirty packed input descriptions, regardless if they're used by the
     // active program. This could lead to slight inefficiencies when the app would repeatedly
@@ -79,9 +108,6 @@
                                            const gl::AttributesMask &activeAttribsMask,
                                            Serial serial);
 
-    void updateElementArrayBufferReadDependency(vk::CommandGraphResource *drawFramebuffer,
-                                                Serial serial);
-
     angle::Result streamIndexData(ContextVk *contextVk,
                                   GLenum indexType,
                                   size_t indexCount,
@@ -93,15 +119,6 @@
                                       size_t attribIndex);
     void ensureConversionReleased(RendererVk *renderer, size_t attribIndex);
 
-    gl::Error onDraw(const gl::Context *context,
-                     const gl::DrawCallParams &drawCallParams,
-                     vk::CommandBuffer *commandBuffer,
-                     bool newCommandBuffer);
-    gl::Error onIndexedDraw(const gl::Context *context,
-                            const gl::DrawCallParams &drawCallParams,
-                            vk::CommandBuffer *commandBuffer,
-                            bool newCommandBuffer);
-
     angle::Result syncDirtyAttrib(ContextVk *contextVk,
                                   const gl::VertexAttribute &attrib,
                                   const gl::VertexBinding &binding,
@@ -132,15 +149,7 @@
     Optional<GLint> mLineLoopBufferFirstIndex;
     Optional<size_t> mLineLoopBufferLastIndex;
     bool mDirtyLineLoopTranslation;
-
-    // Cache variable for determining whether or not to store new dependencies in the node.
-    bool mVertexBuffersDirty;
-    bool mIndexBufferDirty;
-
-    // The offset we had the last time we bound the index buffer.
-    uintptr_t mLastIndexBufferOffset;
 };
-
 }  // namespace rx
 
 #endif  // LIBANGLE_RENDERER_VULKAN_VERTEXARRAYVK_H_