Vulkan: add uniform buffer object support

Support for layout qualifiers in interface blocks are added.  All
interface blocks are adjusted to either be in std140 or std430.

In the Vulkan backend, a new descriptor set is added for UBOs.  A dirty
bit is added for UBO updating and pipeline layouts and descriptor
bindings are updated.

Bug: angleproject:3199, angleproject:3220
Change-Id: I271fc34ac2e1e8b76dee75e54a7cff0fe15fe4ee
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1565061
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h
index c804715..516b7e0 100644
--- a/include/GLSLANG/ShaderLang.h
+++ b/include/GLSLANG/ShaderLang.h
@@ -26,7 +26,7 @@
 
 // Version number for shader translation API.
 // It is incremented every time the API changes.
-#define ANGLE_SH_VERSION 206
+#define ANGLE_SH_VERSION 207
 
 enum ShShaderSpec
 {
@@ -281,6 +281,12 @@
 // http://anglebug.com/3246
 const ShCompileOptions SH_FORCE_ATOMIC_VALUE_RESOLUTION = UINT64_C(1) << 42;
 
+// Change interface block layout qualifiers to std140 for any layout that is not explicitly set to
+// std430.  This is to comply with GL_KHR_vulkan_glsl where shared and packed are not allowed (and
+// std140 could be used instead) and unspecified layouts can assume either std140 or std430 (and we
+// choose std140 as std430 is not yet universally supported).
+const ShCompileOptions SH_REDEFINE_INTERFACE_LAYOUT_QUALIFIERS_WITH_STD = UINT64_C(1) << 43;
+
 // Defines alternate strategies for implementing array index clamping.
 enum ShArrayIndexClampingStrategy
 {
diff --git a/include/platform/FeaturesVk.h b/include/platform/FeaturesVk.h
index 92698ea..183f114 100644
--- a/include/platform/FeaturesVk.h
+++ b/include/platform/FeaturesVk.h
@@ -83,6 +83,12 @@
     // On Qualcomm, a bug is preventing us from using loadOp=Clear with inline commands in the
     // render pass.  http://anglebug.com/2361
     bool restartRenderPassAfterLoadOpClear = false;
+
+    // On Qualcomm, gaps in bound descriptor set indices causes the post-gap sets to misbehave.
+    // For example, binding only descriptor set 3 results in zero being read from a uniform buffer
+    // object within that set.  This flag results in empty descriptor sets being bound for any
+    // unused descriptor set to work around this issue.  http://anglebug.com/2727
+    bool bindEmptyForUnusedDescriptorSets = false;
 };
 
 inline FeaturesVk::FeaturesVk() = default;
diff --git a/src/compiler.gni b/src/compiler.gni
index 9fced4d..5f8a2d1 100644
--- a/src/compiler.gni
+++ b/src/compiler.gni
@@ -160,6 +160,8 @@
   "src/compiler/translator/tree_ops/RewriteDoWhile.h",
   "src/compiler/translator/tree_ops/RewriteExpressionsWithShaderStorageBlock.cpp",
   "src/compiler/translator/tree_ops/RewriteExpressionsWithShaderStorageBlock.h",
+  "src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.cpp",
+  "src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h",
   "src/compiler/translator/tree_ops/RewriteStructSamplers.cpp",
   "src/compiler/translator/tree_ops/RewriteStructSamplers.h",
   "src/compiler/translator/tree_ops/RewriteRepeatedAssignToSwizzled.cpp",
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index a31eda7..bdfb7f3 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -33,6 +33,7 @@
 #include "compiler/translator/tree_ops/InitializeVariables.h"
 #include "compiler/translator/tree_ops/PruneEmptyCases.h"
 #include "compiler/translator/tree_ops/PruneNoOps.h"
+#include "compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h"
 #include "compiler/translator/tree_ops/RegenerateStructNames.h"
 #include "compiler/translator/tree_ops/RemoveArrayLengthMethod.h"
 #include "compiler/translator/tree_ops/RemoveInvariantDeclaration.h"
@@ -723,6 +724,13 @@
         }
     }
 
+    if (compileOptions & SH_REDEFINE_INTERFACE_LAYOUT_QUALIFIERS_WITH_STD)
+    {
+        // Change the interface block layouts based on GL_KHR_vulkan_glsl.  Only std140 and std430
+        // are allowed in Vulkan GLSL.
+        RedefineInterfaceBlockLayoutQualifiersWithStd(root, &mSymbolTable);
+    }
+
     if (shouldCollectVariables(compileOptions))
     {
         ASSERT(!mVariablesCollected);
diff --git a/src/compiler/translator/OutputGLSLBase.cpp b/src/compiler/translator/OutputGLSLBase.cpp
index 21396ea..254177c 100644
--- a/src/compiler/translator/OutputGLSLBase.cpp
+++ b/src/compiler/translator/OutputGLSLBase.cpp
@@ -61,11 +61,12 @@
   private:
     bool mFirst;
 
-    friend TInfoSinkBase &operator<<(TInfoSinkBase &out,
-                                     CommaSeparatedListItemPrefixGenerator &gen);
+    template <typename Stream>
+    friend Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen);
 };
 
-TInfoSinkBase &operator<<(TInfoSinkBase &out, CommaSeparatedListItemPrefixGenerator &gen)
+template <typename Stream>
+Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen)
 {
     if (gen.mFirst)
     {
@@ -160,6 +161,51 @@
     }
 }
 
+// Outputs what goes inside layout(), except for location and binding qualifiers, as they are
+// handled differently between GL GLSL and Vulkan GLSL.
+std::string TOutputGLSLBase::getCommonLayoutQualifiers(TIntermTyped *variable)
+{
+    std::ostringstream out;
+    CommaSeparatedListItemPrefixGenerator listItemPrefix;
+
+    const TType &type                       = variable->getType();
+    const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
+
+    if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn ||
+        IsVarying(type.getQualifier()))
+    {
+        if (type.getQualifier() == EvqFragmentOut && layoutQualifier.index >= 0)
+        {
+            out << listItemPrefix << "index = " << layoutQualifier.index;
+        }
+    }
+
+    if (type.getQualifier() == EvqFragmentOut)
+    {
+        if (layoutQualifier.yuv == true)
+        {
+            out << listItemPrefix << "yuv";
+        }
+    }
+
+    if (IsImage(type.getBasicType()))
+    {
+        if (layoutQualifier.imageInternalFormat != EiifUnspecified)
+        {
+            ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
+            out << listItemPrefix
+                << getImageInternalFormatString(layoutQualifier.imageInternalFormat);
+        }
+    }
+
+    if (IsAtomicCounter(type.getBasicType()))
+    {
+        out << listItemPrefix << "offset = " << layoutQualifier.offset;
+    }
+
+    return out.str();
+}
+
 void TOutputGLSLBase::writeLayoutQualifier(TIntermTyped *variable)
 {
     const TType &type = variable->getType();
@@ -189,18 +235,6 @@
         {
             out << listItemPrefix << "location = " << layoutQualifier.location;
         }
-        if (type.getQualifier() == EvqFragmentOut && layoutQualifier.index >= 0)
-        {
-            out << listItemPrefix << "index = " << layoutQualifier.index;
-        }
-    }
-
-    if (type.getQualifier() == EvqFragmentOut)
-    {
-        if (layoutQualifier.yuv == true)
-        {
-            out << listItemPrefix << "yuv";
-        }
     }
 
     if (IsOpaqueType(type.getBasicType()))
@@ -211,25 +245,16 @@
         }
     }
 
-    if (IsImage(type.getBasicType()))
+    std::string otherQualifiers = getCommonLayoutQualifiers(variable);
+    if (!otherQualifiers.empty())
     {
-        if (layoutQualifier.imageInternalFormat != EiifUnspecified)
-        {
-            ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
-            out << listItemPrefix
-                << getImageInternalFormatString(layoutQualifier.imageInternalFormat);
-        }
-    }
-
-    if (IsAtomicCounter(type.getBasicType()))
-    {
-        out << listItemPrefix << "offset = " << layoutQualifier.offset;
+        out << listItemPrefix << otherQualifiers;
     }
 
     out << ") ";
 }
 
-void TOutputGLSLBase::writeQualifier(TQualifier qualifier, const TSymbol *symbol)
+void TOutputGLSLBase::writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol)
 {
     const char *result = mapQualifierToString(qualifier);
     if (result && result[0] != '\0')
@@ -284,7 +309,7 @@
     }
     if (qualifier != EvqTemporary && qualifier != EvqGlobal)
     {
-        writeQualifier(qualifier, symbol);
+        writeQualifier(qualifier, type, symbol);
     }
 
     const TMemoryQualifier &memoryQualifier = type.getMemoryQualifier();
diff --git a/src/compiler/translator/OutputGLSLBase.h b/src/compiler/translator/OutputGLSLBase.h
index 96f767e..feeffee 100644
--- a/src/compiler/translator/OutputGLSLBase.h
+++ b/src/compiler/translator/OutputGLSLBase.h
@@ -40,6 +40,7 @@
     TInfoSinkBase &objSink() { return mObjSink; }
     void writeFloat(TInfoSinkBase &out, float f);
     void writeTriplet(Visit visit, const char *preStr, const char *inStr, const char *postStr);
+    std::string getCommonLayoutQualifiers(TIntermTyped *variable);
     virtual void writeLayoutQualifier(TIntermTyped *variable);
     void writeInvariantQualifier(const TType &type);
     virtual void writeVariableType(const TType &type, const TSymbol *symbol);
@@ -77,7 +78,7 @@
     virtual ImmutableString translateTextureFunction(const ImmutableString &name) { return name; }
 
     void declareStruct(const TStructure *structure);
-    virtual void writeQualifier(TQualifier qualifier, const TSymbol *symbol);
+    virtual void writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol);
     bool structDeclared(const TStructure *structure) const;
 
   private:
diff --git a/src/compiler/translator/OutputVulkanGLSL.cpp b/src/compiler/translator/OutputVulkanGLSL.cpp
index 2075cfe..73b76de 100644
--- a/src/compiler/translator/OutputVulkanGLSL.cpp
+++ b/src/compiler/translator/OutputVulkanGLSL.cpp
@@ -61,32 +61,73 @@
     TIntermSymbol *symbol = variable->getAsSymbolNode();
     ASSERT(symbol);
 
+    ImmutableString name      = symbol->getName();
+    const char *blockStorage  = nullptr;
+    const char *matrixPacking = nullptr;
+
+    // For interface blocks, use the block name instead.  When the layout qualifier is being
+    // replaced in the backend, that would be the name that's available.
+    if (type.isInterfaceBlock())
+    {
+        const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
+        name                                  = interfaceBlock->name();
+
+        // Make sure block storage format is specified.
+        if (interfaceBlock->blockStorage() != EbsUnspecified)
+        {
+            blockStorage = getBlockStorageString(interfaceBlock->blockStorage());
+        }
+    }
+
+    // Specify matrix packing if necessary.
+    if (layoutQualifier.matrixPacking != EmpUnspecified)
+    {
+        matrixPacking = getMatrixPackingString(layoutQualifier.matrixPacking);
+    }
+
     if (needsCustomLayout)
     {
-        out << "@@ LAYOUT-" << symbol->getName() << "() @@";
+        out << "@@ LAYOUT-" << name << "(";
     }
     else
     {
         out << "layout(";
     }
 
-    if (IsImage(type.getBasicType()) && layoutQualifier.imageInternalFormat != EiifUnspecified)
+    // Output the list of qualifiers already known at this stage, i.e. everything other than
+    // `location` and `set`/`binding`.
+    std::string otherQualifiers = getCommonLayoutQualifiers(variable);
+
+    const char *separator = "";
+    if (blockStorage)
     {
-        ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
-        out << getImageInternalFormatString(layoutQualifier.imageInternalFormat);
+        out << separator << blockStorage;
+        separator = ", ";
+    }
+    if (matrixPacking)
+    {
+        out << separator << matrixPacking;
+        separator = ", ";
+    }
+    if (!otherQualifiers.empty())
+    {
+        out << separator << otherQualifiers;
     }
 
-    if (!needsCustomLayout)
+    out << ") ";
+    if (needsCustomLayout)
     {
-        out << ") ";
+        out << "@@";
     }
 }
 
-void TOutputVulkanGLSL::writeQualifier(TQualifier qualifier, const TSymbol *symbol)
+void TOutputVulkanGLSL::writeQualifier(TQualifier qualifier,
+                                       const TType &type,
+                                       const TSymbol *symbol)
 {
     if (qualifier != EvqUniform && qualifier != EvqAttribute && !sh::IsVarying(qualifier))
     {
-        TOutputGLSLBase::writeQualifier(qualifier, symbol);
+        TOutputGLSLBase::writeQualifier(qualifier, type, symbol);
         return;
     }
 
@@ -95,8 +136,17 @@
         return;
     }
 
+    ImmutableString name = symbol->name();
+
+    // For interface blocks, use the block name instead.  When the qualifier is being replaced in
+    // the backend, that would be the name that's available.
+    if (type.isInterfaceBlock())
+    {
+        name = type.getInterfaceBlock()->name();
+    }
+
     TInfoSinkBase &out = objSink();
-    out << "@@ QUALIFIER-" << symbol->name().data() << " @@ ";
+    out << "@@ QUALIFIER-" << name.data() << " @@ ";
 }
 
 void TOutputVulkanGLSL::writeVariableType(const TType &type, const TSymbol *symbol)
diff --git a/src/compiler/translator/OutputVulkanGLSL.h b/src/compiler/translator/OutputVulkanGLSL.h
index ef49c97..717801d 100644
--- a/src/compiler/translator/OutputVulkanGLSL.h
+++ b/src/compiler/translator/OutputVulkanGLSL.h
@@ -31,7 +31,7 @@
 
   protected:
     void writeLayoutQualifier(TIntermTyped *variable) override;
-    void writeQualifier(TQualifier qualifier, const TSymbol *symbol) override;
+    void writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol) override;
     void writeVariableType(const TType &type, const TSymbol *symbol) override;
 };
 
diff --git a/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.cpp b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.cpp
new file mode 100644
index 0000000..a32281f
--- /dev/null
+++ b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.cpp
@@ -0,0 +1,107 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RedefineInterfaceBlockLayoutQualifiersWithStd: Ensure layout qualifiers are either std140 or
+// std430 to comply with Vulkan GLSL.
+//
+
+#include "compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h"
+
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+namespace
+{
+// Helper to replace the type of a symbol
+TIntermSymbol *RedefineLayoutQualifierOfSymbolNode(TIntermSymbol *symbolNode,
+                                                   const TLayoutQualifier &newLayoutQualifier,
+                                                   TSymbolTable *symbolTable)
+{
+    const TVariable &oldVariable = symbolNode->variable();
+
+    ASSERT(symbolNode->getType().isInterfaceBlock());
+
+    const TType &oldType                     = symbolNode->getType();
+    const TInterfaceBlock *oldInterfaceBlock = oldType.getInterfaceBlock();
+
+    // Create a new type based on the old type, but the memory layout qualifier changed.
+    TType *newType = new TType(oldType);
+    newType->setLayoutQualifier(newLayoutQualifier);
+
+    // Create a new interface block based on the old one, with the new memory layout qualifier as
+    // well.
+    TInterfaceBlock *newInterfaceBlock =
+        new TInterfaceBlock(symbolTable, oldInterfaceBlock->name(), &oldInterfaceBlock->fields(),
+                            newLayoutQualifier, oldInterfaceBlock->symbolType());
+
+    newType->setInterfaceBlock(newInterfaceBlock);
+
+    // Create a new variable with the modified type, to substitute the old variable.
+    TVariable *newVariable =
+        new TVariable(oldVariable.uniqueId(), oldVariable.name(), oldVariable.symbolType(),
+                      oldVariable.extension(), newType);
+
+    return new TIntermSymbol(newVariable);
+}
+
+class Traverser : public TIntermTraverser
+{
+  public:
+    explicit Traverser(TSymbolTable *symbolTable)
+        : TIntermTraverser(true, false, false, symbolTable)
+    {
+        symbolTable->push();
+    }
+
+    ~Traverser() override { mSymbolTable->pop(); }
+
+    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+    {
+        ASSERT(visit == PreVisit);
+
+        if (!mInGlobalScope)
+        {
+            return false;
+        }
+
+        const TIntermSequence &sequence = *(node->getSequence());
+        TIntermTyped *declarator        = sequence.front()->getAsTyped();
+        const TType &type               = declarator->getType();
+
+        if (type.isInterfaceBlock())
+        {
+            ASSERT(declarator->getAsSymbolNode());
+
+            TLayoutQualifier layoutQualifier = type.getLayoutQualifier();
+
+            // If the layout qualifier is not explicitly std140 or std430, change it to std140 for
+            // uniforms and std430 otherwise.  See the comment in the header for more information.
+            if (layoutQualifier.blockStorage != EbsStd140 &&
+                layoutQualifier.blockStorage != EbsStd430)
+            {
+                layoutQualifier.blockStorage =
+                    type.getQualifier() == EvqUniform ? EbsStd140 : EbsStd430;
+
+                TIntermSymbol *replacement = RedefineLayoutQualifierOfSymbolNode(
+                    declarator->getAsSymbolNode(), layoutQualifier, mSymbolTable);
+
+                queueReplacementWithParent(node, declarator, replacement, OriginalNode::IS_DROPPED);
+            }
+        }
+
+        return false;
+    }
+};
+}  // anonymous namespace
+
+void RedefineInterfaceBlockLayoutQualifiersWithStd(TIntermBlock *root, TSymbolTable *symbolTable)
+{
+    Traverser redefineInterfaceBlockLayoutQualifiersWithStd(symbolTable);
+    root->traverse(&redefineInterfaceBlockLayoutQualifiersWithStd);
+    redefineInterfaceBlockLayoutQualifiersWithStd.updateTree();
+}
+}  // namespace sh
diff --git a/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h
new file mode 100644
index 0000000..87b0c39
--- /dev/null
+++ b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h
@@ -0,0 +1,25 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RedefineInterfaceBlockLayoutQualifiersWithStd: Change the memory layout qualifier of interface
+// blocks if not specifically requested to be std140 or std430, i.e. the memory layout qualifier is
+// changed if it's unspecified, shared or packed.  This makes the layout qualifiers conformant with
+// Vulkan GLSL (GL_KHR_vulkan_glsl).
+//
+// - For uniform buffers, std140 is used.  It would have been more efficient to default to std430,
+//   but that would require GL_EXT_scalar_block_layout.
+// - For storage buffers, std430 is used.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REDEFINEINTERFACEBLOCKLAYOUTQUALIFIERSWITHSTD_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REDEFINEINTERFACEBLOCKLAYOUTQUALIFIERSWITHSTD_H_
+
+namespace sh
+{
+class TIntermBlock;
+class TSymbolTable;
+void RedefineInterfaceBlockLayoutQualifiersWithStd(TIntermBlock *root, TSymbolTable *symbolTable);
+}  // namespace sh
+
+#endif  // COMPILER_TRANSLATOR_TREEOPS_REDEFINEINTERFACEBLOCKLAYOUTQUALIFIERSWITHSTD_H_
diff --git a/src/libANGLE/ProgramLinkedResources.cpp b/src/libANGLE/ProgramLinkedResources.cpp
index 787687e..eaea974 100644
--- a/src/libANGLE/ProgramLinkedResources.cpp
+++ b/src/libANGLE/ProgramLinkedResources.cpp
@@ -535,6 +535,116 @@
     ShaderUniformCount mUniformCount;
     unsigned int mStructStackSize = 0;
 };
+
+class InterfaceBlockInfo final : angle::NonCopyable
+{
+  public:
+    InterfaceBlockInfo(CustomBlockLayoutEncoderFactory *customEncoderFactory)
+        : mCustomEncoderFactory(customEncoderFactory)
+    {}
+
+    void getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks);
+
+    bool getBlockSize(const std::string &name, const std::string &mappedName, size_t *sizeOut);
+    bool getBlockMemberInfo(const std::string &name,
+                            const std::string &mappedName,
+                            sh::BlockMemberInfo *infoOut);
+
+  private:
+    size_t getBlockInfo(const sh::InterfaceBlock &interfaceBlock);
+
+    std::map<std::string, size_t> mBlockSizes;
+    sh::BlockLayoutMap mBlockLayout;
+    // Based on the interface block layout, the std140 or std430 encoders are used.  On some
+    // platforms (currently only D3D), there could be another non-standard encoder used.
+    CustomBlockLayoutEncoderFactory *mCustomEncoderFactory;
+};
+
+void InterfaceBlockInfo::getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks)
+{
+    for (const sh::InterfaceBlock &interfaceBlock : interfaceBlocks)
+    {
+        if (!interfaceBlock.active && interfaceBlock.layout == sh::BLOCKLAYOUT_PACKED)
+            continue;
+
+        if (mBlockSizes.count(interfaceBlock.name) > 0)
+            continue;
+
+        size_t dataSize                  = getBlockInfo(interfaceBlock);
+        mBlockSizes[interfaceBlock.name] = dataSize;
+    }
+}
+
+size_t InterfaceBlockInfo::getBlockInfo(const sh::InterfaceBlock &interfaceBlock)
+{
+    ASSERT(interfaceBlock.active || interfaceBlock.layout != sh::BLOCKLAYOUT_PACKED);
+
+    // define member uniforms
+    sh::Std140BlockEncoder std140Encoder;
+    sh::Std430BlockEncoder std430Encoder;
+    sh::BlockLayoutEncoder *customEncoder = nullptr;
+    sh::BlockLayoutEncoder *encoder       = nullptr;
+
+    if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD140)
+    {
+        encoder = &std140Encoder;
+    }
+    else if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD430)
+    {
+        encoder = &std430Encoder;
+    }
+    else if (mCustomEncoderFactory)
+    {
+        encoder = customEncoder = mCustomEncoderFactory->makeEncoder();
+    }
+    else
+    {
+        UNREACHABLE();
+        return 0;
+    }
+
+    sh::GetInterfaceBlockInfo(interfaceBlock.fields, interfaceBlock.fieldPrefix(), encoder,
+                              &mBlockLayout);
+
+    size_t offset = encoder->getCurrentOffset();
+
+    SafeDelete(customEncoder);
+
+    return offset;
+}
+
+bool InterfaceBlockInfo::getBlockSize(const std::string &name,
+                                      const std::string &mappedName,
+                                      size_t *sizeOut)
+{
+    size_t nameLengthWithoutArrayIndex;
+    gl::ParseArrayIndex(name, &nameLengthWithoutArrayIndex);
+    std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex);
+    auto sizeIter        = mBlockSizes.find(baseName);
+    if (sizeIter == mBlockSizes.end())
+    {
+        *sizeOut = 0;
+        return false;
+    }
+
+    *sizeOut = sizeIter->second;
+    return true;
+}
+
+bool InterfaceBlockInfo::getBlockMemberInfo(const std::string &name,
+                                            const std::string &mappedName,
+                                            sh::BlockMemberInfo *infoOut)
+{
+    auto infoIter = mBlockLayout.find(name);
+    if (infoIter == mBlockLayout.end())
+    {
+        *infoOut = sh::kDefaultBlockMemberInfo;
+        return false;
+    }
+
+    *infoOut = infoIter->second;
+    return true;
+}
 }  // anonymous namespace
 
 UniformLinker::UniformLinker(const ProgramState &state) : mState(state) {}
@@ -1121,4 +1231,87 @@
 
 ProgramLinkedResources::~ProgramLinkedResources() = default;
 
+void ProgramLinkedResourcesLinker::linkResources(const gl::ProgramState &programState,
+                                                 const gl::ProgramLinkedResources &resources) const
+{
+    // Gather uniform interface block info.
+    InterfaceBlockInfo uniformBlockInfo(mCustomEncoderFactory);
+    for (gl::ShaderType shaderType : gl::AllShaderTypes())
+    {
+        gl::Shader *shader = programState.getAttachedShader(shaderType);
+        if (shader)
+        {
+            uniformBlockInfo.getShaderBlockInfo(shader->getUniformBlocks());
+        }
+    }
+
+    auto getUniformBlockSize = [&uniformBlockInfo](const std::string &name,
+                                                   const std::string &mappedName, size_t *sizeOut) {
+        return uniformBlockInfo.getBlockSize(name, mappedName, sizeOut);
+    };
+
+    auto getUniformBlockMemberInfo = [&uniformBlockInfo](const std::string &name,
+                                                         const std::string &mappedName,
+                                                         sh::BlockMemberInfo *infoOut) {
+        return uniformBlockInfo.getBlockMemberInfo(name, mappedName, infoOut);
+    };
+
+    // Link uniform interface blocks.
+    resources.uniformBlockLinker.linkBlocks(getUniformBlockSize, getUniformBlockMemberInfo);
+
+    // Gather storage bufer interface block info.
+    InterfaceBlockInfo shaderStorageBlockInfo(mCustomEncoderFactory);
+    for (gl::ShaderType shaderType : gl::AllShaderTypes())
+    {
+        gl::Shader *shader = programState.getAttachedShader(shaderType);
+        if (shader)
+        {
+            shaderStorageBlockInfo.getShaderBlockInfo(shader->getShaderStorageBlocks());
+        }
+    }
+    auto getShaderStorageBlockSize = [&shaderStorageBlockInfo](const std::string &name,
+                                                               const std::string &mappedName,
+                                                               size_t *sizeOut) {
+        return shaderStorageBlockInfo.getBlockSize(name, mappedName, sizeOut);
+    };
+
+    auto getShaderStorageBlockMemberInfo = [&shaderStorageBlockInfo](const std::string &name,
+                                                                     const std::string &mappedName,
+                                                                     sh::BlockMemberInfo *infoOut) {
+        return shaderStorageBlockInfo.getBlockMemberInfo(name, mappedName, infoOut);
+    };
+
+    // Link storage buffer interface blocks.
+    resources.shaderStorageBlockLinker.linkBlocks(getShaderStorageBlockSize,
+                                                  getShaderStorageBlockMemberInfo);
+
+    // Gather and link atomic counter buffer interface blocks.
+    std::map<int, unsigned int> sizeMap;
+    getAtomicCounterBufferSizeMap(programState, sizeMap);
+    resources.atomicCounterBufferLinker.link(sizeMap);
+}
+
+void ProgramLinkedResourcesLinker::getAtomicCounterBufferSizeMap(
+    const gl::ProgramState &programState,
+    std::map<int, unsigned int> &sizeMapOut) const
+{
+    for (unsigned int index : programState.getAtomicCounterUniformRange())
+    {
+        const gl::LinkedUniform &glUniform = programState.getUniforms()[index];
+
+        auto &bufferDataSize = sizeMapOut[glUniform.binding];
+
+        // Calculate the size of the buffer by finding the end of the last uniform with the same
+        // binding. The end of the uniform is calculated by finding the initial offset of the
+        // uniform and adding size of the uniform. For arrays, the size is the number of elements
+        // times the element size (should always by 4 for atomic_units).
+        unsigned dataOffset =
+            glUniform.offset + (glUniform.getBasicTypeElementCount() * glUniform.getElementSize());
+        if (dataOffset > bufferDataSize)
+        {
+            bufferDataSize = dataOffset;
+        }
+    }
+}
+
 }  // namespace gl
diff --git a/src/libANGLE/ProgramLinkedResources.h b/src/libANGLE/ProgramLinkedResources.h
index a4588f9..e91102b 100644
--- a/src/libANGLE/ProgramLinkedResources.h
+++ b/src/libANGLE/ProgramLinkedResources.h
@@ -20,6 +20,7 @@
 
 namespace sh
 {
+class BlockLayoutEncoder;
 struct BlockMemberInfo;
 struct InterfaceBlock;
 struct ShaderVariable;
@@ -209,6 +210,33 @@
     std::vector<UnusedUniform> unusedUniforms;
 };
 
+class CustomBlockLayoutEncoderFactory : angle::NonCopyable
+{
+  public:
+    virtual ~CustomBlockLayoutEncoderFactory() {}
+
+    virtual sh::BlockLayoutEncoder *makeEncoder() = 0;
+};
+
+// Used by the backends in Program*::linkResources to parse interface blocks and provide
+// information to ProgramLinkedResources' linkers.
+class ProgramLinkedResourcesLinker final : angle::NonCopyable
+{
+  public:
+    ProgramLinkedResourcesLinker(CustomBlockLayoutEncoderFactory *customEncoderFactory)
+        : mCustomEncoderFactory(customEncoderFactory)
+    {}
+
+    void linkResources(const gl::ProgramState &programState,
+                       const gl::ProgramLinkedResources &resources) const;
+
+  private:
+    void getAtomicCounterBufferSizeMap(const gl::ProgramState &programState,
+                                       std::map<int, unsigned int> &sizeMapOut) const;
+
+    CustomBlockLayoutEncoderFactory *mCustomEncoderFactory;
+};
+
 }  // namespace gl
 
 #endif  // LIBANGLE_UNIFORMLINKER_H_
diff --git a/src/libANGLE/angletypes.h b/src/libANGLE/angletypes.h
index e7b5bf0..a5f94a5 100644
--- a/src/libANGLE/angletypes.h
+++ b/src/libANGLE/angletypes.h
@@ -461,6 +461,9 @@
 using ActiveTexturePointerArray = ActiveTextureArray<Texture *>;
 using ActiveTextureTypeArray    = ActiveTextureArray<TextureType>;
 
+template <typename T>
+using UniformBuffersArray = std::array<T, IMPLEMENTATION_MAX_UNIFORM_BUFFER_BINDINGS>;
+
 using ImageUnitMask = angle::BitSet<IMPLEMENTATION_MAX_IMAGE_UNITS>;
 
 // OffsetBindingPointer.getSize() returns the size specified by the user, which may be larger than
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index 20139d1..7508723 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -201,102 +201,6 @@
     return false;
 }
 
-class InterfaceBlockInfo final : angle::NonCopyable
-{
-  public:
-    InterfaceBlockInfo() {}
-
-    void getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks);
-
-    bool getBlockSize(const std::string &name, const std::string &mappedName, size_t *sizeOut);
-    bool getBlockMemberInfo(const std::string &name,
-                            const std::string &mappedName,
-                            sh::BlockMemberInfo *infoOut);
-
-  private:
-    size_t getBlockInfo(const sh::InterfaceBlock &interfaceBlock);
-
-    std::map<std::string, size_t> mBlockSizes;
-    sh::BlockLayoutMap mBlockLayout;
-};
-
-void InterfaceBlockInfo::getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks)
-{
-    for (const sh::InterfaceBlock &interfaceBlock : interfaceBlocks)
-    {
-        if (!interfaceBlock.active && interfaceBlock.layout == sh::BLOCKLAYOUT_PACKED)
-            continue;
-
-        if (mBlockSizes.count(interfaceBlock.name) > 0)
-            continue;
-
-        size_t dataSize                  = getBlockInfo(interfaceBlock);
-        mBlockSizes[interfaceBlock.name] = dataSize;
-    }
-}
-
-size_t InterfaceBlockInfo::getBlockInfo(const sh::InterfaceBlock &interfaceBlock)
-{
-    ASSERT(interfaceBlock.active || interfaceBlock.layout != sh::BLOCKLAYOUT_PACKED);
-
-    // define member uniforms
-    sh::Std140BlockEncoder std140Encoder;
-    sh::Std430BlockEncoder std430Encoder;
-    sh::HLSLBlockEncoder hlslEncoder(sh::HLSLBlockEncoder::ENCODE_PACKED, false);
-    sh::BlockLayoutEncoder *encoder = nullptr;
-
-    if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD140)
-    {
-        encoder = &std140Encoder;
-    }
-    else if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD430)
-    {
-        encoder = &std430Encoder;
-    }
-    else
-    {
-        encoder = &hlslEncoder;
-    }
-
-    sh::GetInterfaceBlockInfo(interfaceBlock.fields, interfaceBlock.fieldPrefix(), encoder,
-                              &mBlockLayout);
-
-    return encoder->getCurrentOffset();
-}
-
-bool InterfaceBlockInfo::getBlockSize(const std::string &name,
-                                      const std::string &mappedName,
-                                      size_t *sizeOut)
-{
-    size_t nameLengthWithoutArrayIndex;
-    gl::ParseArrayIndex(name, &nameLengthWithoutArrayIndex);
-    std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex);
-    auto sizeIter        = mBlockSizes.find(baseName);
-    if (sizeIter == mBlockSizes.end())
-    {
-        *sizeOut = 0;
-        return false;
-    }
-
-    *sizeOut = sizeIter->second;
-    return true;
-}
-
-bool InterfaceBlockInfo::getBlockMemberInfo(const std::string &name,
-                                            const std::string &mappedName,
-                                            sh::BlockMemberInfo *infoOut)
-{
-    auto infoIter = mBlockLayout.find(name);
-    if (infoIter == mBlockLayout.end())
-    {
-        *infoOut = sh::kDefaultBlockMemberInfo;
-        return false;
-    }
-
-    *infoOut = infoIter->second;
-    return true;
-}
-
 // Helper class that gathers uniform info from the default uniform block.
 class UniformEncodingVisitorD3D : public sh::BlockEncoderVisitor
 {
@@ -356,6 +260,15 @@
     HLSLRegisterType mRegisterType;
     D3DUniformMap *mUniformMapOut;
 };
+
+class HLSLBlockLayoutEncoderFactory : public gl::CustomBlockLayoutEncoderFactory
+{
+  public:
+    sh::BlockLayoutEncoder *makeEncoder() override
+    {
+        return new sh::HLSLBlockEncoder(sh::HLSLBlockEncoder::ENCODE_PACKED, false);
+    }
+};
 }  // anonymous namespace
 
 // D3DUniform Implementation
@@ -3288,80 +3201,13 @@
 
 void ProgramD3D::linkResources(const gl::ProgramLinkedResources &resources)
 {
-    InterfaceBlockInfo uniformBlockInfo;
-    for (gl::ShaderType shaderType : gl::AllShaderTypes())
-    {
-        gl::Shader *shader = mState.getAttachedShader(shaderType);
-        if (shader)
-        {
-            uniformBlockInfo.getShaderBlockInfo(shader->getUniformBlocks());
-        }
-    }
+    HLSLBlockLayoutEncoderFactory hlslEncoderFactory;
+    gl::ProgramLinkedResourcesLinker linker(&hlslEncoderFactory);
 
-    // Gather interface block info.
-    auto getUniformBlockSize = [&uniformBlockInfo](const std::string &name,
-                                                   const std::string &mappedName, size_t *sizeOut) {
-        return uniformBlockInfo.getBlockSize(name, mappedName, sizeOut);
-    };
+    linker.linkResources(mState, resources);
 
-    auto getUniformBlockMemberInfo = [&uniformBlockInfo](const std::string &name,
-                                                         const std::string &mappedName,
-                                                         sh::BlockMemberInfo *infoOut) {
-        return uniformBlockInfo.getBlockMemberInfo(name, mappedName, infoOut);
-    };
-
-    resources.uniformBlockLinker.linkBlocks(getUniformBlockSize, getUniformBlockMemberInfo);
     initializeUniformBlocks();
-
-    InterfaceBlockInfo shaderStorageBlockInfo;
-    for (gl::ShaderType shaderType : gl::AllShaderTypes())
-    {
-        gl::Shader *shader = mState.getAttachedShader(shaderType);
-        if (shader)
-        {
-            shaderStorageBlockInfo.getShaderBlockInfo(shader->getShaderStorageBlocks());
-        }
-    }
-    auto getShaderStorageBlockSize = [&shaderStorageBlockInfo](const std::string &name,
-                                                               const std::string &mappedName,
-                                                               size_t *sizeOut) {
-        return shaderStorageBlockInfo.getBlockSize(name, mappedName, sizeOut);
-    };
-
-    auto getShaderStorageBlockMemberInfo = [&shaderStorageBlockInfo](const std::string &name,
-                                                                     const std::string &mappedName,
-                                                                     sh::BlockMemberInfo *infoOut) {
-        return shaderStorageBlockInfo.getBlockMemberInfo(name, mappedName, infoOut);
-    };
-
-    resources.shaderStorageBlockLinker.linkBlocks(getShaderStorageBlockSize,
-                                                  getShaderStorageBlockMemberInfo);
     initializeShaderStorageBlocks();
-
-    std::map<int, unsigned int> sizeMap;
-    getAtomicCounterBufferSizeMap(sizeMap);
-    resources.atomicCounterBufferLinker.link(sizeMap);
-}
-
-void ProgramD3D::getAtomicCounterBufferSizeMap(std::map<int, unsigned int> &sizeMapOut) const
-{
-    for (unsigned int index : mState.getAtomicCounterUniformRange())
-    {
-        const gl::LinkedUniform &glUniform = mState.getUniforms()[index];
-
-        auto &bufferDataSize = sizeMapOut[glUniform.binding];
-
-        // Calculate the size of the buffer by finding the end of the last uniform with the same
-        // binding. The end of the uniform is calculated by finding the initial offset of the
-        // uniform and adding size of the uniform. For arrays, the size is the number of elements
-        // times the element size (should always by 4 for atomic_units).
-        unsigned dataOffset =
-            glUniform.offset + (glUniform.getBasicTypeElementCount() * glUniform.getElementSize());
-        if (dataOffset > bufferDataSize)
-        {
-            bufferDataSize = dataOffset;
-        }
-    }
 }
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h
index e06e7b5..e8b54f9 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -455,8 +455,6 @@
                              std::vector<Image> &outImages,
                              gl::RangeUI *outUsedRange);
 
-    void getAtomicCounterBufferSizeMap(std::map<int, unsigned int> &sizeMapOut) const;
-
     template <typename DestT>
     void getUniformInternal(GLint location, DestT *dataOut) const;
 
diff --git a/src/libANGLE/renderer/vulkan/BufferVk.cpp b/src/libANGLE/renderer/vulkan/BufferVk.cpp
index bd0385e..8b9aa96 100644
--- a/src/libANGLE/renderer/vulkan/BufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/BufferVk.cpp
@@ -28,7 +28,7 @@
 constexpr size_t kBufferSizeGranularity = 4;
 }  // namespace
 
-BufferVk::BufferVk(const gl::BufferState &state) : BufferImpl(state) {}
+BufferVk::BufferVk(const gl::BufferState &state) : BufferImpl(state), mDataWriteAccessFlags(0) {}
 
 BufferVk::~BufferVk() {}
 
@@ -63,7 +63,8 @@
         const VkImageUsageFlags usageFlags =
             VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
             VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
-            VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
+            VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
+            VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
 
         VkBufferCreateInfo createInfo    = {};
         createInfo.sType                 = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@@ -154,6 +155,27 @@
     ASSERT(mBuffer.valid());
 
     mBuffer.getDeviceMemory().unmap(contextVk->getDevice());
+    mDataWriteAccessFlags = VK_ACCESS_HOST_WRITE_BIT;
+
+    return angle::Result::Continue;
+}
+
+angle::Result BufferVk::onRead(ContextVk *contextVk,
+                               vk::CommandGraphResource *reader,
+                               VkAccessFlagBits readAccessType)
+{
+    // Now that the buffer helper is being used (and will be part of the command graph), make sure
+    // its data write barrier is executed.
+    if (mDataWriteAccessFlags != 0)
+    {
+        vk::CommandBuffer *commandBuffer;
+        ANGLE_TRY(mBuffer.recordCommands(contextVk, &commandBuffer));
+
+        mBuffer.onWrite(mDataWriteAccessFlags);
+        mDataWriteAccessFlags = 0;
+    }
+
+    mBuffer.onRead(reader, readAccessType);
 
     return angle::Result::Continue;
 }
@@ -221,7 +243,8 @@
 
         // Enqueue a copy command on the GPU.
         VkBufferCopy copyRegion = {0, offset, size};
-        ANGLE_TRY(mBuffer.copyFromBuffer(contextVk, stagingBuffer.getBuffer(), copyRegion));
+        ANGLE_TRY(mBuffer.copyFromBuffer(contextVk, stagingBuffer.getBuffer(),
+                                         VK_ACCESS_HOST_WRITE_BIT, copyRegion));
 
         // Immediately release staging buffer. We should probably be using a DynamicBuffer here.
         renderer->releaseObject(renderer->getCurrentQueueSerial(), &stagingBuffer);
@@ -236,6 +259,7 @@
         memcpy(mapPointer, data, size);
 
         mBuffer.getDeviceMemory().unmap(device);
+        mDataWriteAccessFlags = VK_ACCESS_HOST_WRITE_BIT;
     }
 
     return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/vulkan/BufferVk.h b/src/libANGLE/renderer/vulkan/BufferVk.h
index 0484698..6a258eb 100644
--- a/src/libANGLE/renderer/vulkan/BufferVk.h
+++ b/src/libANGLE/renderer/vulkan/BufferVk.h
@@ -73,6 +73,10 @@
     angle::Result mapImpl(ContextVk *contextVk, void **mapPtr);
     angle::Result unmapImpl(ContextVk *contextVk);
 
+    angle::Result onRead(ContextVk *contextVk,
+                         vk::CommandGraphResource *reader,
+                         VkAccessFlagBits readAccessType);
+
     // Calls copyBuffer internally.
     angle::Result copyToBuffer(ContextVk *contextVk,
                                vk::BufferHelper *destBuffer,
@@ -87,6 +91,7 @@
     void release(RendererVk *renderer);
 
     vk::BufferHelper mBuffer;
+    VkAccessFlags mDataWriteAccessFlags;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 52d8456..3e9503f 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -100,6 +100,7 @@
     mNewCommandBufferDirtyBits.set(DIRTY_BIT_TEXTURES);
     mNewCommandBufferDirtyBits.set(DIRTY_BIT_VERTEX_BUFFERS);
     mNewCommandBufferDirtyBits.set(DIRTY_BIT_INDEX_BUFFER);
+    mNewCommandBufferDirtyBits.set(DIRTY_BIT_UNIFORM_BUFFERS);
     mNewCommandBufferDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
 
     mDirtyBitHandlers[DIRTY_BIT_DEFAULT_ATTRIBS] = &ContextVk::handleDirtyDefaultAttribs;
@@ -108,6 +109,7 @@
     mDirtyBitHandlers[DIRTY_BIT_VERTEX_BUFFERS]  = &ContextVk::handleDirtyVertexBuffers;
     mDirtyBitHandlers[DIRTY_BIT_INDEX_BUFFER]    = &ContextVk::handleDirtyIndexBuffer;
     mDirtyBitHandlers[DIRTY_BIT_DRIVER_UNIFORMS] = &ContextVk::handleDirtyDriverUniforms;
+    mDirtyBitHandlers[DIRTY_BIT_UNIFORM_BUFFERS] = &ContextVk::handleDirtyUniformBuffers;
     mDirtyBitHandlers[DIRTY_BIT_DESCRIPTOR_SETS] = &ContextVk::handleDirtyDescriptorSets;
 
     mDirtyBits = mNewCommandBufferDirtyBits;
@@ -158,10 +160,14 @@
     // Note that this may reserve more sets than strictly necessary for a particular layout.
     VkDescriptorPoolSize uniformSetSize = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
                                            GetUniformBufferDescriptorCount()};
+    VkDescriptorPoolSize uniformBlockSetSize = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+                                                mRenderer->getMaxUniformBlocks()};
     VkDescriptorPoolSize textureSetSize = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                            mRenderer->getMaxActiveTextures()};
     VkDescriptorPoolSize driverSetSize  = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1};
     ANGLE_TRY(mDynamicDescriptorPools[kUniformsDescriptorSetIndex].init(this, &uniformSetSize, 1));
+    ANGLE_TRY(mDynamicDescriptorPools[kUniformBlockDescriptorSetIndex].init(
+        this, &uniformBlockSetSize, 1));
     ANGLE_TRY(mDynamicDescriptorPools[kTextureDescriptorSetIndex].init(this, &textureSetSize, 1));
     ANGLE_TRY(
         mDynamicDescriptorPools[kDriverUniformsDescriptorSetIndex].init(this, &driverSetSize, 1));
@@ -462,6 +468,17 @@
     return angle::Result::Continue;
 }
 
+angle::Result ContextVk::handleDirtyUniformBuffers(const gl::Context *context,
+                                                   vk::CommandBuffer *commandBuffer)
+{
+    if (mProgram->hasUniformBuffers())
+    {
+        ANGLE_TRY(
+            mProgram->updateUniformBuffersDescriptorSet(this, mDrawFramebuffer->getFramebuffer()));
+    }
+    return angle::Result::Continue;
+}
+
 angle::Result ContextVk::handleDirtyDescriptorSets(const gl::Context *context,
                                                    vk::CommandBuffer *commandBuffer)
 {
@@ -914,6 +931,7 @@
             case gl::State::DIRTY_BIT_PROGRAM_EXECUTABLE:
             {
                 invalidateCurrentTextures();
+                invalidateCurrentUniformBuffers();
                 // No additional work is needed here. We will update the pipeline desc later.
                 invalidateDefaultAttributes(context->getStateCache().getActiveDefaultAttribsMask());
                 bool useVertexBuffer = (mProgram->getState().getMaxActiveAttribLocation());
@@ -934,6 +952,7 @@
             case gl::State::DIRTY_BIT_SHADER_STORAGE_BUFFER_BINDING:
                 break;
             case gl::State::DIRTY_BIT_UNIFORM_BUFFER_BINDINGS:
+                invalidateCurrentUniformBuffers();
                 break;
             case gl::State::DIRTY_BIT_ATOMIC_COUNTER_BUFFER_BINDING:
                 break;
@@ -1137,6 +1156,16 @@
     }
 }
 
+void ContextVk::invalidateCurrentUniformBuffers()
+{
+    ASSERT(mProgram);
+    if (mProgram->hasUniformBuffers())
+    {
+        mDirtyBits.set(DIRTY_BIT_UNIFORM_BUFFERS);
+        mDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS);
+    }
+}
+
 void ContextVk::invalidateDriverUniforms()
 {
     mDirtyBits.set(DIRTY_BIT_DRIVER_UNIFORMS);
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.h b/src/libANGLE/renderer/vulkan/ContextVk.h
index 84c5d5e..a336eda 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.h
+++ b/src/libANGLE/renderer/vulkan/ContextVk.h
@@ -226,6 +226,7 @@
         DIRTY_BIT_VERTEX_BUFFERS,
         DIRTY_BIT_INDEX_BUFFER,
         DIRTY_BIT_DRIVER_UNIFORMS,
+        DIRTY_BIT_UNIFORM_BUFFERS,
         DIRTY_BIT_DESCRIPTOR_SETS,
         DIRTY_BIT_MAX,
     };
@@ -277,6 +278,7 @@
     ANGLE_INLINE void invalidateCurrentPipeline() { mDirtyBits.set(DIRTY_BIT_PIPELINE); }
 
     void invalidateCurrentTextures();
+    void invalidateCurrentUniformBuffers();
     void invalidateDriverUniforms();
 
     angle::Result handleDirtyDefaultAttribs(const gl::Context *context,
@@ -289,6 +291,8 @@
                                          vk::CommandBuffer *commandBuffer);
     angle::Result handleDirtyDriverUniforms(const gl::Context *context,
                                             vk::CommandBuffer *commandBuffer);
+    angle::Result handleDirtyUniformBuffers(const gl::Context *context,
+                                            vk::CommandBuffer *commandBuffer);
     angle::Result handleDirtyDescriptorSets(const gl::Context *context,
                                             vk::CommandBuffer *commandBuffer);
 
diff --git a/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp b/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp
index 8d0e4eb..240671a 100644
--- a/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp
+++ b/src/libANGLE/renderer/vulkan/GlslangWrapper.cpp
@@ -27,6 +27,7 @@
 #include "common/utilities.h"
 #include "libANGLE/Caps.h"
 #include "libANGLE/ProgramLinkedResources.h"
+#include "libANGLE/renderer/vulkan/vk_cache_utils.h"
 
 namespace rx
 {
@@ -227,7 +228,7 @@
     {
         if (block.type == TokenType::Layout && block.text == name)
         {
-            const char *separator = specifier.empty() || block.args.empty() ? "" : ",";
+            const char *separator = specifier.empty() || block.args.empty() ? "" : ", ";
 
             block.type = TokenType::Text;
             block.text = "layout(" + block.args + separator + specifier + ")";
@@ -422,19 +423,52 @@
         fragmentSource.eraseLayoutAndQualifierSpecifiers(varyingName);
     }
 
+    // Assign uniform locations
+
     // Bind the default uniforms for vertex and fragment shaders.
     // See corresponding code in OutputVulkanGLSL.cpp.
+    const std::string uniformsSearchString("@@ DEFAULT-UNIFORMS-SET-BINDING @@");
+
+    const std::string driverUniformsDescriptorSet =
+        "set = " + Str(kDriverUniformsDescriptorSetIndex);
+    const std::string uniformsDescriptorSet      = "set = " + Str(kUniformsDescriptorSetIndex);
+    const std::string uniformBlocksDescriptorSet = "set = " + Str(kUniformBlockDescriptorSetIndex);
+    const std::string texturesDescriptorSet      = "set = " + Str(kTextureDescriptorSetIndex);
+
+    std::string vertexDefaultUniformsBinding =
+        uniformsDescriptorSet + ", binding = " + Str(kVertexUniformsBindingIndex);
+    std::string fragmentDefaultUniformsBinding =
+        uniformsDescriptorSet + ", binding = " + Str(kFragmentUniformsBindingIndex);
+
     constexpr char kDefaultUniformsBlockName[] = "defaultUniforms";
-    vertexSource.insertLayoutSpecifier(kDefaultUniformsBlockName, "set = 0, binding = 0");
-    fragmentSource.insertLayoutSpecifier(kDefaultUniformsBlockName, "set = 0, binding = 1");
+    vertexSource.insertLayoutSpecifier(kDefaultUniformsBlockName, vertexDefaultUniformsBinding);
+    fragmentSource.insertLayoutSpecifier(kDefaultUniformsBlockName, fragmentDefaultUniformsBinding);
+
+    // Assign uniform blocks to a descriptor set and binding.
+    const auto &uniformBlocks    = programState.getUniformBlocks();
+    uint32_t uniformBlockBinding = 0;
+    for (const gl::InterfaceBlock &uniformBlock : uniformBlocks)
+    {
+        const std::string setBindingString =
+            uniformBlocksDescriptorSet + ", binding = " + Str(uniformBlockBinding);
+
+        vertexSource.insertLayoutSpecifier(uniformBlock.name, setBindingString);
+        fragmentSource.insertLayoutSpecifier(uniformBlock.name, setBindingString);
+
+        vertexSource.insertQualifierSpecifier(uniformBlock.name, kUniformQualifier);
+        fragmentSource.insertQualifierSpecifier(uniformBlock.name, kUniformQualifier);
+
+        ++uniformBlockBinding;
+    }
 
     // Assign textures to a descriptor set and binding.
-    int textureCount     = 0;
+    uint32_t textureBinding = 0;
     const auto &uniforms = programState.getUniforms();
     for (unsigned int uniformIndex : programState.getSamplerUniformRange())
     {
         const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
-        std::string setBindingString            = "set = 1, binding = " + Str(textureCount);
+        const std::string setBindingString =
+            texturesDescriptorSet + ", binding = " + Str(textureBinding);
 
         // Samplers in structs are extracted and renamed.
         const std::string samplerName = GetMappedSamplerName(samplerUniform.name);
@@ -453,11 +487,13 @@
         }
         fragmentSource.insertQualifierSpecifier(samplerName, kUniformQualifier);
 
-        textureCount++;
+        textureBinding++;
     }
 
-    // Start the unused sampler bindings at something ridiculously high.
-    constexpr int kBaseUnusedSamplerBinding = 100;
+    // Place the unused uniforms in the driver uniforms descriptor set, which has a fixed number of
+    // bindings.  This avoids any possible index collision between uniform bindings set in the
+    // shader and the ones assigned here to the unused ones.
+    constexpr int kBaseUnusedSamplerBinding = kReservedDriverUniformBindingCount;
     int unusedSamplerBinding                = kBaseUnusedSamplerBinding;
 
     for (const gl::UnusedUniform &unusedUniform : resources.unusedUniforms)
@@ -469,7 +505,8 @@
 
             std::stringstream layoutStringStream;
 
-            layoutStringStream << "set = 0, binding = " << unusedSamplerBinding++;
+            layoutStringStream << driverUniformsDescriptorSet + ", binding = "
+                               << unusedSamplerBinding++;
 
             std::string layoutString = layoutStringStream.str();
 
@@ -487,10 +524,10 @@
     }
 
     // Substitute layout and qualifier strings for the driver uniforms block.
-    constexpr char kDriverBlockLayoutString[] = "set = 2, binding = 0";
-    constexpr char kDriverBlockName[]         = "ANGLEUniforms";
-    vertexSource.insertLayoutSpecifier(kDriverBlockName, kDriverBlockLayoutString);
-    fragmentSource.insertLayoutSpecifier(kDriverBlockName, kDriverBlockLayoutString);
+    const std::string driverBlockLayoutString = driverUniformsDescriptorSet + ", binding = 0";
+    constexpr char kDriverBlockName[]         = "ANGLEUniformBlock";
+    vertexSource.insertLayoutSpecifier(kDriverBlockName, driverBlockLayoutString);
+    fragmentSource.insertLayoutSpecifier(kDriverBlockName, driverBlockLayoutString);
 
     vertexSource.insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
     fragmentSource.insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index f9b6e9b..9440136 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -11,7 +11,9 @@
 
 #include "common/debug.h"
 #include "libANGLE/Context.h"
+#include "libANGLE/ProgramLinkedResources.h"
 #include "libANGLE/renderer/renderer_utils.h"
+#include "libANGLE/renderer/vulkan/BufferVk.h"
 #include "libANGLE/renderer/vulkan/ContextVk.h"
 #include "libANGLE/renderer/vulkan/GlslangWrapper.h"
 #include "libANGLE/renderer/vulkan/RendererVk.h"
@@ -131,6 +133,38 @@
     ANGLE_TRY(dynamicBuffer->flush(contextVk));
     return angle::Result::Continue;
 }
+
+uint32_t GetUniformBlockArraySize(const std::vector<gl::InterfaceBlock> &uniformBlocks,
+                                  uint32_t bufferIndex)
+{
+    const gl::InterfaceBlock &uniformBlock = uniformBlocks[bufferIndex];
+
+    if (!uniformBlock.isArray)
+    {
+        return 1;
+    }
+
+    ASSERT(uniformBlock.arrayElement == 0);
+
+    // Search consecutively until all array indices of this block are visited.
+    uint32_t arraySize;
+    for (arraySize = 1; bufferIndex + arraySize < uniformBlocks.size(); ++arraySize)
+    {
+        const gl::InterfaceBlock &nextBlock = uniformBlocks[bufferIndex + arraySize];
+
+        if (nextBlock.arrayElement != arraySize)
+        {
+            break;
+        }
+
+        // It's unexpected for an array to start at a non-zero array size, so we can always rely on
+        // the sequential `arrayElement`s to belong to the same block.
+        ASSERT(nextBlock.name == uniformBlock.name);
+        ASSERT(nextBlock.isArray);
+    }
+
+    return arraySize;
+}
 }  // anonymous namespace
 
 // ProgramVk::ShaderInfo implementation.
@@ -183,7 +217,6 @@
 
 ProgramVk::ProgramVk(const gl::ProgramState &state) : ProgramImpl(state), mUniformBlocksOffsets{}
 {
-    mUsedDescriptorSetRange.invalidate();
 }
 
 ProgramVk::~ProgramVk() = default;
@@ -213,7 +246,7 @@
     mEmptyUniformBlockStorage.release(renderer);
 
     mDescriptorSets.clear();
-    mUsedDescriptorSetRange.invalidate();
+    mEmptyDescriptorSets.fill(VK_NULL_HANDLE);
 
     for (vk::RefCountedDescriptorPoolBinding &binding : mDescriptorPoolBindings)
     {
@@ -262,16 +295,14 @@
 
     reset(renderer);
 
+    // Link resources before calling GetShaderSource to make sure they are ready for the set/binding
+    // assignment done in that function.
+    linkResources(resources);
+
     GlslangWrapper::GetShaderSource(mState, resources, &mVertexSource, &mFragmentSource);
 
     ANGLE_TRY(initDefaultUniformBlocks(glContext));
 
-    if (!mState.getSamplerUniformRange().empty())
-    {
-        // Ensure the descriptor set range includes the textures at position 1.
-        mUsedDescriptorSetRange.extend(kTextureDescriptorSetIndex);
-    }
-
     // Store a reference to the pipeline and descriptor set layouts. This will create them if they
     // don't already exist in the cache.
     vk::DescriptorSetLayoutDesc uniformsSetDesc;
@@ -283,6 +314,21 @@
     ANGLE_TRY(renderer->getDescriptorSetLayout(
         contextVk, uniformsSetDesc, &mDescriptorSetLayouts[kUniformsDescriptorSetIndex]));
 
+    vk::DescriptorSetLayoutDesc uniformBlocksSetDesc;
+
+    const std::vector<gl::InterfaceBlock> &uniformBlocks = mState.getUniformBlocks();
+    for (uint32_t bufferIndex = 0; bufferIndex < uniformBlocks.size();)
+    {
+        const uint32_t arraySize = GetUniformBlockArraySize(uniformBlocks, bufferIndex);
+
+        uniformBlocksSetDesc.update(bufferIndex, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, arraySize);
+
+        bufferIndex += arraySize;
+    }
+
+    ANGLE_TRY(renderer->getDescriptorSetLayout(
+        contextVk, uniformBlocksSetDesc, &mDescriptorSetLayouts[kUniformBlockDescriptorSetIndex]));
+
     vk::DescriptorSetLayoutDesc texturesSetDesc;
 
     for (uint32_t textureIndex = 0; textureIndex < mState.getSamplerBindings().size();
@@ -306,6 +352,8 @@
 
     vk::PipelineLayoutDesc pipelineLayoutDesc;
     pipelineLayoutDesc.updateDescriptorSetLayout(kUniformsDescriptorSetIndex, uniformsSetDesc);
+    pipelineLayoutDesc.updateDescriptorSetLayout(kUniformBlockDescriptorSetIndex,
+                                                 uniformBlocksSetDesc);
     pipelineLayoutDesc.updateDescriptorSetLayout(kTextureDescriptorSetIndex, texturesSetDesc);
     pipelineLayoutDesc.updateDescriptorSetLayout(kDriverUniformsDescriptorSetIndex,
                                                  driverUniformsSetDesc);
@@ -313,26 +361,16 @@
     ANGLE_TRY(renderer->getPipelineLayout(contextVk, pipelineLayoutDesc, mDescriptorSetLayouts,
                                           &mPipelineLayout));
 
-    if (!mState.getUniforms().empty())
-    {
-        const gl::RangeUI &samplerRange = mState.getSamplerUniformRange();
-
-        if (mState.getUniforms().size() > samplerRange.length())
-        {
-            // Ensure the descriptor set range includes the uniform buffers at position 0.
-            mUsedDescriptorSetRange.extend(kUniformsDescriptorSetIndex);
-        }
-
-        if (!samplerRange.empty())
-        {
-            // Ensure the descriptor set range includes the textures at position 1.
-            mUsedDescriptorSetRange.extend(kTextureDescriptorSetIndex);
-        }
-    }
-
     return angle::Result::Continue;
 }
 
+void ProgramVk::linkResources(const gl::ProgramLinkedResources &resources)
+{
+    gl::ProgramLinkedResourcesLinker linker(nullptr);
+
+    linker.linkResources(mState, resources);
+}
+
 angle::Result ProgramVk::initDefaultUniformBlocks(const gl::Context *glContext)
 {
     ContextVk *contextVk = vk::GetImpl(glContext);
@@ -360,7 +398,7 @@
         if (location.used() && !location.ignored)
         {
             const auto &uniform = uniforms[location.index];
-            if (!uniform.isSampler())
+            if (uniform.isInDefaultBlock() && !uniform.isSampler())
             {
                 std::string uniformName = uniform.name;
                 if (uniform.isArray())
@@ -425,10 +463,8 @@
             uniformBufferInfo.queueFamilyIndexCount = 0;
             uniformBufferInfo.pQueueFamilyIndices   = nullptr;
 
-            // Assume host visible/coherent memory available.
-            VkMemoryPropertyFlags flags =
-                (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
-            ANGLE_TRY(mEmptyUniformBlockStorage.init(contextVk, uniformBufferInfo, flags));
+            constexpr VkMemoryPropertyFlags kMemoryType = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+            ANGLE_TRY(mEmptyUniformBlockStorage.init(contextVk, uniformBufferInfo, kMemoryType));
         }
     }
 
@@ -731,6 +767,8 @@
     ANGLE_TRY(dynamicDescriptorPool->allocateSets(contextVk, descriptorSetLayout.ptr(), 1,
                                                   &mDescriptorPoolBindings[descriptorSetIndex],
                                                   &mDescriptorSets[descriptorSetIndex]));
+    mEmptyDescriptorSets[descriptorSetIndex] = VK_NULL_HANDLE;
+
     return angle::Result::Continue;
 }
 
@@ -790,6 +828,7 @@
     gl::ShaderMap<VkDescriptorBufferInfo> descriptorBufferInfo;
     gl::ShaderMap<VkWriteDescriptorSet> writeDescriptorInfo;
 
+    // Write default uniforms for each shader type.
     for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes())
     {
         DefaultUniformBlock &uniformBlock  = mDefaultUniformBlocks[shaderType];
@@ -798,7 +837,8 @@
 
         if (!uniformBlock.uniformData.empty())
         {
-            bufferInfo.buffer = uniformBlock.storage.getCurrentBuffer()->getBuffer().getHandle();
+            const vk::BufferHelper *bufferHelper = uniformBlock.storage.getCurrentBuffer();
+            bufferInfo.buffer                    = bufferHelper->getBuffer().getHandle();
         }
         else
         {
@@ -810,7 +850,7 @@
 
         writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
         writeInfo.pNext            = nullptr;
-        writeInfo.dstSet           = mDescriptorSets[0];
+        writeInfo.dstSet           = mDescriptorSets[kUniformsDescriptorSetIndex];
         writeInfo.dstBinding       = static_cast<uint32_t>(shaderType);
         writeInfo.dstArrayElement  = 0;
         writeInfo.descriptorCount  = 1;
@@ -822,7 +862,94 @@
 
     VkDevice device = contextVk->getDevice();
 
-    vkUpdateDescriptorSets(device, 2, writeDescriptorInfo.data(), 0, nullptr);
+    constexpr uint32_t kShaderTypeMin    = static_cast<uint32_t>(gl::kGLES2ShaderTypeMin);
+    constexpr uint32_t kShaderTypeMax    = static_cast<uint32_t>(gl::kGLES2ShaderTypeMax);
+    constexpr uint32_t kGLES2ShaderCount = kShaderTypeMax - kShaderTypeMin + 1;
+    vkUpdateDescriptorSets(device, kGLES2ShaderCount, writeDescriptorInfo.data(), 0, nullptr);
+
+    return angle::Result::Continue;
+}
+
+angle::Result ProgramVk::updateUniformBuffersDescriptorSet(ContextVk *contextVk,
+                                                           vk::FramebufferHelper *framebuffer)
+{
+    ASSERT(hasUniformBuffers());
+    ANGLE_TRY(allocateDescriptorSet(contextVk, kUniformBlockDescriptorSetIndex));
+
+    VkDescriptorSet descriptorSet = mDescriptorSets[kUniformBlockDescriptorSetIndex];
+
+    gl::UniformBuffersArray<VkDescriptorBufferInfo> descriptorBufferInfo;
+    gl::UniformBuffersArray<VkWriteDescriptorSet> writeDescriptorInfo;
+    uint32_t writeCount     = 0;
+    uint32_t currentBinding = 0;
+
+    // Write uniform buffers.
+    const gl::State &glState                             = contextVk->getState();
+    const std::vector<gl::InterfaceBlock> &uniformBlocks = mState.getUniformBlocks();
+    for (uint32_t bufferIndex = 0; bufferIndex < uniformBlocks.size(); ++bufferIndex)
+    {
+        if (glState.getIndexedUniformBuffer(uniformBlocks[bufferIndex].binding).get() == nullptr)
+        {
+            continue;
+        }
+
+        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[writeCount];
+        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[writeCount];
+
+        const gl::InterfaceBlock &uniformBlock = uniformBlocks[bufferIndex];
+        const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding =
+            glState.getIndexedUniformBuffer(uniformBlock.binding);
+        gl::Buffer *buffer = bufferBinding.get();
+        ASSERT(buffer != nullptr);
+
+        // Make sure there's no possible under/overflow with binding size.
+        static_assert(sizeof(VkDeviceSize) >= sizeof(bufferBinding.getSize()),
+                      "VkDeviceSize too small");
+        ASSERT(bufferBinding.getSize() >= 0);
+
+        BufferVk *bufferVk             = vk::GetImpl(buffer);
+        GLintptr offset                = bufferBinding.getOffset();
+        VkDeviceSize size              = bufferBinding.getSize();
+        VkDeviceSize blockSize         = uniformBlock.dataSize;
+        vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
+
+        ANGLE_TRY(bufferVk->onRead(contextVk, framebuffer, 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 maxUniformBufferRange.  In that case, we use the
+        // minimum of the backing buffer size (what's left after offset) and the uniform buffer
+        // size as defined by the shader.
+        size = std::min(size > 0 ? size : (bufferHelper.getSize() - offset), blockSize);
+
+        bufferInfo.buffer = bufferHelper.getBuffer().getHandle();
+        bufferInfo.offset = offset;
+        bufferInfo.range  = size;
+
+        if (!uniformBlock.isArray || uniformBlock.arrayElement == 0)
+        {
+            // Array indices of the same buffer binding are placed sequentially in `uniformBlocks`.
+            // Thus, the uniform block binding is updated only when array index 0 is encountered.
+            currentBinding = bufferIndex;
+        }
+
+        writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+        writeInfo.pNext            = nullptr;
+        writeInfo.dstSet           = descriptorSet;
+        writeInfo.dstBinding       = currentBinding;
+        writeInfo.dstArrayElement  = uniformBlock.isArray ? uniformBlock.arrayElement : 0;
+        writeInfo.descriptorCount  = 1;
+        writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+        writeInfo.pImageInfo       = nullptr;
+        writeInfo.pBufferInfo      = &bufferInfo;
+        writeInfo.pTexelBufferView = nullptr;
+        ASSERT(writeInfo.pBufferInfo[0].buffer != VK_NULL_HANDLE);
+
+        ++writeCount;
+    }
+
+    VkDevice device = contextVk->getDevice();
+
+    vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
 
     return angle::Result::Continue;
 }
@@ -833,7 +960,6 @@
     ASSERT(hasTextures());
     ANGLE_TRY(allocateDescriptorSet(contextVk, kTextureDescriptorSetIndex));
 
-    ASSERT(mUsedDescriptorSetRange.contains(1));
     VkDescriptorSet descriptorSet = mDescriptorSets[kTextureDescriptorSetIndex];
 
     gl::ActiveTextureArray<VkDescriptorImageInfo> descriptorImageInfo;
@@ -915,27 +1041,63 @@
 {
     // Can probably use better dirty bits here.
 
-    if (mUsedDescriptorSetRange.empty())
+    if (mDescriptorSets.empty())
         return angle::Result::Continue;
 
-    ASSERT(!mDescriptorSets.empty());
-
-    unsigned int low = mUsedDescriptorSetRange.low();
-
-    // No uniforms descriptor set means no need to specify dynamic buffer offsets.
-    if (mUsedDescriptorSetRange.contains(kUniformsDescriptorSetIndex))
+    // Find the maximum non-null descriptor set.  This is used in conjunction with a driver
+    // workaround to bind empty descriptor sets only for gaps in between 0 and max and avoid
+    // binding unnecessary empty descriptor sets for the sets beyond max.
+    size_t descriptorSetRange = 0;
+    for (size_t descriptorSetIndex = 0; descriptorSetIndex < mDescriptorSets.size();
+         ++descriptorSetIndex)
     {
+        if (mDescriptorSets[descriptorSetIndex] != VK_NULL_HANDLE)
+        {
+            descriptorSetRange = descriptorSetIndex + 1;
+        }
+    }
+
+    for (size_t descriptorSetIndex = 0; descriptorSetIndex < descriptorSetRange;
+         ++descriptorSetIndex)
+    {
+        VkDescriptorSet descSet = mDescriptorSets[descriptorSetIndex];
+        if (descSet == VK_NULL_HANDLE)
+        {
+            if (!contextVk->getRenderer()->getFeatures().bindEmptyForUnusedDescriptorSets)
+            {
+                continue;
+            }
+
+            // Workaround a driver bug where missing (though unused) descriptor sets indices cause
+            // later sets to misbehave.
+            if (mEmptyDescriptorSets[descriptorSetIndex] == VK_NULL_HANDLE)
+            {
+                vk::DynamicDescriptorPool *dynamicDescriptorPool =
+                    contextVk->getDynamicDescriptorPool(descriptorSetIndex);
+                const vk::DescriptorSetLayout &descriptorSetLayout =
+                    mDescriptorSetLayouts[descriptorSetIndex].get();
+
+                ANGLE_TRY(dynamicDescriptorPool->allocateSets(
+                    contextVk, descriptorSetLayout.ptr(), 1,
+                    &mDescriptorPoolBindings[descriptorSetIndex],
+                    &mEmptyDescriptorSets[descriptorSetIndex]));
+            }
+            descSet = mEmptyDescriptorSets[descriptorSetIndex];
+        }
+
         constexpr uint32_t kShaderTypeMin = static_cast<uint32_t>(gl::kGLES2ShaderTypeMin);
         constexpr uint32_t kShaderTypeMax = static_cast<uint32_t>(gl::kGLES2ShaderTypeMax);
-        commandBuffer->bindGraphicsDescriptorSets(
-            mPipelineLayout.get(), low, mUsedDescriptorSetRange.length(), &mDescriptorSets[low],
-            kShaderTypeMax - kShaderTypeMin + 1, mUniformBlocksOffsets.data() + kShaderTypeMin);
-    }
-    else
-    {
-        commandBuffer->bindGraphicsDescriptorSets(mPipelineLayout.get(), low,
-                                                  mUsedDescriptorSetRange.length(),
-                                                  &mDescriptorSets[low], 0, nullptr);
+        constexpr uint32_t kShaderTypeCount = kShaderTypeMax - kShaderTypeMin + 1;
+
+        // Default uniforms are encompassed in a block per shader stage, and they are assigned
+        // through dynamic uniform buffers (requiring dynamic offsets).  No other descriptor
+        // requires a dynamic offset.
+        const uint32_t uniformBlockOffsetCount =
+            descriptorSetIndex == kUniformsDescriptorSetIndex ? kShaderTypeCount : 0;
+
+        commandBuffer->bindGraphicsDescriptorSets(mPipelineLayout.get(), descriptorSetIndex, 1,
+                                                  &descSet, uniformBlockOffsetCount,
+                                                  mUniformBlocksOffsets.data() + kShaderTypeMin);
     }
 
     return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h
index e59feef..93dec39 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.h
@@ -107,6 +107,8 @@
     angle::Result updateUniforms(ContextVk *contextVk);
     angle::Result updateTexturesDescriptorSet(ContextVk *contextVk,
                                               vk::FramebufferHelper *framebuffer);
+    angle::Result updateUniformBuffersDescriptorSet(ContextVk *contextVk,
+                                                    vk::FramebufferHelper *framebuffer);
 
     angle::Result updateDescriptorSets(ContextVk *contextVk, vk::CommandBuffer *commandBuffer);
 
@@ -116,6 +118,7 @@
     const vk::PipelineLayout &getPipelineLayout() const { return mPipelineLayout.get(); }
 
     bool hasTextures() const { return !mState.getSamplerBindings().empty(); }
+    bool hasUniformBuffers() const { return !mState.getUniformBlocks().empty(); }
 
     bool dirtyUniforms() const { return mDefaultUniformBlocksDirty.any(); }
 
@@ -157,6 +160,7 @@
     angle::Result linkImpl(const gl::Context *glContext,
                            const gl::ProgramLinkedResources &resources,
                            gl::InfoLog &infoLog);
+    void linkResources(const gl::ProgramLinkedResources &resources);
 
     ANGLE_INLINE angle::Result initShaders(ContextVk *contextVk,
                                            gl::PrimitiveMode mode,
@@ -215,7 +219,7 @@
 
     // Descriptor sets for uniform blocks and textures for this program.
     std::vector<VkDescriptorSet> mDescriptorSets;
-    gl::RangeUI mUsedDescriptorSetRange;
+    vk::DescriptorSetLayoutArray<VkDescriptorSet> mEmptyDescriptorSets;
 
     // We keep a reference to the pipeline and descriptor set layouts. This ensures they don't get
     // deleted while this program is in use.
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index 502e5bb..12a5427 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -1305,12 +1305,14 @@
         mFeatures.disableFifoPresentMode = true;
     }
 
-    if (vk::CommandBuffer::ExecutesInline())
+    if (IsAndroid() && IsQualcomm(mPhysicalDeviceProperties.vendorID))
     {
-        if (IsAndroid() && IsQualcomm(mPhysicalDeviceProperties.vendorID))
+        if (vk::CommandBuffer::ExecutesInline())
         {
             mFeatures.restartRenderPassAfterLoadOpClear = true;
         }
+
+        mFeatures.bindEmptyForUnusedDescriptorSets = true;
     }
 }
 
@@ -1376,10 +1378,16 @@
     return mNativeLimitations;
 }
 
-uint32_t RendererVk::getMaxActiveTextures()
+uint32_t RendererVk::getMaxUniformBlocks() const
+{
+    return std::min<uint32_t>(mPhysicalDeviceProperties.limits.maxDescriptorSetUniformBuffers,
+                              gl::IMPLEMENTATION_MAX_UNIFORM_BUFFER_BINDINGS);
+}
+
+uint32_t RendererVk::getMaxActiveTextures() const
 {
     // TODO(lucferron): expose this limitation to GL in Context Caps
-    return std::min<uint32_t>(mPhysicalDeviceProperties.limits.maxPerStageDescriptorSamplers,
+    return std::min<uint32_t>(mPhysicalDeviceProperties.limits.maxDescriptorSetSamplers,
                               gl::IMPLEMENTATION_MAX_ACTIVE_TEXTURES);
 }
 
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.h b/src/libANGLE/renderer/vulkan/RendererVk.h
index 2df53c7..75baa8b 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.h
+++ b/src/libANGLE/renderer/vulkan/RendererVk.h
@@ -90,7 +90,8 @@
     const gl::TextureCapsMap &getNativeTextureCaps() const;
     const gl::Extensions &getNativeExtensions() const;
     const gl::Limitations &getNativeLimitations() const;
-    uint32_t getMaxActiveTextures();
+    uint32_t getMaxUniformBlocks() const;
+    uint32_t getMaxActiveTextures() const;
 
     Serial getCurrentQueueSerial() const { return mCurrentQueueSerial; }
     Serial getLastSubmittedQueueSerial() const { return mLastSubmittedQueueSerial; }
diff --git a/src/libANGLE/renderer/vulkan/ShaderVk.cpp b/src/libANGLE/renderer/vulkan/ShaderVk.cpp
index 479571c..1f6ff62 100644
--- a/src/libANGLE/renderer/vulkan/ShaderVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ShaderVk.cpp
@@ -25,7 +25,8 @@
                                                         gl::ShCompilerInstance *compilerInstance,
                                                         ShCompileOptions options)
 {
-    ShCompileOptions compileOptions = SH_INITIALIZE_UNINITIALIZED_LOCALS;
+    ShCompileOptions compileOptions =
+        SH_INITIALIZE_UNINITIALIZED_LOCALS | SH_REDEFINE_INTERFACE_LAYOUT_QUALIFIERS_WITH_STD;
 
     ContextVk *contextVk = vk::GetImpl(context);
 
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.cpp b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
index 18e1754..22f1111 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.cpp
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
@@ -220,6 +220,7 @@
     RendererVk *renderer = context->getRenderer();
 
     vk::DescriptorSetLayoutDesc descriptorSetDesc;
+    bool isCompute = function >= Function::ComputeStartIndex;
 
     uint32_t currentBinding = 0;
     for (size_t i = 0; i < setSizesCount; ++i)
@@ -231,9 +232,8 @@
     ANGLE_TRY(renderer->getDescriptorSetLayout(context, descriptorSetDesc,
                                                &mDescriptorSetLayouts[function][kSetIndex]));
 
-    gl::ShaderType pushConstantsShaderStage = function >= Function::ComputeStartIndex
-                                                  ? gl::ShaderType::Compute
-                                                  : gl::ShaderType::Fragment;
+    gl::ShaderType pushConstantsShaderStage =
+        isCompute ? gl::ShaderType::Compute : gl::ShaderType::Fragment;
 
     // Corresponding pipeline layouts:
     vk::PipelineLayoutDesc pipelineLayoutDesc;
diff --git a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
index 92bc6ec..d6de558 100644
--- a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
@@ -1221,8 +1221,8 @@
 
     PackedDescriptorSetBinding &packedBinding = mPackedDescriptorSetLayout[bindingIndex];
 
-    packedBinding.type  = static_cast<uint16_t>(type);
-    packedBinding.count = static_cast<uint16_t>(count);
+    SetBitField(packedBinding.type, type);
+    SetBitField(packedBinding.count, count);
 }
 
 void DescriptorSetLayoutDesc::unpackBindings(DescriptorSetLayoutBindingVector *bindings) const
@@ -1237,8 +1237,7 @@
         binding.binding                      = bindingIndex;
         binding.descriptorCount              = packedBinding.count;
         binding.descriptorType               = static_cast<VkDescriptorType>(packedBinding.type);
-        binding.stageFlags =
-            VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
+        binding.stageFlags                   = VK_SHADER_STAGE_ALL;
         binding.pImmutableSamplers = nullptr;
 
         bindings->push_back(binding);
diff --git a/src/libANGLE/renderer/vulkan/vk_cache_utils.h b/src/libANGLE/renderer/vulkan/vk_cache_utils.h
index ad04674..ca00568 100644
--- a/src/libANGLE/renderer/vulkan/vk_cache_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_cache_utils.h
@@ -442,14 +442,16 @@
 constexpr size_t kGraphicsPipelineDescSize = sizeof(GraphicsPipelineDesc);
 static_assert(kGraphicsPipelineDescSize == kGraphicsPipelineDescSumOfSizes, "Size mismatch");
 
-constexpr uint32_t kMaxDescriptorSetLayoutBindings = gl::IMPLEMENTATION_MAX_ACTIVE_TEXTURES;
+constexpr uint32_t kMaxDescriptorSetLayoutBindings =
+    std::max(gl::IMPLEMENTATION_MAX_ACTIVE_TEXTURES,
+             gl::IMPLEMENTATION_MAX_UNIFORM_BUFFER_BINDINGS);
 
 using DescriptorSetLayoutBindingVector =
     angle::FixedVector<VkDescriptorSetLayoutBinding, kMaxDescriptorSetLayoutBindings>;
 
 // A packed description of a descriptor set layout. Use similarly to RenderPassDesc and
-// GraphicsPipelineDesc. Currently we only need to differentiate layouts based on sampler usage. In
-// the future we could generalize this.
+// GraphicsPipelineDesc. Currently we only need to differentiate layouts based on sampler and ubo
+// usage. In the future we could generalize this.
 class DescriptorSetLayoutDesc final
 {
   public:
@@ -470,7 +472,6 @@
     {
         uint16_t type;   // Stores a packed VkDescriptorType descriptorType.
         uint16_t count;  // Stores a packed uint32_t descriptorCount.
-        // We currently make all descriptors available in the VS and FS shaders.
     };
 
     static_assert(sizeof(PackedDescriptorSetBinding) == sizeof(uint32_t), "Unexpected size");
@@ -480,9 +481,9 @@
         mPackedDescriptorSetLayout;
 };
 
-// The following are for caching descriptor set layouts. Limited to max two descriptor set layouts
+// The following are for caching descriptor set layouts. Limited to max four descriptor set layouts
 // and one push constant per shader stage. This can be extended in the future.
-constexpr size_t kMaxDescriptorSetLayouts = 3;
+constexpr size_t kMaxDescriptorSetLayouts = 4;
 constexpr size_t kMaxPushConstantRanges   = angle::EnumSize<gl::ShaderType>();
 
 struct PackedPushConstantRange
@@ -808,11 +809,32 @@
 };
 
 // Some descriptor set and pipeline layout constants.
-constexpr uint32_t kVertexUniformsBindingIndex       = 0;
-constexpr uint32_t kFragmentUniformsBindingIndex     = 1;
-constexpr uint32_t kUniformsDescriptorSetIndex       = 0;
-constexpr uint32_t kTextureDescriptorSetIndex        = 1;
-constexpr uint32_t kDriverUniformsDescriptorSetIndex = 2;
+//
+// The set/binding assignment is done as following:
+//
+// - Set 0 contains the ANGLE driver uniforms at binding 0.  Note that driver uniforms are updated
+//   only under rare circumstances, such as viewport or depth range change.  However, there is only
+//   one binding in this set.
+// - Set 1 contains uniform blocks created to encompass default uniforms.  Bindings 0 and 1
+//   correspond to default uniforms in the vertex and fragment shaders respectively.
+// - Set 2 contains all textures.
+// - Set 3 contains all uniform blocks.
+
+// ANGLE driver uniforms set index (binding is always 0):
+constexpr uint32_t kDriverUniformsDescriptorSetIndex = 0;
+// Uniforms set index:
+constexpr uint32_t kUniformsDescriptorSetIndex = 1;
+// Textures set index:
+constexpr uint32_t kTextureDescriptorSetIndex = 2;
+// Uniform blocks set index:
+constexpr uint32_t kUniformBlockDescriptorSetIndex = 3;
+
+// Only 1 driver uniform binding is used.
+constexpr uint32_t kReservedDriverUniformBindingCount = 1;
+// Binding index for default uniforms in the vertex shader:
+constexpr uint32_t kVertexUniformsBindingIndex = 0;
+// Binding index for default uniforms in the fragment shader:
+constexpr uint32_t kFragmentUniformsBindingIndex = 1;
 
 }  // namespace rx
 
diff --git a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
index 2f1e908..6b8537d 100644
--- a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
@@ -178,11 +178,30 @@
     mNativeCaps.maxFragmentUniformVectors                            = maxUniformVectors;
     mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Fragment] = maxUniformComponents;
 
-    // TODO(jmadill): this is an ES 3.0 property and we can skip implementing it for now.
-    // This is maxDescriptorSetUniformBuffers minus the number of uniform buffers we
-    // reserve for internal variables. We reserve one per shader stage for default uniforms
-    // and likely one per shader stage for ANGLE internal variables.
-    // mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Vertex] = ...
+    // A number of uniform buffers are reserved for internal use.  There's one dynamic uniform
+    // buffer used per stage for default uniforms, and a single uniform buffer object used for
+    // ANGLE internal variables.  ANGLE implements UBOs as uniform buffers, so the maximum number
+    // of uniform blocks is maxDescriptorSetUniformBuffers - 1:
+    const uint32_t maxUniformBuffers =
+        mPhysicalDeviceProperties.limits.maxDescriptorSetUniformBuffers -
+        kReservedDriverUniformBindingCount;
+
+    mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Vertex]   = maxUniformBuffers;
+    mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Fragment] = maxUniformBuffers;
+    mNativeCaps.maxCombinedUniformBlocks                         = maxUniformBuffers;
+
+    mNativeCaps.maxUniformBufferBindings = maxUniformBuffers;
+    mNativeCaps.maxUniformBlockSize      = mPhysicalDeviceProperties.limits.maxUniformBufferRange;
+    mNativeCaps.uniformBufferOffsetAlignment =
+        static_cast<GLuint>(mPhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment);
+
+    // There is no additional limit to the combined number of components.  We can have up to a
+    // maximum number of uniform buffers, each having the maximum number of components.
+    const uint32_t maxCombinedUniformComponents = maxUniformBuffers * maxUniformComponents;
+    for (gl::ShaderType shaderType : gl::kAllGraphicsShaderTypes)
+    {
+        mNativeCaps.maxCombinedShaderUniformComponents[shaderType] = maxCombinedUniformComponents;
+    }
 
     // we use the same bindings on each stage, so the limitation is the same combined or not.
     mNativeCaps.maxCombinedTextureImageUnits =
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index 34e076d..7530649 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -1159,7 +1159,7 @@
     mDeviceMemory.dumpResources(garbageQueue);
 }
 
-void BufferHelper::onWrite(VkAccessFlagBits writeAccessType)
+void BufferHelper::onWrite(VkAccessFlags writeAccessType)
 {
     if (mCurrentReadAccess != 0 || mCurrentWriteAccess != 0)
     {
@@ -1172,19 +1172,20 @@
 
 angle::Result BufferHelper::copyFromBuffer(Context *context,
                                            const Buffer &buffer,
+                                           VkAccessFlags bufferAccessType,
                                            const VkBufferCopy &copyRegion)
 {
     // 'recordCommands' will implicitly stop any reads from using the old buffer data.
     vk::CommandBuffer *commandBuffer = nullptr;
     ANGLE_TRY(recordCommands(context, &commandBuffer));
 
-    if (mCurrentReadAccess != 0 || mCurrentWriteAccess != 0)
+    if (mCurrentReadAccess != 0 || mCurrentWriteAccess != 0 || bufferAccessType != 0)
     {
         // Insert a barrier to ensure reads/writes are complete.
         // Use a global memory barrier to keep things simple.
         VkMemoryBarrier memoryBarrier = {};
         memoryBarrier.sType           = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
-        memoryBarrier.srcAccessMask   = mCurrentReadAccess | mCurrentWriteAccess;
+        memoryBarrier.srcAccessMask   = mCurrentReadAccess | mCurrentWriteAccess | bufferAccessType;
         memoryBarrier.dstAccessMask   = VK_ACCESS_TRANSFER_WRITE_BIT;
 
         commandBuffer->pipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index d1b82e1..9672faa 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -406,24 +406,26 @@
     bool valid() const { return mBuffer.valid(); }
     const Buffer &getBuffer() const { return mBuffer; }
     const DeviceMemory &getDeviceMemory() const { return mDeviceMemory; }
+    VkDeviceSize getSize() const { return mSize; }
 
     // Helpers for setting the graph dependencies *and* setting the appropriate barrier.
-    ANGLE_INLINE void onRead(CommandGraphResource *reader, VkAccessFlagBits readAccessType)
+    ANGLE_INLINE void onRead(CommandGraphResource *reader, VkAccessFlags readAccessType)
     {
         addReadDependency(reader);
 
-        if (mCurrentWriteAccess != 0 && (mCurrentReadAccess & readAccessType) == 0)
+        if (mCurrentWriteAccess != 0 && (mCurrentReadAccess & readAccessType) != readAccessType)
         {
             reader->addGlobalMemoryBarrier(mCurrentWriteAccess, readAccessType);
             mCurrentReadAccess |= readAccessType;
         }
     }
 
-    void onWrite(VkAccessFlagBits writeAccessType);
+    void onWrite(VkAccessFlags writeAccessType);
 
     // Also implicitly sets up the correct barriers.
     angle::Result copyFromBuffer(Context *context,
                                  const Buffer &buffer,
+                                 VkAccessFlags bufferAccessType,
                                  const VkBufferCopy &copyRegion);
 
     // Note: currently only one view is allowed.  If needs be, multiple views can be created
diff --git a/src/tests/deqp_support/deqp_gles3_test_expectations.txt b/src/tests/deqp_support/deqp_gles3_test_expectations.txt
index 7233432..92f360c 100644
--- a/src/tests/deqp_support/deqp_gles3_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles3_test_expectations.txt
@@ -521,19 +521,12 @@
 // General Vulkan failures
 
 // Limits:
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_elements* = SKIP
+2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_elements_indices = SKIP
+2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_elements_vertices = SKIP
 2950 VULKAN : dEQP-GLES3.functional.implementation_limits.compressed_texture_formats = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.num_compressed_texture_formats = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_vertex_uniform_blocks = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_fragment_uniform_blocks = SKIP
 2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_fragment_input_components = SKIP
 2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_program_texel_offset = SKIP
 2950 VULKAN : dEQP-GLES3.functional.implementation_limits.min_program_texel_offset = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_uniform_buffer_bindings = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_uniform_block_size = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_combined_vertex_uniform_components = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_combined_fragment_uniform_components = SKIP
-2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_combined_uniform_blocks = SKIP
 2950 VULKAN : dEQP-GLES3.functional.implementation_limits.max_transform_feedback_* = SKIP
 
 // 3D texture (anglebug.com/3188), 2D array (anglebug.com/3189):
@@ -656,10 +649,6 @@
 2950 VULKAN : dEQP-GLES3.functional.polygon_offset.float32_result_depth_clamp = FAIL
 2950 VULKAN : dEQP-GLES3.functional.polygon_offset.float32_factor_1_slope = FAIL
 
-// UBO:
-2950 VULKAN : dEQP-GLES3.functional.ubo.* = SKIP
-3219 VULKAN : dEQP-GLES3.functional.shaders.uniform_block.* = SKIP
-
 // PBO:
 2950 VULKAN : dEQP-GLES3.functional.pbo.* = SKIP
 
@@ -704,18 +693,10 @@
 // - ES3 State queries:
 2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_elements_indices_getfloat = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_elements_vertices_getfloat = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_vertex_uniform_blocks_get* = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_fragment_uniform_blocks_get* = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_combined_uniform_blocks_get* = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_fragment_input_components_get* = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_program_texel_offset_get* = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_uniform_buffer_bindings_get* = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.integers.max_samples_get* = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.integers.min_program_texel_offset_get* = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.integers.compressed_texture_formats_get* = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.indexed.uniform_buffer* = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.integers64.max_uniform_block_size_get* = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.integers64.max_combined_vertex_uniform_components_get* = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.integers64.max_combined_fragment_uniform_components_get* = FAIL
 
 // Misc failures:
@@ -723,7 +704,6 @@
 2950 VULKAN : dEQP-GLES3.functional.state_query.fbo.framebuffer_attachment_color_encoding = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.rbo.renderbuffer_internal_format = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.rbo.renderbuffer_component_size_color = FAIL
-2950 VULKAN : dEQP-GLES3.functional.state_query.shader.program_active_uniform_blocks = FAIL
 2950 VULKAN : dEQP-GLES3.functional.state_query.shader.uniform_value_uint = FAIL
 2950 VULKAN : dEQP-GLES3.functional.lifetime.* = SKIP
 2950 VULKAN : dEQP-GLES3.functional.clipping.line.line_clip_viewport_center = FAIL
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index 0c1a59d..9f0cf9b 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -4741,10 +4741,6 @@
 // field.
 TEST_P(GLSLTest_ES3, ErrorMessageOfLinkInterfaceBlockFieldMismatch)
 {
-    // TODO(syoussefi): missing ES3 shader feature support.
-    // http://anglebug.com/3199
-    ANGLE_SKIP_TEST_IF(IsVulkan());
-
     constexpr char kVS[] = R"(#version 300 es
 uniform S {
     vec2 val1;
@@ -4780,10 +4776,6 @@
 // struct field.
 TEST_P(GLSLTest_ES3, ErrorMessageOfLinkInterfaceBlockStructFieldMismatch)
 {
-    // TODO(syoussefi): missing ES3 shader feature support.
-    // http://anglebug.com/3199
-    ANGLE_SKIP_TEST_IF(IsVulkan());
-
     constexpr char kVS[] = R"(#version 300 es
 struct T
 {
diff --git a/src/tests/gl_tests/UniformBufferTest.cpp b/src/tests/gl_tests/UniformBufferTest.cpp
index abede84..01a981f 100644
--- a/src/tests/gl_tests/UniformBufferTest.cpp
+++ b/src/tests/gl_tests/UniformBufferTest.cpp
@@ -1518,7 +1518,7 @@
 
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
 // tests should be run against.
-ANGLE_INSTANTIATE_TEST(UniformBufferTest, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
+ANGLE_INSTANTIATE_TEST(UniformBufferTest, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES(), ES3_VULKAN());
 ANGLE_INSTANTIATE_TEST(UniformBufferTest31, ES31_D3D11(), ES31_OPENGL(), ES31_OPENGLES());
 
 }  // namespace
diff --git a/src/tests/gl_tests/UniformTest.cpp b/src/tests/gl_tests/UniformTest.cpp
index f960595..0d98653 100644
--- a/src/tests/gl_tests/UniformTest.cpp
+++ b/src/tests/gl_tests/UniformTest.cpp
@@ -694,6 +694,9 @@
 // Test queries for transposed arrays of non-square matrix uniforms.
 TEST_P(UniformTestES3, TransposedMatrixArrayUniformStateQuery)
 {
+    // TODO(syoussefi): Bug in glGetUniformfv.  http://anglebug.com/3387
+    ANGLE_SKIP_TEST_IF(IsVulkan());
+
     constexpr char kFS[] =
         "#version 300 es\n"
         "precision mediump float;\n"
@@ -1279,19 +1282,20 @@
 ANGLE_INSTANTIATE_TEST(SimpleUniformTest,
                        ES2_D3D9(),
                        ES2_D3D11(),
-                       ES2_OPENGL(),
                        ES3_D3D11(),
+                       ES2_OPENGL(),
                        ES3_OPENGL(),
-                       ES3_OPENGLES(),
                        ES2_OPENGLES(),
-                       ES2_VULKAN());
+                       ES3_OPENGLES(),
+                       ES2_VULKAN(),
+                       ES3_VULKAN());
 ANGLE_INSTANTIATE_TEST(UniformTest,
                        ES2_D3D9(),
                        ES2_D3D11(),
                        ES2_OPENGL(),
                        ES2_OPENGLES(),
                        ES2_VULKAN());
-ANGLE_INSTANTIATE_TEST(UniformTestES3, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
+ANGLE_INSTANTIATE_TEST(UniformTestES3, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES(), ES3_VULKAN());
 ANGLE_INSTANTIATE_TEST(UniformTestES31, ES31_D3D11(), ES31_OPENGL(), ES31_OPENGLES());
 
 }  // namespace
diff --git a/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp b/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp
index 30be1d2..4f35cbd 100644
--- a/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp
+++ b/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp
@@ -313,6 +313,6 @@
     ASSERT_GL_NO_ERROR();
 }
 
-ANGLE_INSTANTIATE_TEST(VulkanUniformUpdatesTest, ES2_VULKAN());
+ANGLE_INSTANTIATE_TEST(VulkanUniformUpdatesTest, ES2_VULKAN(), ES3_VULKAN());
 
 }  // anonymous namespace