Create the ProgramExecutableVk Class

The ProgramExecutableVk class is being created to collect data
structures that are common to both ProgramVks and ProgramPipelineVks,
as well as any shared functions. This allows callers to make
Program-/ProgramPipeline- specific queries without needing to know
exactly which responded by querying the current ProgramExecutableVk.
This will also allow the necessary data structures to only be
populated and stored within the ProgramExecutableVk when necessary
and reused as often as necessary.

A few things are being moved into this class:
- mVariableInfoMap
This information will be required to defer translating the SPIR-V
until when the vulkan pipeline layout is determined and the actual
locations are known.   This will also allow removing determining
these locations twice (during GLSL->SPIR-V compilation and pipeline
layout).
- createPipelineLayout()
- update*DescriptorSet()
- Descriptor Set Layout lists
- Pipeline Layout
- Various other functions/members related to pipeplines, descriptor
sets, etc.

Bug: angleproject:3570
Change-Id: I4b5ababeafec865148783c8ffd4c15f659f4856d
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2055656
Commit-Queue: Tim Van Patten <timvp@google.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/BUILD.gn b/src/libANGLE/renderer/vulkan/BUILD.gn
index 2468486..3875cad 100644
--- a/src/libANGLE/renderer/vulkan/BUILD.gn
+++ b/src/libANGLE/renderer/vulkan/BUILD.gn
@@ -43,6 +43,8 @@
   "OverlayVk.h",
   "PersistentCommandPool.cpp",
   "PersistentCommandPool.h",
+  "ProgramExecutableVk.cpp",
+  "ProgramExecutableVk.h",
   "ProgramPipelineVk.cpp",
   "ProgramPipelineVk.h",
   "ProgramVk.cpp",
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index ec32e90..e5cd2b5 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -1267,7 +1267,8 @@
 
     if (executable->hasTextures(mState))
     {
-        ANGLE_TRY(mProgram->updateTexturesDescriptorSet(this));
+        ANGLE_TRY(
+            mProgram->getExecutable().updateTexturesDescriptorSet(mProgram->getState(), this));
     }
 
     return angle::Result::Continue;
@@ -1345,8 +1346,8 @@
     if (executable->hasUniformBuffers(mState) || executable->hasStorageBuffers(mState) ||
         executable->hasAtomicCounterBuffers(mState) || executable->hasImages(mState))
     {
-        ANGLE_TRY(mProgram->updateShaderResourcesDescriptorSet(this, &mResourceUseList,
-                                                               commandBufferHelper));
+        ANGLE_TRY(mProgram->getExecutable().updateShaderResourcesDescriptorSet(
+            mProgram->getState(), this, &mResourceUseList, commandBufferHelper));
     }
     return angle::Result::Continue;
 }
@@ -1388,7 +1389,8 @@
                 &mResourceUseList, VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT, &bufferHelper);
         }
 
-        ANGLE_TRY(mProgram->updateTransformFeedbackDescriptorSet(this));
+        ANGLE_TRY(mProgram->getExecutable().updateTransformFeedbackDescriptorSet(
+            mProgram->getState(), mProgram->getDefaultUniformBlocks(), this));
     }
     return angle::Result::Continue;
 }
@@ -1466,7 +1468,7 @@
 angle::Result ContextVk::handleDirtyDescriptorSets(const gl::Context *context,
                                                    vk::CommandBuffer *commandBuffer)
 {
-    ANGLE_TRY(mProgram->updateDescriptorSets(this, commandBuffer));
+    ANGLE_TRY(mProgram->getExecutable().updateDescriptorSets(this, commandBuffer));
     return angle::Result::Continue;
 }
 
diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
new file mode 100644
index 0000000..04e67e1
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
@@ -0,0 +1,1052 @@
+//
+// Copyright 2020 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.
+//
+// ProgramExecutableVk.cpp: Collects the information and interfaces common to both ProgramVks and
+// ProgramPipelineVks in order to execute/draw with either.
+
+#include "libANGLE/renderer/vulkan/ProgramExecutableVk.h"
+
+#include "libANGLE/renderer/vulkan/BufferVk.h"
+#include "libANGLE/renderer/vulkan/TextureVk.h"
+#include "libANGLE/renderer/vulkan/TransformFeedbackVk.h"
+#include "libANGLE/renderer/vulkan/vk_helpers.h"
+
+namespace rx
+{
+
+DefaultUniformBlock::DefaultUniformBlock() = default;
+
+DefaultUniformBlock::~DefaultUniformBlock() = default;
+
+ProgramExecutableVk::ProgramExecutableVk()
+    : mStorageBlockBindingsOffset(0),
+      mAtomicCounterBufferBindingsOffset(0),
+      mImageBindingsOffset(0),
+      mEmptyDescriptorSets{},
+      mDynamicBufferOffsets{}
+{}
+
+ProgramExecutableVk::~ProgramExecutableVk() = default;
+
+void ProgramExecutableVk::reset(ContextVk *contextVk)
+{
+    RendererVk *renderer = contextVk->getRenderer();
+
+    clearVariableInfoMap();
+
+    for (auto &descriptorSetLayout : mDescriptorSetLayouts)
+    {
+        descriptorSetLayout.reset();
+    }
+    mPipelineLayout.reset();
+
+    mEmptyBuffer.release(renderer);
+
+    mDescriptorSets.clear();
+    mEmptyDescriptorSets.fill(VK_NULL_HANDLE);
+
+    for (vk::RefCountedDescriptorPoolBinding &binding : mDescriptorPoolBindings)
+    {
+        binding.reset();
+    }
+
+    for (vk::DynamicDescriptorPool &descriptorPool : mDynamicDescriptorPools)
+    {
+        descriptorPool.release(contextVk);
+    }
+
+    mTextureDescriptorsCache.clear();
+    mDescriptorBuffersCache.clear();
+}
+
+void ProgramExecutableVk::clearVariableInfoMap()
+{
+    mVariableInfoMap.clear();
+}
+
+void ProgramExecutableVk::updateBindingOffsets(const gl::ProgramState &programState)
+{
+    mStorageBlockBindingsOffset = static_cast<uint32_t>(programState.getUniqueUniformBlockCount());
+
+    mAtomicCounterBufferBindingsOffset = static_cast<uint32_t>(
+        mStorageBlockBindingsOffset + programState.getUniqueStorageBlockCount());
+
+    uint32_t atomicCounterBindingCount = programState.getAtomicCounterBuffers().empty() ? 0 : 1;
+    mImageBindingsOffset = mAtomicCounterBufferBindingsOffset + atomicCounterBindingCount;
+}
+
+uint32_t GetInterfaceBlockArraySize(const std::vector<gl::InterfaceBlock> &blocks,
+                                    uint32_t bufferIndex)
+{
+    const gl::InterfaceBlock &block = blocks[bufferIndex];
+
+    if (!block.isArray)
+    {
+        return 1;
+    }
+
+    ASSERT(block.arrayElement == 0);
+
+    // Search consecutively until all array indices of this block are visited.
+    uint32_t arraySize;
+    for (arraySize = 1; bufferIndex + arraySize < blocks.size(); ++arraySize)
+    {
+        const gl::InterfaceBlock &nextBlock = blocks[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 == block.name);
+        ASSERT(nextBlock.isArray);
+    }
+
+    return arraySize;
+}
+
+angle::Result ProgramExecutableVk::allocateDescriptorSet(ContextVk *contextVk,
+                                                         uint32_t descriptorSetIndex)
+{
+    bool ignoreNewPoolAllocated;
+    return allocateDescriptorSetAndGetInfo(contextVk, descriptorSetIndex, &ignoreNewPoolAllocated);
+}
+
+angle::Result ProgramExecutableVk::allocateDescriptorSetAndGetInfo(ContextVk *contextVk,
+                                                                   uint32_t descriptorSetIndex,
+                                                                   bool *newPoolAllocatedOut)
+{
+    vk::DynamicDescriptorPool &dynamicDescriptorPool = mDynamicDescriptorPools[descriptorSetIndex];
+
+    uint32_t potentialNewCount = descriptorSetIndex + 1;
+    if (potentialNewCount > mDescriptorSets.size())
+    {
+        mDescriptorSets.resize(potentialNewCount, VK_NULL_HANDLE);
+    }
+
+    const vk::DescriptorSetLayout &descriptorSetLayout =
+        mDescriptorSetLayouts[descriptorSetIndex].get();
+    ANGLE_TRY(dynamicDescriptorPool.allocateSetsAndGetInfo(
+        contextVk, descriptorSetLayout.ptr(), 1, &mDescriptorPoolBindings[descriptorSetIndex],
+        &mDescriptorSets[descriptorSetIndex], newPoolAllocatedOut));
+    mEmptyDescriptorSets[descriptorSetIndex] = VK_NULL_HANDLE;
+
+    return angle::Result::Continue;
+}
+
+void AddInterfaceBlockDescriptorSetDesc(const std::vector<gl::InterfaceBlock> &blocks,
+                                        uint32_t bindingStart,
+                                        VkDescriptorType descType,
+                                        vk::DescriptorSetLayoutDesc *descOut)
+{
+    uint32_t bindingIndex = 0;
+    for (uint32_t bufferIndex = 0; bufferIndex < blocks.size();)
+    {
+        const uint32_t arraySize = GetInterfaceBlockArraySize(blocks, bufferIndex);
+        VkShaderStageFlags activeStages =
+            gl_vk::GetShaderStageFlags(blocks[bufferIndex].activeShaders());
+
+        descOut->update(bindingStart + bindingIndex, descType, arraySize, activeStages);
+
+        bufferIndex += arraySize;
+        ++bindingIndex;
+    }
+}
+
+void AddAtomicCounterBufferDescriptorSetDesc(
+    const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers,
+    uint32_t bindingStart,
+    vk::DescriptorSetLayoutDesc *descOut)
+{
+    if (atomicCounterBuffers.empty())
+    {
+        return;
+    }
+
+    VkShaderStageFlags activeStages = 0;
+    for (const gl::AtomicCounterBuffer &buffer : atomicCounterBuffers)
+    {
+        activeStages |= gl_vk::GetShaderStageFlags(buffer.activeShaders());
+    }
+
+    // A single storage buffer array is used for all stages for simplicity.
+    descOut->update(bindingStart, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+                    gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS, activeStages);
+}
+
+void AddImageDescriptorSetDesc(const gl::ProgramState &programState,
+                               uint32_t bindingStart,
+                               vk::DescriptorSetLayoutDesc *descOut)
+{
+    const std::vector<gl::ImageBinding> &imageBindings = programState.getImageBindings();
+    const std::vector<gl::LinkedUniform> &uniforms     = programState.getUniforms();
+
+    for (uint32_t imageIndex = 0; imageIndex < imageBindings.size(); ++imageIndex)
+    {
+        const gl::ImageBinding &imageBinding = imageBindings[imageIndex];
+
+        uint32_t uniformIndex = programState.getUniformIndexFromImageIndex(imageIndex);
+        const gl::LinkedUniform &imageUniform = uniforms[uniformIndex];
+
+        // The front-end always binds array image units sequentially.
+        uint32_t arraySize = static_cast<uint32_t>(imageBinding.boundImageUnits.size());
+        VkShaderStageFlags activeStages = gl_vk::GetShaderStageFlags(imageUniform.activeShaders());
+
+        uint32_t bindingIndex = bindingStart + imageIndex;
+        descOut->update(bindingIndex, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, arraySize, activeStages);
+    }
+}
+
+void AddTextureDescriptorSetDesc(const gl::ProgramState &programState,
+                                 bool useOldRewriteStructSamplers,
+                                 vk::DescriptorSetLayoutDesc *descOut)
+{
+    uint32_t bindingIndex                                  = 0;
+    const std::vector<gl::SamplerBinding> &samplerBindings = programState.getSamplerBindings();
+    const std::vector<gl::LinkedUniform> &uniforms         = programState.getUniforms();
+
+    for (uint32_t textureIndex = 0; textureIndex < samplerBindings.size(); ++textureIndex)
+    {
+        const gl::SamplerBinding &samplerBinding = samplerBindings[textureIndex];
+
+        uint32_t uniformIndex = programState.getUniformIndexFromSamplerIndex(textureIndex);
+        const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
+
+        // The front-end always binds array sampler units sequentially.
+        uint32_t arraySize = static_cast<uint32_t>(samplerBinding.boundTextureUnits.size());
+        VkShaderStageFlags activeStages =
+            gl_vk::GetShaderStageFlags(samplerUniform.activeShaders());
+
+        if (!useOldRewriteStructSamplers)
+        {
+            // 2D arrays are split into multiple 1D arrays when generating
+            // LinkedUniforms. Since they are flattened into one array, ignore the
+            // nonzero elements and expand the array to the total array size.
+            if (gl::SamplerNameContainsNonZeroArrayElement(samplerUniform.name))
+            {
+                continue;
+            }
+
+            for (unsigned int outerArraySize : samplerUniform.outerArraySizes)
+            {
+                arraySize *= outerArraySize;
+            }
+        }
+
+        descOut->update(bindingIndex++, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, arraySize,
+                        activeStages);
+    }
+}
+
+void WriteBufferDescriptorSetBinding(const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding,
+                                     VkDeviceSize maxSize,
+                                     VkDescriptorSet descSet,
+                                     VkDescriptorType descType,
+                                     uint32_t bindingIndex,
+                                     uint32_t arrayElement,
+                                     VkDeviceSize requiredOffsetAlignment,
+                                     VkDescriptorBufferInfo *bufferInfoOut,
+                                     VkWriteDescriptorSet *writeInfoOut)
+{
+    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);
+    VkDeviceSize offset            = bufferBinding.getOffset();
+    VkDeviceSize size              = bufferBinding.getSize();
+    vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
+
+    // If size is 0, we can't always use VK_WHOLE_SIZE (or bufferHelper.getSize()), as the
+    // backing buffer may be larger than max*BufferRange.  In that case, we use the minimum of
+    // the backing buffer size (what's left after offset) and the buffer size as defined by the
+    // shader.  That latter is only valid for UBOs, as SSBOs may have variable length arrays.
+    size = size > 0 ? size : (bufferHelper.getSize() - offset);
+    if (maxSize > 0)
+    {
+        size = std::min(size, maxSize);
+    }
+
+    // If requiredOffsetAlignment is 0, the buffer offset is guaranteed to have the necessary
+    // alignment through other means (the backend specifying the alignment through a GLES limit that
+    // the frontend then enforces).  If it's not 0, we need to bind the buffer at an offset that's
+    // aligned.  The difference in offsets is communicated to the shader via driver uniforms.
+    if (requiredOffsetAlignment)
+    {
+        VkDeviceSize alignedOffset = (offset / requiredOffsetAlignment) * requiredOffsetAlignment;
+        VkDeviceSize offsetDiff    = offset - alignedOffset;
+
+        offset = alignedOffset;
+        size += offsetDiff;
+    }
+
+    bufferInfoOut->buffer = bufferHelper.getBuffer().getHandle();
+    bufferInfoOut->offset = offset;
+    bufferInfoOut->range  = size;
+
+    writeInfoOut->sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    writeInfoOut->pNext            = nullptr;
+    writeInfoOut->dstSet           = descSet;
+    writeInfoOut->dstBinding       = bindingIndex;
+    writeInfoOut->dstArrayElement  = arrayElement;
+    writeInfoOut->descriptorCount  = 1;
+    writeInfoOut->descriptorType   = descType;
+    writeInfoOut->pImageInfo       = nullptr;
+    writeInfoOut->pBufferInfo      = bufferInfoOut;
+    writeInfoOut->pTexelBufferView = nullptr;
+    ASSERT(writeInfoOut->pBufferInfo[0].buffer != VK_NULL_HANDLE);
+}
+
+angle::Result ProgramExecutableVk::createPipelineLayout(const gl::Context *glContext,
+                                                        const gl::ProgramExecutable &glExecutable,
+                                                        const gl::ProgramState &programState)
+{
+    const gl::State &glState                   = glContext->getState();
+    ContextVk *contextVk                       = vk::GetImpl(glContext);
+    RendererVk *renderer                       = contextVk->getRenderer();
+    gl::TransformFeedback *transformFeedback   = glState.getCurrentTransformFeedback();
+    const gl::ShaderBitSet &linkedShaderStages = glExecutable.getLinkedShaderStages();
+
+    reset(contextVk);
+    updateBindingOffsets(programState);
+
+    // Store a reference to the pipeline and descriptor set layouts. This will create them if they
+    // don't already exist in the cache.
+
+    // Default uniforms and transform feedback:
+    vk::DescriptorSetLayoutDesc uniformsAndXfbSetDesc;
+    uint32_t uniformBindingIndex = 0;
+    for (const gl::ShaderType shaderType : linkedShaderStages)
+    {
+        uniformsAndXfbSetDesc.update(uniformBindingIndex++,
+                                     VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
+                                     gl_vk::kShaderStageMap[shaderType]);
+    }
+    bool hasVertexShader = glExecutable.hasLinkedShaderStage(gl::ShaderType::Vertex);
+    bool hasXfbVaryings  = !programState.getLinkedTransformFeedbackVaryings().empty();
+    if (hasVertexShader && transformFeedback && hasXfbVaryings)
+    {
+        size_t xfbBufferCount                    = programState.getTransformFeedbackBufferCount();
+        TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(transformFeedback);
+        transformFeedbackVk->updateDescriptorSetLayout(contextVk, xfbBufferCount,
+                                                       &uniformsAndXfbSetDesc);
+    }
+
+    ANGLE_TRY(renderer->getDescriptorSetLayout(
+        contextVk, uniformsAndXfbSetDesc,
+        &mDescriptorSetLayouts[kUniformsAndXfbDescriptorSetIndex]));
+
+    // Uniform and storage buffers, atomic counter buffers and images:
+    vk::DescriptorSetLayoutDesc resourcesSetDesc;
+
+    AddInterfaceBlockDescriptorSetDesc(programState.getUniformBlocks(),
+                                       getUniformBlockBindingsOffset(),
+                                       VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, &resourcesSetDesc);
+    AddInterfaceBlockDescriptorSetDesc(programState.getShaderStorageBlocks(),
+                                       getStorageBlockBindingsOffset(),
+                                       VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resourcesSetDesc);
+    AddAtomicCounterBufferDescriptorSetDesc(programState.getAtomicCounterBuffers(),
+                                            getAtomicCounterBufferBindingsOffset(),
+                                            &resourcesSetDesc);
+    AddImageDescriptorSetDesc(programState, getImageBindingsOffset(), &resourcesSetDesc);
+
+    ANGLE_TRY(renderer->getDescriptorSetLayout(
+        contextVk, resourcesSetDesc, &mDescriptorSetLayouts[kShaderResourceDescriptorSetIndex]));
+
+    // Textures:
+    vk::DescriptorSetLayoutDesc texturesSetDesc;
+
+    AddTextureDescriptorSetDesc(programState, contextVk->useOldRewriteStructSamplers(),
+                                &texturesSetDesc);
+
+    ANGLE_TRY(renderer->getDescriptorSetLayout(contextVk, texturesSetDesc,
+                                               &mDescriptorSetLayouts[kTextureDescriptorSetIndex]));
+
+    // Driver uniforms:
+    VkShaderStageFlags driverUniformsStages = programState.getProgramExecutable().isCompute()
+                                                  ? VK_SHADER_STAGE_COMPUTE_BIT
+                                                  : VK_SHADER_STAGE_ALL_GRAPHICS;
+    vk::DescriptorSetLayoutDesc driverUniformsSetDesc =
+        contextVk->getDriverUniformsDescriptorSetDesc(driverUniformsStages);
+    ANGLE_TRY(renderer->getDescriptorSetLayout(
+        contextVk, driverUniformsSetDesc,
+        &mDescriptorSetLayouts[kDriverUniformsDescriptorSetIndex]));
+
+    // Create pipeline layout with these 4 descriptor sets.
+    vk::PipelineLayoutDesc pipelineLayoutDesc;
+    pipelineLayoutDesc.updateDescriptorSetLayout(kUniformsAndXfbDescriptorSetIndex,
+                                                 uniformsAndXfbSetDesc);
+    pipelineLayoutDesc.updateDescriptorSetLayout(kShaderResourceDescriptorSetIndex,
+                                                 resourcesSetDesc);
+    pipelineLayoutDesc.updateDescriptorSetLayout(kTextureDescriptorSetIndex, texturesSetDesc);
+    pipelineLayoutDesc.updateDescriptorSetLayout(kDriverUniformsDescriptorSetIndex,
+                                                 driverUniformsSetDesc);
+
+    ANGLE_TRY(renderer->getPipelineLayout(contextVk, pipelineLayoutDesc, mDescriptorSetLayouts,
+                                          &mPipelineLayout));
+
+    // Initialize descriptor pools.
+    std::array<VkDescriptorPoolSize, 2> uniformAndXfbSetSize = {
+        {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
+          static_cast<uint32_t>(programState.getProgramExecutable().getLinkedShaderStageCount())},
+         {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS}}};
+
+    uint32_t uniformBlockCount = static_cast<uint32_t>(programState.getUniformBlocks().size());
+    uint32_t storageBlockCount =
+        static_cast<uint32_t>(programState.getShaderStorageBlocks().size());
+    uint32_t atomicCounterBufferCount =
+        static_cast<uint32_t>(programState.getAtomicCounterBuffers().size());
+    uint32_t imageCount   = static_cast<uint32_t>(programState.getImageBindings().size());
+    uint32_t textureCount = static_cast<uint32_t>(programState.getSamplerBindings().size());
+
+    if (renderer->getFeatures().bindEmptyForUnusedDescriptorSets.enabled)
+    {
+        // For this workaround, we have to create an empty descriptor set for each descriptor set
+        // index, so make sure their pools are initialized.
+        uniformBlockCount = std::max(uniformBlockCount, 1u);
+        textureCount      = std::max(textureCount, 1u);
+    }
+
+    constexpr size_t kResourceTypesInResourcesSet = 3;
+    angle::FixedVector<VkDescriptorPoolSize, kResourceTypesInResourcesSet> resourceSetSize;
+    if (uniformBlockCount > 0)
+    {
+        resourceSetSize.emplace_back(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uniformBlockCount);
+    }
+    if (storageBlockCount > 0 || atomicCounterBufferCount > 0)
+    {
+        // Note that we always use an array of IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS storage
+        // buffers for emulating atomic counters, so if there are any atomic counter buffers, we
+        // need to allocate IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS descriptors.
+        const uint32_t atomicCounterStorageBufferCount =
+            atomicCounterBufferCount > 0 ? gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS : 0;
+        const uint32_t storageBufferDescCount = storageBlockCount + atomicCounterStorageBufferCount;
+        resourceSetSize.emplace_back(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, storageBufferDescCount);
+    }
+    if (imageCount > 0)
+    {
+        resourceSetSize.emplace_back(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, imageCount);
+    }
+
+    VkDescriptorPoolSize textureSetSize = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, textureCount};
+
+    ANGLE_TRY(mDynamicDescriptorPools[kUniformsAndXfbDescriptorSetIndex].init(
+        contextVk, uniformAndXfbSetSize.data(), uniformAndXfbSetSize.size()));
+    if (resourceSetSize.size() > 0)
+    {
+        ANGLE_TRY(mDynamicDescriptorPools[kShaderResourceDescriptorSetIndex].init(
+            contextVk, resourceSetSize.data(), static_cast<uint32_t>(resourceSetSize.size())));
+    }
+    if (textureCount > 0)
+    {
+        ANGLE_TRY(mDynamicDescriptorPools[kTextureDescriptorSetIndex].init(contextVk,
+                                                                           &textureSetSize, 1));
+    }
+
+    mDynamicBufferOffsets.resize(programState.getProgramExecutable().getLinkedShaderStageCount());
+
+    // Initialize an "empty" buffer for use with default uniform blocks where there are no uniforms,
+    // or atomic counter buffer array indices that are unused.
+    constexpr VkBufferUsageFlags kEmptyBufferUsage =
+        VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+
+    VkBufferCreateInfo emptyBufferInfo    = {};
+    emptyBufferInfo.sType                 = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    emptyBufferInfo.flags                 = 0;
+    emptyBufferInfo.size                  = 4;
+    emptyBufferInfo.usage                 = kEmptyBufferUsage;
+    emptyBufferInfo.sharingMode           = VK_SHARING_MODE_EXCLUSIVE;
+    emptyBufferInfo.queueFamilyIndexCount = 0;
+    emptyBufferInfo.pQueueFamilyIndices   = nullptr;
+
+    constexpr VkMemoryPropertyFlags kMemoryType = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+    return mEmptyBuffer.init(contextVk, emptyBufferInfo, kMemoryType);
+}
+
+void ProgramExecutableVk::updateDefaultUniformsDescriptorSet(
+    const gl::ProgramState &programState,
+    gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks,
+    ContextVk *contextVk)
+{
+    uint32_t shaderStageCount =
+        static_cast<uint32_t>(programState.getProgramExecutable().getLinkedShaderStageCount());
+
+    gl::ShaderVector<VkDescriptorBufferInfo> descriptorBufferInfo(shaderStageCount);
+    gl::ShaderVector<VkWriteDescriptorSet> writeDescriptorInfo(shaderStageCount);
+
+    uint32_t bindingIndex = 0;
+
+    mDescriptorBuffersCache.clear();
+
+    // Write default uniforms for each shader type.
+    for (const gl::ShaderType shaderType :
+         programState.getProgramExecutable().getLinkedShaderStages())
+    {
+        DefaultUniformBlock &uniformBlock  = defaultUniformBlocks[shaderType];
+        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[bindingIndex];
+        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[bindingIndex];
+
+        if (!uniformBlock.uniformData.empty())
+        {
+            vk::BufferHelper *bufferHelper = uniformBlock.storage.getCurrentBuffer();
+            bufferInfo.buffer              = bufferHelper->getBuffer().getHandle();
+            mDescriptorBuffersCache.emplace_back(bufferHelper);
+        }
+        else
+        {
+            mEmptyBuffer.retain(&contextVk->getResourceUseList());
+            bufferInfo.buffer = mEmptyBuffer.getBuffer().getHandle();
+            mDescriptorBuffersCache.emplace_back(&mEmptyBuffer);
+        }
+
+        bufferInfo.offset = 0;
+        bufferInfo.range  = VK_WHOLE_SIZE;
+
+        writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+        writeInfo.pNext            = nullptr;
+        writeInfo.dstSet           = mDescriptorSets[kUniformsAndXfbDescriptorSetIndex];
+        writeInfo.dstBinding       = bindingIndex;
+        writeInfo.dstArrayElement  = 0;
+        writeInfo.descriptorCount  = 1;
+        writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
+        writeInfo.pImageInfo       = nullptr;
+        writeInfo.pBufferInfo      = &bufferInfo;
+        writeInfo.pTexelBufferView = nullptr;
+
+        ++bindingIndex;
+    }
+
+    VkDevice device = contextVk->getDevice();
+
+    ASSERT(bindingIndex == shaderStageCount);
+    ASSERT(shaderStageCount <= kReservedDefaultUniformBindingCount);
+
+    vkUpdateDescriptorSets(device, shaderStageCount, writeDescriptorInfo.data(), 0, nullptr);
+}
+
+void ProgramExecutableVk::updateBuffersDescriptorSet(ContextVk *contextVk,
+                                                     vk::ResourceUseList *resourceUseList,
+                                                     CommandBufferHelper *commandBufferHelper,
+                                                     const std::vector<gl::InterfaceBlock> &blocks,
+                                                     VkDescriptorType descriptorType)
+{
+    if (blocks.empty())
+    {
+        return;
+    }
+
+    VkDescriptorSet descriptorSet = mDescriptorSets[kShaderResourceDescriptorSetIndex];
+
+    ASSERT(descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER ||
+           descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
+    const bool isStorageBuffer = descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+    const uint32_t bindingStart =
+        isStorageBuffer ? getStorageBlockBindingsOffset() : getUniformBlockBindingsOffset();
+
+    static_assert(
+        gl::IMPLEMENTATION_MAX_SHADER_STORAGE_BUFFER_BINDINGS >=
+            gl::IMPLEMENTATION_MAX_UNIFORM_BUFFER_BINDINGS,
+        "The descriptor arrays here would have inadequate size for uniform buffer objects");
+
+    gl::StorageBuffersArray<VkDescriptorBufferInfo> descriptorBufferInfo;
+    gl::StorageBuffersArray<VkWriteDescriptorSet> writeDescriptorInfo;
+    uint32_t writeCount = 0;
+    // The binding is incremented every time arrayElement 0 is encountered, which means there will
+    // be an increment right at the start.  Start from -1 to get 0 as the first binding.
+    int32_t currentBinding = -1;
+
+    // Write uniform or storage buffers.
+    const gl::State &glState = contextVk->getState();
+    for (uint32_t bufferIndex = 0; bufferIndex < blocks.size(); ++bufferIndex)
+    {
+        const gl::InterfaceBlock &block = blocks[bufferIndex];
+        const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding =
+            isStorageBuffer ? glState.getIndexedShaderStorageBuffer(block.binding)
+                            : glState.getIndexedUniformBuffer(block.binding);
+
+        if (!block.isArray || block.arrayElement == 0)
+        {
+            // Array indices of the same buffer binding are placed sequentially in `blocks`.
+            // Thus, the block binding is updated only when array index 0 is encountered.
+            ++currentBinding;
+        }
+
+        if (bufferBinding.get() == nullptr)
+        {
+            continue;
+        }
+
+        uint32_t binding          = bindingStart + currentBinding;
+        uint32_t arrayElement     = block.isArray ? block.arrayElement : 0;
+        VkDeviceSize maxBlockSize = isStorageBuffer ? 0 : block.dataSize;
+
+        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[writeCount];
+        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[writeCount];
+
+        WriteBufferDescriptorSetBinding(bufferBinding, maxBlockSize, descriptorSet, descriptorType,
+                                        binding, arrayElement, 0, &bufferInfo, &writeInfo);
+
+        BufferVk *bufferVk             = vk::GetImpl(bufferBinding.get());
+        vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
+
+        if (isStorageBuffer)
+        {
+            // We set the SHADER_READ_BIT to be conservative.
+            VkAccessFlags accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+            commandBufferHelper->bufferWrite(resourceUseList, accessFlags, &bufferHelper);
+        }
+        else
+        {
+            commandBufferHelper->bufferRead(resourceUseList, VK_ACCESS_UNIFORM_READ_BIT,
+                                            &bufferHelper);
+        }
+
+        ++writeCount;
+    }
+
+    VkDevice device = contextVk->getDevice();
+
+    vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
+}
+
+void ProgramExecutableVk::updateAtomicCounterBuffersDescriptorSet(
+    const gl::ProgramState &programState,
+    ContextVk *contextVk,
+    vk::ResourceUseList *resourceUseList,
+    CommandBufferHelper *commandBufferHelper)
+{
+    const gl::State &glState = contextVk->getState();
+    const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers =
+        programState.getAtomicCounterBuffers();
+
+    if (atomicCounterBuffers.empty())
+    {
+        return;
+    }
+
+    VkDescriptorSet descriptorSet = mDescriptorSets[kShaderResourceDescriptorSetIndex];
+
+    const uint32_t bindingStart = getAtomicCounterBufferBindingsOffset();
+
+    gl::AtomicCounterBuffersArray<VkDescriptorBufferInfo> descriptorBufferInfo;
+    gl::AtomicCounterBuffersArray<VkWriteDescriptorSet> writeDescriptorInfo;
+    gl::AtomicCounterBufferMask writtenBindings;
+
+    RendererVk *rendererVk = contextVk->getRenderer();
+    const VkDeviceSize requiredOffsetAlignment =
+        rendererVk->getPhysicalDeviceProperties().limits.minStorageBufferOffsetAlignment;
+
+    // Write atomic counter buffers.
+    for (uint32_t bufferIndex = 0; bufferIndex < atomicCounterBuffers.size(); ++bufferIndex)
+    {
+        const gl::AtomicCounterBuffer &atomicCounterBuffer = atomicCounterBuffers[bufferIndex];
+        uint32_t binding                                   = atomicCounterBuffer.binding;
+        const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding =
+            glState.getIndexedAtomicCounterBuffer(binding);
+
+        if (bufferBinding.get() == nullptr)
+        {
+            continue;
+        }
+
+        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[binding];
+        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[binding];
+
+        WriteBufferDescriptorSetBinding(bufferBinding, 0, descriptorSet,
+                                        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, bindingStart, binding,
+                                        requiredOffsetAlignment, &bufferInfo, &writeInfo);
+
+        BufferVk *bufferVk             = vk::GetImpl(bufferBinding.get());
+        vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
+
+        // We set SHADER_READ_BIT to be conservative.
+        commandBufferHelper->bufferWrite(
+            resourceUseList, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, &bufferHelper);
+
+        writtenBindings.set(binding);
+    }
+
+    // Bind the empty buffer to every array slot that's unused.
+    mEmptyBuffer.retain(&contextVk->getResourceUseList());
+    for (size_t binding : ~writtenBindings)
+    {
+        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[binding];
+        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[binding];
+
+        bufferInfo.buffer = mEmptyBuffer.getBuffer().getHandle();
+        bufferInfo.offset = 0;
+        bufferInfo.range  = VK_WHOLE_SIZE;
+
+        writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+        writeInfo.pNext            = nullptr;
+        writeInfo.dstSet           = descriptorSet;
+        writeInfo.dstBinding       = bindingStart;
+        writeInfo.dstArrayElement  = static_cast<uint32_t>(binding);
+        writeInfo.descriptorCount  = 1;
+        writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+        writeInfo.pImageInfo       = nullptr;
+        writeInfo.pBufferInfo      = &bufferInfo;
+        writeInfo.pTexelBufferView = nullptr;
+    }
+
+    VkDevice device = contextVk->getDevice();
+
+    vkUpdateDescriptorSets(device, gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS,
+                           writeDescriptorInfo.data(), 0, nullptr);
+}
+
+angle::Result ProgramExecutableVk::updateImagesDescriptorSet(const gl::ProgramState &programState,
+                                                             ContextVk *contextVk)
+{
+    const gl::State &glState                           = contextVk->getState();
+    const std::vector<gl::ImageBinding> &imageBindings = programState.getImageBindings();
+
+    if (imageBindings.empty())
+    {
+        return angle::Result::Continue;
+    }
+
+    VkDescriptorSet descriptorSet = mDescriptorSets[kShaderResourceDescriptorSetIndex];
+
+    const gl::ActiveTextureArray<TextureVk *> &activeImages = contextVk->getActiveImages();
+
+    const uint32_t bindingStart = getImageBindingsOffset();
+
+    gl::ImagesArray<VkDescriptorImageInfo> descriptorImageInfo;
+    gl::ImagesArray<VkWriteDescriptorSet> writeDescriptorInfo;
+    uint32_t writeCount = 0;
+
+    // Write images.
+    for (uint32_t imageIndex = 0; imageIndex < imageBindings.size(); ++imageIndex)
+    {
+        const gl::ImageBinding &imageBinding = imageBindings[imageIndex];
+
+        ASSERT(!imageBinding.unreferenced);
+
+        for (uint32_t arrayElement = 0; arrayElement < imageBinding.boundImageUnits.size();
+             ++arrayElement)
+        {
+            GLuint imageUnit             = imageBinding.boundImageUnits[arrayElement];
+            const gl::ImageUnit &binding = glState.getImageUnit(imageUnit);
+            TextureVk *textureVk         = activeImages[imageUnit];
+
+            vk::ImageHelper *image         = &textureVk->getImage();
+            const vk::ImageView *imageView = nullptr;
+
+            ANGLE_TRY(textureVk->getStorageImageView(contextVk, (binding.layered == GL_TRUE),
+                                                     binding.level, binding.layer, &imageView));
+
+            // Note: binding.access is unused because it is implied by the shader.
+
+            // TODO(syoussefi): Support image data reinterpretation by using binding.format.
+            // http://anglebug.com/3563
+
+            VkDescriptorImageInfo &imageInfo = descriptorImageInfo[writeCount];
+            VkWriteDescriptorSet &writeInfo  = writeDescriptorInfo[writeCount];
+
+            imageInfo.sampler     = VK_NULL_HANDLE;
+            imageInfo.imageView   = imageView->getHandle();
+            imageInfo.imageLayout = image->getCurrentLayout();
+
+            writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+            writeInfo.pNext            = nullptr;
+            writeInfo.dstSet           = descriptorSet;
+            writeInfo.dstBinding       = bindingStart + imageIndex;
+            writeInfo.dstArrayElement  = arrayElement;
+            writeInfo.descriptorCount  = 1;
+            writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+            writeInfo.pImageInfo       = &imageInfo;
+            writeInfo.pBufferInfo      = nullptr;
+            writeInfo.pTexelBufferView = nullptr;
+
+            ++writeCount;
+        }
+    }
+
+    VkDevice device = contextVk->getDevice();
+
+    vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
+
+    return angle::Result::Continue;
+}
+
+angle::Result ProgramExecutableVk::updateShaderResourcesDescriptorSet(
+    const gl::ProgramState &programState,
+    ContextVk *contextVk,
+    vk::ResourceUseList *resourceUseList,
+    CommandBufferHelper *commandBufferHelper)
+{
+    ANGLE_TRY(allocateDescriptorSet(contextVk, kShaderResourceDescriptorSetIndex));
+
+    updateBuffersDescriptorSet(contextVk, resourceUseList, commandBufferHelper,
+                               programState.getUniformBlocks(), VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
+    updateBuffersDescriptorSet(contextVk, resourceUseList, commandBufferHelper,
+                               programState.getShaderStorageBlocks(),
+                               VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
+    updateAtomicCounterBuffersDescriptorSet(programState, contextVk, resourceUseList,
+                                            commandBufferHelper);
+    return updateImagesDescriptorSet(programState, contextVk);
+}
+
+angle::Result ProgramExecutableVk::updateTransformFeedbackDescriptorSet(
+    const gl::ProgramState &programState,
+    gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks,
+    ContextVk *contextVk)
+{
+    const gl::State &glState                = contextVk->getState();
+    const gl::ProgramExecutable &executable = programState.getProgramExecutable();
+    ASSERT(executable.hasTransformFeedbackOutput(glState));
+
+    ANGLE_TRY(allocateDescriptorSet(contextVk, kUniformsAndXfbDescriptorSetIndex));
+
+    updateDefaultUniformsDescriptorSet(programState, defaultUniformBlocks, contextVk);
+    updateTransformFeedbackDescriptorSetImpl(programState, contextVk);
+
+    return angle::Result::Continue;
+}
+
+void ProgramExecutableVk::updateTransformFeedbackDescriptorSetImpl(
+    const gl::ProgramState &programState,
+    ContextVk *contextVk)
+{
+    const gl::State &glState                 = contextVk->getState();
+    gl::TransformFeedback *transformFeedback = glState.getCurrentTransformFeedback();
+    const gl::ProgramExecutable &executable  = programState.getProgramExecutable();
+
+    if (!executable.hasTransformFeedbackOutput(glState))
+    {
+        // If xfb has no output there is no need to update descriptor set.
+        return;
+    }
+    if (!glState.isTransformFeedbackActive())
+    {
+        // We set empty Buffer to xfb descriptor set because xfb descriptor set
+        // requires valid buffer bindings, even if they are empty buffer,
+        // otherwise Vulkan validation layer generates errors.
+        if (transformFeedback)
+        {
+            TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(transformFeedback);
+            transformFeedbackVk->initDescriptorSet(
+                contextVk, programState.getTransformFeedbackBufferCount(), &mEmptyBuffer,
+                mDescriptorSets[kUniformsAndXfbDescriptorSetIndex]);
+        }
+        return;
+    }
+
+    TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(glState.getCurrentTransformFeedback());
+    transformFeedbackVk->updateDescriptorSet(contextVk, programState,
+                                             mDescriptorSets[kUniformsAndXfbDescriptorSetIndex]);
+}
+
+angle::Result ProgramExecutableVk::updateTexturesDescriptorSet(const gl::ProgramState &programState,
+                                                               ContextVk *contextVk)
+{
+    const gl::State &glState                      = contextVk->getState();
+    const vk::TextureDescriptorDesc &texturesDesc = contextVk->getActiveTexturesDesc();
+    const gl::ProgramExecutable &executable       = programState.getProgramExecutable();
+
+    auto iter = mTextureDescriptorsCache.find(texturesDesc);
+    if (iter != mTextureDescriptorsCache.end())
+    {
+        mDescriptorSets[kTextureDescriptorSetIndex] = iter->second;
+        return angle::Result::Continue;
+    }
+
+    ASSERT(executable.hasTextures(glState));
+    bool newPoolAllocated;
+    ANGLE_TRY(
+        allocateDescriptorSetAndGetInfo(contextVk, kTextureDescriptorSetIndex, &newPoolAllocated));
+
+    // Clear descriptor set cache. It may no longer be valid.
+    if (newPoolAllocated)
+    {
+        mTextureDescriptorsCache.clear();
+    }
+
+    VkDescriptorSet descriptorSet = mDescriptorSets[kTextureDescriptorSetIndex];
+
+    gl::ActiveTextureArray<VkDescriptorImageInfo> descriptorImageInfo;
+    gl::ActiveTextureArray<VkWriteDescriptorSet> writeDescriptorInfo;
+    uint32_t writeCount = 0;
+
+    const gl::ActiveTextureArray<vk::TextureUnit> &activeTextures = contextVk->getActiveTextures();
+
+    bool emulateSeamfulCubeMapSampling = contextVk->emulateSeamfulCubeMapSampling();
+    bool useOldRewriteStructSamplers   = contextVk->useOldRewriteStructSamplers();
+
+    std::unordered_map<std::string, uint32_t> mappedSamplerNameToBindingIndex;
+    std::unordered_map<std::string, uint32_t> mappedSamplerNameToArrayOffset;
+
+    uint32_t currentBindingIndex = 0;
+
+    for (uint32_t textureIndex = 0; textureIndex < programState.getSamplerBindings().size();
+         ++textureIndex)
+    {
+        const gl::SamplerBinding &samplerBinding = programState.getSamplerBindings()[textureIndex];
+
+        ASSERT(!samplerBinding.unreferenced);
+
+        uint32_t uniformIndex = programState.getUniformIndexFromSamplerIndex(textureIndex);
+        const gl::LinkedUniform &samplerUniform = programState.getUniforms()[uniformIndex];
+        std::string mappedSamplerName           = GlslangGetMappedSamplerName(samplerUniform.name);
+
+        if (useOldRewriteStructSamplers ||
+            mappedSamplerNameToBindingIndex.emplace(mappedSamplerName, currentBindingIndex).second)
+        {
+            currentBindingIndex++;
+        }
+
+        uint32_t bindingIndex = textureIndex;
+        uint32_t arrayOffset  = 0;
+        uint32_t arraySize    = static_cast<uint32_t>(samplerBinding.boundTextureUnits.size());
+
+        if (!useOldRewriteStructSamplers)
+        {
+            bindingIndex = mappedSamplerNameToBindingIndex[mappedSamplerName];
+            arrayOffset  = mappedSamplerNameToArrayOffset[mappedSamplerName];
+            // Front-end generates array elements in order, so we can just increment
+            // the offset each time we process a nested array.
+            mappedSamplerNameToArrayOffset[mappedSamplerName] += arraySize;
+        }
+
+        for (uint32_t arrayElement = 0; arrayElement < arraySize; ++arrayElement)
+        {
+            GLuint textureUnit   = samplerBinding.boundTextureUnits[arrayElement];
+            TextureVk *textureVk = activeTextures[textureUnit].texture;
+            SamplerVk *samplerVk = activeTextures[textureUnit].sampler;
+
+            vk::ImageHelper &image = textureVk->getImage();
+
+            VkDescriptorImageInfo &imageInfo = descriptorImageInfo[writeCount];
+
+            // Use bound sampler object if one present, otherwise use texture's sampler
+            const vk::Sampler &sampler =
+                (samplerVk != nullptr) ? samplerVk->getSampler() : textureVk->getSampler();
+
+            imageInfo.sampler     = sampler.getHandle();
+            imageInfo.imageLayout = image.getCurrentLayout();
+
+            if (emulateSeamfulCubeMapSampling)
+            {
+                // If emulating seamful cubemapping, use the fetch image view.  This is basically
+                // the same image view as read, except it's a 2DArray view for cube maps.
+                imageInfo.imageView =
+                    textureVk->getFetchImageViewAndRecordUse(contextVk).getHandle();
+            }
+            else
+            {
+                imageInfo.imageView =
+                    textureVk->getReadImageViewAndRecordUse(contextVk).getHandle();
+            }
+
+            VkWriteDescriptorSet &writeInfo = writeDescriptorInfo[writeCount];
+
+            writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+            writeInfo.pNext            = nullptr;
+            writeInfo.dstSet           = descriptorSet;
+            writeInfo.dstBinding       = bindingIndex;
+            writeInfo.dstArrayElement  = arrayOffset + arrayElement;
+            writeInfo.descriptorCount  = 1;
+            writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+            writeInfo.pImageInfo       = &imageInfo;
+            writeInfo.pBufferInfo      = nullptr;
+            writeInfo.pTexelBufferView = nullptr;
+
+            ++writeCount;
+        }
+    }
+
+    VkDevice device = contextVk->getDevice();
+
+    ASSERT(writeCount > 0);
+
+    vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
+
+    mTextureDescriptorsCache.emplace(texturesDesc, descriptorSet);
+
+    return angle::Result::Continue;
+}
+
+angle::Result ProgramExecutableVk::updateDescriptorSets(ContextVk *contextVk,
+                                                        vk::CommandBuffer *commandBuffer)
+{
+    // Can probably use better dirty bits here.
+
+    if (mDescriptorSets.empty())
+        return angle::Result::Continue;
+
+    // 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.
+    const size_t descriptorSetStart = kUniformsAndXfbDescriptorSetIndex;
+    size_t descriptorSetRange       = 0;
+    for (size_t descriptorSetIndex = descriptorSetStart;
+         descriptorSetIndex < mDescriptorSets.size(); ++descriptorSetIndex)
+    {
+        if (mDescriptorSets[descriptorSetIndex] != VK_NULL_HANDLE)
+        {
+            descriptorSetRange = descriptorSetIndex + 1;
+        }
+    }
+
+    const gl::State &glState                    = contextVk->getState();
+    const VkPipelineBindPoint pipelineBindPoint = glState.getProgramExecutable()->isCompute()
+                                                      ? VK_PIPELINE_BIND_POINT_COMPUTE
+                                                      : VK_PIPELINE_BIND_POINT_GRAPHICS;
+
+    for (uint32_t descriptorSetIndex = descriptorSetStart; descriptorSetIndex < descriptorSetRange;
+         ++descriptorSetIndex)
+    {
+        VkDescriptorSet descSet = mDescriptorSets[descriptorSetIndex];
+        if (descSet == VK_NULL_HANDLE)
+        {
+            if (!contextVk->getRenderer()->getFeatures().bindEmptyForUnusedDescriptorSets.enabled)
+            {
+                continue;
+            }
+
+            // Workaround a driver bug where missing (though unused) descriptor sets indices cause
+            // later sets to misbehave.
+            if (mEmptyDescriptorSets[descriptorSetIndex] == VK_NULL_HANDLE)
+            {
+                const vk::DescriptorSetLayout &descriptorSetLayout =
+                    mDescriptorSetLayouts[descriptorSetIndex].get();
+
+                ANGLE_TRY(mDynamicDescriptorPools[descriptorSetIndex].allocateSets(
+                    contextVk, descriptorSetLayout.ptr(), 1,
+                    &mDescriptorPoolBindings[descriptorSetIndex],
+                    &mEmptyDescriptorSets[descriptorSetIndex]));
+            }
+            descSet = mEmptyDescriptorSets[descriptorSetIndex];
+        }
+
+        // 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 == kUniformsAndXfbDescriptorSetIndex
+                ? static_cast<uint32_t>(mDynamicBufferOffsets.size())
+                : 0;
+
+        commandBuffer->bindDescriptorSets(mPipelineLayout.get(), pipelineBindPoint,
+                                          descriptorSetIndex, 1, &descSet, uniformBlockOffsetCount,
+                                          mDynamicBufferOffsets.data());
+    }
+
+    for (vk::BufferHelper *buffer : mDescriptorBuffersCache)
+    {
+        buffer->retain(&contextVk->getResourceUseList());
+    }
+
+    return angle::Result::Continue;
+}
+
+}  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
new file mode 100644
index 0000000..0435622
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
@@ -0,0 +1,145 @@
+//
+// Copyright 2020 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.
+//
+
+// ProgramExecutableVk.h: Collects the information and interfaces common to both ProgramVks and
+// ProgramPipelineVks in order to execute/draw with either.
+
+#ifndef LIBANGLE_RENDERER_VULKAN_PROGRAMEXECUTABLEVK_H_
+#define LIBANGLE_RENDERER_VULKAN_PROGRAMEXECUTABLEVK_H_
+
+#include "common/utilities.h"
+#include "libANGLE/Context.h"
+#include "libANGLE/InfoLog.h"
+#include "libANGLE/renderer/glslang_wrapper_utils.h"
+#include "libANGLE/renderer/vulkan/ContextVk.h"
+#include "libANGLE/renderer/vulkan/vk_helpers.h"
+
+namespace rx
+{
+
+// State for the default uniform blocks.
+struct DefaultUniformBlock final : private angle::NonCopyable
+{
+    DefaultUniformBlock();
+    ~DefaultUniformBlock();
+
+    vk::DynamicBuffer storage;
+
+    // Shadow copies of the shader uniform data.
+    angle::MemoryBuffer uniformData;
+
+    // Since the default blocks are laid out in std140, this tells us where to write on a call
+    // to a setUniform method. They are arranged in uniform location order.
+    std::vector<sh::BlockMemberInfo> uniformLayout;
+};
+
+class ProgramExecutableVk
+{
+  public:
+    ProgramExecutableVk();
+    virtual ~ProgramExecutableVk();
+
+    void reset(ContextVk *contextVk);
+
+    void clearVariableInfoMap();
+    ShaderInterfaceVariableInfoMap &getShaderInterfaceVariableInfoMap() { return mVariableInfoMap; }
+
+    angle::Result createPipelineLayout(const gl::Context *glContext,
+                                       const gl::ProgramExecutable &glExecutable,
+                                       const gl::ProgramState &programState);
+
+    angle::Result updateTexturesDescriptorSet(const gl::ProgramState &programState,
+                                              ContextVk *contextVk);
+    angle::Result updateShaderResourcesDescriptorSet(const gl::ProgramState &programState,
+                                                     ContextVk *contextVk,
+                                                     vk::ResourceUseList *resourceUseList,
+                                                     CommandBufferHelper *commandBufferHelper);
+    angle::Result updateTransformFeedbackDescriptorSet(
+        const gl::ProgramState &programState,
+        gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks,
+        ContextVk *contextVk);
+
+    angle::Result updateDescriptorSets(ContextVk *contextVk, vk::CommandBuffer *commandBuffer);
+
+  private:
+    friend class ProgramVk;
+    friend class ProgramPipelineVk;
+
+    void updateBindingOffsets(const gl::ProgramState &programState);
+    uint32_t getUniformBlockBindingsOffset() const { return 0; }
+    uint32_t getStorageBlockBindingsOffset() const { return mStorageBlockBindingsOffset; }
+    uint32_t getAtomicCounterBufferBindingsOffset() const
+    {
+        return mAtomicCounterBufferBindingsOffset;
+    }
+    uint32_t getImageBindingsOffset() const { return mImageBindingsOffset; }
+
+    angle::Result allocateDescriptorSet(ContextVk *contextVk, uint32_t descriptorSetIndex);
+    angle::Result allocateDescriptorSetAndGetInfo(ContextVk *contextVk,
+                                                  uint32_t descriptorSetIndex,
+                                                  bool *newPoolAllocatedOut);
+
+    void updateDefaultUniformsDescriptorSet(
+        const gl::ProgramState &programState,
+        gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks,
+        ContextVk *contextVk);
+    void updateTransformFeedbackDescriptorSetImpl(const gl::ProgramState &programState,
+                                                  ContextVk *contextVk);
+    void updateBuffersDescriptorSet(ContextVk *contextVk,
+                                    vk::ResourceUseList *resourceUseList,
+                                    CommandBufferHelper *commandBufferHelper,
+                                    const std::vector<gl::InterfaceBlock> &blocks,
+                                    VkDescriptorType descriptorType);
+    void updateAtomicCounterBuffersDescriptorSet(const gl::ProgramState &programState,
+                                                 ContextVk *contextVk,
+                                                 vk::ResourceUseList *resourceUseList,
+                                                 CommandBufferHelper *commandBufferHelper);
+    angle::Result updateImagesDescriptorSet(const gl::ProgramState &programState,
+                                            ContextVk *contextVk);
+
+    // In their descriptor set, uniform buffers are placed first, then storage buffers, then atomic
+    // counter buffers and then images.  These cached values contain the offsets where storage
+    // buffer, atomic counter buffer and image bindings start.
+    uint32_t mStorageBlockBindingsOffset;
+    uint32_t mAtomicCounterBufferBindingsOffset;
+    uint32_t mImageBindingsOffset;
+
+    // This is a special "empty" placeholder buffer for when a shader has no uniforms or doesn't
+    // use all slots in the atomic counter buffer array.
+    //
+    // It is necessary because we want to keep a compatible pipeline layout in all cases,
+    // and Vulkan does not tolerate having null handles in a descriptor set.
+    vk::BufferHelper mEmptyBuffer;
+
+    // Descriptor sets for uniform blocks and textures for this program.
+    std::vector<VkDescriptorSet> mDescriptorSets;
+    vk::DescriptorSetLayoutArray<VkDescriptorSet> mEmptyDescriptorSets;
+    std::vector<vk::BufferHelper *> mDescriptorBuffersCache;
+
+    std::unordered_map<vk::TextureDescriptorDesc, VkDescriptorSet> mTextureDescriptorsCache;
+
+    // We keep a reference to the pipeline and descriptor set layouts. This ensures they don't get
+    // deleted while this program is in use.
+    vk::BindingPointer<vk::PipelineLayout> mPipelineLayout;
+    vk::DescriptorSetLayoutPointerArray mDescriptorSetLayouts;
+
+    // Keep bindings to the descriptor pools. This ensures the pools stay valid while the Program
+    // is in use.
+    vk::DescriptorSetLayoutArray<vk::RefCountedDescriptorPoolBinding> mDescriptorPoolBindings;
+
+    // Store descriptor pools here. We store the descriptors in the Program to facilitate descriptor
+    // cache management. It can also allow fewer descriptors for shaders which use fewer
+    // textures/buffers.
+    vk::DescriptorSetLayoutArray<vk::DynamicDescriptorPool> mDynamicDescriptorPools;
+
+    gl::ShaderVector<uint32_t> mDynamicBufferOffsets;
+
+    ShaderInterfaceVariableInfoMap mVariableInfoMap;
+};
+
+}  // namespace rx
+
+#endif  // LIBANGLE_RENDERER_VULKAN_PROGRAMEXECUTABLEVK_H_
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index 1b33f92..b365a1f 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -153,205 +153,6 @@
     return angle::Result::Continue;
 }
 
-uint32_t GetInterfaceBlockArraySize(const std::vector<gl::InterfaceBlock> &blocks,
-                                    uint32_t bufferIndex)
-{
-    const gl::InterfaceBlock &block = blocks[bufferIndex];
-
-    if (!block.isArray)
-    {
-        return 1;
-    }
-
-    ASSERT(block.arrayElement == 0);
-
-    // Search consecutively until all array indices of this block are visited.
-    uint32_t arraySize;
-    for (arraySize = 1; bufferIndex + arraySize < blocks.size(); ++arraySize)
-    {
-        const gl::InterfaceBlock &nextBlock = blocks[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 == block.name);
-        ASSERT(nextBlock.isArray);
-    }
-
-    return arraySize;
-}
-
-void AddInterfaceBlockDescriptorSetDesc(const std::vector<gl::InterfaceBlock> &blocks,
-                                        uint32_t bindingStart,
-                                        VkDescriptorType descType,
-                                        vk::DescriptorSetLayoutDesc *descOut)
-{
-    uint32_t bindingIndex = 0;
-    for (uint32_t bufferIndex = 0; bufferIndex < blocks.size();)
-    {
-        const uint32_t arraySize = GetInterfaceBlockArraySize(blocks, bufferIndex);
-        VkShaderStageFlags activeStages =
-            gl_vk::GetShaderStageFlags(blocks[bufferIndex].activeShaders());
-
-        descOut->update(bindingStart + bindingIndex, descType, arraySize, activeStages);
-
-        bufferIndex += arraySize;
-        ++bindingIndex;
-    }
-}
-
-void AddAtomicCounterBufferDescriptorSetDesc(
-    const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers,
-    uint32_t bindingStart,
-    vk::DescriptorSetLayoutDesc *descOut)
-{
-    if (atomicCounterBuffers.empty())
-    {
-        return;
-    }
-
-    VkShaderStageFlags activeStages = 0;
-    for (const gl::AtomicCounterBuffer &buffer : atomicCounterBuffers)
-    {
-        activeStages |= gl_vk::GetShaderStageFlags(buffer.activeShaders());
-    }
-
-    // A single storage buffer array is used for all stages for simplicity.
-    descOut->update(bindingStart, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
-                    gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS, activeStages);
-}
-
-void AddImageDescriptorSetDesc(const gl::ProgramState &programState,
-                               uint32_t bindingStart,
-                               vk::DescriptorSetLayoutDesc *descOut)
-{
-    const std::vector<gl::ImageBinding> &imageBindings = programState.getImageBindings();
-    const std::vector<gl::LinkedUniform> &uniforms     = programState.getUniforms();
-
-    for (uint32_t imageIndex = 0; imageIndex < imageBindings.size(); ++imageIndex)
-    {
-        const gl::ImageBinding &imageBinding = imageBindings[imageIndex];
-
-        uint32_t uniformIndex = programState.getUniformIndexFromImageIndex(imageIndex);
-        const gl::LinkedUniform &imageUniform = uniforms[uniformIndex];
-
-        // The front-end always binds array image units sequentially.
-        uint32_t arraySize = static_cast<uint32_t>(imageBinding.boundImageUnits.size());
-        VkShaderStageFlags activeStages = gl_vk::GetShaderStageFlags(imageUniform.activeShaders());
-
-        uint32_t bindingIndex = bindingStart + imageIndex;
-        descOut->update(bindingIndex, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, arraySize, activeStages);
-    }
-}
-
-void AddTextureDescriptorSetDesc(const gl::ProgramState &programState,
-                                 bool useOldRewriteStructSamplers,
-                                 vk::DescriptorSetLayoutDesc *descOut)
-{
-    uint32_t bindingIndex                                  = 0;
-    const std::vector<gl::SamplerBinding> &samplerBindings = programState.getSamplerBindings();
-    const std::vector<gl::LinkedUniform> &uniforms         = programState.getUniforms();
-
-    for (uint32_t textureIndex = 0; textureIndex < samplerBindings.size(); ++textureIndex)
-    {
-        const gl::SamplerBinding &samplerBinding = samplerBindings[textureIndex];
-
-        uint32_t uniformIndex = programState.getUniformIndexFromSamplerIndex(textureIndex);
-        const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
-
-        // The front-end always binds array sampler units sequentially.
-        uint32_t arraySize = static_cast<uint32_t>(samplerBinding.boundTextureUnits.size());
-        VkShaderStageFlags activeStages =
-            gl_vk::GetShaderStageFlags(samplerUniform.activeShaders());
-
-        if (!useOldRewriteStructSamplers)
-        {
-            // 2D arrays are split into multiple 1D arrays when generating
-            // LinkedUniforms. Since they are flattened into one array, ignore the
-            // nonzero elements and expand the array to the total array size.
-            if (gl::SamplerNameContainsNonZeroArrayElement(samplerUniform.name))
-            {
-                continue;
-            }
-
-            for (unsigned int outerArraySize : samplerUniform.outerArraySizes)
-            {
-                arraySize *= outerArraySize;
-            }
-        }
-
-        descOut->update(bindingIndex++, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, arraySize,
-                        activeStages);
-    }
-}
-
-void WriteBufferDescriptorSetBinding(const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding,
-                                     VkDeviceSize maxSize,
-                                     VkDescriptorSet descSet,
-                                     VkDescriptorType descType,
-                                     uint32_t bindingIndex,
-                                     uint32_t arrayElement,
-                                     VkDeviceSize requiredOffsetAlignment,
-                                     VkDescriptorBufferInfo *bufferInfoOut,
-                                     VkWriteDescriptorSet *writeInfoOut)
-{
-    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);
-    VkDeviceSize offset            = bufferBinding.getOffset();
-    VkDeviceSize size              = bufferBinding.getSize();
-    vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
-
-    // If size is 0, we can't always use VK_WHOLE_SIZE (or bufferHelper.getSize()), as the
-    // backing buffer may be larger than max*BufferRange.  In that case, we use the minimum of
-    // the backing buffer size (what's left after offset) and the buffer size as defined by the
-    // shader.  That latter is only valid for UBOs, as SSBOs may have variable length arrays.
-    size = size > 0 ? size : (bufferHelper.getSize() - offset);
-    if (maxSize > 0)
-    {
-        size = std::min(size, maxSize);
-    }
-
-    // If requiredOffsetAlignment is 0, the buffer offset is guaranteed to have the necessary
-    // alignment through other means (the backend specifying the alignment through a GLES limit that
-    // the frontend then enforces).  If it's not 0, we need to bind the buffer at an offset that's
-    // aligned.  The difference in offsets is communicated to the shader via driver uniforms.
-    if (requiredOffsetAlignment)
-    {
-        VkDeviceSize alignedOffset = (offset / requiredOffsetAlignment) * requiredOffsetAlignment;
-        VkDeviceSize offsetDiff    = offset - alignedOffset;
-
-        offset = alignedOffset;
-        size += offsetDiff;
-    }
-
-    bufferInfoOut->buffer = bufferHelper.getBuffer().getHandle();
-    bufferInfoOut->offset = offset;
-    bufferInfoOut->range  = size;
-
-    writeInfoOut->sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-    writeInfoOut->pNext            = nullptr;
-    writeInfoOut->dstSet           = descSet;
-    writeInfoOut->dstBinding       = bindingIndex;
-    writeInfoOut->dstArrayElement  = arrayElement;
-    writeInfoOut->descriptorCount  = 1;
-    writeInfoOut->descriptorType   = descType;
-    writeInfoOut->pImageInfo       = nullptr;
-    writeInfoOut->pBufferInfo      = bufferInfoOut;
-    writeInfoOut->pTexelBufferView = nullptr;
-    ASSERT(writeInfoOut->pBufferInfo[0].buffer != VK_NULL_HANDLE);
-}
-
 class Std140BlockLayoutEncoderFactory : public gl::CustomBlockLayoutEncoderFactory
 {
   public:
@@ -460,16 +261,7 @@
 }
 
 // ProgramVk implementation.
-ProgramVk::DefaultUniformBlock::DefaultUniformBlock() {}
-
-ProgramVk::DefaultUniformBlock::~DefaultUniformBlock() = default;
-
-ProgramVk::ProgramVk(const gl::ProgramState &state)
-    : ProgramImpl(state),
-      mDynamicBufferOffsets{},
-      mStorageBlockBindingsOffset(0),
-      mAtomicCounterBufferBindingsOffset(0),
-      mImageBindingsOffset(0)
+ProgramVk::ProgramVk(const gl::ProgramState &state) : ProgramImpl(state)
 {
     GlslangWrapperVk::ResetGlslangProgramInterfaceInfo(&mGlslangProgramInterfaceInfo);
 }
@@ -484,42 +276,20 @@
 
 void ProgramVk::reset(ContextVk *contextVk)
 {
-    for (auto &descriptorSetLayout : mDescriptorSetLayouts)
-    {
-        descriptorSetLayout.reset();
-    }
-    mPipelineLayout.reset();
-
     RendererVk *renderer = contextVk->getRenderer();
 
+    mShaderInfo.release(contextVk);
+    mDefaultProgramInfo.release(contextVk);
+    mLineRasterProgramInfo.release(contextVk);
+
     for (auto &uniformBlock : mDefaultUniformBlocks)
     {
         uniformBlock.storage.release(renderer);
     }
 
-    mShaderInfo.release(contextVk);
-    mDefaultProgramInfo.release(contextVk);
-    mLineRasterProgramInfo.release(contextVk);
-
-    mEmptyBuffer.release(renderer);
-
-    mDescriptorSets.clear();
-    mEmptyDescriptorSets.fill(VK_NULL_HANDLE);
-
-    for (vk::RefCountedDescriptorPoolBinding &binding : mDescriptorPoolBindings)
-    {
-        binding.reset();
-    }
-
-    for (vk::DynamicDescriptorPool &descriptorPool : mDynamicDescriptorPools)
-    {
-        descriptorPool.release(contextVk);
-    }
-
-    mTextureDescriptorsCache.clear();
-    mDescriptorBuffersCache.clear();
-
     GlslangWrapperVk::ResetGlslangProgramInterfaceInfo(&mGlslangProgramInterfaceInfo);
+
+    mExecutable.reset(contextVk);
 }
 
 std::unique_ptr<rx::LinkEvent> ProgramVk::load(const gl::Context *context,
@@ -559,7 +329,9 @@
         return std::make_unique<LinkEventDone>(status);
     }
 
-    return std::make_unique<LinkEventDone>(linkImpl(context, infoLog));
+    const gl::ProgramExecutable &glExecutable = mState.getProgramExecutable();
+    status = mExecutable.createPipelineLayout(context, glExecutable, mState);
+    return std::make_unique<LinkEventDone>(status);
 }
 
 void ProgramVk::save(const gl::Context *context, gl::BinaryOutputStream *stream)
@@ -609,13 +381,13 @@
 
     // Gather variable info and transform sources.
     gl::ShaderMap<std::string> shaderSources;
-    ShaderInterfaceVariableInfoMap variableInfoMap;
     GlslangWrapperVk::GetShaderSource(contextVk->getRenderer()->getFeatures(), mState, resources,
                                       &mGlslangProgramInterfaceInfo, &shaderSources,
-                                      &variableInfoMap);
+                                      &mExecutable.mVariableInfoMap);
 
     // Compile the shaders.
-    angle::Result status = mShaderInfo.initShaders(contextVk, shaderSources, variableInfoMap);
+    angle::Result status =
+        mShaderInfo.initShaders(contextVk, shaderSources, mExecutable.mVariableInfoMap);
     if (status != angle::Result::Continue)
     {
         return std::make_unique<LinkEventDone>(status);
@@ -629,176 +401,9 @@
 
     // TODO(jie.a.chen@intel.com): Parallelize linking.
     // http://crbug.com/849576
-    return std::make_unique<LinkEventDone>(linkImpl(context, infoLog));
-}
-
-angle::Result ProgramVk::linkImpl(const gl::Context *glContext, gl::InfoLog &infoLog)
-{
-    const gl::State &glState                  = glContext->getState();
-    ContextVk *contextVk                      = vk::GetImpl(glContext);
-    RendererVk *renderer                      = contextVk->getRenderer();
-    gl::TransformFeedback *transformFeedback  = glState.getCurrentTransformFeedback();
     const gl::ProgramExecutable &glExecutable = mState.getProgramExecutable();
-
-    updateBindingOffsets();
-
-    // Store a reference to the pipeline and descriptor set layouts. This will create them if they
-    // don't already exist in the cache.
-
-    // Default uniforms and transform feedback:
-    vk::DescriptorSetLayoutDesc uniformsAndXfbSetDesc;
-    uint32_t uniformBindingIndex = 0;
-    for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
-    {
-        uniformsAndXfbSetDesc.update(uniformBindingIndex++,
-                                     VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
-                                     gl_vk::kShaderStageMap[shaderType]);
-    }
-    if (glExecutable.hasLinkedShaderStage(gl::ShaderType::Vertex) && transformFeedback &&
-        !mState.getLinkedTransformFeedbackVaryings().empty())
-    {
-        size_t xfbBufferCount                    = mState.getTransformFeedbackBufferCount();
-        TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(transformFeedback);
-        transformFeedbackVk->updateDescriptorSetLayout(contextVk, xfbBufferCount,
-                                                       &uniformsAndXfbSetDesc);
-    }
-
-    ANGLE_TRY(renderer->getDescriptorSetLayout(
-        contextVk, uniformsAndXfbSetDesc,
-        &mDescriptorSetLayouts[kUniformsAndXfbDescriptorSetIndex]));
-
-    // Uniform and storage buffers, atomic counter buffers and images:
-    vk::DescriptorSetLayoutDesc resourcesSetDesc;
-
-    AddInterfaceBlockDescriptorSetDesc(mState.getUniformBlocks(), getUniformBlockBindingsOffset(),
-                                       VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, &resourcesSetDesc);
-    AddInterfaceBlockDescriptorSetDesc(mState.getShaderStorageBlocks(),
-                                       getStorageBlockBindingsOffset(),
-                                       VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resourcesSetDesc);
-    AddAtomicCounterBufferDescriptorSetDesc(mState.getAtomicCounterBuffers(),
-                                            getAtomicCounterBufferBindingsOffset(),
-                                            &resourcesSetDesc);
-    AddImageDescriptorSetDesc(mState, getImageBindingsOffset(), &resourcesSetDesc);
-
-    ANGLE_TRY(renderer->getDescriptorSetLayout(
-        contextVk, resourcesSetDesc, &mDescriptorSetLayouts[kShaderResourceDescriptorSetIndex]));
-
-    // Textures:
-    vk::DescriptorSetLayoutDesc texturesSetDesc;
-
-    AddTextureDescriptorSetDesc(mState, contextVk->useOldRewriteStructSamplers(), &texturesSetDesc);
-
-    ANGLE_TRY(renderer->getDescriptorSetLayout(contextVk, texturesSetDesc,
-                                               &mDescriptorSetLayouts[kTextureDescriptorSetIndex]));
-
-    // Driver uniforms:
-    VkShaderStageFlags driverUniformsStages =
-        glExecutable.isCompute() ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_ALL_GRAPHICS;
-    vk::DescriptorSetLayoutDesc driverUniformsSetDesc =
-        contextVk->getDriverUniformsDescriptorSetDesc(driverUniformsStages);
-    ANGLE_TRY(renderer->getDescriptorSetLayout(
-        contextVk, driverUniformsSetDesc,
-        &mDescriptorSetLayouts[kDriverUniformsDescriptorSetIndex]));
-
-    // Create pipeline layout with these 4 descriptor sets.
-    vk::PipelineLayoutDesc pipelineLayoutDesc;
-    pipelineLayoutDesc.updateDescriptorSetLayout(kUniformsAndXfbDescriptorSetIndex,
-                                                 uniformsAndXfbSetDesc);
-    pipelineLayoutDesc.updateDescriptorSetLayout(kShaderResourceDescriptorSetIndex,
-                                                 resourcesSetDesc);
-    pipelineLayoutDesc.updateDescriptorSetLayout(kTextureDescriptorSetIndex, texturesSetDesc);
-    pipelineLayoutDesc.updateDescriptorSetLayout(kDriverUniformsDescriptorSetIndex,
-                                                 driverUniformsSetDesc);
-
-    ANGLE_TRY(renderer->getPipelineLayout(contextVk, pipelineLayoutDesc, mDescriptorSetLayouts,
-                                          &mPipelineLayout));
-
-    // Initialize descriptor pools.
-    std::array<VkDescriptorPoolSize, 2> uniformAndXfbSetSize = {
-        {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
-          static_cast<uint32_t>(glExecutable.getLinkedShaderStageCount())},
-         {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS}}};
-
-    uint32_t uniformBlockCount = static_cast<uint32_t>(mState.getUniformBlocks().size());
-    uint32_t storageBlockCount = static_cast<uint32_t>(mState.getShaderStorageBlocks().size());
-    uint32_t atomicCounterBufferCount =
-        static_cast<uint32_t>(mState.getAtomicCounterBuffers().size());
-    uint32_t imageCount   = static_cast<uint32_t>(mState.getImageBindings().size());
-    uint32_t textureCount = static_cast<uint32_t>(mState.getSamplerBindings().size());
-
-    if (renderer->getFeatures().bindEmptyForUnusedDescriptorSets.enabled)
-    {
-        // For this workaround, we have to create an empty descriptor set for each descriptor set
-        // index, so make sure their pools are initialized.
-        uniformBlockCount = std::max(uniformBlockCount, 1u);
-        textureCount      = std::max(textureCount, 1u);
-    }
-
-    constexpr size_t kResourceTypesInResourcesSet = 3;
-    angle::FixedVector<VkDescriptorPoolSize, kResourceTypesInResourcesSet> resourceSetSize;
-    if (uniformBlockCount > 0)
-    {
-        resourceSetSize.emplace_back(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uniformBlockCount);
-    }
-    if (storageBlockCount > 0 || atomicCounterBufferCount > 0)
-    {
-        // Note that we always use an array of IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS storage
-        // buffers for emulating atomic counters, so if there are any atomic counter buffers, we
-        // need to allocate IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS descriptors.
-        const uint32_t atomicCounterStorageBufferCount =
-            atomicCounterBufferCount > 0 ? gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS : 0;
-        const uint32_t storageBufferDescCount = storageBlockCount + atomicCounterStorageBufferCount;
-        resourceSetSize.emplace_back(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, storageBufferDescCount);
-    }
-    if (imageCount > 0)
-    {
-        resourceSetSize.emplace_back(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, imageCount);
-    }
-
-    VkDescriptorPoolSize textureSetSize = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, textureCount};
-
-    ANGLE_TRY(mDynamicDescriptorPools[kUniformsAndXfbDescriptorSetIndex].init(
-        contextVk, uniformAndXfbSetSize.data(), uniformAndXfbSetSize.size()));
-    if (resourceSetSize.size() > 0)
-    {
-        ANGLE_TRY(mDynamicDescriptorPools[kShaderResourceDescriptorSetIndex].init(
-            contextVk, resourceSetSize.data(), static_cast<uint32_t>(resourceSetSize.size())));
-    }
-    if (textureCount > 0)
-    {
-        ANGLE_TRY(mDynamicDescriptorPools[kTextureDescriptorSetIndex].init(contextVk,
-                                                                           &textureSetSize, 1));
-    }
-
-    mDynamicBufferOffsets.resize(glExecutable.getLinkedShaderStageCount());
-
-    // Initialize an "empty" buffer for use with default uniform blocks where there are no uniforms,
-    // or atomic counter buffer array indices that are unused.
-    constexpr VkBufferUsageFlags kEmptyBufferUsage =
-        VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
-
-    VkBufferCreateInfo emptyBufferInfo    = {};
-    emptyBufferInfo.sType                 = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
-    emptyBufferInfo.flags                 = 0;
-    emptyBufferInfo.size                  = 4;
-    emptyBufferInfo.usage                 = kEmptyBufferUsage;
-    emptyBufferInfo.sharingMode           = VK_SHARING_MODE_EXCLUSIVE;
-    emptyBufferInfo.queueFamilyIndexCount = 0;
-    emptyBufferInfo.pQueueFamilyIndices   = nullptr;
-
-    constexpr VkMemoryPropertyFlags kMemoryType = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
-    return mEmptyBuffer.init(contextVk, emptyBufferInfo, kMemoryType);
-}
-
-void ProgramVk::updateBindingOffsets()
-{
-    mStorageBlockBindingsOffset = static_cast<uint32_t>(mState.getUniqueUniformBlockCount());
-
-    mAtomicCounterBufferBindingsOffset =
-        static_cast<uint32_t>(mStorageBlockBindingsOffset + mState.getUniqueStorageBlockCount());
-
-    uint32_t atomicCounterBindingCount = mState.getAtomicCounterBuffers().empty() ? 0 : 1;
-    mImageBindingsOffset = mAtomicCounterBufferBindingsOffset + atomicCounterBindingCount;
+    status = mExecutable.createPipelineLayout(context, glExecutable, mState);
+    return std::make_unique<LinkEventDone>(status);
 }
 
 void ProgramVk::linkResources(const gl::ProgramLinkedResources &resources)
@@ -1191,34 +796,6 @@
     setUniformMatrixfv<4, 3>(location, count, transpose, value);
 }
 
-angle::Result ProgramVk::allocateDescriptorSet(ContextVk *contextVk, uint32_t descriptorSetIndex)
-{
-    bool ignoreNewPoolAllocated;
-    return allocateDescriptorSetAndGetInfo(contextVk, descriptorSetIndex, &ignoreNewPoolAllocated);
-}
-
-angle::Result ProgramVk::allocateDescriptorSetAndGetInfo(ContextVk *contextVk,
-                                                         uint32_t descriptorSetIndex,
-                                                         bool *newPoolAllocatedOut)
-{
-    vk::DynamicDescriptorPool &dynamicDescriptorPool = mDynamicDescriptorPools[descriptorSetIndex];
-
-    uint32_t potentialNewCount = descriptorSetIndex + 1;
-    if (potentialNewCount > mDescriptorSets.size())
-    {
-        mDescriptorSets.resize(potentialNewCount, VK_NULL_HANDLE);
-    }
-
-    const vk::DescriptorSetLayout &descriptorSetLayout =
-        mDescriptorSetLayouts[descriptorSetIndex].get();
-    ANGLE_TRY(dynamicDescriptorPool.allocateSetsAndGetInfo(
-        contextVk, descriptorSetLayout.ptr(), 1, &mDescriptorPoolBindings[descriptorSetIndex],
-        &mDescriptorSets[descriptorSetIndex], newPoolAllocatedOut));
-    mEmptyDescriptorSets[descriptorSetIndex] = VK_NULL_HANDLE;
-
-    return angle::Result::Continue;
-}
-
 void ProgramVk::getUniformfv(const gl::Context *context, GLint location, GLfloat *params) const
 {
     getUniformImpl(location, params, GL_FLOAT);
@@ -1250,9 +827,9 @@
         if (mDefaultUniformBlocksDirty[shaderType])
         {
             bool bufferModified = false;
-            ANGLE_TRY(
-                SyncDefaultUniformBlock(contextVk, &uniformBlock.storage, uniformBlock.uniformData,
-                                        &mDynamicBufferOffsets[offsetIndex], &bufferModified));
+            ANGLE_TRY(SyncDefaultUniformBlock(
+                contextVk, &uniformBlock.storage, uniformBlock.uniformData,
+                &mExecutable.mDynamicBufferOffsets[offsetIndex], &bufferModified));
             mDefaultUniformBlocksDirty.reset(shaderType);
 
             if (bufferModified)
@@ -1268,497 +845,14 @@
     {
         // 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, kUniformsAndXfbDescriptorSetIndex));
-        updateDefaultUniformsDescriptorSet(contextVk);
-        updateTransformFeedbackDescriptorSetImpl(contextVk);
+        ANGLE_TRY(mExecutable.allocateDescriptorSet(contextVk, kUniformsAndXfbDescriptorSetIndex));
+        mExecutable.updateDefaultUniformsDescriptorSet(mState, mDefaultUniformBlocks, contextVk);
+        mExecutable.updateTransformFeedbackDescriptorSetImpl(mState, contextVk);
     }
 
     return angle::Result::Continue;
 }
 
-void ProgramVk::updateDefaultUniformsDescriptorSet(ContextVk *contextVk)
-{
-    const gl::ProgramExecutable &glExecutable = mState.getProgramExecutable();
-    uint32_t shaderStageCount = static_cast<uint32_t>(glExecutable.getLinkedShaderStageCount());
-
-    gl::ShaderVector<VkDescriptorBufferInfo> descriptorBufferInfo(shaderStageCount);
-    gl::ShaderVector<VkWriteDescriptorSet> writeDescriptorInfo(shaderStageCount);
-
-    uint32_t bindingIndex = 0;
-
-    mDescriptorBuffersCache.clear();
-
-    // Write default uniforms for each shader type.
-    for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
-    {
-        DefaultUniformBlock &uniformBlock  = mDefaultUniformBlocks[shaderType];
-        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[bindingIndex];
-        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[bindingIndex];
-
-        if (!uniformBlock.uniformData.empty())
-        {
-            vk::BufferHelper *bufferHelper = uniformBlock.storage.getCurrentBuffer();
-            bufferInfo.buffer              = bufferHelper->getBuffer().getHandle();
-            mDescriptorBuffersCache.emplace_back(bufferHelper);
-        }
-        else
-        {
-            mEmptyBuffer.retain(&contextVk->getResourceUseList());
-            bufferInfo.buffer = mEmptyBuffer.getBuffer().getHandle();
-            mDescriptorBuffersCache.emplace_back(&mEmptyBuffer);
-        }
-
-        bufferInfo.offset = 0;
-        bufferInfo.range  = VK_WHOLE_SIZE;
-
-        writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-        writeInfo.pNext            = nullptr;
-        writeInfo.dstSet           = mDescriptorSets[kUniformsAndXfbDescriptorSetIndex];
-        writeInfo.dstBinding       = bindingIndex;
-        writeInfo.dstArrayElement  = 0;
-        writeInfo.descriptorCount  = 1;
-        writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
-        writeInfo.pImageInfo       = nullptr;
-        writeInfo.pBufferInfo      = &bufferInfo;
-        writeInfo.pTexelBufferView = nullptr;
-
-        ++bindingIndex;
-    }
-
-    VkDevice device = contextVk->getDevice();
-
-    ASSERT(bindingIndex == shaderStageCount);
-    ASSERT(shaderStageCount <= kReservedDefaultUniformBindingCount);
-
-    vkUpdateDescriptorSets(device, shaderStageCount, writeDescriptorInfo.data(), 0, nullptr);
-}
-
-void ProgramVk::updateBuffersDescriptorSet(ContextVk *contextVk,
-                                           vk::ResourceUseList *resourceUseList,
-                                           CommandBufferHelper *commandBufferHelper,
-                                           const std::vector<gl::InterfaceBlock> &blocks,
-                                           VkDescriptorType descriptorType)
-{
-    if (blocks.empty())
-    {
-        return;
-    }
-
-    VkDescriptorSet descriptorSet = mDescriptorSets[kShaderResourceDescriptorSetIndex];
-
-    ASSERT(descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER ||
-           descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
-    const bool isStorageBuffer = descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
-    const uint32_t bindingStart =
-        isStorageBuffer ? getStorageBlockBindingsOffset() : getUniformBlockBindingsOffset();
-
-    static_assert(
-        gl::IMPLEMENTATION_MAX_SHADER_STORAGE_BUFFER_BINDINGS >=
-            gl::IMPLEMENTATION_MAX_UNIFORM_BUFFER_BINDINGS,
-        "The descriptor arrays here would have inadequate size for uniform buffer objects");
-
-    gl::StorageBuffersArray<VkDescriptorBufferInfo> descriptorBufferInfo;
-    gl::StorageBuffersArray<VkWriteDescriptorSet> writeDescriptorInfo;
-    uint32_t writeCount = 0;
-    // The binding is incremented every time arrayElement 0 is encountered, which means there will
-    // be an increment right at the start.  Start from -1 to get 0 as the first binding.
-    int32_t currentBinding = -1;
-
-    // Write uniform or storage buffers.
-    const gl::State &glState = contextVk->getState();
-    for (uint32_t bufferIndex = 0; bufferIndex < blocks.size(); ++bufferIndex)
-    {
-        const gl::InterfaceBlock &block = blocks[bufferIndex];
-        const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding =
-            isStorageBuffer ? glState.getIndexedShaderStorageBuffer(block.binding)
-                            : glState.getIndexedUniformBuffer(block.binding);
-
-        if (!block.isArray || block.arrayElement == 0)
-        {
-            // Array indices of the same buffer binding are placed sequentially in `blocks`.
-            // Thus, the block binding is updated only when array index 0 is encountered.
-            ++currentBinding;
-        }
-
-        if (bufferBinding.get() == nullptr)
-        {
-            continue;
-        }
-
-        uint32_t binding          = bindingStart + currentBinding;
-        uint32_t arrayElement     = block.isArray ? block.arrayElement : 0;
-        VkDeviceSize maxBlockSize = isStorageBuffer ? 0 : block.dataSize;
-
-        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[writeCount];
-        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[writeCount];
-
-        WriteBufferDescriptorSetBinding(bufferBinding, maxBlockSize, descriptorSet, descriptorType,
-                                        binding, arrayElement, 0, &bufferInfo, &writeInfo);
-
-        BufferVk *bufferVk             = vk::GetImpl(bufferBinding.get());
-        vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
-
-        if (isStorageBuffer)
-        {
-            // We set the SHADER_READ_BIT to be conservative.
-            VkAccessFlags accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
-            commandBufferHelper->bufferWrite(resourceUseList, accessFlags, &bufferHelper);
-        }
-        else
-        {
-            commandBufferHelper->bufferRead(resourceUseList, VK_ACCESS_UNIFORM_READ_BIT,
-                                            &bufferHelper);
-        }
-
-        ++writeCount;
-    }
-
-    VkDevice device = contextVk->getDevice();
-
-    vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
-}
-
-void ProgramVk::updateAtomicCounterBuffersDescriptorSet(ContextVk *contextVk,
-                                                        vk::ResourceUseList *resourceUseList,
-                                                        CommandBufferHelper *commandBufferHelper)
-{
-    const gl::State &glState = contextVk->getState();
-    const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers =
-        mState.getAtomicCounterBuffers();
-
-    if (atomicCounterBuffers.empty())
-    {
-        return;
-    }
-
-    VkDescriptorSet descriptorSet = mDescriptorSets[kShaderResourceDescriptorSetIndex];
-
-    const uint32_t bindingStart = getAtomicCounterBufferBindingsOffset();
-
-    gl::AtomicCounterBuffersArray<VkDescriptorBufferInfo> descriptorBufferInfo;
-    gl::AtomicCounterBuffersArray<VkWriteDescriptorSet> writeDescriptorInfo;
-    gl::AtomicCounterBufferMask writtenBindings;
-
-    RendererVk *rendererVk = contextVk->getRenderer();
-    const VkDeviceSize requiredOffsetAlignment =
-        rendererVk->getPhysicalDeviceProperties().limits.minStorageBufferOffsetAlignment;
-
-    // Write atomic counter buffers.
-    for (uint32_t bufferIndex = 0; bufferIndex < atomicCounterBuffers.size(); ++bufferIndex)
-    {
-        const gl::AtomicCounterBuffer &atomicCounterBuffer = atomicCounterBuffers[bufferIndex];
-        uint32_t binding                                   = atomicCounterBuffer.binding;
-        const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding =
-            glState.getIndexedAtomicCounterBuffer(binding);
-
-        if (bufferBinding.get() == nullptr)
-        {
-            continue;
-        }
-
-        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[binding];
-        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[binding];
-
-        WriteBufferDescriptorSetBinding(bufferBinding, 0, descriptorSet,
-                                        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, bindingStart, binding,
-                                        requiredOffsetAlignment, &bufferInfo, &writeInfo);
-
-        BufferVk *bufferVk             = vk::GetImpl(bufferBinding.get());
-        vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
-
-        // We set SHADER_READ_BIT to be conservative.
-        commandBufferHelper->bufferWrite(
-            resourceUseList, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, &bufferHelper);
-
-        writtenBindings.set(binding);
-    }
-
-    // Bind the empty buffer to every array slot that's unused.
-    mEmptyBuffer.retain(&contextVk->getResourceUseList());
-    for (size_t binding : ~writtenBindings)
-    {
-        VkDescriptorBufferInfo &bufferInfo = descriptorBufferInfo[binding];
-        VkWriteDescriptorSet &writeInfo    = writeDescriptorInfo[binding];
-
-        bufferInfo.buffer = mEmptyBuffer.getBuffer().getHandle();
-        bufferInfo.offset = 0;
-        bufferInfo.range  = VK_WHOLE_SIZE;
-
-        writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-        writeInfo.pNext            = nullptr;
-        writeInfo.dstSet           = descriptorSet;
-        writeInfo.dstBinding       = bindingStart;
-        writeInfo.dstArrayElement  = static_cast<uint32_t>(binding);
-        writeInfo.descriptorCount  = 1;
-        writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
-        writeInfo.pImageInfo       = nullptr;
-        writeInfo.pBufferInfo      = &bufferInfo;
-        writeInfo.pTexelBufferView = nullptr;
-    }
-
-    VkDevice device = contextVk->getDevice();
-
-    vkUpdateDescriptorSets(device, gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS,
-                           writeDescriptorInfo.data(), 0, nullptr);
-}
-
-angle::Result ProgramVk::updateImagesDescriptorSet(ContextVk *contextVk)
-{
-    const gl::State &glState                           = contextVk->getState();
-    const std::vector<gl::ImageBinding> &imageBindings = mState.getImageBindings();
-
-    if (imageBindings.empty())
-    {
-        return angle::Result::Continue;
-    }
-
-    VkDescriptorSet descriptorSet = mDescriptorSets[kShaderResourceDescriptorSetIndex];
-
-    const gl::ActiveTextureArray<TextureVk *> &activeImages = contextVk->getActiveImages();
-
-    const uint32_t bindingStart = getImageBindingsOffset();
-
-    gl::ImagesArray<VkDescriptorImageInfo> descriptorImageInfo;
-    gl::ImagesArray<VkWriteDescriptorSet> writeDescriptorInfo;
-    uint32_t writeCount = 0;
-
-    // Write images.
-    for (uint32_t imageIndex = 0; imageIndex < imageBindings.size(); ++imageIndex)
-    {
-        const gl::ImageBinding &imageBinding = imageBindings[imageIndex];
-
-        ASSERT(!imageBinding.unreferenced);
-
-        for (uint32_t arrayElement = 0; arrayElement < imageBinding.boundImageUnits.size();
-             ++arrayElement)
-        {
-            GLuint imageUnit             = imageBinding.boundImageUnits[arrayElement];
-            const gl::ImageUnit &binding = glState.getImageUnit(imageUnit);
-            TextureVk *textureVk         = activeImages[imageUnit];
-
-            vk::ImageHelper *image         = &textureVk->getImage();
-            const vk::ImageView *imageView = nullptr;
-
-            ANGLE_TRY(textureVk->getStorageImageView(contextVk, (binding.layered == GL_TRUE),
-                                                     binding.level, binding.layer, &imageView));
-
-            // Note: binding.access is unused because it is implied by the shader.
-
-            // TODO(syoussefi): Support image data reinterpretation by using binding.format.
-            // http://anglebug.com/3563
-
-            VkDescriptorImageInfo &imageInfo = descriptorImageInfo[writeCount];
-            VkWriteDescriptorSet &writeInfo  = writeDescriptorInfo[writeCount];
-
-            imageInfo.sampler     = VK_NULL_HANDLE;
-            imageInfo.imageView   = imageView->getHandle();
-            imageInfo.imageLayout = image->getCurrentLayout();
-
-            writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-            writeInfo.pNext            = nullptr;
-            writeInfo.dstSet           = descriptorSet;
-            writeInfo.dstBinding       = bindingStart + imageIndex;
-            writeInfo.dstArrayElement  = arrayElement;
-            writeInfo.descriptorCount  = 1;
-            writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
-            writeInfo.pImageInfo       = &imageInfo;
-            writeInfo.pBufferInfo      = nullptr;
-            writeInfo.pTexelBufferView = nullptr;
-
-            ++writeCount;
-        }
-    }
-
-    VkDevice device = contextVk->getDevice();
-
-    vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
-
-    return angle::Result::Continue;
-}
-
-angle::Result ProgramVk::updateShaderResourcesDescriptorSet(
-    ContextVk *contextVk,
-    vk::ResourceUseList *resourceUseList,
-    CommandBufferHelper *commandBufferHelper)
-{
-    ANGLE_TRY(allocateDescriptorSet(contextVk, kShaderResourceDescriptorSetIndex));
-
-    updateBuffersDescriptorSet(contextVk, resourceUseList, commandBufferHelper,
-                               mState.getUniformBlocks(), VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
-    updateBuffersDescriptorSet(contextVk, resourceUseList, commandBufferHelper,
-                               mState.getShaderStorageBlocks(), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
-    updateAtomicCounterBuffersDescriptorSet(contextVk, resourceUseList, commandBufferHelper);
-    return updateImagesDescriptorSet(contextVk);
-}
-
-angle::Result ProgramVk::updateTransformFeedbackDescriptorSet(ContextVk *contextVk)
-{
-    ASSERT(hasTransformFeedbackOutput());
-
-    ANGLE_TRY(allocateDescriptorSet(contextVk, kUniformsAndXfbDescriptorSetIndex));
-
-    updateDefaultUniformsDescriptorSet(contextVk);
-    updateTransformFeedbackDescriptorSetImpl(contextVk);
-
-    return angle::Result::Continue;
-}
-
-void ProgramVk::updateTransformFeedbackDescriptorSetImpl(ContextVk *contextVk)
-{
-    const gl::State &glState                 = contextVk->getState();
-    gl::TransformFeedback *transformFeedback = glState.getCurrentTransformFeedback();
-
-    if (!hasTransformFeedbackOutput())
-    {
-        // If xfb has no output there is no need to update descriptor set.
-        return;
-    }
-    if (!glState.isTransformFeedbackActive())
-    {
-        // We set empty Buffer to xfb descriptor set because xfb descriptor set
-        // requires valid buffer bindings, even if they are empty buffer,
-        // otherwise Vulkan validation layer generates errors.
-        if (transformFeedback)
-        {
-            TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(transformFeedback);
-            transformFeedbackVk->initDescriptorSet(
-                contextVk, mState.getTransformFeedbackBufferCount(), &mEmptyBuffer,
-                mDescriptorSets[kUniformsAndXfbDescriptorSetIndex]);
-        }
-        return;
-    }
-
-    TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(glState.getCurrentTransformFeedback());
-    transformFeedbackVk->updateDescriptorSet(contextVk, mState,
-                                             mDescriptorSets[kUniformsAndXfbDescriptorSetIndex]);
-}
-
-angle::Result ProgramVk::updateTexturesDescriptorSet(ContextVk *contextVk)
-{
-    const vk::TextureDescriptorDesc &texturesDesc = contextVk->getActiveTexturesDesc();
-
-    auto iter = mTextureDescriptorsCache.find(texturesDesc);
-    if (iter != mTextureDescriptorsCache.end())
-    {
-        mDescriptorSets[kTextureDescriptorSetIndex] = iter->second;
-        return angle::Result::Continue;
-    }
-
-    ASSERT(hasTextures());
-    bool newPoolAllocated;
-    ANGLE_TRY(
-        allocateDescriptorSetAndGetInfo(contextVk, kTextureDescriptorSetIndex, &newPoolAllocated));
-
-    // Clear descriptor set cache. It may no longer be valid.
-    if (newPoolAllocated)
-    {
-        mTextureDescriptorsCache.clear();
-    }
-
-    VkDescriptorSet descriptorSet = mDescriptorSets[kTextureDescriptorSetIndex];
-
-    gl::ActiveTextureArray<VkDescriptorImageInfo> descriptorImageInfo;
-    gl::ActiveTextureArray<VkWriteDescriptorSet> writeDescriptorInfo;
-    uint32_t writeCount = 0;
-
-    const gl::ActiveTextureArray<vk::TextureUnit> &activeTextures = contextVk->getActiveTextures();
-
-    bool emulateSeamfulCubeMapSampling = contextVk->emulateSeamfulCubeMapSampling();
-    bool useOldRewriteStructSamplers   = contextVk->useOldRewriteStructSamplers();
-
-    std::unordered_map<std::string, uint32_t> mappedSamplerNameToBindingIndex;
-    std::unordered_map<std::string, uint32_t> mappedSamplerNameToArrayOffset;
-
-    uint32_t currentBindingIndex = 0;
-
-    for (uint32_t textureIndex = 0; textureIndex < mState.getSamplerBindings().size();
-         ++textureIndex)
-    {
-        const gl::SamplerBinding &samplerBinding = mState.getSamplerBindings()[textureIndex];
-
-        ASSERT(!samplerBinding.unreferenced);
-
-        uint32_t uniformIndex = mState.getUniformIndexFromSamplerIndex(textureIndex);
-        const gl::LinkedUniform &samplerUniform = mState.getUniforms()[uniformIndex];
-        std::string mappedSamplerName           = GlslangGetMappedSamplerName(samplerUniform.name);
-
-        if (useOldRewriteStructSamplers ||
-            mappedSamplerNameToBindingIndex.emplace(mappedSamplerName, currentBindingIndex).second)
-        {
-            currentBindingIndex++;
-        }
-
-        uint32_t bindingIndex = textureIndex;
-        uint32_t arrayOffset  = 0;
-        uint32_t arraySize    = static_cast<uint32_t>(samplerBinding.boundTextureUnits.size());
-
-        if (!useOldRewriteStructSamplers)
-        {
-            bindingIndex = mappedSamplerNameToBindingIndex[mappedSamplerName];
-            arrayOffset  = mappedSamplerNameToArrayOffset[mappedSamplerName];
-            // Front-end generates array elements in order, so we can just increment
-            // the offset each time we process a nested array.
-            mappedSamplerNameToArrayOffset[mappedSamplerName] += arraySize;
-        }
-
-        for (uint32_t arrayElement = 0; arrayElement < arraySize; ++arrayElement)
-        {
-            GLuint textureUnit   = samplerBinding.boundTextureUnits[arrayElement];
-            TextureVk *textureVk = activeTextures[textureUnit].texture;
-            SamplerVk *samplerVk = activeTextures[textureUnit].sampler;
-
-            vk::ImageHelper &image = textureVk->getImage();
-
-            VkDescriptorImageInfo &imageInfo = descriptorImageInfo[writeCount];
-
-            // Use bound sampler object if one present, otherwise use texture's sampler
-            const vk::Sampler &sampler =
-                (samplerVk != nullptr) ? samplerVk->getSampler() : textureVk->getSampler();
-
-            imageInfo.sampler     = sampler.getHandle();
-            imageInfo.imageLayout = image.getCurrentLayout();
-
-            if (emulateSeamfulCubeMapSampling)
-            {
-                // If emulating seamful cubemapping, use the fetch image view.  This is basically
-                // the same image view as read, except it's a 2DArray view for cube maps.
-                imageInfo.imageView =
-                    textureVk->getFetchImageViewAndRecordUse(contextVk).getHandle();
-            }
-            else
-            {
-                imageInfo.imageView =
-                    textureVk->getReadImageViewAndRecordUse(contextVk).getHandle();
-            }
-
-            VkWriteDescriptorSet &writeInfo = writeDescriptorInfo[writeCount];
-
-            writeInfo.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-            writeInfo.pNext            = nullptr;
-            writeInfo.dstSet           = descriptorSet;
-            writeInfo.dstBinding       = bindingIndex;
-            writeInfo.dstArrayElement  = arrayOffset + arrayElement;
-            writeInfo.descriptorCount  = 1;
-            writeInfo.descriptorType   = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-            writeInfo.pImageInfo       = &imageInfo;
-            writeInfo.pBufferInfo      = nullptr;
-            writeInfo.pTexelBufferView = nullptr;
-
-            ++writeCount;
-        }
-    }
-
-    VkDevice device = contextVk->getDevice();
-
-    ASSERT(writeCount > 0);
-
-    vkUpdateDescriptorSets(device, writeCount, writeDescriptorInfo.data(), 0, nullptr);
-
-    mTextureDescriptorsCache.emplace(texturesDesc, descriptorSet);
-
-    return angle::Result::Continue;
-}
-
 void ProgramVk::setDefaultUniformBlocksMinSizeForTesting(size_t minSize)
 {
     for (DefaultUniformBlock &block : mDefaultUniformBlocks)
@@ -1767,76 +861,4 @@
     }
 }
 
-angle::Result ProgramVk::updateDescriptorSets(ContextVk *contextVk,
-                                              vk::CommandBuffer *commandBuffer)
-{
-    // Can probably use better dirty bits here.
-
-    if (mDescriptorSets.empty())
-        return angle::Result::Continue;
-
-    // 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.
-    const size_t descriptorSetStart = kUniformsAndXfbDescriptorSetIndex;
-    size_t descriptorSetRange       = 0;
-    for (size_t descriptorSetIndex = descriptorSetStart;
-         descriptorSetIndex < mDescriptorSets.size(); ++descriptorSetIndex)
-    {
-        if (mDescriptorSets[descriptorSetIndex] != VK_NULL_HANDLE)
-        {
-            descriptorSetRange = descriptorSetIndex + 1;
-        }
-    }
-
-    const VkPipelineBindPoint pipelineBindPoint = mState.getProgramExecutable().isCompute()
-                                                      ? VK_PIPELINE_BIND_POINT_COMPUTE
-                                                      : VK_PIPELINE_BIND_POINT_GRAPHICS;
-
-    for (uint32_t descriptorSetIndex = descriptorSetStart; descriptorSetIndex < descriptorSetRange;
-         ++descriptorSetIndex)
-    {
-        VkDescriptorSet descSet = mDescriptorSets[descriptorSetIndex];
-        if (descSet == VK_NULL_HANDLE)
-        {
-            if (!contextVk->getRenderer()->getFeatures().bindEmptyForUnusedDescriptorSets.enabled)
-            {
-                continue;
-            }
-
-            // Workaround a driver bug where missing (though unused) descriptor sets indices cause
-            // later sets to misbehave.
-            if (mEmptyDescriptorSets[descriptorSetIndex] == VK_NULL_HANDLE)
-            {
-                const vk::DescriptorSetLayout &descriptorSetLayout =
-                    mDescriptorSetLayouts[descriptorSetIndex].get();
-
-                ANGLE_TRY(mDynamicDescriptorPools[descriptorSetIndex].allocateSets(
-                    contextVk, descriptorSetLayout.ptr(), 1,
-                    &mDescriptorPoolBindings[descriptorSetIndex],
-                    &mEmptyDescriptorSets[descriptorSetIndex]));
-            }
-            descSet = mEmptyDescriptorSets[descriptorSetIndex];
-        }
-
-        // 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 == kUniformsAndXfbDescriptorSetIndex
-                ? static_cast<uint32_t>(mDynamicBufferOffsets.size())
-                : 0;
-
-        commandBuffer->bindDescriptorSets(mPipelineLayout.get(), pipelineBindPoint,
-                                          descriptorSetIndex, 1, &descSet, uniformBlockOffsetCount,
-                                          mDynamicBufferOffsets.data());
-    }
-
-    for (vk::BufferHelper *buffer : mDescriptorBuffersCache)
-    {
-        buffer->retain(&contextVk->getResourceUseList());
-    }
-
-    return angle::Result::Continue;
-}
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h
index bf26618..ef6ce5b 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.h
@@ -16,6 +16,7 @@
 #include "libANGLE/renderer/ProgramImpl.h"
 #include "libANGLE/renderer/glslang_wrapper_utils.h"
 #include "libANGLE/renderer/vulkan/ContextVk.h"
+#include "libANGLE/renderer/vulkan/ProgramExecutableVk.h"
 #include "libANGLE/renderer/vulkan/RendererVk.h"
 #include "libANGLE/renderer/vulkan/TransformFeedbackVk.h"
 #include "libANGLE/renderer/vulkan/vk_helpers.h"
@@ -99,31 +100,14 @@
     void getUniformiv(const gl::Context *context, GLint location, GLint *params) const override;
     void getUniformuiv(const gl::Context *context, GLint location, GLuint *params) const override;
 
-    // Also initializes the pipeline layout, descriptor set layouts, and used descriptor ranges.
-
     angle::Result updateUniforms(ContextVk *contextVk);
-    angle::Result updateTexturesDescriptorSet(ContextVk *contextVk);
-    angle::Result updateShaderResourcesDescriptorSet(ContextVk *contextVk,
-                                                     vk::ResourceUseList *resourceUseList,
-                                                     CommandBufferHelper *commandBufferHelper);
-    angle::Result updateTransformFeedbackDescriptorSet(ContextVk *contextVk);
-
-    angle::Result updateDescriptorSets(ContextVk *contextVk, vk::CommandBuffer *commandBuffer);
 
     // For testing only.
     void setDefaultUniformBlocksMinSizeForTesting(size_t minSize);
 
-    const vk::PipelineLayout &getPipelineLayout() const { return mPipelineLayout.get(); }
-
-    bool hasDefaultUniforms() const { return !mState.getDefaultUniformRange().empty(); }
-    bool hasTextures() const { return !mState.getSamplerBindings().empty(); }
-    bool hasUniformBuffers() const { return !mState.getUniformBlocks().empty(); }
-    bool hasStorageBuffers() const { return !mState.getShaderStorageBlocks().empty(); }
-    bool hasAtomicCounterBuffers() const { return !mState.getAtomicCounterBuffers().empty(); }
-    bool hasImages() const { return !mState.getImageBindings().empty(); }
-    bool hasTransformFeedbackOutput() const
+    const vk::PipelineLayout &getPipelineLayout() const
     {
-        return !mState.getLinkedTransformFeedbackVaryings().empty();
+        return mExecutable.mPipelineLayout.get();
     }
 
     bool dirtyUniforms() const { return mDefaultUniformBlocksDirty.any(); }
@@ -143,8 +127,9 @@
         ANGLE_TRY(renderer->getPipelineCache(&pipelineCache));
         return shaderProgram->getGraphicsPipeline(
             contextVk, &contextVk->getRenderPassCache(), *pipelineCache,
-            contextVk->getCurrentQueueSerial(), mPipelineLayout.get(), desc, activeAttribLocations,
-            mState.getProgramExecutable().getAttributesTypeMask(), descPtrOut, pipelineOut);
+            contextVk->getCurrentQueueSerial(), mExecutable.mPipelineLayout.get(), desc,
+            activeAttribLocations, mState.getProgramExecutable().getAttributesTypeMask(),
+            descPtrOut, pipelineOut);
     }
 
     angle::Result getComputePipeline(ContextVk *contextVk, vk::PipelineAndSerial **pipelineOut)
@@ -152,15 +137,21 @@
         vk::ShaderProgramHelper *shaderProgram;
         ANGLE_TRY(initComputeProgram(contextVk, &shaderProgram));
         ASSERT(!shaderProgram->isGraphicsProgram());
-        return shaderProgram->getComputePipeline(contextVk, mPipelineLayout.get(), pipelineOut);
+        return shaderProgram->getComputePipeline(contextVk, mExecutable.mPipelineLayout.get(),
+                                                 pipelineOut);
     }
 
     // Used in testing only.
     vk::DynamicDescriptorPool *getDynamicDescriptorPool(uint32_t poolIndex)
     {
-        return &mDynamicDescriptorPools[poolIndex];
+        return &mExecutable.mDynamicDescriptorPools[poolIndex];
     }
 
+    const ProgramExecutableVk &getExecutable() const { return mExecutable; }
+    ProgramExecutableVk &getExecutable() { return mExecutable; }
+
+    gl::ShaderMap<DefaultUniformBlock> &getDefaultUniformBlocks() { return mDefaultUniformBlocks; }
+
   private:
     template <int cols, int rows>
     void setUniformMatrixfv(GLint location,
@@ -169,10 +160,6 @@
                             const GLfloat *value);
 
     void reset(ContextVk *contextVk);
-    angle::Result allocateDescriptorSet(ContextVk *contextVk, uint32_t descriptorSetIndex);
-    angle::Result allocateDescriptorSetAndGetInfo(ContextVk *contextVk,
-                                                  uint32_t descriptorSetIndex,
-                                                  bool *newPoolAllocatedOut);
     angle::Result initDefaultUniformBlocks(const gl::Context *glContext);
     void generateUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> &layoutMap,
                                       gl::ShaderMap<size_t> &requiredBufferSize);
@@ -180,18 +167,6 @@
     angle::Result resizeUniformBlockMemory(ContextVk *contextVk,
                                            gl::ShaderMap<size_t> &requiredBufferSize);
 
-    void updateDefaultUniformsDescriptorSet(ContextVk *contextVk);
-    void updateTransformFeedbackDescriptorSetImpl(ContextVk *contextVk);
-    void updateBuffersDescriptorSet(ContextVk *contextVk,
-                                    vk::ResourceUseList *resourceUseList,
-                                    CommandBufferHelper *commandBufferHelper,
-                                    const std::vector<gl::InterfaceBlock> &blocks,
-                                    VkDescriptorType descriptorType);
-    void updateAtomicCounterBuffersDescriptorSet(ContextVk *contextVk,
-                                                 vk::ResourceUseList *resourceUseList,
-                                                 CommandBufferHelper *commandBufferHelper);
-    angle::Result updateImagesDescriptorSet(ContextVk *contextVk);
-
     template <class T>
     void getUniformImpl(GLint location, T *v, GLenum entryPointType) const;
 
@@ -200,15 +175,6 @@
     angle::Result linkImpl(const gl::Context *glContext, gl::InfoLog &infoLog);
     void linkResources(const gl::ProgramLinkedResources &resources);
 
-    void updateBindingOffsets();
-    uint32_t getUniformBlockBindingsOffset() const { return 0; }
-    uint32_t getStorageBlockBindingsOffset() const { return mStorageBlockBindingsOffset; }
-    uint32_t getAtomicCounterBufferBindingsOffset() const
-    {
-        return mAtomicCounterBufferBindingsOffset;
-    }
-    uint32_t getImageBindingsOffset() const { return mImageBindingsOffset; }
-
     class ProgramInfo;
     ANGLE_INLINE angle::Result initProgram(ContextVk *contextVk,
                                            bool enableLineRasterEmulation,
@@ -247,50 +213,9 @@
         return initProgram(contextVk, false, &mDefaultProgramInfo, shaderProgramOut);
     }
 
-    // State for the default uniform blocks.
-    struct DefaultUniformBlock final : private angle::NonCopyable
-    {
-        DefaultUniformBlock();
-        ~DefaultUniformBlock();
-
-        vk::DynamicBuffer storage;
-
-        // Shadow copies of the shader uniform data.
-        angle::MemoryBuffer uniformData;
-
-        // Since the default blocks are laid out in std140, this tells us where to write on a call
-        // to a setUniform method. They are arranged in uniform location order.
-        std::vector<sh::BlockMemberInfo> uniformLayout;
-    };
-
     gl::ShaderMap<DefaultUniformBlock> mDefaultUniformBlocks;
     gl::ShaderBitSet mDefaultUniformBlocksDirty;
 
-    gl::ShaderVector<uint32_t> mDynamicBufferOffsets;
-
-    // This is a special "empty" placeholder buffer for when a shader has no uniforms or doesn't
-    // use all slots in the atomic counter buffer array.
-    //
-    // It is necessary because we want to keep a compatible pipeline layout in all cases,
-    // and Vulkan does not tolerate having null handles in a descriptor set.
-    vk::BufferHelper mEmptyBuffer;
-
-    // Descriptor sets for uniform blocks and textures for this program.
-    std::vector<VkDescriptorSet> mDescriptorSets;
-    vk::DescriptorSetLayoutArray<VkDescriptorSet> mEmptyDescriptorSets;
-    std::vector<vk::BufferHelper *> mDescriptorBuffersCache;
-
-    std::unordered_map<vk::TextureDescriptorDesc, VkDescriptorSet> mTextureDescriptorsCache;
-
-    // We keep a reference to the pipeline and descriptor set layouts. This ensures they don't get
-    // deleted while this program is in use.
-    vk::BindingPointer<vk::PipelineLayout> mPipelineLayout;
-    vk::DescriptorSetLayoutPointerArray mDescriptorSetLayouts;
-
-    // Keep bindings to the descriptor pools. This ensures the pools stay valid while the Program
-    // is in use.
-    vk::DescriptorSetLayoutArray<vk::RefCountedDescriptorPoolBinding> mDescriptorPoolBindings;
-
     class ShaderInfo final : angle::NonCopyable
     {
       public:
@@ -341,19 +266,9 @@
     // We keep the SPIR-V code to use for draw call pipeline creation.
     ShaderInfo mShaderInfo;
 
-    // In their descriptor set, uniform buffers are placed first, then storage buffers, then atomic
-    // counter buffers and then images.  These cached values contain the offsets where storage
-    // buffer, atomic counter buffer and image bindings start.
-    uint32_t mStorageBlockBindingsOffset;
-    uint32_t mAtomicCounterBufferBindingsOffset;
-    uint32_t mImageBindingsOffset;
-
-    // Store descriptor pools here. We store the descriptors in the Program to facilitate descriptor
-    // cache management. It can also allow fewer descriptors for shaders which use fewer
-    // textures/buffers.
-    vk::DescriptorSetLayoutArray<vk::DynamicDescriptorPool> mDynamicDescriptorPools;
-
     GlslangProgramInterfaceInfo mGlslangProgramInterfaceInfo;
+
+    ProgramExecutableVk mExecutable;
 };
 
 }  // namespace rx