Vulkan: Compute shader support

A DispatchHelper class is created as the equivalent of FramebufferHelper
as a command graph resource.  There's currently a single dispatcher and
all dispatch calls are recorded on that.  Context dirty bits are set up
in such a way that graphics and compute workloads are independently
handled, so that issuing a dispatch call wouldn't cause a framebuffer's
render pass to rebind resources.

Bug: angleproject:3562
Change-Id: Ib96db48297074d99b04324e44b067cfbfd43e333
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1688504
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 339ea1e..42a556f 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -1429,6 +1429,16 @@
     return false;
 }
 
+void EmitWorkGroupSizeGLSL(const TCompiler &compiler, TInfoSinkBase &sink)
+{
+    if (compiler.isComputeShaderLocalSizeDeclared())
+    {
+        const sh::WorkGroupSize &localSize = compiler.getComputeShaderLocalSize();
+        sink << "layout (local_size_x=" << localSize[0] << ", local_size_y=" << localSize[1]
+             << ", local_size_z=" << localSize[2] << ") in;\n";
+    }
+}
+
 void EmitMultiviewGLSL(const TCompiler &compiler,
                        const ShCompileOptions &compileOptions,
                        const TBehavior behavior,
diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h
index d831c4d..70c0285 100644
--- a/src/compiler/translator/Compiler.h
+++ b/src/compiler/translator/Compiler.h
@@ -296,6 +296,7 @@
 TCompiler *ConstructCompiler(sh::GLenum type, ShShaderSpec spec, ShShaderOutput output);
 void DeleteCompiler(TCompiler *);
 
+void EmitWorkGroupSizeGLSL(const TCompiler &, TInfoSinkBase &sink);
 void EmitMultiviewGLSL(const TCompiler &, const ShCompileOptions &, TBehavior, TInfoSinkBase &sink);
 
 }  // namespace sh
diff --git a/src/compiler/translator/TranslatorESSL.cpp b/src/compiler/translator/TranslatorESSL.cpp
index 8a3efb5..9631a32 100644
--- a/src/compiler/translator/TranslatorESSL.cpp
+++ b/src/compiler/translator/TranslatorESSL.cpp
@@ -84,11 +84,9 @@
     // Write array bounds clamping emulation if needed.
     getArrayBoundsClamper().OutputClampingFunctionDefinition(sink);
 
-    if (getShaderType() == GL_COMPUTE_SHADER && isComputeShaderLocalSizeDeclared())
+    if (getShaderType() == GL_COMPUTE_SHADER)
     {
-        const sh::WorkGroupSize &localSize = getComputeShaderLocalSize();
-        sink << "layout (local_size_x=" << localSize[0] << ", local_size_y=" << localSize[1]
-             << ", local_size_z=" << localSize[2] << ") in;\n";
+        EmitWorkGroupSizeGLSL(*this, sink);
     }
 
     if (getShaderType() == GL_GEOMETRY_SHADER_EXT)
diff --git a/src/compiler/translator/TranslatorGLSL.cpp b/src/compiler/translator/TranslatorGLSL.cpp
index d82b279..81b43b8 100644
--- a/src/compiler/translator/TranslatorGLSL.cpp
+++ b/src/compiler/translator/TranslatorGLSL.cpp
@@ -194,11 +194,9 @@
         }
     }
 
-    if (getShaderType() == GL_COMPUTE_SHADER && isComputeShaderLocalSizeDeclared())
+    if (getShaderType() == GL_COMPUTE_SHADER)
     {
-        const sh::WorkGroupSize &localSize = getComputeShaderLocalSize();
-        sink << "layout (local_size_x=" << localSize[0] << ", local_size_y=" << localSize[1]
-             << ", local_size_z=" << localSize[2] << ") in;\n";
+        EmitWorkGroupSizeGLSL(*this, sink);
     }
 
     if (getShaderType() == GL_GEOMETRY_SHADER_EXT)
diff --git a/src/compiler/translator/TranslatorVulkan.cpp b/src/compiler/translator/TranslatorVulkan.cpp
index 6ce8568..2dd7c38 100644
--- a/src/compiler/translator/TranslatorVulkan.cpp
+++ b/src/compiler/translator/TranslatorVulkan.cpp
@@ -692,9 +692,12 @@
         sink << "};\n";
     }
 
-    const TVariable *driverUniforms = AddDriverUniformsToShader(root, &getSymbolTable());
-
-    ReplaceGLDepthRangeWithDriverUniform(root, driverUniforms, &getSymbolTable());
+    const TVariable *driverUniforms = nullptr;
+    if (getShaderType() != GL_COMPUTE_SHADER)
+    {
+        driverUniforms = AddDriverUniformsToShader(root, &getSymbolTable());
+        ReplaceGLDepthRangeWithDriverUniform(root, driverUniforms, &getSymbolTable());
+    }
 
     // Declare gl_FragColor and glFragData as webgl_FragColor and webgl_FragData
     // if it's core profile shaders and they are used.
@@ -775,10 +778,8 @@
             RewriteDfdy(root, getSymbolTable(), getShaderVersion(), viewportYScale);
         }
     }
-    else
+    else if (getShaderType() == GL_VERTEX_SHADER)
     {
-        ASSERT(getShaderType() == GL_VERTEX_SHADER);
-
         AddANGLEPositionVarying(root, &getSymbolTable());
 
         // Add a macro to declare transform feedback buffers.
@@ -790,6 +791,11 @@
         // Append depth range translation to main.
         AppendVertexShaderDepthCorrectionToMain(root, &getSymbolTable());
     }
+    else
+    {
+        ASSERT(getShaderType() == GL_COMPUTE_SHADER);
+        EmitWorkGroupSizeGLSL(*this, sink);
+    }
 
     // Write translated shader.
     root->traverse(&outputGLSL);
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index c65eb12..499d1c0 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -3344,18 +3344,22 @@
 
     // Limit textures as well, so we can use fast bitsets with texture bindings.
     LimitCap(&mState.mCaps.maxCombinedTextureImageUnits, IMPLEMENTATION_MAX_ACTIVE_TEXTURES);
-    LimitCap(&mState.mCaps.maxShaderTextureImageUnits[ShaderType::Vertex],
-             IMPLEMENTATION_MAX_ACTIVE_TEXTURES / 2);
-    LimitCap(&mState.mCaps.maxShaderTextureImageUnits[ShaderType::Fragment],
-             IMPLEMENTATION_MAX_ACTIVE_TEXTURES / 2);
+    for (ShaderType shaderType : AllShaderTypes())
+    {
+        LimitCap(&mState.mCaps.maxShaderTextureImageUnits[shaderType],
+                 IMPLEMENTATION_MAX_SHADER_TEXTURES);
+    }
 
     LimitCap(&mState.mCaps.maxImageUnits, IMPLEMENTATION_MAX_IMAGE_UNITS);
 
     LimitCap(&mState.mCaps.maxCombinedAtomicCounterBuffers,
              IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS);
 
-    LimitCap(&mState.mCaps.maxShaderStorageBlocks[ShaderType::Compute],
-             IMPLEMENTATION_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
+    for (ShaderType shaderType : AllShaderTypes())
+    {
+        LimitCap(&mState.mCaps.maxShaderStorageBlocks[shaderType],
+                 IMPLEMENTATION_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
+    }
     LimitCap(&mState.mCaps.maxShaderStorageBufferBindings,
              IMPLEMENTATION_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
     LimitCap(&mState.mCaps.maxCombinedShaderStorageBlocks,
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index f246a55..f147b44 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -384,9 +384,10 @@
     const ShaderBitSet &getLinkedShaderStages() const { return mLinkedShaderStages; }
     bool hasLinkedShaderStage(ShaderType shaderType) const
     {
-        return mLinkedShaderStages.test(shaderType);
+        return mLinkedShaderStages[shaderType];
     }
     size_t getLinkedShaderStageCount() const { return mLinkedShaderStages.count(); }
+    bool isCompute() const { return hasLinkedShaderStage(ShaderType::Compute); }
 
     bool hasAttachedShader() const;
 
@@ -588,8 +589,9 @@
     bool hasLinkedShaderStage(ShaderType shaderType) const
     {
         ASSERT(shaderType != ShaderType::InvalidEnum);
-        return mState.mLinkedShaderStages[shaderType];
+        return mState.hasLinkedShaderStage(shaderType);
     }
+    bool isCompute() const { return mState.isCompute(); }
 
     angle::Result loadBinary(const Context *context,
                              GLenum binaryFormat,
diff --git a/src/libANGLE/renderer/vulkan/BufferVk.cpp b/src/libANGLE/renderer/vulkan/BufferVk.cpp
index 7c76255..cc555a3 100644
--- a/src/libANGLE/renderer/vulkan/BufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/BufferVk.cpp
@@ -231,10 +231,16 @@
 
 angle::Result BufferVk::unmap(const gl::Context *context, GLboolean *result)
 {
-    return unmapImpl(vk::GetImpl(context));
+    unmapImpl(vk::GetImpl(context));
+
+    // This should be false if the contents have been corrupted through external means.  Vulkan
+    // doesn't provide such information.
+    *result = true;
+
+    return angle::Result::Continue;
 }
 
-angle::Result BufferVk::unmapImpl(ContextVk *contextVk)
+void BufferVk::unmapImpl(ContextVk *contextVk)
 {
     ASSERT(mBuffer.valid());
 
@@ -242,8 +248,6 @@
     mBuffer.onExternalWrite(VK_ACCESS_HOST_WRITE_BIT);
 
     markConversionBuffersDirty();
-
-    return angle::Result::Continue;
 }
 
 angle::Result BufferVk::getIndexRange(const gl::Context *context,
diff --git a/src/libANGLE/renderer/vulkan/BufferVk.h b/src/libANGLE/renderer/vulkan/BufferVk.h
index 3504c41..cd4a593 100644
--- a/src/libANGLE/renderer/vulkan/BufferVk.h
+++ b/src/libANGLE/renderer/vulkan/BufferVk.h
@@ -97,7 +97,7 @@
                                VkDeviceSize length,
                                GLbitfield access,
                                void **mapPtr);
-    angle::Result unmapImpl(ContextVk *contextVk);
+    void unmapImpl(ContextVk *contextVk);
 
     // Calls copyBuffer internally.
     angle::Result copyToBuffer(ContextVk *contextVk,
diff --git a/src/libANGLE/renderer/vulkan/CommandGraph.cpp b/src/libANGLE/renderer/vulkan/CommandGraph.cpp
index 666a361..25da4e5 100644
--- a/src/libANGLE/renderer/vulkan/CommandGraph.cpp
+++ b/src/libANGLE/renderer/vulkan/CommandGraph.cpp
@@ -88,6 +88,8 @@
                     UNREACHABLE();
                     return "Query";
             }
+        case CommandGraphResourceType::Dispatcher:
+            return "Dispatcher";
         case CommandGraphResourceType::EmulatedQuery:
             switch (function)
             {
@@ -1032,6 +1034,7 @@
     int framebufferIDCounter = 1;
     int imageIDCounter       = 1;
     int queryIDCounter       = 1;
+    int dispatcherIDCounter  = 1;
     int fenceIDCounter       = 1;
     int xfbIDCounter         = 1;
 
@@ -1107,6 +1110,9 @@
                     case CommandGraphResourceType::Image:
                         id = imageIDCounter++;
                         break;
+                    case CommandGraphResourceType::Dispatcher:
+                        id = dispatcherIDCounter++;
+                        break;
                     case CommandGraphResourceType::FenceSync:
                         id = fenceIDCounter++;
                         break;
diff --git a/src/libANGLE/renderer/vulkan/CommandGraph.h b/src/libANGLE/renderer/vulkan/CommandGraph.h
index fc9c309..ca00e70 100644
--- a/src/libANGLE/renderer/vulkan/CommandGraph.h
+++ b/src/libANGLE/renderer/vulkan/CommandGraph.h
@@ -32,6 +32,7 @@
     Framebuffer,
     Image,
     Query,
+    Dispatcher,
     // Transform feedback queries could be handled entirely on the CPU (if not using
     // VK_EXT_transform_feedback), but still need to generate a command graph barrier node.
     EmulatedQuery,
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index dc87007..413ff06 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -171,6 +171,7 @@
     : ContextImpl(state, errorSet),
       vk::Context(renderer),
       mCurrentGraphicsPipeline(nullptr),
+      mCurrentComputePipeline(nullptr),
       mCurrentDrawMode(gl::PrimitiveMode::InvalidEnum),
       mCurrentWindowSurface(nullptr),
       mVertexArray(nullptr),
@@ -209,6 +210,11 @@
     mNewGraphicsCommandBufferDirtyBits.set(DIRTY_BIT_TRANSFORM_FEEDBACK_BUFFERS);
     mNewGraphicsCommandBufferDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
 
+    mNewComputeCommandBufferDirtyBits.set(DIRTY_BIT_PIPELINE);
+    mNewComputeCommandBufferDirtyBits.set(DIRTY_BIT_TEXTURES);
+    mNewComputeCommandBufferDirtyBits.set(DIRTY_BIT_UNIFORM_AND_STORAGE_BUFFERS);
+    mNewComputeCommandBufferDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
+
     mGraphicsDirtyBitHandlers[DIRTY_BIT_DEFAULT_ATTRIBS] =
         &ContextVk::handleDirtyGraphicsDefaultAttribs;
     mGraphicsDirtyBitHandlers[DIRTY_BIT_PIPELINE] = &ContextVk::handleDirtyGraphicsPipeline;
@@ -225,7 +231,15 @@
     mGraphicsDirtyBitHandlers[DIRTY_BIT_DESCRIPTOR_SETS] =
         &ContextVk::handleDirtyGraphicsDescriptorSets;
 
+    mComputeDirtyBitHandlers[DIRTY_BIT_PIPELINE] = &ContextVk::handleDirtyComputePipeline;
+    mComputeDirtyBitHandlers[DIRTY_BIT_TEXTURES] = &ContextVk::handleDirtyComputeTextures;
+    mComputeDirtyBitHandlers[DIRTY_BIT_UNIFORM_AND_STORAGE_BUFFERS] =
+        &ContextVk::handleDirtyComputeUniformAndStorageBuffers;
+    mComputeDirtyBitHandlers[DIRTY_BIT_DESCRIPTOR_SETS] =
+        &ContextVk::handleDirtyComputeDescriptorSets;
+
     mGraphicsDirtyBits = mNewGraphicsCommandBufferDirtyBits;
+    mComputeDirtyBits  = mNewComputeCommandBufferDirtyBits;
 
     mActiveTextures.fill(nullptr);
 
@@ -502,6 +516,30 @@
                      mIndexedDirtyBitsMask, commandBufferOut);
 }
 
+angle::Result ContextVk::setupDispatch(const gl::Context *context,
+                                       vk::CommandBuffer **commandBufferOut)
+{
+    ANGLE_TRY(mDispatcher.recordCommands(this, commandBufferOut));
+
+    if (mProgram->dirtyUniforms())
+    {
+        ANGLE_TRY(mProgram->updateUniforms(this));
+        mComputeDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
+    }
+
+    DirtyBits dirtyBits = mComputeDirtyBits;
+
+    // Flush any relevant dirty bits.
+    for (size_t dirtyBit : dirtyBits)
+    {
+        ANGLE_TRY((this->*mComputeDirtyBitHandlers[dirtyBit])(context, *commandBufferOut));
+    }
+
+    mComputeDirtyBits.reset();
+
+    return angle::Result::Continue;
+}
+
 angle::Result ContextVk::handleDirtyGraphicsDefaultAttribs(const gl::Context *context,
                                                            vk::CommandBuffer *commandBuffer)
 {
@@ -557,18 +595,47 @@
     return angle::Result::Continue;
 }
 
-angle::Result ContextVk::handleDirtyGraphicsTextures(const gl::Context *context,
-                                                     vk::CommandBuffer *commandBuffer)
+angle::Result ContextVk::handleDirtyComputePipeline(const gl::Context *context,
+                                                    vk::CommandBuffer *commandBuffer)
 {
-    ANGLE_TRY(updateActiveTextures(context));
+    if (!mCurrentComputePipeline)
+    {
+        ANGLE_TRY(mProgram->getComputePipeline(this, &mCurrentComputePipeline));
+    }
+
+    commandBuffer->bindComputePipeline(mCurrentComputePipeline->get());
+    mCurrentComputePipeline->updateSerial(getCurrentQueueSerial());
+
+    return angle::Result::Continue;
+}
+
+ANGLE_INLINE angle::Result ContextVk::handleDirtyTexturesImpl(const gl::Context *context,
+                                                              vk::CommandBuffer *commandBuffer,
+                                                              vk::CommandGraphResource *recorder)
+{
+
+    ANGLE_TRY(updateActiveTextures(context, recorder));
 
     if (mProgram->hasTextures())
     {
-        ANGLE_TRY(mProgram->updateTexturesDescriptorSet(this, mDrawFramebuffer->getFramebuffer()));
+        ANGLE_TRY(mProgram->updateTexturesDescriptorSet(this));
     }
+
     return angle::Result::Continue;
 }
 
+angle::Result ContextVk::handleDirtyGraphicsTextures(const gl::Context *context,
+                                                     vk::CommandBuffer *commandBuffer)
+{
+    return handleDirtyTexturesImpl(context, commandBuffer, mDrawFramebuffer->getFramebuffer());
+}
+
+angle::Result ContextVk::handleDirtyComputeTextures(const gl::Context *context,
+                                                    vk::CommandBuffer *commandBuffer)
+{
+    return handleDirtyTexturesImpl(context, commandBuffer, &mDispatcher);
+}
+
 angle::Result ContextVk::handleDirtyGraphicsVertexBuffers(const gl::Context *context,
                                                           vk::CommandBuffer *commandBuffer)
 {
@@ -611,16 +678,31 @@
     return angle::Result::Continue;
 }
 
+ANGLE_INLINE angle::Result ContextVk::handleDirtyUniformAndStorageBuffersImpl(
+    const gl::Context *context,
+    vk::CommandBuffer *commandBuffer,
+    vk::CommandGraphResource *recorder)
+{
+    if (mProgram->hasUniformBuffers() || mProgram->hasStorageBuffers())
+    {
+        ANGLE_TRY(mProgram->updateUniformAndStorageBuffersDescriptorSet(this, recorder));
+    }
+    return angle::Result::Continue;
+}
+
 angle::Result ContextVk::handleDirtyGraphicsUniformAndStorageBuffers(
     const gl::Context *context,
     vk::CommandBuffer *commandBuffer)
 {
-    if (mProgram->hasUniformBuffers() || mProgram->hasStorageBuffers())
-    {
-        ANGLE_TRY(mProgram->updateUniformAndStorageBuffersDescriptorSet(
-            this, mDrawFramebuffer->getFramebuffer()));
-    }
-    return angle::Result::Continue;
+    return handleDirtyUniformAndStorageBuffersImpl(context, commandBuffer,
+                                                   mDrawFramebuffer->getFramebuffer());
+}
+
+angle::Result ContextVk::handleDirtyComputeUniformAndStorageBuffers(
+    const gl::Context *context,
+    vk::CommandBuffer *commandBuffer)
+{
+    return handleDirtyUniformAndStorageBuffersImpl(context, commandBuffer, &mDispatcher);
 }
 
 angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackBuffers(
@@ -648,6 +730,12 @@
     return angle::Result::Continue;
 }
 
+angle::Result ContextVk::handleDirtyComputeDescriptorSets(const gl::Context *context,
+                                                          vk::CommandBuffer *commandBuffer)
+{
+    return mProgram->updateDescriptorSets(this, commandBuffer);
+}
+
 angle::Result ContextVk::submitFrame(const VkSubmitInfo &submitInfo,
                                      vk::PrimaryCommandBuffer &&commandBuffer)
 {
@@ -671,6 +759,7 @@
     // recording command buffer is valid. Thus we need to explicitly notify every other Context
     // using this VkQueue that they their current command buffer is no longer valid.
     onRenderPassFinished();
+    mComputeDirtyBits |= mNewComputeCommandBufferDirtyBits;
 
     // Store this command buffer in the in-flight list.
     batch.commandPool = std::move(mCommandPool);
@@ -1376,13 +1465,14 @@
                                    const gl::State::DirtyBits &dirtyBits,
                                    const gl::State::DirtyBits &bitMask)
 {
-    if ((dirtyBits & mPipelineDirtyBitsMask).any())
+    const gl::State &glState = context->getState();
+
+    if ((dirtyBits & mPipelineDirtyBitsMask).any() &&
+        (glState.getProgram() == nullptr || !glState.getProgram()->isCompute()))
     {
         invalidateVertexAndIndexBuffers();
     }
 
-    const gl::State &glState = context->getState();
-
     for (size_t dirtyBit : dirtyBits)
     {
         switch (dirtyBit)
@@ -1609,13 +1699,21 @@
             {
                 invalidateCurrentTextures();
                 invalidateCurrentUniformAndStorageBuffers();
-                // No additional work is needed here. We will update the pipeline desc later.
-                invalidateDefaultAttributes(context->getStateCache().getActiveDefaultAttribsMask());
-                bool useVertexBuffer = (mProgram->getState().getMaxActiveAttribLocation());
-                mNonIndexedDirtyBitsMask.set(DIRTY_BIT_VERTEX_BUFFERS, useVertexBuffer);
-                mIndexedDirtyBitsMask.set(DIRTY_BIT_VERTEX_BUFFERS, useVertexBuffer);
-                mCurrentGraphicsPipeline = nullptr;
-                mGraphicsPipelineTransition.reset();
+                if (glState.getProgram()->isCompute())
+                {
+                    invalidateCurrentComputePipeline();
+                }
+                else
+                {
+                    // No additional work is needed here. We will update the pipeline desc later.
+                    invalidateDefaultAttributes(
+                        context->getStateCache().getActiveDefaultAttribsMask());
+                    bool useVertexBuffer = (mProgram->getState().getMaxActiveAttribLocation());
+                    mNonIndexedDirtyBitsMask.set(DIRTY_BIT_VERTEX_BUFFERS, useVertexBuffer);
+                    mIndexedDirtyBitsMask.set(DIRTY_BIT_VERTEX_BUFFERS, useVertexBuffer);
+                    mCurrentGraphicsPipeline = nullptr;
+                    mGraphicsPipelineTransition.reset();
+                }
                 break;
             }
             case gl::State::DIRTY_BIT_TEXTURE_BINDINGS:
@@ -1848,6 +1946,8 @@
     {
         mGraphicsDirtyBits.set(DIRTY_BIT_TEXTURES);
         mGraphicsDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
+        mComputeDirtyBits.set(DIRTY_BIT_TEXTURES);
+        mComputeDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
     }
 }
 
@@ -1858,6 +1958,8 @@
     {
         mGraphicsDirtyBits.set(DIRTY_BIT_UNIFORM_AND_STORAGE_BUFFERS);
         mGraphicsDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
+        mComputeDirtyBits.set(DIRTY_BIT_UNIFORM_AND_STORAGE_BUFFERS);
+        mComputeDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
     }
 }
 
@@ -1892,8 +1994,12 @@
                                          GLuint numGroupsY,
                                          GLuint numGroupsZ)
 {
-    ANGLE_VK_UNREACHABLE(this);
-    return angle::Result::Stop;
+    vk::CommandBuffer *commandBuffer;
+    ANGLE_TRY(setupDispatch(context, &commandBuffer));
+
+    commandBuffer->dispatch(numGroupsX, numGroupsY, numGroupsZ);
+
+    return angle::Result::Continue;
 }
 
 angle::Result ContextVk::dispatchComputeIndirect(const gl::Context *context, GLintptr indirect)
@@ -2039,7 +2145,8 @@
     mErrors->handleError(glErrorCode, errorStream.str().c_str(), file, function, line);
 }
 
-angle::Result ContextVk::updateActiveTextures(const gl::Context *context)
+angle::Result ContextVk::updateActiveTextures(const gl::Context *context,
+                                              vk::CommandGraphResource *recorder)
 {
     const gl::State &glState   = mState;
     const gl::Program *program = glState.getProgram();
@@ -2052,6 +2159,10 @@
     const gl::ActiveTextureMask &activeTextures    = program->getActiveSamplersMask();
     const gl::ActiveTextureTypeArray &textureTypes = program->getActiveSamplerTypes();
 
+    const vk::ImageLayout textureLayout = program->isCompute()
+                                              ? vk::ImageLayout::ComputeShaderReadOnly
+                                              : vk::ImageLayout::FragmentShaderReadOnly;
+
     for (size_t textureUnit : activeTextures)
     {
         gl::Texture *texture        = textures[textureUnit];
@@ -2072,18 +2183,17 @@
         // we can't verify it has no staged updates right here.
 
         // Ensure the image is in read-only layout
-        if (image.isLayoutChangeNecessary(vk::ImageLayout::FragmentShaderReadOnly))
+        if (image.isLayoutChangeNecessary(textureLayout))
         {
             vk::CommandBuffer *srcLayoutChange;
             ANGLE_TRY(image.recordCommands(this, &srcLayoutChange));
 
             VkImageAspectFlags aspectFlags = image.getAspectFlags();
             ASSERT(aspectFlags != 0);
-            image.changeLayout(aspectFlags, vk::ImageLayout::FragmentShaderReadOnly,
-                               srcLayoutChange);
+            image.changeLayout(aspectFlags, textureLayout, srcLayoutChange);
         }
 
-        image.addReadDependency(mDrawFramebuffer->getFramebuffer());
+        image.addReadDependency(recorder);
 
         mActiveTextures[textureUnit] = textureVk;
         mActiveTexturesDesc.update(textureUnit, textureVk->getSerial());
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.h b/src/libANGLE/renderer/vulkan/ContextVk.h
index 4b01a04..daecc53 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.h
+++ b/src/libANGLE/renderer/vulkan/ContextVk.h
@@ -331,6 +331,7 @@
                                                          vk::CommandBuffer *commandBuffer);
 
     std::array<DirtyBitHandler, DIRTY_BIT_MAX> mGraphicsDirtyBitHandlers;
+    std::array<DirtyBitHandler, DIRTY_BIT_MAX> mComputeDirtyBitHandlers;
 
     angle::Result setupDraw(const gl::Context *context,
                             gl::PrimitiveMode mode,
@@ -356,6 +357,7 @@
                                     const void *indices,
                                     vk::CommandBuffer **commandBufferOut,
                                     size_t *numIndicesOut);
+    angle::Result setupDispatch(const gl::Context *context, vk::CommandBuffer **commandBufferOut);
 
     void updateViewport(FramebufferVk *framebufferVk,
                         const gl::Rectangle &viewport,
@@ -367,13 +369,19 @@
     void updateFlipViewportDrawFramebuffer(const gl::State &glState);
     void updateFlipViewportReadFramebuffer(const gl::State &glState);
 
-    angle::Result updateActiveTextures(const gl::Context *context);
+    angle::Result updateActiveTextures(const gl::Context *context,
+                                       vk::CommandGraphResource *recorder);
     angle::Result updateDefaultAttribute(size_t attribIndex);
 
     ANGLE_INLINE void invalidateCurrentGraphicsPipeline()
     {
         mGraphicsDirtyBits.set(DIRTY_BIT_PIPELINE);
     }
+    ANGLE_INLINE void invalidateCurrentComputePipeline()
+    {
+        mComputeDirtyBits.set(DIRTY_BIT_PIPELINE);
+        mCurrentComputePipeline = nullptr;
+    }
 
     void invalidateCurrentTextures();
     void invalidateCurrentUniformAndStorageBuffers();
@@ -399,6 +407,24 @@
     angle::Result handleDirtyGraphicsDescriptorSets(const gl::Context *context,
                                                     vk::CommandBuffer *commandBuffer);
 
+    // Handlers for compute pipeline dirty bits.
+    angle::Result handleDirtyComputePipeline(const gl::Context *context,
+                                             vk::CommandBuffer *commandBuffer);
+    angle::Result handleDirtyComputeTextures(const gl::Context *context,
+                                             vk::CommandBuffer *commandBuffer);
+    angle::Result handleDirtyComputeUniformAndStorageBuffers(const gl::Context *context,
+                                                             vk::CommandBuffer *commandBuffer);
+    angle::Result handleDirtyComputeDescriptorSets(const gl::Context *context,
+                                                   vk::CommandBuffer *commandBuffer);
+
+    // Common parts of the common dirty bit handlers.
+    angle::Result handleDirtyTexturesImpl(const gl::Context *context,
+                                          vk::CommandBuffer *commandBuffer,
+                                          vk::CommandGraphResource *recorder);
+    angle::Result handleDirtyUniformAndStorageBuffersImpl(const gl::Context *context,
+                                                          vk::CommandBuffer *commandBuffer,
+                                                          vk::CommandGraphResource *recorder);
+
     angle::Result submitFrame(const VkSubmitInfo &submitInfo,
                               vk::PrimaryCommandBuffer &&commandBuffer);
     void freeAllInFlightResources();
@@ -416,6 +442,7 @@
     void waitForSwapchainImageIfNecessary();
 
     vk::PipelineHelper *mCurrentGraphicsPipeline;
+    vk::PipelineAndSerial *mCurrentComputePipeline;
     gl::PrimitiveMode mCurrentDrawMode;
 
     WindowSurfaceVk *mCurrentWindowSurface;
@@ -434,15 +461,20 @@
 
     // Dirty bits.
     DirtyBits mGraphicsDirtyBits;
+    DirtyBits mComputeDirtyBits;
     DirtyBits mNonIndexedDirtyBitsMask;
     DirtyBits mIndexedDirtyBitsMask;
     DirtyBits mNewGraphicsCommandBufferDirtyBits;
+    DirtyBits mNewComputeCommandBufferDirtyBits;
 
     // Cached back-end objects.
     VertexArrayVk *mVertexArray;
     FramebufferVk *mDrawFramebuffer;
     ProgramVk *mProgram;
 
+    // Graph resource used to record dispatch commands and hold resource dependencies.
+    vk::DispatchHelper mDispatcher;
+
     // The offset we had the last time we bound the index buffer.
     const GLvoid *mLastIndexBufferOffset;
     gl::DrawElementsType mCurrentDrawElementsType;
diff --git a/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp b/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp
index 06dcdcb..841aa76 100644
--- a/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp
+++ b/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp
@@ -98,7 +98,8 @@
 class IntermediateShaderSource final : angle::NonCopyable
 {
   public:
-    IntermediateShaderSource(const std::string &source);
+    void init(const std::string &source);
+    bool empty() const { return mTokens.empty(); }
 
     // Find @@ LAYOUT-name(extra, args) @@ and replace it with:
     //
@@ -217,7 +218,7 @@
     return readCount;
 }
 
-IntermediateShaderSource::IntermediateShaderSource(const std::string &source)
+void IntermediateShaderSource::init(const std::string &source)
 {
     size_t cur = 0;
 
@@ -491,7 +492,7 @@
 void AssignAttributeLocations(const gl::ProgramState &programState,
                               IntermediateShaderSource *vertexSource)
 {
-    ASSERT(vertexSource != nullptr);
+    ASSERT(!vertexSource->empty());
 
     // Parse attribute locations and replace them in the vertex shader.
     // See corresponding code in OutputVulkanGLSL.cpp.
@@ -510,7 +511,7 @@
 void AssignOutputLocations(const gl::ProgramState &programState,
                            IntermediateShaderSource *fragmentSource)
 {
-    ASSERT(fragmentSource != nullptr);
+    ASSERT(!fragmentSource->empty());
 
     // Parse output locations and replace them in the fragment shader.
     // See corresponding code in OutputVulkanGLSL.cpp.
@@ -549,6 +550,9 @@
                             IntermediateShaderSource *outStageSource,
                             IntermediateShaderSource *inStageSource)
 {
+    ASSERT(!outStageSource->empty());
+    ASSERT(!inStageSource->empty());
+
     // Assign varying locations.
     for (const gl::PackedVaryingRegister &varyingReg : resources.varyingPacking.getRegisterList())
     {
@@ -623,7 +627,7 @@
     inStageSource->insertQualifierSpecifier(kVaryingName, "in");
 }
 
-void AssignUniformBindings(const gl::ShaderMap<IntermediateShaderSource *> &shaderSources)
+void AssignUniformBindings(gl::ShaderMap<IntermediateShaderSource> *shaderSources)
 {
     // Bind the default uniforms for vertex and fragment shaders.
     // See corresponding code in OutputVulkanGLSL.cpp.
@@ -631,18 +635,18 @@
 
     constexpr char kDefaultUniformsBlockName[] = "defaultUniforms";
     size_t bindingIndex                        = 0;
-    for (IntermediateShaderSource *shaderSource : shaderSources)
+    for (IntermediateShaderSource &shaderSource : *shaderSources)
     {
-        if (shaderSource)
+        if (!shaderSource.empty())
         {
             std::string defaultUniformsBinding =
                 uniformsDescriptorSet + ", binding = " + Str(bindingIndex++);
 
-            shaderSource->insertLayoutSpecifier(kDefaultUniformsBlockName, defaultUniformsBinding);
+            shaderSource.insertLayoutSpecifier(kDefaultUniformsBlockName, defaultUniformsBinding);
         }
     }
 
-    if (shaderSources[gl::ShaderType::Compute] != nullptr)
+    if (!(*shaderSources)[gl::ShaderType::Compute].empty())
     {
         // Compute doesn't need driver uniforms.
         return;
@@ -653,13 +657,10 @@
         "set = " + Str(kDriverUniformsDescriptorSetIndex) + ", binding = 0";
     constexpr char kDriverBlockName[] = "ANGLEUniformBlock";
 
-    for (IntermediateShaderSource *shaderSource : shaderSources)
+    for (IntermediateShaderSource &shaderSource : *shaderSources)
     {
-        if (shaderSource)
-        {
-            shaderSource->insertLayoutSpecifier(kDriverBlockName, driverBlockLayoutString);
-            shaderSource->insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
-        }
+        shaderSource.insertLayoutSpecifier(kDriverBlockName, driverBlockLayoutString);
+        shaderSource.insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
     }
 }
 
@@ -669,31 +670,30 @@
                            const std::string &bindingString,
                            const char *qualifier,
                            const char *unusedSubstitution,
-                           const gl::ShaderMap<IntermediateShaderSource *> &shaderSources)
+                           gl::ShaderMap<IntermediateShaderSource> *shaderSources)
 {
     for (const gl::ShaderType shaderType : gl::AllShaderTypes())
     {
-        IntermediateShaderSource *shaderSource = shaderSources[shaderType];
-        if (shaderSource)
+        IntermediateShaderSource &shaderSource = (*shaderSources)[shaderType];
+        if (!shaderSource.empty())
         {
             if (activeShaders[shaderType])
             {
-                shaderSource->insertLayoutSpecifier(name, bindingString);
-                shaderSource->insertQualifierSpecifier(name, qualifier);
+                shaderSource.insertLayoutSpecifier(name, bindingString);
+                shaderSource.insertQualifierSpecifier(name, qualifier);
             }
             else
             {
-                shaderSource->eraseLayoutAndQualifierSpecifiers(name, unusedSubstitution);
+                shaderSource.eraseLayoutAndQualifierSpecifiers(name, unusedSubstitution);
             }
         }
     }
 }
 
-uint32_t AssignInterfaceBlockBindings(
-    const std::vector<gl::InterfaceBlock> &blocks,
-    const char *qualifier,
-    uint32_t bindingStart,
-    const gl::ShaderMap<IntermediateShaderSource *> &shaderSources)
+uint32_t AssignInterfaceBlockBindings(const std::vector<gl::InterfaceBlock> &blocks,
+                                      const char *qualifier,
+                                      uint32_t bindingStart,
+                                      gl::ShaderMap<IntermediateShaderSource> *shaderSources)
 {
     const std::string buffersDescriptorSet = "set = " + Str(kBufferDescriptorSetIndex);
 
@@ -714,7 +714,7 @@
 }
 
 void AssignBufferBindings(const gl::ProgramState &programState,
-                          const gl::ShaderMap<IntermediateShaderSource *> &shaderSources)
+                          gl::ShaderMap<IntermediateShaderSource> *shaderSources)
 {
     uint32_t bindingStart = 0;
 
@@ -730,7 +730,7 @@
 }
 
 void AssignTextureBindings(const gl::ProgramState &programState,
-                           const gl::ShaderMap<IntermediateShaderSource *> &shaderSources)
+                           gl::ShaderMap<IntermediateShaderSource> *shaderSources)
 {
     const std::string texturesDescriptorSet = "set = " + Str(kTextureDescriptorSetIndex);
 
@@ -755,10 +755,10 @@
 void CleanupUnusedEntities(const gl::ProgramState &programState,
                            const gl::ProgramLinkedResources &resources,
                            gl::Shader *glVertexShader,
-                           const gl::ShaderMap<IntermediateShaderSource *> &shaderSources)
+                           gl::ShaderMap<IntermediateShaderSource> *shaderSources)
 {
-    IntermediateShaderSource *vertexSource = shaderSources[gl::ShaderType::Vertex];
-    if (vertexSource)
+    IntermediateShaderSource &vertexSource = (*shaderSources)[gl::ShaderType::Vertex];
+    if (!vertexSource.empty())
     {
         ASSERT(glVertexShader != nullptr);
 
@@ -772,32 +772,26 @@
                 continue;
             }
 
-            vertexSource->eraseLayoutAndQualifierSpecifiers(attribute.name, "");
+            vertexSource.eraseLayoutAndQualifierSpecifiers(attribute.name, "");
         }
     }
 
     // Remove all the markers for unused varyings.
     for (const std::string &varyingName : resources.varyingPacking.getInactiveVaryingNames())
     {
-        for (IntermediateShaderSource *shaderSource : shaderSources)
+        for (IntermediateShaderSource &shaderSource : *shaderSources)
         {
-            if (shaderSource)
-            {
-                shaderSource->eraseLayoutAndQualifierSpecifiers(varyingName, "");
-            }
+            shaderSource.eraseLayoutAndQualifierSpecifiers(varyingName, "");
         }
     }
 
     // Remove all the markers for unused interface blocks, and replace them with |struct|.
     for (const std::string &unusedInterfaceBlock : resources.unusedInterfaceBlocks)
     {
-        for (IntermediateShaderSource *shaderSource : shaderSources)
+        for (IntermediateShaderSource &shaderSource : *shaderSources)
         {
-            if (shaderSource)
-            {
-                shaderSource->eraseLayoutAndQualifierSpecifiers(unusedInterfaceBlock,
-                                                                kUnusedBlockSubstitution);
-            }
+            shaderSource.eraseLayoutAndQualifierSpecifiers(unusedInterfaceBlock,
+                                                           kUnusedBlockSubstitution);
         }
     }
 
@@ -808,16 +802,19 @@
         std::string uniformName =
             unusedUniform.isSampler ? GetMappedSamplerName(unusedUniform.name) : unusedUniform.name;
 
-        for (IntermediateShaderSource *shaderSource : shaderSources)
+        for (IntermediateShaderSource &shaderSource : *shaderSources)
         {
-            if (shaderSource)
-            {
-                shaderSource->eraseLayoutAndQualifierSpecifiers(uniformName,
-                                                                kUnusedUniformSubstitution);
-            }
+            shaderSource.eraseLayoutAndQualifierSpecifiers(uniformName, kUnusedUniformSubstitution);
         }
     }
 }
+
+constexpr gl::ShaderMap<EShLanguage> kShLanguageMap = {
+    {gl::ShaderType::Vertex, EShLangVertex},
+    {gl::ShaderType::Geometry, EShLangGeometry},
+    {gl::ShaderType::Fragment, EShLangFragment},
+    {gl::ShaderType::Compute, EShLangCompute},
+};
 }  // anonymous namespace
 
 // static
@@ -837,127 +834,138 @@
 // static
 void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
                                      const gl::ProgramLinkedResources &resources,
-                                     std::string *vertexSourceOut,
-                                     std::string *fragmentSourceOut)
+                                     gl::ShaderMap<std::string> *shaderSourcesOut)
 {
-    gl::Shader *glVertexShader   = programState.getAttachedShader(gl::ShaderType::Vertex);
-    gl::Shader *glFragmentShader = programState.getAttachedShader(gl::ShaderType::Fragment);
+    gl::ShaderMap<IntermediateShaderSource> intermediateSources;
 
-    IntermediateShaderSource vertexSource(glVertexShader->getTranslatedSource());
-    IntermediateShaderSource fragmentSource(glFragmentShader->getTranslatedSource());
+    for (const gl::ShaderType shaderType : gl::AllShaderTypes())
+    {
+        gl::Shader *glShader = programState.getAttachedShader(shaderType);
+        if (glShader)
+        {
+            intermediateSources[shaderType].init(glShader->getTranslatedSource());
+        }
+    }
 
-    gl::ShaderMap<IntermediateShaderSource *> shaderSources{};
+    IntermediateShaderSource *vertexSource   = &intermediateSources[gl::ShaderType::Vertex];
+    IntermediateShaderSource *fragmentSource = &intermediateSources[gl::ShaderType::Fragment];
 
-    shaderSources[gl::ShaderType::Vertex]   = &vertexSource;
-    shaderSources[gl::ShaderType::Fragment] = &fragmentSource;
+    if (!vertexSource->empty())
+    {
+        AssignAttributeLocations(programState, vertexSource);
+        AssignOutputLocations(programState, fragmentSource);
+        AssignVaryingLocations(resources, vertexSource, fragmentSource);
+    }
+    AssignUniformBindings(&intermediateSources);
+    AssignBufferBindings(programState, &intermediateSources);
+    AssignTextureBindings(programState, &intermediateSources);
 
-    AssignAttributeLocations(programState, shaderSources[gl::ShaderType::Vertex]);
-    AssignOutputLocations(programState, shaderSources[gl::ShaderType::Fragment]);
-    AssignVaryingLocations(resources, shaderSources[gl::ShaderType::Vertex],
-                           shaderSources[gl::ShaderType::Fragment]);
-    AssignUniformBindings(shaderSources);
-    AssignBufferBindings(programState, shaderSources);
-    AssignTextureBindings(programState, shaderSources);
-
-    CleanupUnusedEntities(programState, resources, glVertexShader, shaderSources);
+    CleanupUnusedEntities(programState, resources,
+                          programState.getAttachedShader(gl::ShaderType::Vertex),
+                          &intermediateSources);
 
     // Write transform feedback output code.
-    if (programState.getLinkedTransformFeedbackVaryings().empty())
+    if (!vertexSource->empty())
     {
-        vertexSource.insertTransformFeedbackDeclaration("");
-        vertexSource.insertTransformFeedbackOutput("");
-    }
-    else
-    {
-        GenerateTransformFeedbackOutputs(programState, &vertexSource);
+        if (programState.getLinkedTransformFeedbackVaryings().empty())
+        {
+            vertexSource->insertTransformFeedbackDeclaration("");
+            vertexSource->insertTransformFeedbackOutput("");
+        }
+        else
+        {
+            GenerateTransformFeedbackOutputs(programState, vertexSource);
+        }
     }
 
-    *vertexSourceOut   = vertexSource.getShaderSource();
-    *fragmentSourceOut = fragmentSource.getShaderSource();
+    for (const gl::ShaderType shaderType : gl::AllShaderTypes())
+    {
+        (*shaderSourcesOut)[shaderType] = intermediateSources[shaderType].getShaderSource();
+    }
 }
 
 // static
 angle::Result GlslangWrapper::GetShaderCode(vk::Context *context,
                                             const gl::Caps &glCaps,
                                             bool enableLineRasterEmulation,
-                                            const std::string &vertexSource,
-                                            const std::string &fragmentSource,
-                                            std::vector<uint32_t> *vertexCodeOut,
-                                            std::vector<uint32_t> *fragmentCodeOut)
+                                            const gl::ShaderMap<std::string> &shaderSources,
+                                            gl::ShaderMap<std::vector<uint32_t>> *shaderCodeOut)
 {
     if (enableLineRasterEmulation)
     {
-        std::string patchedVertexSource   = vertexSource;
-        std::string patchedFragmentSource = fragmentSource;
+        ASSERT(shaderSources[gl::ShaderType::Compute].empty());
+
+        gl::ShaderMap<std::string> patchedSources = shaderSources;
 
         // #defines must come after the #version directive.
-        ANGLE_VK_CHECK(
-            context,
-            angle::ReplaceSubstring(&patchedVertexSource, kVersionDefine, kLineRasterDefine),
-            VK_ERROR_INVALID_SHADER_NV);
-        ANGLE_VK_CHECK(
-            context,
-            angle::ReplaceSubstring(&patchedFragmentSource, kVersionDefine, kLineRasterDefine),
-            VK_ERROR_INVALID_SHADER_NV);
+        ANGLE_VK_CHECK(context,
+                       angle::ReplaceSubstring(&patchedSources[gl::ShaderType::Vertex],
+                                               kVersionDefine, kLineRasterDefine),
+                       VK_ERROR_INVALID_SHADER_NV);
+        ANGLE_VK_CHECK(context,
+                       angle::ReplaceSubstring(&patchedSources[gl::ShaderType::Fragment],
+                                               kVersionDefine, kLineRasterDefine),
+                       VK_ERROR_INVALID_SHADER_NV);
 
-        return GetShaderCodeImpl(context, glCaps, patchedVertexSource, patchedFragmentSource,
-                                 vertexCodeOut, fragmentCodeOut);
+        return GetShaderCodeImpl(context, glCaps, patchedSources, shaderCodeOut);
     }
     else
     {
-        return GetShaderCodeImpl(context, glCaps, vertexSource, fragmentSource, vertexCodeOut,
-                                 fragmentCodeOut);
+        return GetShaderCodeImpl(context, glCaps, shaderSources, shaderCodeOut);
     }
 }
 
 // static
 angle::Result GlslangWrapper::GetShaderCodeImpl(vk::Context *context,
                                                 const gl::Caps &glCaps,
-                                                const std::string &vertexSource,
-                                                const std::string &fragmentSource,
-                                                std::vector<uint32_t> *vertexCodeOut,
-                                                std::vector<uint32_t> *fragmentCodeOut)
+                                                const gl::ShaderMap<std::string> &shaderSources,
+                                                gl::ShaderMap<std::vector<uint32_t>> *shaderCodeOut)
 {
-    std::array<const char *, 2> strings = {{vertexSource.c_str(), fragmentSource.c_str()}};
-    std::array<int, 2> lengths          = {
-        {static_cast<int>(vertexSource.length()), static_cast<int>(fragmentSource.length())}};
-
     // Enable SPIR-V and Vulkan rules when parsing GLSL
     EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);
 
-    glslang::TShader vertexShader(EShLangVertex);
-    vertexShader.setStringsWithLengths(&strings[0], &lengths[0], 1);
-    vertexShader.setEntryPoint("main");
-
     TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource);
     GetBuiltInResourcesFromCaps(glCaps, &builtInResources);
 
-    bool vertexResult =
-        vertexShader.parse(&builtInResources, 450, ECoreProfile, false, false, messages);
-    if (!vertexResult)
-    {
-        ERR() << "Internal error parsing Vulkan vertex shader:\n"
-              << vertexShader.getInfoLog() << "\n"
-              << vertexShader.getInfoDebugLog() << "\n";
-        ANGLE_VK_CHECK(context, false, VK_ERROR_INVALID_SHADER_NV);
-    }
-
+    glslang::TShader vertexShader(EShLangVertex);
     glslang::TShader fragmentShader(EShLangFragment);
-    fragmentShader.setStringsWithLengths(&strings[1], &lengths[1], 1);
-    fragmentShader.setEntryPoint("main");
-    bool fragmentResult =
-        fragmentShader.parse(&builtInResources, 450, ECoreProfile, false, false, messages);
-    if (!fragmentResult)
+    glslang::TShader geometryShader(EShLangGeometry);
+    glslang::TShader computeShader(EShLangCompute);
+
+    gl::ShaderMap<glslang::TShader *> shaders = {
+        {gl::ShaderType::Vertex, &vertexShader},
+        {gl::ShaderType::Fragment, &fragmentShader},
+        {gl::ShaderType::Geometry, &geometryShader},
+        {gl::ShaderType::Compute, &computeShader},
+    };
+    glslang::TProgram program;
+
+    for (const gl::ShaderType shaderType : gl::AllShaderTypes())
     {
-        ERR() << "Internal error parsing Vulkan fragment shader:\n"
-              << fragmentShader.getInfoLog() << "\n"
-              << fragmentShader.getInfoDebugLog() << "\n";
-        ANGLE_VK_CHECK(context, false, VK_ERROR_INVALID_SHADER_NV);
+        if (shaderSources[shaderType].empty())
+        {
+            continue;
+        }
+
+        const char *shaderString = shaderSources[shaderType].c_str();
+        int shaderLength         = static_cast<int>(shaderSources[shaderType].size());
+
+        glslang::TShader *shader = shaders[shaderType];
+        shader->setStringsWithLengths(&shaderString, &shaderLength, 1);
+        shader->setEntryPoint("main");
+
+        bool result = shader->parse(&builtInResources, 450, ECoreProfile, false, false, messages);
+        if (!result)
+        {
+            ERR() << "Internal error parsing Vulkan shader corresponding to " << shaderType << ":\n"
+                  << shader->getInfoLog() << "\n"
+                  << shader->getInfoDebugLog() << "\n";
+            ANGLE_VK_CHECK(context, false, VK_ERROR_INVALID_SHADER_NV);
+        }
+
+        program.addShader(shader);
     }
 
-    glslang::TProgram program;
-    program.addShader(&vertexShader);
-    program.addShader(&fragmentShader);
     bool linkResult = program.link(messages);
     if (!linkResult)
     {
@@ -965,10 +973,16 @@
         ANGLE_VK_CHECK(context, false, VK_ERROR_INVALID_SHADER_NV);
     }
 
-    glslang::TIntermediate *vertexStage   = program.getIntermediate(EShLangVertex);
-    glslang::TIntermediate *fragmentStage = program.getIntermediate(EShLangFragment);
-    glslang::GlslangToSpv(*vertexStage, *vertexCodeOut);
-    glslang::GlslangToSpv(*fragmentStage, *fragmentCodeOut);
+    for (const gl::ShaderType shaderType : gl::AllShaderTypes())
+    {
+        if (shaderSources[shaderType].empty())
+        {
+            continue;
+        }
+
+        glslang::TIntermediate *intermediate = program.getIntermediate(kShLanguageMap[shaderType]);
+        glslang::GlslangToSpv(*intermediate, (*shaderCodeOut)[shaderType]);
+    }
 
     return angle::Result::Continue;
 }
diff --git a/src/libANGLE/renderer/vulkan/GlslangWrapper.h b/src/libANGLE/renderer/vulkan/GlslangWrapper.h
index 6adccd4..4ab47be 100644
--- a/src/libANGLE/renderer/vulkan/GlslangWrapper.h
+++ b/src/libANGLE/renderer/vulkan/GlslangWrapper.h
@@ -24,24 +24,19 @@
 
     static void GetShaderSource(const gl::ProgramState &programState,
                                 const gl::ProgramLinkedResources &resources,
-                                std::string *vertexSourceOut,
-                                std::string *fragmentSourceOut);
+                                gl::ShaderMap<std::string> *shaderSourcesOut);
 
     static angle::Result GetShaderCode(vk::Context *context,
                                        const gl::Caps &glCaps,
                                        bool enableLineRasterEmulation,
-                                       const std::string &vertexSource,
-                                       const std::string &fragmentSource,
-                                       std::vector<uint32_t> *vertexCodeOut,
-                                       std::vector<uint32_t> *fragmentCodeOut);
+                                       const gl::ShaderMap<std::string> &shaderSources,
+                                       gl::ShaderMap<std::vector<uint32_t>> *shaderCodesOut);
 
   private:
     static angle::Result GetShaderCodeImpl(vk::Context *context,
                                            const gl::Caps &glCaps,
-                                           const std::string &vertexSource,
-                                           const std::string &fragmentSource,
-                                           std::vector<uint32_t> *vertexCodeOut,
-                                           std::vector<uint32_t> *fragmentCodeOut);
+                                           const gl::ShaderMap<std::string> &shaderSources,
+                                           gl::ShaderMap<std::vector<uint32_t>> *shaderCodesOut);
 };
 }  // namespace rx
 
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index 7a9040a..eb62701 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -195,25 +195,26 @@
 ProgramVk::ShaderInfo::~ShaderInfo() = default;
 
 angle::Result ProgramVk::ShaderInfo::initShaders(ContextVk *contextVk,
-                                                 const std::string &vertexSource,
-                                                 const std::string &fragmentSource,
+                                                 const gl::ShaderMap<std::string> &shaderSources,
                                                  bool enableLineRasterEmulation)
 {
     ASSERT(!valid());
 
-    std::vector<uint32_t> vertexCode;
-    std::vector<uint32_t> fragmentCode;
-    ANGLE_TRY(GlslangWrapper::GetShaderCode(contextVk, contextVk->getCaps(),
-                                            enableLineRasterEmulation, vertexSource, fragmentSource,
-                                            &vertexCode, &fragmentCode));
+    gl::ShaderMap<std::vector<uint32_t>> shaderCodes;
+    ANGLE_TRY(GlslangWrapper::GetShaderCode(
+        contextVk, contextVk->getCaps(), enableLineRasterEmulation, shaderSources, &shaderCodes));
 
-    ANGLE_TRY(vk::InitShaderAndSerial(contextVk, &mShaders[gl::ShaderType::Vertex].get(),
-                                      vertexCode.data(), vertexCode.size() * sizeof(uint32_t)));
-    ANGLE_TRY(vk::InitShaderAndSerial(contextVk, &mShaders[gl::ShaderType::Fragment].get(),
-                                      fragmentCode.data(), fragmentCode.size() * sizeof(uint32_t)));
+    for (const gl::ShaderType shaderType : gl::AllShaderTypes())
+    {
+        if (!shaderSources[shaderType].empty())
+        {
+            ANGLE_TRY(vk::InitShaderAndSerial(contextVk, &mShaders[shaderType].get(),
+                                              shaderCodes[shaderType].data(),
+                                              shaderCodes[shaderType].size() * sizeof(uint32_t)));
 
-    mProgramHelper.setShader(gl::ShaderType::Vertex, &mShaders[gl::ShaderType::Vertex]);
-    mProgramHelper.setShader(gl::ShaderType::Fragment, &mShaders[gl::ShaderType::Fragment]);
+            mProgramHelper.setShader(shaderType, &mShaders[shaderType]);
+        }
+    }
 
     return angle::Result::Continue;
 }
@@ -223,7 +224,7 @@
     // Read in shader sources for all shader types
     for (const gl::ShaderType shaderType : gl::AllShaderTypes())
     {
-        mShaderSource[shaderType] = stream->readString();
+        mShaderSources[shaderType] = stream->readString();
     }
 
     return angle::Result::Continue;
@@ -234,7 +235,7 @@
     // Write out shader sources for all shader types
     for (const gl::ShaderType shaderType : gl::AllShaderTypes())
     {
-        stream->writeString(mShaderSource[shaderType]);
+        stream->writeString(mShaderSources[shaderType]);
     }
 }
 
@@ -338,8 +339,7 @@
     // assignment done in that function.
     linkResources(resources);
 
-    GlslangWrapper::GetShaderSource(mState, resources, &mShaderSource[gl::ShaderType::Vertex],
-                                    &mShaderSource[gl::ShaderType::Fragment]);
+    GlslangWrapper::GetShaderSource(mState, resources, &mShaderSources);
 
     // TODO(jie.a.chen@intel.com): Parallelize linking.
     // http://crbug.com/849576
@@ -1013,7 +1013,7 @@
 }
 
 void ProgramVk::updateBuffersDescriptorSet(ContextVk *contextVk,
-                                           vk::FramebufferHelper *framebufferVk,
+                                           vk::CommandGraphResource *recorder,
                                            const std::vector<gl::InterfaceBlock> &blocks,
                                            VkDescriptorType descriptorType)
 {
@@ -1074,19 +1074,23 @@
 
         if (isStorageBuffer)
         {
-            bufferHelper.onWrite(contextVk, framebufferVk, VK_ACCESS_SHADER_READ_BIT,
+            bufferHelper.onWrite(contextVk, recorder, VK_ACCESS_SHADER_READ_BIT,
                                  VK_ACCESS_SHADER_WRITE_BIT);
         }
         else
         {
-            bufferHelper.onRead(framebufferVk, VK_ACCESS_UNIFORM_READ_BIT);
+            bufferHelper.onRead(recorder, VK_ACCESS_UNIFORM_READ_BIT);
         }
 
         // If size is 0, we can't always use VK_WHOLE_SIZE (or bufferHelper.getSize()), as the
         // backing buffer may be larger than max*BufferRange.  In that case, we use the minimum of
         // the backing buffer size (what's left after offset) and the buffer size as defined by the
-        // shader.
-        size = std::min(size > 0 ? size : (bufferHelper.getSize() - offset), blockSize);
+        // shader.  That latter is only valid for UBOs, as SSBOs may have variable length arrays.
+        size = size > 0 ? size : (bufferHelper.getSize() - offset);
+        if (!isStorageBuffer)
+        {
+            size = std::min(size, blockSize);
+        }
 
         VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[writeCount];
 
@@ -1118,13 +1122,13 @@
 
 angle::Result ProgramVk::updateUniformAndStorageBuffersDescriptorSet(
     ContextVk *contextVk,
-    vk::FramebufferHelper *framebufferVk)
+    vk::CommandGraphResource *recorder)
 {
     ANGLE_TRY(allocateDescriptorSet(contextVk, kBufferDescriptorSetIndex));
 
-    updateBuffersDescriptorSet(contextVk, framebufferVk, mState.getUniformBlocks(),
+    updateBuffersDescriptorSet(contextVk, recorder, mState.getUniformBlocks(),
                                VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
-    updateBuffersDescriptorSet(contextVk, framebufferVk, mState.getShaderStorageBlocks(),
+    updateBuffersDescriptorSet(contextVk, recorder, mState.getShaderStorageBlocks(),
                                VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
 
     return angle::Result::Continue;
@@ -1163,8 +1167,7 @@
                                              mDescriptorSets[kUniformsAndXfbDescriptorSetIndex]);
 }
 
-angle::Result ProgramVk::updateTexturesDescriptorSet(ContextVk *contextVk,
-                                                     vk::FramebufferHelper *framebuffer)
+angle::Result ProgramVk::updateTexturesDescriptorSet(ContextVk *contextVk)
 {
     const vk::TextureDescriptorDesc &texturesDesc = contextVk->getActiveTexturesDesc();
 
@@ -1272,6 +1275,9 @@
         }
     }
 
+    const VkPipelineBindPoint pipelineBindPoint =
+        mState.isCompute() ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS;
+
     for (size_t descriptorSetIndex = 0; descriptorSetIndex < descriptorSetRange;
          ++descriptorSetIndex)
     {
@@ -1305,7 +1311,7 @@
             descriptorSetIndex == kUniformsAndXfbDescriptorSetIndex ? mDynamicBufferOffsets.size()
                                                                     : 0;
 
-        commandBuffer->bindDescriptorSets(mPipelineLayout.get(), VK_PIPELINE_BIND_POINT_GRAPHICS,
+        commandBuffer->bindDescriptorSets(mPipelineLayout.get(), pipelineBindPoint,
                                           descriptorSetIndex, 1, &descSet, uniformBlockOffsetCount,
                                           mDynamicBufferOffsets.data());
     }
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h
index 2b5c80f..885755e 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.h
@@ -106,10 +106,9 @@
     // Also initializes the pipeline layout, descriptor set layouts, and used descriptor ranges.
 
     angle::Result updateUniforms(ContextVk *contextVk);
-    angle::Result updateTexturesDescriptorSet(ContextVk *contextVk,
-                                              vk::FramebufferHelper *framebuffer);
+    angle::Result updateTexturesDescriptorSet(ContextVk *contextVk);
     angle::Result updateUniformAndStorageBuffersDescriptorSet(ContextVk *contextVk,
-                                                              vk::FramebufferHelper *framebuffer);
+                                                              vk::CommandGraphResource *recorder);
     angle::Result updateTransformFeedbackDescriptorSet(ContextVk *contextVk,
                                                        vk::FramebufferHelper *framebuffer);
 
@@ -138,7 +137,7 @@
                                       vk::PipelineHelper **pipelineOut)
     {
         vk::ShaderProgramHelper *shaderProgram;
-        ANGLE_TRY(initShaders(contextVk, mode, &shaderProgram));
+        ANGLE_TRY(initGraphicsShaders(contextVk, mode, &shaderProgram));
         ASSERT(shaderProgram->isGraphicsProgram());
         RendererVk *renderer = contextVk->getRenderer();
         return shaderProgram->getGraphicsPipeline(
@@ -147,6 +146,14 @@
             mState.getAttributesTypeMask(), descPtrOut, pipelineOut);
     }
 
+    angle::Result getComputePipeline(ContextVk *contextVk, vk::PipelineAndSerial **pipelineOut)
+    {
+        vk::ShaderProgramHelper *shaderProgram;
+        ANGLE_TRY(initComputeShader(contextVk, &shaderProgram));
+        ASSERT(!shaderProgram->isGraphicsProgram());
+        return shaderProgram->getComputePipeline(contextVk, mPipelineLayout.get(), pipelineOut);
+    }
+
     // Used in testing only.
     vk::DynamicDescriptorPool *getDynamicDescriptorPool(uint32_t poolIndex)
     {
@@ -170,7 +177,7 @@
     void updateDefaultUniformsDescriptorSet(ContextVk *contextVk);
     void updateTransformFeedbackDescriptorSetImpl(ContextVk *contextVk);
     void updateBuffersDescriptorSet(ContextVk *contextVk,
-                                    vk::FramebufferHelper *framebufferVk,
+                                    vk::CommandGraphResource *recorder,
                                     const std::vector<gl::InterfaceBlock> &blocks,
                                     VkDescriptorType descriptorType);
 
@@ -186,26 +193,40 @@
     uint32_t getUniformBlockBindingsOffset() const { return 0; }
     uint32_t getStorageBlockBindingsOffset() const { return mStorageBlockBindingsOffset; }
 
+    class ShaderInfo;
     ANGLE_INLINE angle::Result initShaders(ContextVk *contextVk,
-                                           gl::PrimitiveMode mode,
+                                           bool enableLineRasterEmulation,
+                                           ShaderInfo *shaderInfo,
                                            vk::ShaderProgramHelper **shaderProgramOut)
     {
+        if (!shaderInfo->valid())
+        {
+            ANGLE_TRY(
+                shaderInfo->initShaders(contextVk, mShaderSources, enableLineRasterEmulation));
+        }
+
+        ASSERT(shaderInfo->valid());
+        *shaderProgramOut = &shaderInfo->getShaderProgram();
+
+        return angle::Result::Continue;
+    }
+
+    ANGLE_INLINE angle::Result initGraphicsShaders(ContextVk *contextVk,
+                                                   gl::PrimitiveMode mode,
+                                                   vk::ShaderProgramHelper **shaderProgramOut)
+    {
         bool enableLineRasterEmulation = UseLineRaster(contextVk, mode);
 
         ShaderInfo &shaderInfo =
             enableLineRasterEmulation ? mLineRasterShaderInfo : mDefaultShaderInfo;
 
-        if (!shaderInfo.valid())
-        {
-            ANGLE_TRY(shaderInfo.initShaders(contextVk, mShaderSource[gl::ShaderType::Vertex],
-                                             mShaderSource[gl::ShaderType::Fragment],
-                                             enableLineRasterEmulation));
-        }
+        return initShaders(contextVk, enableLineRasterEmulation, &shaderInfo, shaderProgramOut);
+    }
 
-        ASSERT(shaderInfo.valid());
-        *shaderProgramOut = &shaderInfo.getShaderProgram();
-
-        return angle::Result::Continue;
+    ANGLE_INLINE angle::Result initComputeShader(ContextVk *contextVk,
+                                                 vk::ShaderProgramHelper **shaderProgramOut)
+    {
+        return initShaders(contextVk, false, &mDefaultShaderInfo, shaderProgramOut);
     }
 
     // Save and load implementation for GLES Program Binary support.
@@ -260,12 +281,15 @@
         ~ShaderInfo();
 
         angle::Result initShaders(ContextVk *contextVk,
-                                  const std::string &vertexSource,
-                                  const std::string &fragmentSource,
+                                  const gl::ShaderMap<std::string> &shaderSources,
                                   bool enableLineRasterEmulation);
         void release(ContextVk *contextVk);
 
-        ANGLE_INLINE bool valid() const { return mShaders[gl::ShaderType::Vertex].get().valid(); }
+        ANGLE_INLINE bool valid() const
+        {
+            return mShaders[gl::ShaderType::Vertex].get().valid() ||
+                   mShaders[gl::ShaderType::Compute].get().valid();
+        }
 
         vk::ShaderProgramHelper &getShaderProgram() { return mProgramHelper; }
 
@@ -278,7 +302,7 @@
     ShaderInfo mLineRasterShaderInfo;
 
     // We keep the translated linked shader sources to use with shader draw call patching.
-    gl::ShaderMap<std::string> mShaderSource;
+    gl::ShaderMap<std::string> mShaderSources;
 
     // Storage buffers are placed after uniform buffers in their descriptor set.  This cached value
     // contains the offset where storage buffer bindings start.
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
index 445470c..16a9757 100644
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
@@ -304,7 +304,7 @@
                                0, numVertices, binding.getStride(), vertexFormat.vertexLoadFunction,
                                &mCurrentArrayBuffers[attribIndex],
                                &conversion->lastAllocationOffset));
-    ANGLE_TRY(srcBuffer->unmapImpl(contextVk));
+    srcBuffer->unmapImpl(contextVk);
 
     ASSERT(conversion->dirty);
     conversion->dirty = false;
diff --git a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
index 5b4304f..d603842 100644
--- a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
@@ -35,6 +35,7 @@
     ASSERT(mCurrentQueueFamilyIndex < mQueueFamilyProperties.size());
     const VkQueueFamilyProperties &queueFamilyProperties =
         mQueueFamilyProperties[mCurrentQueueFamilyIndex];
+    const VkPhysicalDeviceLimits &limitsVk = mPhysicalDeviceProperties.limits;
 
     mNativeExtensions.setTextureExtensionSupport(mNativeTextureCaps);
 
@@ -98,12 +99,9 @@
     mNativeExtensions.queryCounterBitsTimestamp   = queueFamilyProperties.timestampValidBits;
 
     mNativeExtensions.textureFilterAnisotropic =
-        mPhysicalDeviceFeatures.samplerAnisotropy &&
-        mPhysicalDeviceProperties.limits.maxSamplerAnisotropy > 1.0f;
+        mPhysicalDeviceFeatures.samplerAnisotropy && limitsVk.maxSamplerAnisotropy > 1.0f;
     mNativeExtensions.maxTextureAnisotropy =
-        mNativeExtensions.textureFilterAnisotropic
-            ? mPhysicalDeviceProperties.limits.maxSamplerAnisotropy
-            : 0.0f;
+        mNativeExtensions.textureFilterAnisotropic ? limitsVk.maxSamplerAnisotropy : 0.0f;
 
     // Vulkan natively supports non power-of-two textures
     mNativeExtensions.textureNPOT = true;
@@ -118,41 +116,34 @@
 
     // https://vulkan.lunarg.com/doc/view/1.0.30.0/linux/vkspec.chunked/ch31s02.html
     mNativeCaps.maxElementIndex       = std::numeric_limits<GLuint>::max() - 1;
-    mNativeCaps.max3DTextureSize      = mPhysicalDeviceProperties.limits.maxImageDimension3D;
-    mNativeCaps.max2DTextureSize      = mPhysicalDeviceProperties.limits.maxImageDimension2D;
-    mNativeCaps.maxArrayTextureLayers = mPhysicalDeviceProperties.limits.maxImageArrayLayers;
-    mNativeCaps.maxLODBias            = mPhysicalDeviceProperties.limits.maxSamplerLodBias;
-    mNativeCaps.maxCubeMapTextureSize = mPhysicalDeviceProperties.limits.maxImageDimensionCube;
+    mNativeCaps.max3DTextureSize      = limitsVk.maxImageDimension3D;
+    mNativeCaps.max2DTextureSize      = limitsVk.maxImageDimension2D;
+    mNativeCaps.maxArrayTextureLayers = limitsVk.maxImageArrayLayers;
+    mNativeCaps.maxLODBias            = limitsVk.maxSamplerLodBias;
+    mNativeCaps.maxCubeMapTextureSize = limitsVk.maxImageDimensionCube;
     mNativeCaps.maxRenderbufferSize   = mNativeCaps.max2DTextureSize;
-    mNativeCaps.minAliasedPointSize =
-        std::max(1.0f, mPhysicalDeviceProperties.limits.pointSizeRange[0]);
-    mNativeCaps.maxAliasedPointSize = mPhysicalDeviceProperties.limits.pointSizeRange[1];
+    mNativeCaps.minAliasedPointSize   = std::max(1.0f, limitsVk.pointSizeRange[0]);
+    mNativeCaps.maxAliasedPointSize   = limitsVk.pointSizeRange[1];
 
     mNativeCaps.minAliasedLineWidth = 1.0f;
     mNativeCaps.maxAliasedLineWidth = 1.0f;
 
     mNativeCaps.maxDrawBuffers =
-        std::min<uint32_t>(mPhysicalDeviceProperties.limits.maxColorAttachments,
-                           mPhysicalDeviceProperties.limits.maxFragmentOutputAttachments);
-    mNativeCaps.maxFramebufferWidth  = mPhysicalDeviceProperties.limits.maxFramebufferWidth;
-    mNativeCaps.maxFramebufferHeight = mPhysicalDeviceProperties.limits.maxFramebufferHeight;
-    mNativeCaps.maxColorAttachments  = mPhysicalDeviceProperties.limits.maxColorAttachments;
-    mNativeCaps.maxViewportWidth     = mPhysicalDeviceProperties.limits.maxViewportDimensions[0];
-    mNativeCaps.maxViewportHeight    = mPhysicalDeviceProperties.limits.maxViewportDimensions[1];
-    mNativeCaps.maxSampleMaskWords   = mPhysicalDeviceProperties.limits.maxSampleMaskWords;
-    mNativeCaps.maxColorTextureSamples =
-        mPhysicalDeviceProperties.limits.sampledImageColorSampleCounts;
-    mNativeCaps.maxDepthTextureSamples =
-        mPhysicalDeviceProperties.limits.sampledImageDepthSampleCounts;
-    mNativeCaps.maxIntegerSamples =
-        mPhysicalDeviceProperties.limits.sampledImageIntegerSampleCounts;
+        std::min<uint32_t>(limitsVk.maxColorAttachments, limitsVk.maxFragmentOutputAttachments);
+    mNativeCaps.maxFramebufferWidth    = limitsVk.maxFramebufferWidth;
+    mNativeCaps.maxFramebufferHeight   = limitsVk.maxFramebufferHeight;
+    mNativeCaps.maxColorAttachments    = limitsVk.maxColorAttachments;
+    mNativeCaps.maxViewportWidth       = limitsVk.maxViewportDimensions[0];
+    mNativeCaps.maxViewportHeight      = limitsVk.maxViewportDimensions[1];
+    mNativeCaps.maxSampleMaskWords     = limitsVk.maxSampleMaskWords;
+    mNativeCaps.maxColorTextureSamples = limitsVk.sampledImageColorSampleCounts;
+    mNativeCaps.maxDepthTextureSamples = limitsVk.sampledImageDepthSampleCounts;
+    mNativeCaps.maxIntegerSamples      = limitsVk.sampledImageIntegerSampleCounts;
 
-    mNativeCaps.maxVertexAttributes     = mPhysicalDeviceProperties.limits.maxVertexInputAttributes;
-    mNativeCaps.maxVertexAttribBindings = mPhysicalDeviceProperties.limits.maxVertexInputBindings;
-    mNativeCaps.maxVertexAttribRelativeOffset =
-        mPhysicalDeviceProperties.limits.maxVertexInputAttributeOffset;
-    mNativeCaps.maxVertexAttribStride =
-        mPhysicalDeviceProperties.limits.maxVertexInputBindingStride;
+    mNativeCaps.maxVertexAttributes           = limitsVk.maxVertexInputAttributes;
+    mNativeCaps.maxVertexAttribBindings       = limitsVk.maxVertexInputBindings;
+    mNativeCaps.maxVertexAttribRelativeOffset = limitsVk.maxVertexInputAttributeOffset;
+    mNativeCaps.maxVertexAttribStride         = limitsVk.maxVertexInputBindingStride;
 
     mNativeCaps.maxElementsIndices  = std::numeric_limits<GLint>::max();
     mNativeCaps.maxElementsVertices = std::numeric_limits<GLint>::max();
@@ -174,13 +165,23 @@
     mNativeCaps.fragmentMediumpInt.setTwosComplementInt(32);
     mNativeCaps.fragmentLowpInt.setTwosComplementInt(32);
 
+    // Compute shader limits.
+    mNativeCaps.maxComputeWorkGroupCount[0]    = limitsVk.maxComputeWorkGroupCount[0];
+    mNativeCaps.maxComputeWorkGroupCount[1]    = limitsVk.maxComputeWorkGroupCount[1];
+    mNativeCaps.maxComputeWorkGroupCount[2]    = limitsVk.maxComputeWorkGroupCount[2];
+    mNativeCaps.maxComputeWorkGroupSize[0]     = limitsVk.maxComputeWorkGroupSize[0];
+    mNativeCaps.maxComputeWorkGroupSize[1]     = limitsVk.maxComputeWorkGroupSize[1];
+    mNativeCaps.maxComputeWorkGroupSize[2]     = limitsVk.maxComputeWorkGroupSize[2];
+    mNativeCaps.maxComputeWorkGroupInvocations = limitsVk.maxComputeWorkGroupInvocations;
+    mNativeCaps.maxComputeSharedMemorySize     = limitsVk.maxComputeSharedMemorySize;
+
     // TODO(lucferron): This is something we'll need to implement custom in the back-end.
     // Vulkan doesn't do any waiting for you, our back-end code is going to manage sync objects,
     // and we'll have to check that we've exceeded the max wait timeout. Also, this is ES 3.0 so
     // we'll defer the implementation until we tackle the next version.
     // mNativeCaps.maxServerWaitTimeout
 
-    GLuint maxUniformBlockSize = mPhysicalDeviceProperties.limits.maxUniformBufferRange;
+    GLuint maxUniformBlockSize = limitsVk.maxUniformBufferRange;
 
     // Clamp the maxUniformBlockSize to 64KB (majority of devices support up to this size
     // currently), on AMD the maxUniformBufferRange is near uint32_t max.
@@ -192,10 +193,11 @@
     // Uniforms are implemented using a uniform buffer, so the max number of uniforms we can
     // support is the max buffer range divided by the size of a single uniform (4X float).
     mNativeCaps.maxVertexUniformVectors                              = maxUniformVectors;
-    mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Vertex]   = maxUniformComponents;
     mNativeCaps.maxFragmentUniformVectors                            = maxUniformVectors;
     mNativeCaps.maxFragmentInputComponents                           = maxUniformComponents;
+    mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Vertex]   = maxUniformComponents;
     mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Fragment] = maxUniformComponents;
+    mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Compute]  = maxUniformComponents;
 
     // Every stage has 1 reserved uniform buffer for the default uniforms, and 1 for the driver
     // uniforms.
@@ -205,41 +207,33 @@
         kReservedDriverUniformBindingCount + kReservedDefaultUniformBindingCount;
 
     const uint32_t maxPerStageUniformBuffers =
-        mPhysicalDeviceProperties.limits.maxPerStageDescriptorUniformBuffers -
-        kTotalReservedPerStageUniformBuffers;
+        limitsVk.maxPerStageDescriptorUniformBuffers - kTotalReservedPerStageUniformBuffers;
     const uint32_t maxCombinedUniformBuffers =
-        mPhysicalDeviceProperties.limits.maxDescriptorSetUniformBuffers -
-        kTotalReservedUniformBuffers;
+        limitsVk.maxDescriptorSetUniformBuffers - kTotalReservedUniformBuffers;
     mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Vertex]   = maxPerStageUniformBuffers;
     mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Fragment] = maxPerStageUniformBuffers;
+    mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Compute]  = maxPerStageUniformBuffers;
     mNativeCaps.maxCombinedUniformBlocks                         = maxCombinedUniformBuffers;
 
     mNativeCaps.maxUniformBufferBindings = maxCombinedUniformBuffers;
     mNativeCaps.maxUniformBlockSize      = maxUniformBlockSize;
     mNativeCaps.uniformBufferOffsetAlignment =
-        static_cast<GLuint>(mPhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment);
+        static_cast<GLuint>(limitsVk.minUniformBufferOffsetAlignment);
 
     // Note that Vulkan currently implements textures as combined image+samplers, so the limit is
     // the minimum of supported samplers and sampled images.
-    const uint32_t maxPerStageTextures =
-        std::min(mPhysicalDeviceProperties.limits.maxPerStageDescriptorSamplers,
-                 mPhysicalDeviceProperties.limits.maxPerStageDescriptorSampledImages);
+    const uint32_t maxPerStageTextures = std::min(limitsVk.maxPerStageDescriptorSamplers,
+                                                  limitsVk.maxPerStageDescriptorSampledImages);
     const uint32_t maxCombinedTextures =
-        std::min(mPhysicalDeviceProperties.limits.maxDescriptorSetSamplers,
-                 mPhysicalDeviceProperties.limits.maxDescriptorSetSampledImages);
+        std::min(limitsVk.maxDescriptorSetSamplers, limitsVk.maxDescriptorSetSampledImages);
     mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Vertex]   = maxPerStageTextures;
     mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Fragment] = maxPerStageTextures;
+    mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Compute]  = maxPerStageTextures;
     mNativeCaps.maxCombinedTextureImageUnits                         = maxCombinedTextures;
 
-    const uint32_t maxPerStageStorageBuffers =
-        mPhysicalDeviceProperties.limits.maxPerStageDescriptorStorageBuffers;
-    const uint32_t maxCombinedStorageBuffers =
-        mPhysicalDeviceProperties.limits.maxDescriptorSetStorageBuffers;
-    mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Vertex] =
-        mPhysicalDeviceFeatures.vertexPipelineStoresAndAtomics ? maxPerStageStorageBuffers : 0;
-    mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Fragment] =
-        mPhysicalDeviceFeatures.fragmentStoresAndAtomics ? maxPerStageStorageBuffers : 0;
-    mNativeCaps.maxCombinedShaderStorageBlocks = maxCombinedStorageBuffers;
+    uint32_t maxPerStageStorageBuffers    = limitsVk.maxPerStageDescriptorStorageBuffers;
+    uint32_t maxVertexStageStorageBuffers = maxPerStageStorageBuffers;
+    uint32_t maxCombinedStorageBuffers    = limitsVk.maxDescriptorSetStorageBuffers;
 
     // A number of storage buffer slots are used in the vertex shader to emulate transform feedback.
     // Note that Vulkan requires maxPerStageDescriptorStorageBuffers to be at least 4 (i.e. the same
@@ -253,17 +247,26 @@
         "Limit to ES2.0 if supported SSBO count < supporting transform feedback buffer count");
     if (mPhysicalDeviceFeatures.vertexPipelineStoresAndAtomics)
     {
-        ASSERT(maxPerStageStorageBuffers >= gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS);
-        mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Vertex] -=
-            gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS;
-        mNativeCaps.maxCombinedShaderStorageBlocks -=
-            gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS;
+        ASSERT(maxVertexStageStorageBuffers >= gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS);
+        maxVertexStageStorageBuffers -= gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS;
+        maxCombinedStorageBuffers -= gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS;
+
+        // Cap the per-stage limit of the other stages to the combined limit, in case the combined
+        // limit is now lower than that.
+        maxPerStageStorageBuffers = std::min(maxPerStageStorageBuffers, maxCombinedStorageBuffers);
     }
 
+    mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Vertex] =
+        mPhysicalDeviceFeatures.vertexPipelineStoresAndAtomics ? maxVertexStageStorageBuffers : 0;
+    mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Fragment] =
+        mPhysicalDeviceFeatures.fragmentStoresAndAtomics ? maxPerStageStorageBuffers : 0;
+    mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Compute] = maxPerStageStorageBuffers;
+    mNativeCaps.maxCombinedShaderStorageBlocks                  = maxCombinedStorageBuffers;
+
     mNativeCaps.maxShaderStorageBufferBindings = maxCombinedStorageBuffers;
-    mNativeCaps.maxShaderStorageBlockSize = mPhysicalDeviceProperties.limits.maxStorageBufferRange;
+    mNativeCaps.maxShaderStorageBlockSize      = limitsVk.maxStorageBufferRange;
     mNativeCaps.shaderStorageBufferOffsetAlignment =
-        static_cast<GLuint>(mPhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment);
+        static_cast<GLuint>(limitsVk.minStorageBufferOffsetAlignment);
 
     mNativeCaps.minProgramTexelOffset = mPhysicalDeviceProperties.limits.minTexelOffset;
     mNativeCaps.maxProgramTexelOffset = mPhysicalDeviceProperties.limits.maxTexelOffset;
@@ -274,7 +277,7 @@
     const uint32_t maxCombinedUniformComponents =
         (maxPerStageUniformBuffers + kReservedPerStageDefaultUniformBindingCount) *
         maxUniformComponents;
-    for (gl::ShaderType shaderType : gl::kAllGraphicsShaderTypes)
+    for (gl::ShaderType shaderType : gl::AllShaderTypes())
     {
         mNativeCaps.maxCombinedShaderUniformComponents[shaderType] = maxCombinedUniformComponents;
     }
@@ -294,8 +297,8 @@
     // which total a minimum of 44 resources, so no underflow is possible here.  Limit the total
     // number of resources reported by Vulkan to 2 billion though to avoid seeing negative numbers
     // in applications that take the value as signed int (including dEQP).
-    const uint32_t maxPerStageResources = std::min<uint32_t>(
-        std::numeric_limits<int32_t>::max(), mPhysicalDeviceProperties.limits.maxPerStageResources);
+    const uint32_t maxPerStageResources =
+        std::min<uint32_t>(std::numeric_limits<int32_t>::max(), limitsVk.maxPerStageResources);
     mNativeCaps.maxCombinedShaderOutputResources =
         maxPerStageResources - kReservedPerStageBindingCount;
 
@@ -308,7 +311,7 @@
     // often crash. Reserving an additional varying just for them bringing the total to 2.
     constexpr GLint kReservedVaryingCount = 2;
     mNativeCaps.maxVaryingVectors =
-        (mPhysicalDeviceProperties.limits.maxVertexOutputComponents / 4) - kReservedVaryingCount;
+        (limitsVk.maxVertexOutputComponents / 4) - kReservedVaryingCount;
     mNativeCaps.maxVertexOutputComponents = mNativeCaps.maxVaryingVectors * 4;
 
     mNativeCaps.maxTransformFeedbackInterleavedComponents =
@@ -318,17 +321,16 @@
     mNativeCaps.maxTransformFeedbackSeparateComponents =
         gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS;
 
-    mNativeCaps.minProgramTexelOffset = mPhysicalDeviceProperties.limits.minTexelOffset;
-    mNativeCaps.maxProgramTexelOffset = mPhysicalDeviceProperties.limits.maxTexelOffset;
+    mNativeCaps.minProgramTexelOffset = limitsVk.minTexelOffset;
+    mNativeCaps.maxProgramTexelOffset = limitsVk.maxTexelOffset;
 
-    const VkPhysicalDeviceLimits &limits = mPhysicalDeviceProperties.limits;
-    const uint32_t sampleCounts          = limits.framebufferColorSampleCounts &
-                                  limits.framebufferDepthSampleCounts &
-                                  limits.framebufferStencilSampleCounts;
+    const uint32_t sampleCounts = limitsVk.framebufferColorSampleCounts &
+                                  limitsVk.framebufferDepthSampleCounts &
+                                  limitsVk.framebufferStencilSampleCounts;
 
     mNativeCaps.maxSamples = vk_gl::GetMaxSampleCount(sampleCounts);
 
-    mNativeCaps.subPixelBits = mPhysicalDeviceProperties.limits.subPixelPrecisionBits;
+    mNativeCaps.subPixelBits = limitsVk.subPixelPrecisionBits;
 }
 
 namespace egl_vk
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index b7104ca..53b240f 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -1136,7 +1136,7 @@
         ANGLE_TRY(streamIndices(contextVk, glIndexType, indexCount,
                                 static_cast<const uint8_t *>(srcDataMapping) + elementArrayOffset,
                                 bufferOut, bufferOffsetOut, indexCountOut));
-        ANGLE_TRY(elementArrayBufferVk->unmapImpl(contextVk));
+        elementArrayBufferVk->unmapImpl(contextVk);
         return angle::Result::Continue;
     }
 
@@ -2628,6 +2628,11 @@
     contextVk->releaseObject(getStoredQueueSerial(), &mFramebuffer);
 }
 
+// FramebufferHelper implementation.
+DispatchHelper::DispatchHelper() : CommandGraphResource(CommandGraphResourceType::Dispatcher) {}
+
+DispatchHelper::~DispatchHelper() = default;
+
 // ShaderProgramHelper implementation.
 ShaderProgramHelper::ShaderProgramHelper() = default;
 
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index 5bd1d02..7f17692 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -970,6 +970,15 @@
     Framebuffer mFramebuffer;
 };
 
+// A special command graph resource to hold resource dependencies for dispatch calls.  It's the
+// equivalent of FramebufferHelper, though it doesn't contain a Vulkan object.
+class DispatchHelper : public CommandGraphResource
+{
+  public:
+    DispatchHelper();
+    ~DispatchHelper() override;
+};
+
 class ShaderProgramHelper : angle::NonCopyable
 {
   public:
diff --git a/src/tests/deqp_support/deqp_gles31_test_expectations.txt b/src/tests/deqp_support/deqp_gles31_test_expectations.txt
index cc5cc1e..32372f4 100644
--- a/src/tests/deqp_support/deqp_gles31_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles31_test_expectations.txt
@@ -627,17 +627,17 @@
 // GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET not set.
 3605 VULKAN : dEQP-GLES31.functional.texture.gather.offset.* = FAIL
 
-// Compute shaders:
-3562 VULKAN : dEQP-GLES31.functional.shaders.builtin_var.compute.* = FAIL
-3562 VULKAN : dEQP-GLES31.functional.compute.* = FAIL
-3566 VULKAN : dEQP-GLES31.functional.ssbo.* = FAIL
-3561 VULKAN : dEQP-GLES31.functional.synchronization.*.ssbo* = FAIL
-3562 VULKAN : dEQP-GLES31.functional.shaders.builtin_functions.common.*compute = FAIL
-3562 VULKAN : dEQP-GLES31.functional.shaders.builtin_functions.precision*compute* = FAIL
-3563 VULKAN : dEQP-GLES31.functional.state_query.*compute* = SKIP
-3562 VULKAN : dEQP-GLES31.functional.state_query.program.compute_work_group_size_get_programiv = FAIL
-3562 VULKAN : dEQP-GLES31.functional.program_interface_query.buffer_limited_query.resource_*query = FAIL
-3562 VULKAN : dEQP-GLES31.functional.program_interface_query.program_*.resource_list.compute.empty = FAIL
+// Front-end query bugs:
+3520 VULKAN : dEQP-GLES31.functional.program_interface_query.buffer_limited_query.resource_*query = FAIL
+3520 VULKAN : dEQP-GLES31.functional.program_interface_query.program_*.resource_list.compute.empty = FAIL
+3520 VULKAN : dEQP-GLES31.functional.program_interface_query.shader_storage_block.buffer_data_size.* = FAIL
+
+// glMemoryBarrier support:
+3574 VULKAN : dEQP-GLES31.functional.compute.*barrier* = SKIP
+3574 VULKAN : dEQP-GLES31.functional.synchronization.*memory_barrier* = SKIP
+
+// Indirect dispatch:
+3601 VULKAN : dEQP-GLES31.functional.compute.*indirect* = SKIP
 
 // Shader support:
 3569 VULKAN : dEQP-GLES31.functional.shaders.builtin_functions.common.frexp.* = FAIL
@@ -655,10 +655,14 @@
 3569 VULKAN : dEQP-GLES31.functional.shaders.builtin_constants.core.min_program_texel_offset = FAIL
 3569 VULKAN : dEQP-GLES31.functional.shaders.uniform_block.es31.valid.* = FAIL
 
+// SSBO and Image qualifiers:
+3602 VULKAN : dEQP-GLES31.functional.synchronization.in_invocation.ssbo_alias_overwrite = FAIL
+
 // Array of array and struct default uniforms:
 3604 VULKAN : dEQP-GLES31.functional.program_interface_query.*float_struct = FAIL
+3604 VULKAN : dEQP-GLES31.functional.program_interface_query.*float_struct_struct = FAIL
 3604 VULKAN : dEQP-GLES31.functional.program_interface_query.*random* = FAIL
-3604 VULKAN : dEQP-GLES31.functional.program_interface_query.program_output.* = SKIP
+3604 VULKAN : dEQP-GLES31.functional.program_interface_query.*array*array* = SKIP
 
 // Block name matching failure:
 3459 VULKAN : dEQP-GLES31.functional.shaders.linkage.es31.shader_storage_block.mismatch_with_and_without_instance_name = FAIL
@@ -674,10 +678,12 @@
 
 // Atomic counters:
 3566 VULKAN : dEQP-GLES31.functional.*atomic_counter* = FAIL
+3566 VULKAN : dEQP-GLES31.functional.ssbo.layout.* = FAIL
 
 // Storage image:
 3563 VULKAN : dEQP-GLES31.functional.state_query.*image* = FAIL
 3563 VULKAN : dEQP-GLES31.functional.synchronization.*.image* = FAIL
+3563 VULKAN : dEQP-GLES31.functional.compute.*image* = FAIL
 
 // Framebuffer parameters (FRAMEBUFFER_DEFAULT_SAMPLES):
 3520 VULKAN : dEQP-GLES31.functional.state_query.framebuffer_default.framebuffer_default_samples_get_framebuffer_parameteriv = FAIL
diff --git a/src/tests/deqp_support/deqp_khr_gles31_test_expectations.txt b/src/tests/deqp_support/deqp_khr_gles31_test_expectations.txt
index a1de85c..d0aab38 100644
--- a/src/tests/deqp_support/deqp_khr_gles31_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_khr_gles31_test_expectations.txt
@@ -36,12 +36,12 @@
 // General Vulkan expectations
 
 // Limits:
-// GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET not set.
-3605 VULKAN : KHR-GLES31.core.texture_gather* = FAIL
+// GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET not set.  Also crashes on memory barrier support missing.
+3605 VULKAN : KHR-GLES31.core.texture_gather.* = SKIP
 
-// Compute shaders
-3562 VULKAN : KHR-GLES31.core.constant_expressions.* = SKIP
-3520 VULKAN : KHR-GLES31.core.compute_shader* = SKIP
+// Memory barriers
+3574 VULKAN : KHR-GLES31.core.compute_shader* = SKIP
+3574 VULKAN : KHR-GLES31.core.shader_atomic_counters.basic-glsl-built-in = SKIP
 
 // Multisampled textures:
 3565 VULKAN : KHR-GLES31.core.texture_storage_multisample.* = SKIP
@@ -67,14 +67,21 @@
 3570 VULKAN : KHR-GLES31.core.sepshaderobjs* = FAIL
 
 // Shader support:
-3569 VULKAN : KHR-GLES31.core.shader_bitfield_operation* = FAIL
 3569 VULKAN : KHR-GLES31.core.shader_integer_mix.* = FAIL
 3569 VULKAN : KHR-GLES31.core.shader_image* = SKIP
 3569 VULKAN : KHR-GLES31.core.shader_macros* = FAIL
+3569 VULKAN : KHR-GLES31.core.shader_bitfield_operation.frexp.* = SKIP
+3569 VULKAN : KHR-GLES31.core.shader_bitfield_operation.uaddCarry.* = SKIP
+3569 VULKAN : KHR-GLES31.core.shader_bitfield_operation.usubBorrow.* = SKIP
+3569 VULKAN : KHR-GLES31.core.shader_bitfield_operation.umulExtended.* = SKIP
+3569 VULKAN : KHR-GLES31.core.shader_bitfield_operation.imulExtended.* = SKIP
 /// Crash due to missing unsupported check in dEQP
 3571 VULKAN : KHR-GLES31.core.shader_macros*_geometry = SKIP
 3571 VULKAN : KHR-GLES31.core.shader_macros*_tess_control = SKIP
 3571 VULKAN : KHR-GLES31.core.shader_macros*_tess_eval = SKIP
+3571 VULKAN : KHR-GLES31.core.constant_expressions.*geometry = SKIP
+3571 VULKAN : KHR-GLES31.core.constant_expressions.*tess_control = SKIP
+3571 VULKAN : KHR-GLES31.core.constant_expressions.*tess_eval = SKIP
 
 3520 VULKAN : KHR-GLES31.core.vertex_attrib_binding* = SKIP
 3520 VULKAN : KHR-GLES31.core.internalformat.texture2d.* = FAIL
@@ -85,7 +92,8 @@
 // Draw indirect:
 3564 VULKAN : KHR-GLES31.core.draw_indirect.* = SKIP
 
-3520 VULKAN : KHR-GLES31.core.explicit_uniform_location.* = FAIL
+// Explicit uniform locations:
+3597  VULKAN : KHR-GLES31.core.explicit_uniform_location.* = SKIP
 
 3520 VULKAN : KHR-GLES31.core.program_interface_query.* = SKIP
 
@@ -97,6 +105,10 @@
 // Blend equations:
 3586 VULKAN : KHR-GLES31.core.blend_equation_advanced.* = SKIP
 
+// Storage image:
+3563 VULKAN : KHR-GLES31.core.layout_binding.sampler2D_layout_binding_texture_ComputeShader = FAIL
+3563 VULKAN : KHR-GLES31.core.layout_binding.block_layout_binding_block_ComputeShader = FAIL
+
 3520 VULKAN : KHR-GLES31.core.internalformat.copy_tex_image* = FAIL
 3520 VULKAN : KHR-GLES31.core.internalformat.renderbuffer* = FAIL
 
diff --git a/src/tests/gl_tests/ShaderStorageBufferTest.cpp b/src/tests/gl_tests/ShaderStorageBufferTest.cpp
index ef5dc31..bcfd234 100644
--- a/src/tests/gl_tests/ShaderStorageBufferTest.cpp
+++ b/src/tests/gl_tests/ShaderStorageBufferTest.cpp
@@ -315,6 +315,9 @@
 // Tests modifying an existing shader storage buffer
 TEST_P(ShaderStorageBufferTest31, ShaderStorageBufferReadWriteSame)
 {
+    // Vulkan doesn't support memory barriers yet. http://anglebug.com/3574
+    ANGLE_SKIP_TEST_IF(IsVulkan());
+
     constexpr char kComputeShaderSource[] =
         R"(#version 310 es
 layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
@@ -410,6 +413,9 @@
 // Tests reading and writing to a shader storage buffer bound at an offset.
 TEST_P(ShaderStorageBufferTest31, ShaderStorageBufferReadWriteOffset)
 {
+    // Vulkan doesn't support memory barriers yet. http://anglebug.com/3574
+    ANGLE_SKIP_TEST_IF(IsVulkan());
+
     constexpr char kCS[] = R"(#version 310 es
 layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
 
@@ -1593,8 +1599,10 @@
     // http://anglebug.com/1951
     ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux());
 
-    // Seems to fail on Windows NVIDIA GL when tests are run without interruption.
+    // Seems to fail on Windows NVIDIA GL when tests are run without interruption.  It also happens
+    // on Vulkan.  http://anglebug.com/3694
     ANGLE_SKIP_TEST_IF(IsWindows() && IsNVIDIA() && IsOpenGL());
+    ANGLE_SKIP_TEST_IF(IsWindows() && IsVulkan());
 
     constexpr char kComputeShaderSource[] = R"(#version 310 es
 layout (local_size_x=1) in;
@@ -2072,6 +2080,10 @@
     EXPECT_GL_NO_ERROR();
 }
 
-ANGLE_INSTANTIATE_TEST(ShaderStorageBufferTest31, ES31_OPENGL(), ES31_OPENGLES(), ES31_D3D11());
+ANGLE_INSTANTIATE_TEST(ShaderStorageBufferTest31,
+                       ES31_OPENGL(),
+                       ES31_OPENGLES(),
+                       ES31_D3D11(),
+                       ES31_VULKAN());
 
 }  // namespace