Vulkan: Add PipelineLayout and DescriptorSetLayout caches.

This will be necessary to support more than one PipelineLayout per
instance of ANGLE. Sampler array handling requires different layouts
for different sampler uses.

Bug: angleproject:2462
Change-Id: I1d8b4919eed1a589002ad1898b05186f420061c7
Reviewed-on: https://chromium-review.googlesource.com/1089806
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Luc Ferron <lucferron@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 00d09f1..8ef55dd 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -125,9 +125,11 @@
     // Ensure that the RenderPass description is updated.
     mPipelineDesc->updateRenderPassDesc(framebufferVk->getRenderPassDesc());
 
+    const vk::PipelineLayout &pipelineLayout = mRenderer->getGraphicsPipelineLayout();
+
     // TODO(jmadill): Validate with ASSERT against physical device limits/caps?
     ANGLE_TRY(mRenderer->getAppPipeline(programVk, *mPipelineDesc, activeAttribLocationsMask,
-                                        &mCurrentPipeline));
+                                        pipelineLayout, &mCurrentPipeline));
 
     return gl::NoError();
 }
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index 6334ae4..36a809e 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -705,7 +705,6 @@
 
     // Write out to a new a descriptor set.
     vk::DynamicDescriptorPool *dynamicDescriptorPool = contextVk->getDynamicDescriptorPool();
-    const auto &descriptorSetLayouts = renderer->getGraphicsDescriptorSetLayouts();
 
     uint32_t potentialNewCount = descriptorSetIndex + 1;
     if (potentialNewCount > mDescriptorSets.size())
@@ -713,10 +712,10 @@
         mDescriptorSets.resize(potentialNewCount, VK_NULL_HANDLE);
     }
 
-    const VkDescriptorSetLayout *descriptorSetLayout =
-        descriptorSetLayouts[descriptorSetIndex].ptr();
+    const vk::DescriptorSetLayout &descriptorSetLayout =
+        renderer->getGraphicsDescriptorSetLayout(descriptorSetIndex);
 
-    ANGLE_TRY(dynamicDescriptorPool->allocateDescriptorSets(contextVk, descriptorSetLayout, 1,
+    ANGLE_TRY(dynamicDescriptorPool->allocateDescriptorSets(contextVk, descriptorSetLayout.ptr(), 1,
                                                             &mDescriptorSets[descriptorSetIndex]));
     return vk::NoError();
 }
@@ -772,7 +771,7 @@
     {
         // We need to reinitialize the descriptor sets if we newly allocated buffers since we can't
         // modify the descriptor sets once initialized.
-        ANGLE_TRY(allocateDescriptorSet(contextVk, vk::UniformBufferIndex));
+        ANGLE_TRY(allocateDescriptorSet(contextVk, kUniformsDescriptorSetIndex));
         ANGLE_TRY(updateDefaultUniformsDescriptorSet(contextVk));
     }
 
@@ -857,10 +856,10 @@
     }
 
     ContextVk *contextVk = GetImplAs<ContextVk>(context);
-    ANGLE_TRY(allocateDescriptorSet(contextVk, vk::TextureIndex));
+    ANGLE_TRY(allocateDescriptorSet(contextVk, kTextureDescriptorSetIndex));
 
     ASSERT(mUsedDescriptorSetRange.contains(1));
-    VkDescriptorSet descriptorSet = mDescriptorSets[1];
+    VkDescriptorSet descriptorSet = mDescriptorSets[kTextureDescriptorSetIndex];
 
     // TODO(jmadill): Don't hard-code the texture limit.
     ShaderTextureArray<VkDescriptorImageInfo> descriptorImageInfo;
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index 8a2ed93..fd6855c 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -210,10 +210,13 @@
 
     for (auto &descriptorSetLayout : mGraphicsDescriptorSetLayouts)
     {
-        descriptorSetLayout.destroy(mDevice);
+        descriptorSetLayout.reset();
     }
 
-    mGraphicsPipelineLayout.destroy(mDevice);
+    mGraphicsPipelineLayout.reset();
+    mPipelineLayoutCache.destroy(mDevice);
+    mDescriptorSetLayoutCache.destroy(mDevice);
+
     mInternalPushConstantPipelineLayout.destroy(mDevice);
 
     mRenderPassCache.destroy(mDevice);
@@ -842,12 +845,12 @@
 
 const vk::PipelineLayout &RendererVk::getGraphicsPipelineLayout() const
 {
-    return mGraphicsPipelineLayout;
+    return mGraphicsPipelineLayout.get();
 }
 
-const std::vector<vk::DescriptorSetLayout> &RendererVk::getGraphicsDescriptorSetLayouts() const
+const vk::DescriptorSetLayout &RendererVk::getGraphicsDescriptorSetLayout(uint32_t setIndex) const
 {
-    return mGraphicsDescriptorSetLayouts;
+    return mGraphicsDescriptorSetLayouts[setIndex].get();
 }
 
 vk::Error RendererVk::initGraphicsPipelineLayout()
@@ -855,84 +858,32 @@
     ASSERT(!mGraphicsPipelineLayout.valid());
 
     // Create two descriptor set layouts: one for default uniform info, and one for textures.
-    // Skip one or both if there are no uniforms.
-    VkDescriptorSetLayoutBinding uniformBindings[2];
-    uint32_t blockCount = 0;
+    vk::DescriptorSetLayoutDesc uniformsSetDesc;
+    uniformsSetDesc.update(kVertexUniformsBindingIndex, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
+                           1);
+    uniformsSetDesc.update(kFragmentUniformsBindingIndex, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
+                           1);
+    ANGLE_TRY(mDescriptorSetLayoutCache.getDescriptorSetLayout(mDevice, uniformsSetDesc,
+                                                               &mGraphicsDescriptorSetLayouts[0]));
 
+    const uint32_t maxTextures = getMaxActiveTextures();
+
+    vk::DescriptorSetLayoutDesc texturesSetDesc;
+    for (uint32_t textureIndex = 0; textureIndex < maxTextures; ++textureIndex)
     {
-        VkDescriptorSetLayoutBinding &layoutBinding = uniformBindings[blockCount];
-
-        layoutBinding.binding            = blockCount;
-        layoutBinding.descriptorType     = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
-        layoutBinding.descriptorCount    = 1;
-        layoutBinding.stageFlags         = VK_SHADER_STAGE_VERTEX_BIT;
-        layoutBinding.pImmutableSamplers = nullptr;
-
-        blockCount++;
+        // TODO(jmadll): Sampler arrays. http://anglebug.com/2462
+        texturesSetDesc.update(textureIndex, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1);
     }
 
-    {
-        VkDescriptorSetLayoutBinding &layoutBinding = uniformBindings[blockCount];
+    ANGLE_TRY(mDescriptorSetLayoutCache.getDescriptorSetLayout(mDevice, texturesSetDesc,
+                                                               &mGraphicsDescriptorSetLayouts[1]));
 
-        layoutBinding.binding            = blockCount;
-        layoutBinding.descriptorType     = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
-        layoutBinding.descriptorCount    = 1;
-        layoutBinding.stageFlags         = VK_SHADER_STAGE_FRAGMENT_BIT;
-        layoutBinding.pImmutableSamplers = nullptr;
+    vk::PipelineLayoutDesc pipelineLayoutDesc;
+    pipelineLayoutDesc.updateDescriptorSetLayout(kUniformsDescriptorSetIndex, uniformsSetDesc);
+    pipelineLayoutDesc.updateDescriptorSetLayout(kTextureDescriptorSetIndex, texturesSetDesc);
 
-        blockCount++;
-    }
-
-    {
-        VkDescriptorSetLayoutCreateInfo uniformInfo;
-        uniformInfo.sType        = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
-        uniformInfo.pNext        = nullptr;
-        uniformInfo.flags        = 0;
-        uniformInfo.bindingCount = blockCount;
-        uniformInfo.pBindings    = uniformBindings;
-
-        vk::DescriptorSetLayout uniformLayout;
-        ANGLE_TRY(uniformLayout.init(mDevice, uniformInfo));
-        mGraphicsDescriptorSetLayouts.push_back(std::move(uniformLayout));
-    }
-
-    std::vector<VkDescriptorSetLayoutBinding> textureBindings(getMaxActiveTextures());
-
-    // TODO(jmadill): This approach might not work well for texture arrays.
-    for (uint32_t textureIndex = 0; textureIndex < textureBindings.size(); ++textureIndex)
-    {
-        VkDescriptorSetLayoutBinding &layoutBinding = textureBindings[textureIndex];
-
-        layoutBinding.binding         = textureIndex;
-        layoutBinding.descriptorType  = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-        layoutBinding.descriptorCount = 1;
-        layoutBinding.stageFlags      = (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
-        layoutBinding.pImmutableSamplers = nullptr;
-    }
-
-    {
-        VkDescriptorSetLayoutCreateInfo textureInfo;
-        textureInfo.sType        = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
-        textureInfo.pNext        = nullptr;
-        textureInfo.flags        = 0;
-        textureInfo.bindingCount = static_cast<uint32_t>(textureBindings.size());
-        textureInfo.pBindings    = textureBindings.data();
-
-        vk::DescriptorSetLayout textureLayout;
-        ANGLE_TRY(textureLayout.init(mDevice, textureInfo));
-        mGraphicsDescriptorSetLayouts.push_back(std::move(textureLayout));
-    }
-
-    VkPipelineLayoutCreateInfo createInfo;
-    createInfo.sType                  = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
-    createInfo.pNext                  = nullptr;
-    createInfo.flags                  = 0;
-    createInfo.setLayoutCount         = static_cast<uint32_t>(mGraphicsDescriptorSetLayouts.size());
-    createInfo.pSetLayouts            = mGraphicsDescriptorSetLayouts[0].ptr();
-    createInfo.pushConstantRangeCount = 0;
-    createInfo.pPushConstantRanges    = nullptr;
-
-    ANGLE_TRY(mGraphicsPipelineLayout.init(mDevice, createInfo));
+    ANGLE_TRY(mPipelineLayoutCache.getPipelineLayout(
+        mDevice, pipelineLayoutDesc, mGraphicsDescriptorSetLayouts, &mGraphicsPipelineLayout));
 
     return vk::NoError();
 }
@@ -973,6 +924,7 @@
 vk::Error RendererVk::getAppPipeline(const ProgramVk *programVk,
                                      const vk::PipelineDesc &desc,
                                      const gl::AttributesMask &activeAttribLocationsMask,
+                                     const vk::PipelineLayout &pipelineLayout,
                                      vk::PipelineAndSerial **pipelineOut)
 {
     ASSERT(programVk->getVertexModuleSerial() ==
@@ -984,7 +936,7 @@
     vk::RenderPass *compatibleRenderPass = nullptr;
     ANGLE_TRY(getCompatibleRenderPass(desc.getRenderPassDesc(), &compatibleRenderPass));
 
-    return mPipelineCache.getPipeline(mDevice, *compatibleRenderPass, mGraphicsPipelineLayout,
+    return mPipelineCache.getPipeline(mDevice, *compatibleRenderPass, pipelineLayout,
                                       activeAttribLocationsMask, programVk->getLinkedVertexModule(),
                                       programVk->getLinkedFragmentModule(), desc, pipelineOut);
 }
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.h b/src/libANGLE/renderer/vulkan/RendererVk.h
index a580b99..5e9d67d 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.h
+++ b/src/libANGLE/renderer/vulkan/RendererVk.h
@@ -108,6 +108,7 @@
     vk::Error getAppPipeline(const ProgramVk *programVk,
                              const vk::PipelineDesc &desc,
                              const gl::AttributesMask &activeAttribLocationsMask,
+                             const vk::PipelineLayout &pipelineLayout,
                              vk::PipelineAndSerial **pipelineOut);
 
     // For getting a vk::Pipeline for an internal draw call. Use an explicit RenderPass.
@@ -122,10 +123,12 @@
     // TODO(jmadill): Keep in ContextVk to enable threaded rendering.
     vk::CommandGraph *getCommandGraph();
 
+    // TODO(jmadill): Use PipelineLayout cache. http://anglebug.com/2462
     const vk::PipelineLayout &getGraphicsPipelineLayout() const;
-    const std::vector<vk::DescriptorSetLayout> &getGraphicsDescriptorSetLayouts() const;
+    const vk::DescriptorSetLayout &getGraphicsDescriptorSetLayout(uint32_t setIndex) const;
 
     // Used in internal shaders.
+    // TODO(jmadill): Use PipelineLayout cache. http://anglebug.com/2462
     vk::Error getInternalPushConstantPipelineLayout(const vk::PipelineLayout **pipelineLayoutOut);
 
     // Issues a new serial for linked shader modules. Used in the pipeline cache.
@@ -187,12 +190,18 @@
     // See CommandGraph.h for a desription of the Command Graph.
     vk::CommandGraph mCommandGraph;
 
-    // ANGLE uses a single pipeline layout for all GL programs. It is owned here in the Renderer.
-    // See the design doc for an overview of the pipeline layout structure.
-    vk::PipelineLayout mGraphicsPipelineLayout;
-    std::vector<vk::DescriptorSetLayout> mGraphicsDescriptorSetLayouts;
+    // ANGLE uses a PipelineLayout cache to store compatible pipeline layouts.
+    PipelineLayoutCache mPipelineLayoutCache;
+
+    // DescriptorSetLayouts are also managed in a cache.
+    DescriptorSetLayoutCache mDescriptorSetLayoutCache;
+
+    // TODO(jmadill): Only use the pipeline layout cache. http://anglebug.com/2462
+    vk::BindingPointer<vk::PipelineLayout> mGraphicsPipelineLayout;
+    vk::DescriptorSetLayoutPointerArray mGraphicsDescriptorSetLayouts;
 
     // Used for internal shaders.
+    // TODO(jmadill): Use PipelineLayout cache. http://anglebug.com/2462
     vk::PipelineLayout mInternalPushConstantPipelineLayout;
 
     // Internal shader library.
diff --git a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
index fd51a55..24ebc1d 100644
--- a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
@@ -860,6 +860,108 @@
 {
     return (memcmp(&lhs, &rhs, sizeof(AttachmentOpsArray)) == 0);
 }
+
+// DescriptorSetLayoutDesc implementation.
+DescriptorSetLayoutDesc::DescriptorSetLayoutDesc() : mPackedDescriptorSetLayout{}
+{
+}
+
+DescriptorSetLayoutDesc::~DescriptorSetLayoutDesc() = default;
+
+DescriptorSetLayoutDesc::DescriptorSetLayoutDesc(const DescriptorSetLayoutDesc &other) = default;
+
+DescriptorSetLayoutDesc &DescriptorSetLayoutDesc::operator=(const DescriptorSetLayoutDesc &other) =
+    default;
+
+size_t DescriptorSetLayoutDesc::hash() const
+{
+    return angle::ComputeGenericHash(mPackedDescriptorSetLayout);
+}
+
+bool DescriptorSetLayoutDesc::operator==(const DescriptorSetLayoutDesc &other) const
+{
+    return (memcmp(&mPackedDescriptorSetLayout, &other.mPackedDescriptorSetLayout,
+                   sizeof(mPackedDescriptorSetLayout)) == 0);
+}
+
+void DescriptorSetLayoutDesc::update(uint32_t bindingIndex, VkDescriptorType type, uint32_t count)
+{
+    ASSERT(static_cast<size_t>(type) < std::numeric_limits<uint16_t>::max());
+    ASSERT(count < std::numeric_limits<uint16_t>::max());
+
+    PackedDescriptorSetBinding &packedBinding = mPackedDescriptorSetLayout[bindingIndex];
+
+    packedBinding.type  = static_cast<uint16_t>(type);
+    packedBinding.count = static_cast<uint16_t>(count);
+}
+
+void DescriptorSetLayoutDesc::unpackBindings(DescriptorSetLayoutBindingVector *bindings) const
+{
+    for (uint32_t bindingIndex = 0; bindingIndex < kMaxDescriptorSetLayoutBindings; ++bindingIndex)
+    {
+        const PackedDescriptorSetBinding &packedBinding = mPackedDescriptorSetLayout[bindingIndex];
+        if (packedBinding.count == 0)
+            continue;
+
+        VkDescriptorSetLayoutBinding binding;
+        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);
+        binding.pImmutableSamplers = nullptr;
+
+        bindings->push_back(binding);
+    }
+}
+
+// PipelineLayoutDesc implementation.
+PipelineLayoutDesc::PipelineLayoutDesc() : mDescriptorSetLayouts{}, mPushConstantRanges{}
+{
+}
+
+PipelineLayoutDesc::~PipelineLayoutDesc() = default;
+
+PipelineLayoutDesc::PipelineLayoutDesc(const PipelineLayoutDesc &other) = default;
+
+PipelineLayoutDesc &PipelineLayoutDesc::operator=(const PipelineLayoutDesc &rhs)
+{
+    mDescriptorSetLayouts = rhs.mDescriptorSetLayouts;
+    mPushConstantRanges   = rhs.mPushConstantRanges;
+    return *this;
+}
+
+size_t PipelineLayoutDesc::hash() const
+{
+    return angle::ComputeGenericHash(*this);
+}
+
+bool PipelineLayoutDesc::operator==(const PipelineLayoutDesc &other) const
+{
+    return memcmp(this, &other, sizeof(PipelineLayoutDesc)) == 0;
+}
+
+void PipelineLayoutDesc::updateDescriptorSetLayout(uint32_t setIndex,
+                                                   const DescriptorSetLayoutDesc &desc)
+{
+    ASSERT(setIndex < mDescriptorSetLayouts.size());
+    mDescriptorSetLayouts[setIndex] = desc;
+}
+
+void PipelineLayoutDesc::updatePushConstantRange(gl::ShaderType shaderType,
+                                                 uint32_t offset,
+                                                 uint32_t size)
+{
+    ASSERT(shaderType == gl::ShaderType::Vertex || shaderType == gl::ShaderType::Fragment);
+    PackedPushConstantRange &packed = mPushConstantRanges[static_cast<size_t>(shaderType)];
+    packed.offset                   = offset;
+    packed.size                     = size;
+}
+
+const PushConstantRangeArray<PackedPushConstantRange> &PipelineLayoutDesc::getPushConstantRanges()
+    const
+{
+    return mPushConstantRanges;
+}
 }  // namespace vk
 
 // RenderPassCache implementation.
@@ -1021,4 +1123,123 @@
     mPayload.emplace(desc, vk::PipelineAndSerial(std::move(pipeline), Serial()));
 }
 
+// DescriptorSetLayoutCache implementation.
+DescriptorSetLayoutCache::DescriptorSetLayoutCache() = default;
+
+DescriptorSetLayoutCache::~DescriptorSetLayoutCache()
+{
+    ASSERT(mPayload.empty());
+}
+
+void DescriptorSetLayoutCache::destroy(VkDevice device)
+{
+    for (auto &item : mPayload)
+    {
+        vk::SharedDescriptorSetLayout &layout = item.second;
+        ASSERT(!layout.isReferenced());
+        layout.get().destroy(device);
+    }
+
+    mPayload.clear();
+}
+
+vk::Error DescriptorSetLayoutCache::getDescriptorSetLayout(
+    VkDevice device,
+    const vk::DescriptorSetLayoutDesc &desc,
+    vk::BindingPointer<vk::DescriptorSetLayout> *descriptorSetLayoutOut)
+{
+    auto iter = mPayload.find(desc);
+    if (iter != mPayload.end())
+    {
+        vk::SharedDescriptorSetLayout &layout = iter->second;
+        descriptorSetLayoutOut->set(&layout);
+        return vk::NoError();
+    }
+
+    // We must unpack the descriptor set layout description.
+    vk::DescriptorSetLayoutBindingVector bindings;
+    desc.unpackBindings(&bindings);
+
+    VkDescriptorSetLayoutCreateInfo createInfo;
+    createInfo.sType        = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    createInfo.pNext        = nullptr;
+    createInfo.flags        = 0;
+    createInfo.bindingCount = static_cast<uint32_t>(bindings.size());
+    createInfo.pBindings    = bindings.data();
+
+    vk::DescriptorSetLayout newLayout;
+    ANGLE_TRY(newLayout.init(device, createInfo));
+
+    auto insertedItem = mPayload.emplace(desc, vk::SharedDescriptorSetLayout(std::move(newLayout)));
+    vk::SharedDescriptorSetLayout &insertedLayout = insertedItem.first->second;
+    descriptorSetLayoutOut->set(&insertedLayout);
+
+    return vk::NoError();
+}
+
+// PipelineLayoutCache implementation.
+PipelineLayoutCache::PipelineLayoutCache() = default;
+
+PipelineLayoutCache::~PipelineLayoutCache()
+{
+    ASSERT(mPayload.empty());
+}
+
+void PipelineLayoutCache::destroy(VkDevice device)
+{
+    for (auto &item : mPayload)
+    {
+        vk::SharedPipelineLayout &layout = item.second;
+        layout.get().destroy(device);
+    }
+
+    mPayload.clear();
+}
+
+vk::Error PipelineLayoutCache::getPipelineLayout(
+    VkDevice device,
+    const vk::PipelineLayoutDesc &desc,
+    const vk::DescriptorSetLayoutPointerArray &descriptorSetLayouts,
+    vk::BindingPointer<vk::PipelineLayout> *pipelineLayoutOut)
+{
+    auto iter = mPayload.find(desc);
+    if (iter != mPayload.end())
+    {
+        vk::SharedPipelineLayout &layout = iter->second;
+        pipelineLayoutOut->set(&layout);
+        return vk::NoError();
+    }
+
+    // Note this does not handle gaps in descriptor set layouts gracefully.
+    angle::FixedVector<VkDescriptorSetLayout, vk::kMaxDescriptorSetLayouts> setLayoutHandles;
+    for (const vk::BindingPointer<vk::DescriptorSetLayout> &layoutPtr : descriptorSetLayouts)
+    {
+        VkDescriptorSetLayout setLayout = layoutPtr.get().getHandle();
+        if (setLayout != VK_NULL_HANDLE)
+        {
+            setLayoutHandles.push_back(setLayout);
+        }
+    }
+
+    // No pipeline layout found. We must create a new one.
+    VkPipelineLayoutCreateInfo createInfo;
+    createInfo.sType          = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    createInfo.pNext          = nullptr;
+    createInfo.flags          = 0;
+    createInfo.setLayoutCount = static_cast<uint32_t>(setLayoutHandles.size());
+    createInfo.pSetLayouts    = setLayoutHandles.data();
+
+    // TODO(jmadill): Init push constant ranges. http://anglebug.com/2462
+    createInfo.pushConstantRangeCount = 0;
+    createInfo.pPushConstantRanges    = nullptr;
+
+    vk::PipelineLayout newLayout;
+    ANGLE_TRY(newLayout.init(device, createInfo));
+
+    auto insertedItem = mPayload.emplace(desc, vk::SharedPipelineLayout(std::move(newLayout)));
+    vk::SharedPipelineLayout &insertedLayout = insertedItem.first->second;
+    pipelineLayoutOut->set(&insertedLayout);
+
+    return vk::NoError();
+}
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/vk_cache_utils.h b/src/libANGLE/renderer/vulkan/vk_cache_utils.h
index 77a49f5..a5c2aac 100644
--- a/src/libANGLE/renderer/vulkan/vk_cache_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_cache_utils.h
@@ -21,6 +21,88 @@
 {
 class ImageHelper;
 
+// This is a very simple RefCount class that has no autoreleasing. Used in the descriptor set and
+// pipeline layout caches.
+template <typename T>
+class RefCounted : angle::NonCopyable
+{
+  public:
+    RefCounted() : mRefCount(0) {}
+    explicit RefCounted(T &&newObject) : mRefCount(0), mObject(std::move(newObject)) {}
+    ~RefCounted() { ASSERT(mRefCount == 0 && !mObject.valid()); }
+
+    RefCounted(RefCounted &&copy) : mRefCount(copy.mRefCount), mObject(std::move(copy.mObject))
+    {
+        copy.mRefCount = 0;
+    }
+
+    RefCounted &operator=(RefCounted &&rhs)
+    {
+        std::swap(mRefCount, rhs.mRefCount);
+        mObject = std::move(rhs.mObject);
+        return *this;
+    }
+
+    void addRef()
+    {
+        ASSERT(mRefCount != std::numeric_limits<uint32_t>::max());
+        mRefCount++;
+    }
+    void releaseRef()
+    {
+        ASSERT(isReferenced());
+        mRefCount--;
+    }
+    bool isReferenced() const { return mRefCount != 0; }
+
+    T &get() { return mObject; }
+    const T &get() const { return mObject; }
+
+  private:
+    uint32_t mRefCount;
+    T mObject;
+};
+
+template <typename T>
+class BindingPointer final : angle::NonCopyable
+{
+  public:
+    BindingPointer() : mRefCounted(nullptr) {}
+
+    ~BindingPointer() { reset(); }
+
+    void set(RefCounted<T> *refCounted)
+    {
+        if (mRefCounted)
+        {
+            mRefCounted->releaseRef();
+        }
+
+        mRefCounted = refCounted;
+
+        if (mRefCounted)
+        {
+            mRefCounted->addRef();
+        }
+    }
+
+    void reset() { set(nullptr); }
+
+    T &get() { return mRefCounted->get(); }
+    const T &get() const { return mRefCounted->get(); }
+
+    bool valid() const { return mRefCounted != nullptr; }
+
+  private:
+    RefCounted<T> *mRefCounted;
+};
+
+using RenderPassAndSerial = ObjectAndSerial<RenderPass>;
+using PipelineAndSerial   = ObjectAndSerial<Pipeline>;
+
+using SharedDescriptorSetLayout = RefCounted<DescriptorSetLayout>;
+using SharedPipelineLayout      = RefCounted<PipelineLayout>;
+
 // Packed Vk resource descriptions.
 // Most Vk types use many more bits than required to represent the underlying data.
 // Since ANGLE wants cache things like RenderPasses and Pipeline State Objects using
@@ -336,17 +418,105 @@
 // This is not guaranteed by the spec, but is validated by a compile-time check.
 // No gaps or padding at the end ensures that hashing and memcmp checks will not run
 // into uninitialized memory regions.
-constexpr size_t PipelineDescSumOfSizes =
+constexpr size_t kPipelineDescSumOfSizes =
     sizeof(ShaderStageInfo) + sizeof(VertexInputBindings) + sizeof(VertexInputAttributes) +
     sizeof(PackedInputAssemblyInfo) + sizeof(VkViewport) + sizeof(VkRect2D) +
     sizeof(PackedRasterizationStateInfo) + sizeof(PackedMultisampleStateInfo) +
     sizeof(PackedDepthStencilStateInfo) + sizeof(PackedColorBlendStateInfo) +
     sizeof(RenderPassDesc);
 
-static_assert(sizeof(PipelineDesc) == PipelineDescSumOfSizes, "Size mismatch");
+static_assert(sizeof(PipelineDesc) == kPipelineDescSumOfSizes, "Size mismatch");
 
-using RenderPassAndSerial = ObjectAndSerial<RenderPass>;
-using PipelineAndSerial   = ObjectAndSerial<Pipeline>;
+constexpr uint32_t kMaxDescriptorSetLayoutBindings = gl::IMPLEMENTATION_MAX_ACTIVE_TEXTURES;
+
+using DescriptorSetLayoutBindingVector =
+    angle::FixedVector<VkDescriptorSetLayoutBinding, kMaxDescriptorSetLayoutBindings>;
+
+// A packed description of a descriptor set layout. Use similarly to RenderPassDesc and
+// PipelineDesc. Currently we only need to differentiate layouts based on sampler usage. In the
+// future we could generalize this.
+class DescriptorSetLayoutDesc final
+{
+  public:
+    DescriptorSetLayoutDesc();
+    ~DescriptorSetLayoutDesc();
+    DescriptorSetLayoutDesc(const DescriptorSetLayoutDesc &other);
+    DescriptorSetLayoutDesc &operator=(const DescriptorSetLayoutDesc &other);
+
+    size_t hash() const;
+    bool operator==(const DescriptorSetLayoutDesc &other) const;
+
+    void update(uint32_t bindingIndex, VkDescriptorType type, uint32_t count);
+
+    void unpackBindings(DescriptorSetLayoutBindingVector *bindings) const;
+
+  private:
+    struct PackedDescriptorSetBinding
+    {
+        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");
+
+    // This is a compact representation of a descriptor set layout.
+    std::array<PackedDescriptorSetBinding, kMaxDescriptorSetLayoutBindings>
+        mPackedDescriptorSetLayout;
+};
+
+// The following are for caching descriptor set layouts. Limited to max two descriptor set layouts
+// and two push constants. One push constant per shader stage. This can be extended in the future.
+constexpr size_t kMaxDescriptorSetLayouts = 2;
+constexpr size_t kMaxPushConstantRanges   = 2;
+
+struct PackedPushConstantRange
+{
+    uint32_t offset;
+    uint32_t size;
+};
+
+template <typename T>
+using DescriptorSetLayoutArray = std::array<T, kMaxDescriptorSetLayouts>;
+using DescriptorSetLayoutPointerArray =
+    DescriptorSetLayoutArray<BindingPointer<DescriptorSetLayout>>;
+template <typename T>
+using PushConstantRangeArray = std::array<T, kMaxPushConstantRanges>;
+
+class PipelineLayoutDesc final
+{
+  public:
+    PipelineLayoutDesc();
+    ~PipelineLayoutDesc();
+    PipelineLayoutDesc(const PipelineLayoutDesc &other);
+    PipelineLayoutDesc &operator=(const PipelineLayoutDesc &rhs);
+
+    size_t hash() const;
+    bool operator==(const PipelineLayoutDesc &other) const;
+
+    void updateDescriptorSetLayout(uint32_t setIndex, const DescriptorSetLayoutDesc &desc);
+    void updatePushConstantRange(gl::ShaderType shaderType, uint32_t offset, uint32_t size);
+
+    const PushConstantRangeArray<PackedPushConstantRange> &getPushConstantRanges() const;
+
+  private:
+    DescriptorSetLayoutArray<DescriptorSetLayoutDesc> mDescriptorSetLayouts;
+    PushConstantRangeArray<PackedPushConstantRange> mPushConstantRanges;
+
+    // Verify the arrays are properly packed.
+    static_assert(sizeof(decltype(mDescriptorSetLayouts)) ==
+                      (sizeof(DescriptorSetLayoutDesc) * kMaxDescriptorSetLayouts),
+                  "Unexpected size");
+    static_assert(sizeof(decltype(mPushConstantRanges)) ==
+                      (sizeof(PackedPushConstantRange) * kMaxPushConstantRanges),
+                  "Unexpected size");
+};
+
+// Verify the structure is properly packed.
+static_assert(sizeof(PipelineLayoutDesc) ==
+                  (sizeof(DescriptorSetLayoutArray<DescriptorSetLayoutDesc>) +
+                   sizeof(std::array<PackedPushConstantRange, kMaxPushConstantRanges>)),
+              "Unexpected Size");
 }  // namespace vk
 }  // namespace rx
 
@@ -371,6 +541,17 @@
     size_t operator()(const rx::vk::PipelineDesc &key) const { return key.hash(); }
 };
 
+template <>
+struct hash<rx::vk::DescriptorSetLayoutDesc>
+{
+    size_t operator()(const rx::vk::DescriptorSetLayoutDesc &key) const { return key.hash(); }
+};
+
+template <>
+struct hash<rx::vk::PipelineLayoutDesc>
+{
+    size_t operator()(const rx::vk::PipelineLayoutDesc &key) const { return key.hash(); }
+};
 }  // namespace std
 
 namespace rx
@@ -426,6 +607,47 @@
     std::unordered_map<vk::PipelineDesc, vk::PipelineAndSerial> mPayload;
 };
 
+class DescriptorSetLayoutCache final : angle::NonCopyable
+{
+  public:
+    DescriptorSetLayoutCache();
+    ~DescriptorSetLayoutCache();
+
+    void destroy(VkDevice device);
+
+    vk::Error getDescriptorSetLayout(
+        VkDevice device,
+        const vk::DescriptorSetLayoutDesc &desc,
+        vk::BindingPointer<vk::DescriptorSetLayout> *descriptorSetLayoutOut);
+
+  private:
+    std::unordered_map<vk::DescriptorSetLayoutDesc, vk::SharedDescriptorSetLayout> mPayload;
+};
+
+class PipelineLayoutCache final : angle::NonCopyable
+{
+  public:
+    PipelineLayoutCache();
+    ~PipelineLayoutCache();
+
+    void destroy(VkDevice device);
+
+    vk::Error getPipelineLayout(VkDevice device,
+                                const vk::PipelineLayoutDesc &desc,
+                                const vk::DescriptorSetLayoutPointerArray &descriptorSetLayouts,
+                                vk::BindingPointer<vk::PipelineLayout> *pipelineLayoutOut);
+
+  private:
+    std::unordered_map<vk::PipelineLayoutDesc, vk::SharedPipelineLayout> mPayload;
+};
+
+// 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 kDescriptorSetCount           = 2;
+
 }  // namespace rx
 
 #endif  // LIBANGLE_RENDERER_VULKAN_VK_CACHE_UTILS_H_
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index 6179b1f..f86beb2 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -331,12 +331,12 @@
 
 Error DynamicDescriptorPool::allocateNewPool(const VkDevice &device)
 {
-    VkDescriptorPoolSize poolSizes[DescriptorPoolIndexCount];
-    poolSizes[UniformBufferIndex].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
-    poolSizes[UniformBufferIndex].descriptorCount =
+    VkDescriptorPoolSize poolSizes[kDescriptorSetCount];
+    poolSizes[kUniformsDescriptorSetIndex].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
+    poolSizes[kUniformsDescriptorSetIndex].descriptorCount =
         mUniformBufferDescriptorsPerSet * mMaxSetsPerPool;
-    poolSizes[TextureIndex].type            = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-    poolSizes[TextureIndex].descriptorCount =
+    poolSizes[kTextureDescriptorSetIndex].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    poolSizes[kTextureDescriptorSetIndex].descriptorCount =
         mCombinedImageSamplerDescriptorsPerSet * mMaxSetsPerPool;
 
     VkDescriptorPoolCreateInfo descriptorPoolInfo;
@@ -346,7 +346,7 @@
     descriptorPoolInfo.maxSets = mMaxSetsPerPool;
 
     // Reserve pools for uniform blocks and textures.
-    descriptorPoolInfo.poolSizeCount = DescriptorPoolIndexCount;
+    descriptorPoolInfo.poolSizeCount = kDescriptorSetCount;
     descriptorPoolInfo.pPoolSizes    = poolSizes;
 
     mCurrentAllocatedDescriptorSetCount = 0;
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index 10c6dd8..7c77cbc 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -86,13 +86,6 @@
 // Uses DescriptorPool to allocate descriptor sets as needed. If the descriptor pool
 // is full, we simply allocate a new pool to keep allocating descriptor sets as needed and
 // leave the renderer take care of the life time of the pools that become unused.
-enum DescriptorPoolIndex : uint8_t
-{
-    UniformBufferIndex       = 0,
-    TextureIndex             = 1,
-    DescriptorPoolIndexCount = 2
-};
-
 class DynamicDescriptorPool final : angle::NonCopyable
 {
   public: