//
// Copyright 2018 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.
//
// vk_cache_utils.h:
//    Contains the classes for the Pipeline State Object cache as well as the RenderPass cache.
//    Also contains the structures for the packed descriptions for the RenderPass and Pipeline.
//

#ifndef LIBANGLE_RENDERER_VULKAN_VK_CACHE_UTILS_H_
#define LIBANGLE_RENDERER_VULKAN_VK_CACHE_UTILS_H_

#include <deque>
#include "common/unsafe_buffers.h"

#include "common/Color.h"
#include "common/FixedVector.h"
#include "common/SimpleMutex.h"
#include "common/WorkerThread.h"
#include "libANGLE/Uniform.h"
#include "libANGLE/renderer/vulkan/ShaderInterfaceVariableInfoMap.h"
#include "libANGLE/renderer/vulkan/vk_resource.h"
#include "libANGLE/renderer/vulkan/vk_utils.h"

namespace gl
{
class ProgramExecutable;
}  // namespace gl

namespace rx
{
class ShaderInterfaceVariableInfoMap;
class UpdateDescriptorSetsBuilder;

// Some descriptor set and pipeline layout constants.
//
// The set/binding assignment is done as follows:
//
// - Set 0 contains uniform blocks created to encompass default uniforms.  1 binding is used per
//   pipeline stage.  Additionally, transform feedback buffers are bound from binding 2 and up.
//   For internal shaders, set 0 is used for all the needed resources.
// - Set 1 contains all textures (including texture buffers).
// - Set 2 contains all uniform buffers
// - Set 3 contains all other shader resources, such as storage buffers, atomic counter
//   buffers, images and image buffers.

enum class DescriptorSetIndex : uint32_t
{
    Internal       = 0,         // Internal shaders
    UniformsAndXfb = Internal,  // Uniforms set index
    Texture        = 1,         // Textures set index
    UniformBuffers = 2,         // Uniform buffers set index
    ShaderResource = 3,         // Other shader resources set index

    // CL specific naming for set indices
    LiteralSampler  = 0,
    KernelArguments = 1,
    ModuleConstants = 2,
    Printf          = 3,

    InvalidEnum = 4,
    EnumCount   = InvalidEnum,
};

namespace vk
{
class Context;
class BufferHelper;
class DynamicDescriptorPool;
class SamplerHelper;
enum class ImageAccess;
class PipelineCacheAccess;
class RenderPassCommandBufferHelper;
class PackedClearValuesArray;
class AttachmentOpsArray;
class CommandBufferHelperCommon;

using PipelineLayoutPtr      = AtomicSharedPtr<PipelineLayout>;
using DescriptorSetLayoutPtr = AtomicSharedPtr<DescriptorSetLayout>;

// Packed Vk resource descriptions.
// Most Vk types use many more bits than required to represent the underlying data.
// Since ANGLE wants to cache things like RenderPasses and Pipeline State Objects using
// hashing (and also needs to check equality) we can optimize these operations by
// using fewer bits. Hence the packed types.
//
// One implementation note: these types could potentially be improved by using even
// fewer bits. For example, boolean values could be represented by a single bit instead
// of a uint8_t. However at the current time there are concerns about the portability
// of bitfield operators, and complexity issues with using bit mask operations. This is
// something we will likely want to investigate as the Vulkan implementation progresses.
//
// Second implementation note: the struct packing is also a bit fragile, and some of the
// packing requirements depend on using alignas and field ordering to get the result of
// packing nicely into the desired space. This is something we could also potentially fix
// with a redesign to use bitfields or bit mask operations.

// Enable struct padding warnings for the code below since it is used in caches.
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS

enum class ResourceAccess
{
    Unused    = 0x0,
    ReadOnly  = 0x1,
    WriteOnly = 0x2,
    ReadWrite = ReadOnly | WriteOnly,
};

inline void UpdateAccess(ResourceAccess *oldAccess, ResourceAccess newAccess)
{
    *oldAccess = static_cast<ResourceAccess>(ToUnderlying(newAccess) | ToUnderlying(*oldAccess));
}
inline bool HasResourceWriteAccess(ResourceAccess access)
{
    return (ToUnderlying(access) & ToUnderlying(ResourceAccess::WriteOnly)) != 0;
}

enum class RenderPassLoadOp
{
    Load     = VK_ATTACHMENT_LOAD_OP_LOAD,
    Clear    = VK_ATTACHMENT_LOAD_OP_CLEAR,
    DontCare = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
    None,
};
enum class RenderPassStoreOp
{
    Store    = VK_ATTACHMENT_STORE_OP_STORE,
    DontCare = VK_ATTACHMENT_STORE_OP_DONT_CARE,
    None,
};

enum class FramebufferFetchMode
{
    None,
    Color,
    DepthStencil,
    ColorAndDepthStencil,
};
FramebufferFetchMode GetProgramFramebufferFetchMode(const gl::ProgramExecutable *executable);
ANGLE_INLINE bool FramebufferFetchModeHasColor(FramebufferFetchMode framebufferFetchMode)
{
    static_assert(ToUnderlying(FramebufferFetchMode::Color) == 0x1);
    static_assert(ToUnderlying(FramebufferFetchMode::ColorAndDepthStencil) == 0x3);
    return (ToUnderlying(framebufferFetchMode) & 0x1) != 0;
}
ANGLE_INLINE bool FramebufferFetchModeHasDepthStencil(FramebufferFetchMode framebufferFetchMode)
{
    static_assert(ToUnderlying(FramebufferFetchMode::DepthStencil) == 0x2);
    static_assert(ToUnderlying(FramebufferFetchMode::ColorAndDepthStencil) == 0x3);
    return (ToUnderlying(framebufferFetchMode) & 0x2) != 0;
}
ANGLE_INLINE FramebufferFetchMode FramebufferFetchModeMerge(FramebufferFetchMode mode1,
                                                            FramebufferFetchMode mode2)
{
    constexpr uint32_t kNone         = ToUnderlying(FramebufferFetchMode::None);
    constexpr uint32_t kColor        = ToUnderlying(FramebufferFetchMode::Color);
    constexpr uint32_t kDepthStencil = ToUnderlying(FramebufferFetchMode::DepthStencil);
    constexpr uint32_t kColorAndDepthStencil =
        ToUnderlying(FramebufferFetchMode::ColorAndDepthStencil);
    static_assert(kNone == 0);
    static_assert((kColor & kColorAndDepthStencil) == kColor);
    static_assert((kDepthStencil & kColorAndDepthStencil) == kDepthStencil);
    static_assert((kColor | kDepthStencil) == kColorAndDepthStencil);

    return static_cast<FramebufferFetchMode>(ToUnderlying(mode1) | ToUnderlying(mode2));
}

// There can be a maximum of IMPLEMENTATION_MAX_DRAW_BUFFERS color and resolve attachments, plus -
// - one depth/stencil attachment
// - one depth/stencil resolve attachment
// - one fragment shading rate attachment
constexpr size_t kMaxFramebufferAttachments = gl::IMPLEMENTATION_MAX_DRAW_BUFFERS * 2 + 3;
template <typename T>
using FramebufferAttachmentArray = std::array<T, kMaxFramebufferAttachments>;
template <typename T>
using FramebufferAttachmentsVector = angle::FixedVector<T, kMaxFramebufferAttachments>;
using FramebufferAttachmentMask    = angle::BitSet<kMaxFramebufferAttachments>;

constexpr size_t kMaxFramebufferNonResolveAttachments = gl::IMPLEMENTATION_MAX_DRAW_BUFFERS + 1;
template <typename T>
using FramebufferNonResolveAttachmentArray = std::array<T, kMaxFramebufferNonResolveAttachments>;
using FramebufferNonResolveAttachmentMask  = angle::BitSet16<kMaxFramebufferNonResolveAttachments>;

class PackedAttachmentIndex;

class alignas(4) RenderPassDesc final
{
  public:
    RenderPassDesc();
    ~RenderPassDesc();
    RenderPassDesc(const RenderPassDesc &other);
    RenderPassDesc &operator=(const RenderPassDesc &other);

    // Set format for an enabled GL color attachment.
    void packColorAttachment(size_t colorIndexGL, angle::FormatID formatID);
    // The caller must pack the depth/stencil attachment last, which is packed right after the color
    // attachments (including gaps), i.e. with an index starting from |colorAttachmentRange()|.
    void packDepthStencilAttachment(angle::FormatID angleFormatID);
    void updateDepthStencilAccess(ResourceAccess access);
    // Indicate that a color attachment should have a corresponding resolve attachment.
    void packColorResolveAttachment(size_t colorIndexGL);
    // Indicate that a YUV texture is attached to the resolve attachment.
    void packYUVResolveAttachment(size_t colorIndexGL);
    // Remove the resolve attachment.  Used when optimizing blit through resolve attachment to
    // temporarily pack a resolve attachment and then remove it.
    void removeColorResolveAttachment(size_t colorIndexGL);
    // Indicate that a color attachment should take its data from the resolve attachment initially.
    void packColorUnresolveAttachment(size_t colorIndexGL);
    void removeColorUnresolveAttachment(size_t colorIndexGL);
    void resetColorUnresolveAttachments();
    // Indicate that a depth/stencil attachment should have a corresponding resolve attachment.
    void packDepthResolveAttachment();
    void packStencilResolveAttachment();
    // Indicate that a depth/stencil attachment should take its data from the resolve attachment
    // initially.
    void packDepthUnresolveAttachment();
    void packStencilUnresolveAttachment();
    void removeDepthUnresolveAttachment();
    void removeStencilUnresolveAttachment();

    PackedAttachmentIndex getPackedColorAttachmentIndex(size_t colorIndexGL);

    void setWriteControlMode(gl::SrgbWriteControlMode mode);

    size_t hash() const;

    // Color attachments are in [0, colorAttachmentRange()), with possible gaps.
    size_t colorAttachmentRange() const { return mColorAttachmentRange; }
    size_t depthStencilAttachmentIndex() const { return colorAttachmentRange(); }

    bool isColorAttachmentEnabled(size_t colorIndexGL) const;
    bool hasYUVResolveAttachment() const { return mIsYUVResolve; }
    bool hasDepthStencilAttachment() const;
    gl::DrawBufferMask getColorResolveAttachmentMask() const { return mColorResolveAttachmentMask; }
    bool hasColorResolveAttachment(size_t colorIndexGL) const
    {
        return mColorResolveAttachmentMask.test(colorIndexGL);
    }
    gl::DrawBufferMask getColorUnresolveAttachmentMask() const
    {
        return mColorUnresolveAttachmentMask;
    }
    bool hasColorUnresolveAttachment(size_t colorIndexGL) const
    {
        return mColorUnresolveAttachmentMask.test(colorIndexGL);
    }
    bool hasDepthStencilResolveAttachment() const { return mResolveDepth || mResolveStencil; }
    bool hasDepthResolveAttachment() const { return mResolveDepth; }
    bool hasStencilResolveAttachment() const { return mResolveStencil; }
    bool hasDepthStencilUnresolveAttachment() const { return mUnresolveDepth || mUnresolveStencil; }
    bool hasDepthUnresolveAttachment() const { return mUnresolveDepth; }
    bool hasStencilUnresolveAttachment() const { return mUnresolveStencil; }
    gl::SrgbWriteControlMode getSRGBWriteControlMode() const
    {
        return static_cast<gl::SrgbWriteControlMode>(mSrgbWriteControl);
    }

    bool isLegacyDitherEnabled() const { return mLegacyDitherEnabled; }

    void setLegacyDither(bool enabled);

    // Get the number of clearable attachments in the Vulkan render pass, i.e. after removing
    // disabled color attachments.
    size_t clearableAttachmentCount() const;
    // Get the total number of attachments in the Vulkan render pass, i.e. after removing disabled
    // color attachments.
    size_t attachmentCount() const;

    void setSamples(GLint samples) { mSamples = static_cast<uint8_t>(samples); }
    uint8_t samples() const { return mSamples; }

    void setViewCount(GLsizei viewCount) { mViewCount = static_cast<uint8_t>(viewCount); }
    uint8_t viewCount() const { return mViewCount; }

    void setFramebufferFetchMode(FramebufferFetchMode framebufferFetchMode)
    {
        SetBitField(mFramebufferFetchMode, framebufferFetchMode);
    }
    FramebufferFetchMode framebufferFetchMode() const
    {
        return static_cast<FramebufferFetchMode>(mFramebufferFetchMode);
    }
    bool hasColorFramebufferFetch() const
    {
        return FramebufferFetchModeHasColor(framebufferFetchMode());
    }
    bool hasDepthStencilFramebufferFetch() const
    {
        return FramebufferFetchModeHasDepthStencil(framebufferFetchMode());
    }

    void updateRenderToTexture(bool isRenderToTexture) { mIsRenderToTexture = isRenderToTexture; }
    bool isRenderToTexture() const { return mIsRenderToTexture; }

    void setDynamicMSRTTUnresolve(bool isDynamicMSRTTUnresolve)
    {
        mIsDynamicMSRTTUnresolve = isDynamicMSRTTUnresolve;
    }
    bool isDynamicMSRTTUnresolve() const { return mIsDynamicMSRTTUnresolve; }

    void setFragmentShadingAttachment(bool value) { mHasFragmentShadingAttachment = value; }
    bool hasFragmentShadingAttachment() const { return mHasFragmentShadingAttachment; }

    angle::FormatID operator[](size_t index) const
    {
        ASSERT(index < gl::IMPLEMENTATION_MAX_DRAW_BUFFERS + 1);
        return static_cast<angle::FormatID>(mAttachmentFormats[index]);
    }

    // Start a render pass with a render pass object.
    void beginRenderPass(ErrorContext *context,
                         PrimaryCommandBuffer *primary,
                         const RenderPass &renderPass,
                         VkFramebuffer framebuffer,
                         const gl::Rectangle &renderArea,
                         VkSubpassContents subpassContents,
                         PackedClearValuesArray &clearValues,
                         const VkRenderPassAttachmentBeginInfo *attachmentBeginInfo) const;

    // Start a render pass with dynamic rendering.
    void beginRendering(ErrorContext *context,
                        PrimaryCommandBuffer *primary,
                        const gl::Rectangle &renderArea,
                        VkSubpassContents subpassContents,
                        const FramebufferAttachmentsVector<VkImageView> &attachmentViews,
                        const AttachmentOpsArray &ops,
                        PackedClearValuesArray &clearValues,
                        uint32_t layerCount) const;

    void populateRenderingInheritanceInfo(
        Renderer *renderer,
        VkCommandBufferInheritanceRenderingInfo *infoOut,
        gl::DrawBuffersArray<VkFormat> *colorFormatStorageOut) const;

    // Calculate perf counters for a dynamic rendering render pass instance.  For render pass
    // objects, the perf counters are updated when creating the render pass, where access to
    // ContextVk is available.
    void updatePerfCounters(ErrorContext *context,
                            const FramebufferAttachmentsVector<VkImageView> &attachmentViews,
                            const AttachmentOpsArray &ops,
                            angle::VulkanPerfCounters *countersOut);

  private:
    uint8_t mSamples;
    uint8_t mColorAttachmentRange;

    // Multiview
    uint8_t mViewCount;

    // sRGB
    uint8_t mSrgbWriteControl : 1;

    // Framebuffer fetch, one of FramebufferFetchMode values
    uint8_t mFramebufferFetchMode : 2;

    // Depth/stencil resolve
    uint8_t mResolveDepth : 1;
    uint8_t mResolveStencil : 1;

    // Multisampled render to texture
    uint8_t mIsRenderToTexture : 1;
    uint8_t mUnresolveDepth : 1;
    uint8_t mUnresolveStencil : 1;
    uint8_t mIsDynamicMSRTTUnresolve : 1;

    // Dithering state when using VK_EXT_legacy_dithering
    uint8_t mLegacyDitherEnabled : 1;

    // external_format_resolve
    uint8_t mIsYUVResolve : 1;

    // Foveated rendering
    uint8_t mHasFragmentShadingAttachment : 1;

    // Available space for expansion.
    uint8_t mPadding2 : 4;

    // Whether each color attachment has a corresponding resolve attachment.  Color resolve
    // attachments can be used to optimize resolve through glBlitFramebuffer() as well as support
    // GL_EXT_multisampled_render_to_texture and GL_EXT_multisampled_render_to_texture2.
    gl::DrawBufferMask mColorResolveAttachmentMask;

    // Whether each color attachment with a corresponding resolve attachment should be initialized
    // with said resolve attachment in an initial subpass.  This is an optimization to avoid
    // loadOp=LOAD on the implicit multisampled image used with multisampled-render-to-texture
    // render targets.  This operation is referred to as "unresolve".
    //
    // Unused when VK_EXT_multisampled_render_to_single_sampled is available.
    gl::DrawBufferMask mColorUnresolveAttachmentMask;

    // Color attachment formats are stored with their GL attachment indices.  The depth/stencil
    // attachment formats follow the last enabled color attachment.  When creating a render pass,
    // the disabled attachments are removed and the resulting attachments are packed.
    //
    // The attachment indices provided as input to various functions in this file are thus GL
    // attachment indices.  These indices are marked as such, e.g. colorIndexGL.  The render pass
    // (and corresponding framebuffer object) lists the packed attachments, with the corresponding
    // indices marked with Vk, e.g. colorIndexVk.  The subpass attachment references create the
    // link between the two index spaces.  The subpass declares attachment references with GL
    // indices (which corresponds to the location decoration of shader outputs).  The attachment
    // references then contain the Vulkan indices or VK_ATTACHMENT_UNUSED.
    //
    // For example, if GL uses color attachments 0 and 3, then there are two render pass
    // attachments (indexed 0 and 1) and 4 subpass attachments:
    //
    //  - Subpass attachment 0 -> Renderpass attachment 0
    //  - Subpass attachment 1 -> VK_ATTACHMENT_UNUSED
    //  - Subpass attachment 2 -> VK_ATTACHMENT_UNUSED
    //  - Subpass attachment 3 -> Renderpass attachment 1
    //
    // The resolve attachments are packed after the non-resolve attachments.  They use the same
    // formats, so they are not specified in this array.
    FramebufferNonResolveAttachmentArray<uint8_t> mAttachmentFormats;
};

bool operator==(const RenderPassDesc &lhs, const RenderPassDesc &rhs);

constexpr size_t kRenderPassDescSize = sizeof(RenderPassDesc);
static_assert(kRenderPassDescSize == 16, "Size check failed");

enum class GraphicsPipelineSubset
{
    Complete,  // Include all subsets
    Shaders,   // Include only the shader subsets, excluding vertex input and fragment output state.
};

enum class CacheLookUpFeedback
{
    None,
    Hit,
    Miss,
    LinkedDrawHit,
    LinkedDrawMiss,
    WarmUpHit,
    WarmUpMiss,
    UtilsHit,
    UtilsMiss,
};

struct PackedAttachmentOpsDesc final
{
    // RenderPassLoadOp is in range [0, 3], and RenderPassStoreOp is in range [0, 2].
    uint16_t loadOp : 2;
    uint16_t storeOp : 2;
    uint16_t stencilLoadOp : 2;
    uint16_t stencilStoreOp : 2;
    // If a corresponding resolve attachment exists, storeOp may already be DONT_CARE, and it's
    // unclear whether the attachment was invalidated or not.  This information is passed along here
    // so that the resolve attachment's storeOp can be set to DONT_CARE if the attachment is
    // invalidated, and if possible removed from the list of resolve attachments altogether.  Note
    // that the latter may not be possible if the render pass has multiple subpasses due to Vulkan
    // render pass compatibility rules (not an issue with dynamic rendering).
    uint16_t isInvalidated : 1;
    uint16_t isStencilInvalidated : 1;
    uint16_t padding1 : 6;

    // Layouts take values from ImageAccess, so they are small.  Layouts that are possible here are
    // placed at the beginning of that enum.
    uint16_t initialLayout : 5;
    uint16_t finalLayout : 5;
    uint16_t finalResolveLayout : 5;
    uint16_t padding2 : 1;
};

static_assert(sizeof(PackedAttachmentOpsDesc) == 4, "Size check failed");

class AttachmentOpsArray final
{
  public:
    AttachmentOpsArray();
    ~AttachmentOpsArray();
    AttachmentOpsArray(const AttachmentOpsArray &other);
    AttachmentOpsArray &operator=(const AttachmentOpsArray &other);

    const PackedAttachmentOpsDesc &operator[](PackedAttachmentIndex index) const
    {
        return mOps[index.get()];
    }
    PackedAttachmentOpsDesc &operator[](PackedAttachmentIndex index) { return mOps[index.get()]; }

    // Initialize an attachment op with all load and store operations.
    void initWithLoadStore(PackedAttachmentIndex index,
                           ImageAccess initialLayout,
                           ImageAccess finalLayout);

    void setLayouts(PackedAttachmentIndex index,
                    ImageAccess initialLayout,
                    ImageAccess finalLayout);
    void setOps(PackedAttachmentIndex index, RenderPassLoadOp loadOp, RenderPassStoreOp storeOp);
    void setStencilOps(PackedAttachmentIndex index,
                       RenderPassLoadOp loadOp,
                       RenderPassStoreOp storeOp);

    void setClearOp(PackedAttachmentIndex index);
    void setClearStencilOp(PackedAttachmentIndex index);

    size_t hash() const;

  private:
    gl::AttachmentArray<PackedAttachmentOpsDesc> mOps;
};

bool operator==(const AttachmentOpsArray &lhs, const AttachmentOpsArray &rhs);

static_assert(sizeof(AttachmentOpsArray) == 40, "Size check failed");

struct PackedAttribDesc final
{
    uint8_t format;
    uint8_t divisor;
    uint16_t offset : kAttributeOffsetMaxBits;
};
static_assert(kAttributeOffsetMaxBits == 16, "Keep PackedAttribDesc struct tightly packed");

constexpr size_t kPackedAttribDescSize = sizeof(PackedAttribDesc);
static_assert(kPackedAttribDescSize == 4, "Size mismatch");

struct PackedVertexInputAttributes final
{
    PackedAttribDesc attribs[gl::MAX_VERTEX_ATTRIBS];

    // Component type of the corresponding input in the program.  Used to adjust the format if
    // necessary.  Takes values from gl::ComponentType.
    uint32_t shaderAttribComponentType;

    // Although technically stride can be any value in ES 2.0, in practice supporting stride
    // greater than MAX_USHORT should not be that helpful. Note that stride limits are
    // introduced in ES 3.1.
    // Dynamic in VK_EXT_extended_dynamic_state
    uint16_t strides[gl::MAX_VERTEX_ATTRIBS];
};

constexpr size_t kPackedVertexInputAttributesSize = sizeof(PackedVertexInputAttributes);
static_assert(kPackedVertexInputAttributesSize == 100, "Size mismatch");

struct PackedInputAssemblyState final
{
    struct
    {
        uint32_t topology : 4;

        // Dynamic in VK_EXT_extended_dynamic_state2
        uint32_t primitiveRestartEnable : 1;  // ds2

        // Whether dynamic state for vertex stride from VK_EXT_extended_dynamic_state can be used
        // for.  Used by GraphicsPipelineDesc::hash() to exclude |vertexStrides| from the hash
        uint32_t useVertexInputBindingStrideDynamicState : 1;

        // Whether dynamic state for vertex input state from VK_EXT_vertex_input_dynamic_state can
        // be used by GraphicsPipelineDesc::hash() to exclude |PackedVertexInputAttributes| from the
        // hash
        uint32_t useVertexInputDynamicState : 1;

        // Whether the pipeline is robust (vertex input copy)
        uint32_t isRobustContext : 1;
        // Whether the pipeline needs access to protected content (vertex input copy)
        uint32_t isProtectedContext : 1;

        // Which attributes are actually active in the program and should affect the pipeline.
        uint32_t programActiveAttributeLocations : gl::MAX_VERTEX_ATTRIBS;

        uint32_t padding : 23 - gl::MAX_VERTEX_ATTRIBS;
    } bits;
};

constexpr size_t kPackedInputAssemblyStateSize = sizeof(PackedInputAssemblyState);
static_assert(kPackedInputAssemblyStateSize == 4, "Size mismatch");

struct PackedStencilOpState final
{
    uint8_t fail : 4;
    uint8_t pass : 4;
    uint8_t depthFail : 4;
    uint8_t compare : 4;
};

constexpr size_t kPackedStencilOpSize = sizeof(PackedStencilOpState);
static_assert(kPackedStencilOpSize == 2, "Size check failed");

struct PackedPreRasterizationAndFragmentStates final
{
    struct
    {
        // Affecting VkPipelineViewportStateCreateInfo
        uint32_t viewportNegativeOneToOne : 1;

        // Affecting VkPipelineRasterizationStateCreateInfo
        uint32_t depthClampEnable : 1;
        uint32_t polygonMode : 2;
        // Dynamic in VK_EXT_extended_dynamic_state
        uint32_t cullMode : 4;
        uint32_t frontFace : 4;
        // Dynamic in VK_EXT_extended_dynamic_state2
        uint32_t rasterizerDiscardEnable : 1;
        uint32_t depthBiasEnable : 1;

        // Affecting VkPipelineTessellationStateCreateInfo
        uint32_t patchVertices : 6;

        // Affecting VkPipelineDepthStencilStateCreateInfo
        uint32_t depthBoundsTest : 1;
        // Dynamic in VK_EXT_extended_dynamic_state
        uint32_t depthTest : 1;
        uint32_t depthWrite : 1;
        uint32_t stencilTest : 1;
        uint32_t nonZeroStencilWriteMaskWorkaround : 1;
        // Dynamic in VK_EXT_extended_dynamic_state2
        uint32_t depthCompareOp : 4;

        // Whether the pipeline is robust (shader stages copy)
        uint32_t isRobustContext : 1;
        // Whether the pipeline needs access to protected content (shader stages copy)
        uint32_t isProtectedContext : 1;

        uint32_t padding : 1;
    } bits;

    // Affecting specialization constants
    static_assert(gl::IMPLEMENTATION_MAX_DRAW_BUFFERS <= 8,
                  "2 bits per draw buffer is needed for dither emulation");
    uint16_t emulatedDitherControl;
    uint16_t padding;

    // Affecting VkPipelineDepthStencilStateCreateInfo
    // Dynamic in VK_EXT_extended_dynamic_state
    PackedStencilOpState front;
    PackedStencilOpState back;
};

constexpr size_t kPackedPreRasterizationAndFragmentStatesSize =
    sizeof(PackedPreRasterizationAndFragmentStates);
static_assert(kPackedPreRasterizationAndFragmentStatesSize == 12, "Size check failed");

struct PackedMultisampleAndSubpassState final
{
    struct
    {
        // Affecting VkPipelineMultisampleStateCreateInfo
        // Note: Only up to 16xMSAA is supported in the Vulkan backend.
        uint16_t sampleMask;
        // Stored as minus one so sample count 16 can fit in 4 bits.
        uint16_t rasterizationSamplesMinusOne : 4;
        uint16_t sampleShadingEnable : 1;
        uint16_t alphaToCoverageEnable : 1;
        uint16_t alphaToOneEnable : 1;
        // The subpass index affects both the shader stages and the fragment output similarly to
        // multisampled state, so they are grouped together.
        // Note: Currently only 2 subpasses possible.
        uint16_t subpass : 1;
        // 8-bit normalized instead of float to align the struct.
        uint16_t minSampleShading : 8;
    } bits;
};

constexpr size_t kPackedMultisampleAndSubpassStateSize = sizeof(PackedMultisampleAndSubpassState);
static_assert(kPackedMultisampleAndSubpassStateSize == 4, "Size check failed");

struct PackedColorBlendAttachmentState final
{
    uint16_t srcColorBlendFactor : 5;
    uint16_t dstColorBlendFactor : 5;
    uint16_t colorBlendOp : 6;
    uint16_t srcAlphaBlendFactor : 5;
    uint16_t dstAlphaBlendFactor : 5;
    uint16_t alphaBlendOp : 6;
};

constexpr size_t kPackedColorBlendAttachmentStateSize = sizeof(PackedColorBlendAttachmentState);
static_assert(kPackedColorBlendAttachmentStateSize == 4, "Size check failed");

struct PackedColorBlendState final
{
    uint8_t colorWriteMaskBits[gl::IMPLEMENTATION_MAX_DRAW_BUFFERS / 2];
    PackedColorBlendAttachmentState attachments[gl::IMPLEMENTATION_MAX_DRAW_BUFFERS];
};

constexpr size_t kPackedColorBlendStateSize = sizeof(PackedColorBlendState);
static_assert(kPackedColorBlendStateSize == 36, "Size check failed");

struct PackedBlendMaskAndLogicOpState final
{
    struct
    {
        uint32_t blendEnableMask : 8;
        uint32_t logicOpEnable : 1;
        // Dynamic in VK_EXT_extended_dynamic_state2
        uint32_t logicOp : 4;

        // Whether the pipeline needs access to protected content (fragment output copy)
        uint32_t isProtectedContext : 1;

        // Output that is present in the framebuffer but is never written to in the shader.  Used by
        // GL_ANGLE_robust_fragment_shader_output which defines the behavior in this case (which is
        // to mask these outputs)
        uint32_t missingOutputsMask : gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;

        uint32_t padding : 18 - gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
    } bits;
};

constexpr size_t kPackedBlendMaskAndLogicOpStateSize = sizeof(PackedBlendMaskAndLogicOpState);
static_assert(kPackedBlendMaskAndLogicOpStateSize == 4, "Size check failed");

// The vertex input subset of the pipeline.
struct PipelineVertexInputState final
{
    PackedInputAssemblyState inputAssembly;
    PackedVertexInputAttributes vertex;
};

// The pre-rasterization and fragment shader subsets of the pipeline.  This is excluding
// multisampled and render pass states which are shared with fragment output.
struct PipelineShadersState final
{
    PackedPreRasterizationAndFragmentStates shaders;
};

// Multisampled and render pass states.
struct PipelineSharedNonVertexInputState final
{
    PackedMultisampleAndSubpassState multisample;
    RenderPassDesc renderPass;
};

// The fragment output subset of the pipeline.  This is excluding multisampled and render pass
// states which are shared with the shader subsets.
struct PipelineFragmentOutputState final
{
    PackedColorBlendState blend;
    PackedBlendMaskAndLogicOpState blendMaskAndLogic;
};

constexpr size_t kGraphicsPipelineVertexInputStateSize =
    kPackedVertexInputAttributesSize + kPackedInputAssemblyStateSize;
constexpr size_t kGraphicsPipelineShadersStateSize = kPackedPreRasterizationAndFragmentStatesSize;
constexpr size_t kGraphicsPipelineSharedNonVertexInputStateSize =
    kPackedMultisampleAndSubpassStateSize + kRenderPassDescSize;
constexpr size_t kGraphicsPipelineFragmentOutputStateSize =
    kPackedColorBlendStateSize + kPackedBlendMaskAndLogicOpStateSize;

constexpr size_t kGraphicsPipelineDescSumOfSizes =
    kGraphicsPipelineVertexInputStateSize + kGraphicsPipelineShadersStateSize +
    kGraphicsPipelineSharedNonVertexInputStateSize + kGraphicsPipelineFragmentOutputStateSize;

// Number of dirty bits in the dirty bit set.
constexpr size_t kGraphicsPipelineDirtyBitBytes = 4;
constexpr static size_t kNumGraphicsPipelineDirtyBits =
    kGraphicsPipelineDescSumOfSizes / kGraphicsPipelineDirtyBitBytes;
static_assert(kNumGraphicsPipelineDirtyBits <= 64, "Too many pipeline dirty bits");

// Set of dirty bits. Each bit represents kGraphicsPipelineDirtyBitBytes in the desc.
using GraphicsPipelineTransitionBits = angle::BitSet<kNumGraphicsPipelineDirtyBits>;

GraphicsPipelineTransitionBits GetGraphicsPipelineTransitionBitsMask(GraphicsPipelineSubset subset);

// Disable padding warnings for a few helper structs that aggregate Vulkan state objects.  These are
// not used as hash keys, they just simplify passing them around to functions.
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

struct GraphicsPipelineVertexInputVulkanStructs
{
    VkPipelineVertexInputStateCreateInfo vertexInputState       = {};
    VkPipelineInputAssemblyStateCreateInfo inputAssemblyState   = {};
    VkPipelineVertexInputDivisorStateCreateInfoEXT divisorState = {};

    // Support storage
    gl::AttribArray<VkVertexInputBindingDescription> bindingDescs;
    gl::AttribArray<VkVertexInputAttributeDescription> attributeDescs;
    gl::AttribArray<VkVertexInputBindingDivisorDescriptionEXT> divisorDesc;
};

struct GraphicsPipelineShadersVulkanStructs
{
    VkPipelineViewportStateCreateInfo viewportState                               = {};
    VkPipelineRasterizationStateCreateInfo rasterState                            = {};
    VkPipelineDepthStencilStateCreateInfo depthStencilState                       = {};
    VkPipelineTessellationStateCreateInfo tessellationState                       = {};
    VkPipelineTessellationDomainOriginStateCreateInfo domainOriginState           = {};
    VkPipelineViewportDepthClipControlCreateInfoEXT depthClipControl              = {};
    VkPipelineRasterizationLineStateCreateInfoEXT rasterLineState                 = {};
    VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provokingVertexState = {};
    VkPipelineRasterizationStateStreamCreateInfoEXT rasterStreamState             = {};

    // Support storage
    angle::FixedVector<VkPipelineShaderStageCreateInfo, 5> shaderStages;
};

struct GraphicsPipelineSharedNonVertexInputVulkanStructs
{
    VkPipelineMultisampleStateCreateInfo multisampleState = {};

    // Support storage
    uint32_t sampleMask;
};

struct GraphicsPipelineFragmentOutputVulkanStructs
{
    VkPipelineColorBlendStateCreateInfo blendState = {};

    // Support storage
    gl::DrawBuffersArray<VkPipelineColorBlendAttachmentState> blendAttachmentState;
};

ANGLE_ENABLE_STRUCT_PADDING_WARNINGS

using GraphicsPipelineDynamicStateList = angle::FixedVector<VkDynamicState, 24>;

enum class PipelineRobustness
{
    NonRobust,
    Robust,
};

enum class PipelineProtectedAccess
{
    Unprotected,
    Protected,
};

// Context state that can affect a compute pipeline
union ComputePipelineOptions final
{
    struct
    {
        // Whether VK_EXT_pipeline_robustness should be used to make the pipeline robust.  Note that
        // programs are allowed to be shared between robust and non-robust contexts, so different
        // pipelines can be created for the same compute program.
        uint8_t robustness : 1;
        // Whether VK_EXT_pipeline_protected_access should be used to make the pipeline
        // protected-only. Similar to robustness, EGL allows protected and unprotected to be in the
        // same share group.
        uint8_t protectedAccess : 1;
        uint8_t reserved : 6;  // must initialize to zero
    };
    uint8_t permutationIndex;
    static constexpr uint32_t kPermutationCount = 0x1 << 2;
};
static_assert(sizeof(ComputePipelineOptions) == 1, "Size check failed");
ComputePipelineOptions GetComputePipelineOptions(vk::PipelineRobustness robustness,
                                                 vk::PipelineProtectedAccess protectedAccess);

// Compute Pipeline Description
class ComputePipelineDesc final
{
  public:
    void *operator new(std::size_t size);
    void operator delete(void *ptr);

    ComputePipelineDesc();
    ComputePipelineDesc(const ComputePipelineDesc &other);
    ComputePipelineDesc &operator=(const ComputePipelineDesc &other);

    ComputePipelineDesc(VkSpecializationInfo *specializationInfo,
                        vk::ComputePipelineOptions pipelineOptions);
    ~ComputePipelineDesc() = default;

    size_t hash() const;
    bool keyEqual(const ComputePipelineDesc &other) const;

    template <typename T>
    const T *getPtr() const
    {
        return reinterpret_cast<const T *>(this);
    }

    std::vector<uint32_t> getConstantIds() const { return mConstantIds; }
    std::vector<uint32_t> getConstants() const { return mConstants; }
    ComputePipelineOptions getPipelineOptions() const { return mPipelineOptions; }

  private:
    std::vector<uint32_t> mConstantIds, mConstants;
    ComputePipelineOptions mPipelineOptions = {};
    char mPadding[7]                        = {};
};

class PipelineHelper;

// When a graphics pipeline is created, the shaders state is either directly specified (monolithic
// pipeline) or is specified in a pipeline library.  This struct encapsulates the choices.
struct GraphicsPipelineShadersInfo final
{
  public:
    GraphicsPipelineShadersInfo(const ShaderModuleMap *shaders) : mShaders(shaders) {}
    GraphicsPipelineShadersInfo(vk::PipelineHelper *pipelineLibrary)
        : mPipelineLibrary(pipelineLibrary)
    {}

    vk::PipelineHelper *pipelineLibrary() const { return mPipelineLibrary; }
    bool usePipelineLibrary() const { return mPipelineLibrary != nullptr; }

  private:
    // If the shaders state should be directly specified in the final pipeline.
    const ShaderModuleMap *mShaders            = nullptr;

    // If the shaders state is provided via a pipeline library.
    vk::PipelineHelper *mPipelineLibrary = nullptr;

    friend class GraphicsPipelineDesc;
};

angle::FormatID PatchVertexAttribComponentType(angle::FormatID format,
                                               gl::ComponentType vsInputType);

// State changes are applied through the update methods. Each update method can also have a
// sibling method that applies the update without marking a state transition. The non-transition
// update methods are used for internal shader pipelines. Not every non-transition update method
// is implemented yet as not every state is used in internal shaders.
class GraphicsPipelineDesc final
{
  public:
    // Use aligned allocation and free so we can use the alignas keyword.
    void *operator new(std::size_t size);
    void operator delete(void *ptr);

    GraphicsPipelineDesc();
    ~GraphicsPipelineDesc();
    GraphicsPipelineDesc(const GraphicsPipelineDesc &other);
    GraphicsPipelineDesc &operator=(const GraphicsPipelineDesc &other);

    size_t hash(GraphicsPipelineSubset subset) const;
    bool keyEqual(const GraphicsPipelineDesc &other, GraphicsPipelineSubset subset) const;

    void initDefaults(const ErrorContext *context,
                      GraphicsPipelineSubset subset,
                      PipelineRobustness contextRobustness,
                      PipelineProtectedAccess contextProtectedAccess);

    // For custom comparisons.
    template <typename T>
    const T *getPtr() const
    {
        return reinterpret_cast<const T *>(this);
    }

    VkResult initializePipeline(ErrorContext *context,
                                PipelineCacheAccess *pipelineCache,
                                GraphicsPipelineSubset subset,
                                const RenderPass &compatibleRenderPass,
                                const PipelineLayout &pipelineLayout,
                                const GraphicsPipelineShadersInfo &shaders,
                                Pipeline *pipelineOut,
                                CacheLookUpFeedback *feedbackOut) const;

    // Vertex input state. For ES 3.1 this should be separated into binding and attribute.
    void updateVertexInput(ContextVk *contextVk,
                           GraphicsPipelineTransitionBits *transition,
                           uint32_t attribIndex,
                           GLuint stride,
                           GLuint divisor,
                           angle::FormatID format,
                           GLuint relativeOffset);
    void updateVertexInputWithStride(ContextVk *contextVk,
                                     GraphicsPipelineTransitionBits *transition,
                                     uint32_t attribIndex,
                                     GLuint stride);

    void setVertexShaderComponentTypes(gl::AttributesMask activeAttribLocations,
                                       gl::ComponentTypeMask componentTypeMask);
    void updateVertexShaderComponentTypes(GraphicsPipelineTransitionBits *transition,
                                          gl::AttributesMask activeAttribLocations,
                                          gl::ComponentTypeMask componentTypeMask);

    // Input assembly info
    void setTopology(gl::PrimitiveMode drawMode);
    void updateTopology(GraphicsPipelineTransitionBits *transition, gl::PrimitiveMode drawMode);
    void updatePrimitiveRestartEnabled(GraphicsPipelineTransitionBits *transition,
                                       bool primitiveRestartEnabled);

    // Viewport states
    void updateDepthClipControl(GraphicsPipelineTransitionBits *transition, bool negativeOneToOne);

    // Raster states
    void updatePolygonMode(GraphicsPipelineTransitionBits *transition, gl::PolygonMode polygonMode);
    void updateCullMode(GraphicsPipelineTransitionBits *transition,
                        const gl::RasterizerState &rasterState);
    void updateFrontFace(GraphicsPipelineTransitionBits *transition,
                         const gl::RasterizerState &rasterState,
                         bool invertFrontFace);
    void updateRasterizerDiscardEnabled(GraphicsPipelineTransitionBits *transition,
                                        bool rasterizerDiscardEnabled);

    // Multisample states
    uint32_t getRasterizationSamples() const;
    void setRasterizationSamples(uint32_t rasterizationSamples);
    void updateRasterizationSamples(GraphicsPipelineTransitionBits *transition,
                                    uint32_t rasterizationSamples);
    void updateAlphaToCoverageEnable(GraphicsPipelineTransitionBits *transition, bool enable);
    void updateAlphaToOneEnable(GraphicsPipelineTransitionBits *transition, bool enable);
    void updateSampleMask(GraphicsPipelineTransitionBits *transition,
                          uint32_t maskNumber,
                          uint32_t mask);

    void updateSampleShading(GraphicsPipelineTransitionBits *transition, bool enable, float value);

    // RenderPass description.
    const RenderPassDesc &getRenderPassDesc() const { return mSharedNonVertexInput.renderPass; }

    void setRenderPassDesc(const RenderPassDesc &renderPassDesc);
    void updateRenderPassDesc(GraphicsPipelineTransitionBits *transition,
                              const angle::FeaturesVk &features,
                              const RenderPassDesc &renderPassDesc,
                              FramebufferFetchMode framebufferFetchMode);
    void setRenderPassSampleCount(GLint samples);
    void setRenderPassFramebufferFetchMode(FramebufferFetchMode framebufferFetchMode);
    bool getRenderPassColorFramebufferFetchMode() const
    {
        return mSharedNonVertexInput.renderPass.hasColorFramebufferFetch();
    }
    bool getRenderPassDepthStencilFramebufferFetchMode() const
    {
        return mSharedNonVertexInput.renderPass.hasDepthStencilFramebufferFetch();
    }

    void setRenderPassFoveation(bool isFoveated);
    bool getRenderPassFoveation() const
    {
        return mSharedNonVertexInput.renderPass.hasFragmentShadingAttachment();
    }

    void setRenderPassColorAttachmentFormat(size_t colorIndexGL, angle::FormatID formatID);

    // Blend states
    void setSingleBlend(uint32_t colorIndexGL,
                        bool enabled,
                        VkBlendOp op,
                        VkBlendFactor srcFactor,
                        VkBlendFactor dstFactor);
    void updateBlendEnabled(GraphicsPipelineTransitionBits *transition,
                            gl::DrawBufferMask blendEnabledMask);
    void updateBlendFuncs(GraphicsPipelineTransitionBits *transition,
                          const gl::BlendStateExt &blendStateExt,
                          gl::DrawBufferMask attachmentMask);
    void updateBlendEquations(GraphicsPipelineTransitionBits *transition,
                              const gl::BlendStateExt &blendStateExt,
                              gl::DrawBufferMask attachmentMask);
    void resetBlendFuncsAndEquations(GraphicsPipelineTransitionBits *transition,
                                     const gl::BlendStateExt &blendStateExt,
                                     gl::DrawBufferMask previousAttachmentsMask,
                                     gl::DrawBufferMask newAttachmentsMask);
    void setColorWriteMasks(gl::BlendStateExt::ColorMaskStorage::Type colorMasks,
                            const gl::DrawBufferMask &alphaMask,
                            const gl::DrawBufferMask &enabledDrawBuffers);
    void setSingleColorWriteMask(uint32_t colorIndexGL, VkColorComponentFlags colorComponentFlags);
    void updateColorWriteMasks(GraphicsPipelineTransitionBits *transition,
                               gl::BlendStateExt::ColorMaskStorage::Type colorMasks,
                               const gl::DrawBufferMask &alphaMask,
                               const gl::DrawBufferMask &enabledDrawBuffers);
    void updateMissingOutputsMask(GraphicsPipelineTransitionBits *transition,
                                  gl::DrawBufferMask missingOutputsMask);

    // Logic op
    void updateLogicOpEnabled(GraphicsPipelineTransitionBits *transition, bool enable);
    void updateLogicOp(GraphicsPipelineTransitionBits *transition, VkLogicOp logicOp);

    // Depth/stencil states.
    void setDepthTestEnabled(bool enabled);
    void setDepthWriteEnabled(bool enabled);
    void setDepthFunc(VkCompareOp op);
    void setDepthClampEnabled(bool enabled);
    void setStencilTestEnabled(bool enabled);
    void setStencilFrontFuncs(VkCompareOp compareOp);
    void setStencilBackFuncs(VkCompareOp compareOp);
    void setStencilFrontOps(VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp);
    void setStencilBackOps(VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp);
    void setStencilFrontWriteMask(uint8_t mask);
    void setStencilBackWriteMask(uint8_t mask);
    void updateDepthTestEnabled(GraphicsPipelineTransitionBits *transition,
                                const gl::DepthStencilState &depthStencilState,
                                const gl::Framebuffer *drawFramebuffer);
    void updateDepthFunc(GraphicsPipelineTransitionBits *transition,
                         const gl::DepthStencilState &depthStencilState);
    void updateDepthClampEnabled(GraphicsPipelineTransitionBits *transition, bool enabled);
    void updateDepthWriteEnabled(GraphicsPipelineTransitionBits *transition,
                                 const gl::DepthStencilState &depthStencilState,
                                 const gl::Framebuffer *drawFramebuffer);
    void updateStencilTestEnabled(GraphicsPipelineTransitionBits *transition,
                                  const gl::DepthStencilState &depthStencilState,
                                  const gl::Framebuffer *drawFramebuffer);
    void updateStencilFrontFuncs(GraphicsPipelineTransitionBits *transition,
                                 const gl::DepthStencilState &depthStencilState);
    void updateStencilBackFuncs(GraphicsPipelineTransitionBits *transition,
                                const gl::DepthStencilState &depthStencilState);
    void updateStencilFrontOps(GraphicsPipelineTransitionBits *transition,
                               const gl::DepthStencilState &depthStencilState);
    void updateStencilBackOps(GraphicsPipelineTransitionBits *transition,
                              const gl::DepthStencilState &depthStencilState);

    // Depth offset.
    void updatePolygonOffsetEnabled(GraphicsPipelineTransitionBits *transition, bool enabled);

    // Tessellation
    void updatePatchVertices(GraphicsPipelineTransitionBits *transition, GLuint value);

    // Subpass
    void resetSubpass(GraphicsPipelineTransitionBits *transition);
    void nextSubpass(GraphicsPipelineTransitionBits *transition);
    void setSubpass(uint32_t subpass);
    uint32_t getSubpass() const;

    void updateEmulatedDitherControl(GraphicsPipelineTransitionBits *transition, uint16_t value);
    uint32_t getEmulatedDitherControl() const { return mShaders.shaders.emulatedDitherControl; }

    bool isLegacyDitherEnabled() const
    {
        return mSharedNonVertexInput.renderPass.isLegacyDitherEnabled();
    }

    void updateNonZeroStencilWriteMaskWorkaround(GraphicsPipelineTransitionBits *transition,
                                                 bool enabled);

    void setSupportsDynamicStateForTest(bool supports)
    {
        mVertexInput.inputAssembly.bits.useVertexInputBindingStrideDynamicState = supports;
        mShaders.shaders.bits.nonZeroStencilWriteMaskWorkaround                 = false;
    }

    static VkFormat getPipelineVertexInputStateFormat(ErrorContext *context,
                                                      angle::FormatID formatID,
                                                      const gl::ComponentType programAttribType,
                                                      uint32_t attribIndex);

    // Helpers to dump the state
    const PipelineVertexInputState &getVertexInputStateForLog() const { return mVertexInput; }
    const PipelineShadersState &getShadersStateForLog() const { return mShaders; }
    const PipelineSharedNonVertexInputState &getSharedNonVertexInputStateForLog() const
    {
        return mSharedNonVertexInput;
    }
    const PipelineFragmentOutputState &getFragmentOutputStateForLog() const
    {
        return mFragmentOutput;
    }

    bool hasPipelineProtectedAccess() const
    {
        ASSERT(mShaders.shaders.bits.isProtectedContext ==
               mVertexInput.inputAssembly.bits.isProtectedContext);
        ASSERT(mShaders.shaders.bits.isProtectedContext ==
               mFragmentOutput.blendMaskAndLogic.bits.isProtectedContext);

        return mShaders.shaders.bits.isProtectedContext;
    }

  private:
    void updateSubpass(GraphicsPipelineTransitionBits *transition, uint32_t subpass);

    const void *getPipelineSubsetMemory(GraphicsPipelineSubset subset, size_t *sizeOut) const;

    void initializePipelineVertexInputState(
        ErrorContext *context,
        GraphicsPipelineVertexInputVulkanStructs *stateOut,
        GraphicsPipelineDynamicStateList *dynamicStateListOut) const;

    void initializePipelineShadersState(
        ErrorContext *context,
        const ShaderModuleMap &shaders,
        GraphicsPipelineShadersVulkanStructs *stateOut,
        GraphicsPipelineDynamicStateList *dynamicStateListOut) const;

    void initializePipelineSharedNonVertexInputState(
        ErrorContext *context,
        GraphicsPipelineSharedNonVertexInputVulkanStructs *stateOut,
        GraphicsPipelineDynamicStateList *dynamicStateListOut) const;

    void initializePipelineFragmentOutputState(
        ErrorContext *context,
        GraphicsPipelineFragmentOutputVulkanStructs *stateOut,
        GraphicsPipelineDynamicStateList *dynamicStateListOut) const;

    PipelineShadersState mShaders;
    PipelineSharedNonVertexInputState mSharedNonVertexInput;
    PipelineFragmentOutputState mFragmentOutput;
    PipelineVertexInputState mVertexInput;
};

// Verify the packed pipeline description has no gaps in the packing.
// This is not guaranteed by the spec, but is validated by a compile-time check.
// No gaps or padding at the end ensures that hashing and memcmp checks will not run
// into uninitialized memory regions.
constexpr size_t kGraphicsPipelineDescSize = sizeof(GraphicsPipelineDesc);
static_assert(kGraphicsPipelineDescSize == kGraphicsPipelineDescSumOfSizes, "Size mismatch");

// Values are based on data recorded here -> https://anglebug.com/42267114#comment5
constexpr size_t kDefaultDescriptorSetLayoutBindingsCount = 8;
constexpr size_t kDefaultImmutableSamplerBindingsCount    = 1;
using DescriptorSetLayoutBindingVector =
    angle::FastVector<VkDescriptorSetLayoutBinding, kDefaultDescriptorSetLayoutBindingsCount>;

// A packed description of a descriptor set layout. Use similarly to RenderPassDesc and
// GraphicsPipelineDesc. Currently we only need to differentiate layouts based on sampler and ubo
// usage. In the future we could generalize this.
class DescriptorSetLayoutDesc final
{
  public:
    DescriptorSetLayoutDesc();
    ~DescriptorSetLayoutDesc();
    DescriptorSetLayoutDesc(const DescriptorSetLayoutDesc &other);
    DescriptorSetLayoutDesc &operator=(const DescriptorSetLayoutDesc &other);

    size_t hash() const;
    bool operator==(const DescriptorSetLayoutDesc &other) const;

    void addBinding(uint32_t bindingIndex,
                    VkDescriptorType descriptorType,
                    uint32_t count,
                    VkShaderStageFlags stages,
                    const Sampler *immutableSampler);

    void unpackBindings(DescriptorSetLayoutBindingVector *bindings) const;

    bool empty() const { return mDescriptorSetLayoutBindings.empty(); }

  private:
    // There is a small risk of an issue if the sampler cache is evicted but not the descriptor
    // cache we would have an invalid handle here. Thus propose follow-up work:
    // TODO: https://issuetracker.google.com/issues/159156775: Have immutable sampler use serial
    union PackedDescriptorSetBinding
    {
        static constexpr uint8_t kInvalidType = 255;

        struct
        {
            uint8_t type;                      // Stores a packed VkDescriptorType descriptorType.
            uint8_t stages;                    // Stores a packed VkShaderStageFlags.
            uint16_t count : 15;               // Stores a packed uint32_t descriptorCount
            uint16_t hasImmutableSampler : 1;  // Whether this binding has an immutable sampler
        };
        uint32_t value;

        bool operator==(const PackedDescriptorSetBinding &other) const
        {
            return value == other.value;
        }
    };

    // 1x 32bit
    static_assert(sizeof(PackedDescriptorSetBinding) == 4, "Unexpected size");

    angle::FastVector<VkSampler, kDefaultImmutableSamplerBindingsCount> mImmutableSamplers;
    angle::FastVector<PackedDescriptorSetBinding, kDefaultDescriptorSetLayoutBindingsCount>
        mDescriptorSetLayoutBindings;

#if !defined(ANGLE_IS_64_BIT_CPU)
    ANGLE_MAYBE_UNUSED_PRIVATE_FIELD uint32_t mPadding = 0;
#endif
};

// The following are for caching descriptor set layouts. Limited to max four descriptor set
// layouts. This can be extended in the future.
constexpr size_t kMaxDescriptorSetLayouts = ToUnderlying(DescriptorSetIndex::EnumCount);

union PackedPushConstantRange
{
    struct
    {
        uint8_t offset;
        uint8_t size;
        uint16_t stageMask;
    };
    uint32_t value;

    bool operator==(const PackedPushConstantRange &other) const { return value == other.value; }
};

static_assert(sizeof(PackedPushConstantRange) == sizeof(uint32_t), "Unexpected Size");

template <typename T>
using DescriptorSetArray = angle::PackedEnumMap<DescriptorSetIndex, T>;
using DescriptorSetLayoutPointerArray = DescriptorSetArray<DescriptorSetLayoutPtr>;

class PipelineLayoutDesc final
{
  public:
    PipelineLayoutDesc();
    ~PipelineLayoutDesc();
    PipelineLayoutDesc(const PipelineLayoutDesc &other);
    PipelineLayoutDesc &operator=(const PipelineLayoutDesc &rhs);

    size_t hash() const;
    bool operator==(const PipelineLayoutDesc &other) const;

    void updateDescriptorSetLayout(DescriptorSetIndex setIndex,
                                   const DescriptorSetLayoutDesc &desc);
    void updatePushConstantRange(VkShaderStageFlags stageMask, uint32_t offset, uint32_t size);

    const PackedPushConstantRange &getPushConstantRange() const { return mPushConstantRange; }

  private:
    DescriptorSetArray<DescriptorSetLayoutDesc> mDescriptorSetLayouts;
    PackedPushConstantRange mPushConstantRange;
    ANGLE_MAYBE_UNUSED_PRIVATE_FIELD uint32_t mPadding;

    // Verify the arrays are properly packed.
    static_assert(sizeof(decltype(mDescriptorSetLayouts)) ==
                      (sizeof(DescriptorSetLayoutDesc) * kMaxDescriptorSetLayouts),
                  "Unexpected size");
};

// Verify the structure is properly packed.
static_assert(sizeof(PipelineLayoutDesc) == sizeof(DescriptorSetArray<DescriptorSetLayoutDesc>) +
                                                sizeof(PackedPushConstantRange) + sizeof(uint32_t),
              "Unexpected Size");

enum class YcbcrLinearFilterSupport
{
    Unsupported,
    Supported,
};

class YcbcrConversionDesc final
{
  public:
    YcbcrConversionDesc();
    ~YcbcrConversionDesc();
    YcbcrConversionDesc(const YcbcrConversionDesc &other);
    YcbcrConversionDesc &operator=(const YcbcrConversionDesc &other);

    size_t hash() const;
    bool operator==(const YcbcrConversionDesc &other) const;
    bool operator!=(const YcbcrConversionDesc &other) const;

    bool valid() const { return mExternalOrVkFormat != 0; }
    void reset();
    void update(Renderer *renderer,
                uint64_t externalFormat,
                VkSamplerYcbcrModelConversion conversionModel,
                VkSamplerYcbcrRange colorRange,
                VkChromaLocation xChromaOffset,
                VkChromaLocation yChromaOffset,
                VkFilter chromaFilter,
                VkComponentMapping components,
                angle::FormatID intendedFormatID,
                YcbcrLinearFilterSupport linearFilterSupported);
    VkFilter getChromaFilter() const { return static_cast<VkFilter>(mChromaFilter); }
    bool updateChromaFilter(Renderer *renderer, VkFilter filter);
    void updateConversionModel(VkSamplerYcbcrModelConversion conversionModel);
    uint64_t getExternalFormat() const { return mIsExternalFormat ? mExternalOrVkFormat : 0; }

    angle::Result init(ErrorContext *context, SamplerYcbcrConversion *conversionOut) const;

  private:
    // If the sampler needs to convert the image content (e.g. from YUV to RGB) then
    // mExternalOrVkFormat will be non-zero. The value is either the external format
    // as returned by vkGetAndroidHardwareBufferPropertiesANDROID or a YUV VkFormat.
    // For VkSamplerYcbcrConversion, mExternalOrVkFormat along with mIsExternalFormat,
    // mConversionModel and mColorRange works as a Serial() used elsewhere in ANGLE.
    uint64_t mExternalOrVkFormat;
    // 1 bit to identify if external format is used
    uint32_t mIsExternalFormat : 1;
    // 3 bits to identify conversion model
    uint32_t mConversionModel : 3;
    // 1 bit to identify color component range
    uint32_t mColorRange : 1;
    // 1 bit to identify x chroma location
    uint32_t mXChromaOffset : 1;
    // 1 bit to identify y chroma location
    uint32_t mYChromaOffset : 1;
    // 1 bit to identify chroma filtering
    uint32_t mChromaFilter : 1;
    // 3 bit to identify R component swizzle
    uint32_t mRSwizzle : 3;
    // 3 bit to identify G component swizzle
    uint32_t mGSwizzle : 3;
    // 3 bit to identify B component swizzle
    uint32_t mBSwizzle : 3;
    // 3 bit to identify A component swizzle
    uint32_t mASwizzle : 3;
    // 1 bit for whether linear filtering is supported (independent of whether currently enabled)
    uint32_t mLinearFilterSupported : 1;
    uint32_t mPadding : 11;
    uint32_t mReserved;
};

static_assert(sizeof(YcbcrConversionDesc) == 16, "Unexpected YcbcrConversionDesc size");

// Packed sampler description for the sampler cache.
class SamplerDesc final
{
  public:
    SamplerDesc();
    SamplerDesc(ErrorContext *context,
                const gl::SamplerState &samplerState,
                bool stencilMode,
                const YcbcrConversionDesc *ycbcrConversionDesc,
                angle::FormatID intendedFormatID);
    ~SamplerDesc();

    SamplerDesc(const SamplerDesc &other);
    SamplerDesc &operator=(const SamplerDesc &rhs);

    void update(Renderer *renderer,
                const gl::SamplerState &samplerState,
                bool stencilMode,
                const YcbcrConversionDesc *ycbcrConversionDesc,
                angle::FormatID intendedFormatID);
    void reset();
    angle::Result init(ContextVk *contextVk, Sampler *sampler) const;

    size_t hash() const;
    bool operator==(const SamplerDesc &other) const;

  private:
    // 32*4 bits for floating point data.
    // Note: anisotropy enabled is implicitly determined by maxAnisotropy and caps.
    float mMipLodBias;
    float mMaxAnisotropy;
    float mMinLod;
    float mMaxLod;

    // 16*8 bits to uniquely identify a YCbCr conversion sampler.
    YcbcrConversionDesc mYcbcrConversionDesc;

    // 16 bits for modes + states.
    // 1 bit per filter (only 2 possible values in GL: linear/nearest)
    uint16_t mMagFilter : 1;
    uint16_t mMinFilter : 1;
    uint16_t mMipmapMode : 1;

    // 3 bits per address mode (5 possible values)
    uint16_t mAddressModeU : 3;
    uint16_t mAddressModeV : 3;
    uint16_t mAddressModeW : 3;

    // 1 bit for compare enabled (2 possible values)
    uint16_t mCompareEnabled : 1;

    // 3 bits for compare op. (8 possible values)
    uint16_t mCompareOp : 3;

    // Values from angle::ColorGeneric::Type. Float is 0 and others are 1.
    uint16_t mBorderColorType : 1;

    uint16_t mUsesSecondComponentForStencil : 1;

    uint16_t mPadding : 14;

    // 16*8 bits for BorderColor
    angle::ColorF mBorderColor;

    // 32 bits reserved for future use.
    uint32_t mReserved;
};

static_assert(sizeof(SamplerDesc) == 56, "Unexpected SamplerDesc size");

// Disable warnings about struct padding.
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

struct GraphicsPipelineTransition
{
    GraphicsPipelineTransition();
    GraphicsPipelineTransition(const GraphicsPipelineTransition &other);
    GraphicsPipelineTransition(GraphicsPipelineTransitionBits bits,
                               const GraphicsPipelineDesc *desc,
                               PipelineHelper *pipeline);

    GraphicsPipelineTransitionBits bits;
    const GraphicsPipelineDesc *desc;
    PipelineHelper *target;
};

ANGLE_INLINE GraphicsPipelineTransition::GraphicsPipelineTransition() = default;

ANGLE_INLINE GraphicsPipelineTransition::GraphicsPipelineTransition(
    const GraphicsPipelineTransition &other) = default;

ANGLE_INLINE GraphicsPipelineTransition::GraphicsPipelineTransition(
    GraphicsPipelineTransitionBits bits,
    const GraphicsPipelineDesc *desc,
    PipelineHelper *pipeline)
    : bits(bits), desc(desc), target(pipeline)
{}

ANGLE_INLINE bool GraphicsPipelineTransitionMatch(GraphicsPipelineTransitionBits bitsA,
                                                  GraphicsPipelineTransitionBits bitsB,
                                                  const GraphicsPipelineDesc &descA,
                                                  const GraphicsPipelineDesc &descB)
{
    if (bitsA != bitsB)
        return false;

    // We currently mask over 4 bytes of the pipeline description with each dirty bit.
    // We could consider using 8 bytes and a mask of 32 bits. This would make some parts
    // of the code faster. The for loop below would scan over twice as many bits per iteration.
    // But there may be more collisions between the same dirty bit masks leading to different
    // transitions. Thus there may be additional cost when applications use many transitions.
    // We should revisit this in the future and investigate using different bit widths.
    static_assert(sizeof(uint32_t) == kGraphicsPipelineDirtyBitBytes, "Size mismatch");

    const uint32_t *rawPtrA = descA.getPtr<uint32_t>();
    const uint32_t *rawPtrB = descB.getPtr<uint32_t>();

    for (size_t dirtyBit : bitsA)
    {
        if (ANGLE_UNSAFE_TODO(rawPtrA[dirtyBit] != rawPtrB[dirtyBit]))
        {
            return false;
        }
    }

    return true;
}

// A class that encapsulates the vk::PipelineCache and associated mutex.  The mutex may be nullptr
// if synchronization is not necessary.
class PipelineCacheAccess
{
  public:
    PipelineCacheAccess()  = default;
    ~PipelineCacheAccess() = default;

    void init(const vk::PipelineCache *pipelineCache, angle::SimpleMutex *mutex)
    {
        mPipelineCache = pipelineCache;
        mMutex         = mutex;
    }

    VkResult createGraphicsPipeline(vk::ErrorContext *context,
                                    const VkGraphicsPipelineCreateInfo &createInfo,
                                    vk::Pipeline *pipelineOut);
    VkResult createComputePipeline(vk::ErrorContext *context,
                                   const VkComputePipelineCreateInfo &createInfo,
                                   vk::Pipeline *pipelineOut);

    VkResult getCacheData(vk::ErrorContext *context, size_t *cacheSize, void *cacheData);

    void merge(Renderer *renderer, const vk::PipelineCache &pipelineCache);

    bool isThreadSafe() const { return mMutex != nullptr; }

  private:
    std::unique_lock<angle::SimpleMutex> getLock();

    const vk::PipelineCache *mPipelineCache = nullptr;
    angle::SimpleMutex *mMutex;
};

// Monolithic pipeline creation tasks are created as soon as a pipeline is created out of libraries.
// However, they are not immediately posted to the worker queue to allow pacing.  On each use of a
// pipeline, an attempt is made to post the task.
class CreateMonolithicPipelineTask : public ErrorContext, public angle::Closure
{
  public:
    CreateMonolithicPipelineTask(Renderer *renderer,
                                 const PipelineCacheAccess &pipelineCache,
                                 const PipelineLayout &pipelineLayout,
                                 const ShaderModuleMap &shaders,
                                 const GraphicsPipelineDesc &desc);

    // The compatible render pass is set only when the task is ready to run.  This is because the
    // render pass cache may have been cleared since the task was created (e.g. to accomodate
    // framebuffer fetch).  Such render pass cache clears ensure there are no active tasks, so it's
    // safe to hold on to this pointer for the brief period between task post and completion.
    //
    // Not applicable to dynamic rendering.
    const RenderPassDesc &getRenderPassDesc() const { return mDesc.getRenderPassDesc(); }
    void setCompatibleRenderPass(const RenderPass *compatibleRenderPass);

    void operator()() override;

    VkResult getResult() const { return mResult; }
    Pipeline &getPipeline() { return mPipeline; }
    CacheLookUpFeedback getFeedback() const { return mFeedback; }

    void handleError(VkResult result,
                     const char *file,
                     const char *function,
                     unsigned int line) override;

  private:
    // Input to pipeline creation
    PipelineCacheAccess mPipelineCache;
    const RenderPass *mCompatibleRenderPass;
    const PipelineLayout &mPipelineLayout;
    const ShaderModuleMap &mShaders;
    GraphicsPipelineDesc mDesc;

    // Results
    VkResult mResult;
    Pipeline mPipeline;
    CacheLookUpFeedback mFeedback;
};

class WaitableMonolithicPipelineCreationTask
{
  public:
    ~WaitableMonolithicPipelineCreationTask();

    void setTask(std::shared_ptr<CreateMonolithicPipelineTask> &&task) { mTask = std::move(task); }
    void setRenderPass(const RenderPass *compatibleRenderPass)
    {
        mTask->setCompatibleRenderPass(compatibleRenderPass);
    }
    void onSchedule(const std::shared_ptr<angle::WaitableEvent> &waitableEvent)
    {
        mWaitableEvent = waitableEvent;
    }
    void reset()
    {
        mWaitableEvent.reset();
        mTask.reset();
    }

    bool isValid() const { return mTask.get() != nullptr; }
    bool isPosted() const { return mWaitableEvent.get() != nullptr; }
    bool isReady() { return mWaitableEvent->isReady(); }
    void wait() { return mWaitableEvent->wait(); }

    std::shared_ptr<CreateMonolithicPipelineTask> getTask() const { return mTask; }

  private:
    std::shared_ptr<angle::WaitableEvent> mWaitableEvent;
    std::shared_ptr<CreateMonolithicPipelineTask> mTask;
};

class PipelineHelper final : public Resource
{
  public:
    PipelineHelper();
    ~PipelineHelper() override;
    inline explicit PipelineHelper(Pipeline &&pipeline, CacheLookUpFeedback feedback);
    PipelineHelper &operator=(PipelineHelper &&other);

    void destroy(VkDevice device);
    void release(ErrorContext *context);

    bool valid() const { return mPipeline.valid(); }
    const Pipeline &getPipeline() const { return mPipeline; }

    // Get the pipeline.  If there is a monolithic pipeline creation task pending, scheduling it is
    // attempted.  If that task is done, the pipeline is replaced with the results and the old
    // pipeline released.
    angle::Result getPreferredPipeline(ContextVk *contextVk, const Pipeline **pipelineOut);

    ANGLE_INLINE bool findTransition(GraphicsPipelineTransitionBits bits,
                                     const GraphicsPipelineDesc &desc,
                                     PipelineHelper **pipelineOut) const
    {
        // Search could be improved using sorting or hashing.
        for (const GraphicsPipelineTransition &transition : mTransitions)
        {
            if (GraphicsPipelineTransitionMatch(transition.bits, bits, *transition.desc, desc))
            {
                *pipelineOut = transition.target;
                return true;
            }
        }

        return false;
    }

    void addTransition(GraphicsPipelineTransitionBits bits,
                       const GraphicsPipelineDesc *desc,
                       PipelineHelper *pipeline);

    const std::vector<GraphicsPipelineTransition> &getTransitions() const { return mTransitions; }

    void setComputePipeline(Pipeline &&pipeline, CacheLookUpFeedback feedback)
    {
        ASSERT(!mPipeline.valid());
        mPipeline = std::move(pipeline);

        ASSERT(mCacheLookUpFeedback == CacheLookUpFeedback::None);
        mCacheLookUpFeedback = feedback;
    }
    CacheLookUpFeedback getCacheLookUpFeedback() const { return mCacheLookUpFeedback; }

    void setLinkedLibraryReferences(vk::PipelineHelper *shadersPipeline);

    void retainInRenderPass(RenderPassCommandBufferHelper *renderPassCommands);

    void setMonolithicPipelineCreationTask(std::shared_ptr<CreateMonolithicPipelineTask> &&task)
    {
        mMonolithicPipelineCreationTask.setTask(std::move(task));
    }

  private:
    void reset();

    std::vector<GraphicsPipelineTransition> mTransitions;
    Pipeline mPipeline;
    CacheLookUpFeedback mCacheLookUpFeedback           = CacheLookUpFeedback::None;
    CacheLookUpFeedback mMonolithicCacheLookUpFeedback = CacheLookUpFeedback::None;

    // The list of pipeline helpers that were referenced when creating a linked pipeline.  These
    // pipelines must be kept alive, so their serial is updated at the same time as this object.
    // The shaders pipeline is the only library so far.
    PipelineHelper *mLinkedShaders = nullptr;

    // If pipeline libraries are used and monolithic pipelines are created in parallel, this is the
    // temporary library created (previously in |mPipeline|) that is now replaced by the monolithic
    // one.  It is not immediately garbage collected when replaced, because there is currently a bug
    // with that.  http://anglebug.com/42266335
    Pipeline mLinkedPipelineToRelease;

    // An async task to create a monolithic pipeline.  Only used if the pipeline was originally
    // created as a linked library.  The |getPipeline()| call will attempt to schedule this task
    // through the share group, which manages and paces these tasks.  Once the task results are
    // ready, |mPipeline| is released and replaced by the result of this task.
    WaitableMonolithicPipelineCreationTask mMonolithicPipelineCreationTask;
};

ANGLE_INLINE PipelineHelper::PipelineHelper(Pipeline &&pipeline, CacheLookUpFeedback feedback)
    : mPipeline(std::move(pipeline)), mCacheLookUpFeedback(feedback)
{}

ANGLE_INLINE PipelineHelper &PipelineHelper::operator=(PipelineHelper &&other)
{
    ASSERT(!mPipeline.valid());

    std::swap(mPipeline, other.mPipeline);
    mCacheLookUpFeedback = other.mCacheLookUpFeedback;

    return *this;
}

ANGLE_ENABLE_STRUCT_PADDING_WARNINGS

struct ImageSubresourceRange
{
    // GL max is 1000 (fits in 10 bits).
    uint32_t level : 10;
    // Max 31 levels (2 ** 5 - 1). Can store levelCount-1 if we need to save another bit.
    uint32_t levelCount : 5;
    // Implementation max is 4096 (12 bits).
    uint32_t layer : 12;
    // One of vk::LayerMode values.  If 0, it means all layers.  Otherwise it's the count of layers
    // which is usually 1, except for multiview in which case it can be up to
    // gl::IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS.
    uint32_t layerMode : 3;
    // For reads: Values are either ImageViewColorspace::Linear or ImageViewColorspace::SRGB
    uint32_t readColorspace : 1;
    // For writes: Values are either ImageViewColorspace::Linear or ImageViewColorspace::SRGB
    uint32_t writeColorspace : 1;

    static_assert(gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS < (1 << 5),
                  "Not enough bits for level count");
    static_assert(gl::IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS <= (1 << 12),
                  "Not enough bits for layer index");
    static_assert(gl::IMPLEMENTATION_ANGLE_MULTIVIEW_MAX_VIEWS <= (1 << 3),
                  "Not enough bits for layer count");
};

static_assert(sizeof(ImageSubresourceRange) == sizeof(uint32_t), "Size mismatch");

inline bool operator==(const ImageSubresourceRange &a, const ImageSubresourceRange &b)
{
    return a.level == b.level && a.levelCount == b.levelCount && a.layer == b.layer &&
           a.layerMode == b.layerMode && a.readColorspace == b.readColorspace &&
           a.writeColorspace == b.writeColorspace;
}

constexpr ImageSubresourceRange kInvalidImageSubresourceRange = {0, 0, 0, 0, 0, 0};

struct ImageOrBufferViewSubresourceSerial
{
    ImageOrBufferViewSerial viewSerial;
    ImageSubresourceRange subresource;
    uint32_t padding;
};

static_assert(sizeof(ImageOrBufferViewSubresourceSerial) == 16, "Size check failed");

inline bool operator==(const ImageOrBufferViewSubresourceSerial &a,
                       const ImageOrBufferViewSubresourceSerial &b)
{
    return a.viewSerial == b.viewSerial && a.subresource == b.subresource;
}

constexpr ImageOrBufferViewSubresourceSerial kInvalidImageOrBufferViewSubresourceSerial = {
    kInvalidImageOrBufferViewSerial, kInvalidImageSubresourceRange, 0};

// Always starts with array element zero, with descriptorCount descriptors.
struct WriteDescriptorDesc
{
    uint8_t binding;              // Redundant: determined by the containing WriteDesc array.
    uint8_t descriptorCount;      // Number of array elements in this descriptor write.
    uint8_t descriptorType;       // Packed VkDescriptorType.
    uint8_t descriptorInfoIndex;  // Base index into an array of DescriptorInfoDescs.
};

static_assert(sizeof(WriteDescriptorDesc) == 4, "Size mismatch");

struct DescriptorInfoDesc
{
    uint64_t samplerOrBufferSerialOrStorageFormat;
    uint64_t imageViewSerialOrOffset;
    uint32_t imageLayoutOrRange;
    uint32_t imageSubresourceRange;
};

static_assert(sizeof(DescriptorInfoDesc) == 24, "Size mismatch");

ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

// Generic description of a descriptor set. Used as a key when indexing descriptor set caches. The
// key storage is an angle:FixedVector. Beyond a certain fixed size we'll end up using heap memory
// to store keys. Currently we specialize the structure for three use cases: uniforms, textures,
// and other shader resources. Because of the way the specialization works we can't currently cache
// programs that use some types of resources.
static constexpr size_t kFastDescriptorSetDescLimit = 8;

struct DescriptorDescHandles
{
    VkBuffer buffer;
    VkImageView imageView;
    VkBufferView bufferView;
};

constexpr uint32_t kInvalidDescriptorDescIndex = static_cast<uint32_t>(-1);
class WriteDescriptorDescs
{
  public:
    void reset()
    {
        mDescs.clear();
        mDynamicDescriptorSetCount = 0;
        mCurrentInfoIndex          = 0;
        mUniformBlockIndexToDescriptorDescIndex.clear();
        mStorageBlockIndexToDescriptorDescIndex.clear();
    }

    void updateShaderBuffers(const ShaderInterfaceVariableInfoMap &variableInfoMap,
                             const std::vector<gl::InterfaceBlock> &blocks,
                             VkDescriptorType descriptorType);

    void updateAtomicCounters(const ShaderInterfaceVariableInfoMap &variableInfoMap,
                              const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers);

    void updateImages(const gl::ProgramExecutable &executable,
                      const ShaderInterfaceVariableInfoMap &variableInfoMap);

    void updateExecutableActiveTextures(const ShaderInterfaceVariableInfoMap &variableInfoMap,
                                        const gl::ProgramExecutable &executable);

    void updateDefaultUniform(gl::ShaderBitSet shaderTypes,
                              const ShaderInterfaceVariableInfoMap &variableInfoMap,
                              const gl::ProgramExecutable &executable);

    void updateTransformFeedbackWrite(const ShaderInterfaceVariableInfoMap &variableInfoMap,
                                      const gl::ProgramExecutable &executable);

    void updateDynamicDescriptorsCount();

    void initInputAttachments(const gl::ProgramExecutable &executable,
                              const ShaderInterfaceVariableInfoMap &variableInfoMap,
                              uint32_t maxColorCount);

    size_t size() const { return mDescs.size(); }
    bool empty() const { return mDescs.size() == 0; }

    const WriteDescriptorDesc &operator[](uint32_t bindingIndex) const
    {
        return mDescs[bindingIndex];
    }

    WriteDescriptorDesc &operator[](uint32_t bindingIndex) { return mDescs[bindingIndex]; }

    size_t getTotalDescriptorCount() const { return mCurrentInfoIndex; }
    size_t getDynamicDescriptorSetCount() const { return mDynamicDescriptorSetCount; }

    uint32_t getDescriptorDescIndexForUniformBufferBlockIndex(size_t bindingIndex) const
    {
        ASSERT(bindingIndex < mUniformBlockIndexToDescriptorDescIndex.size());

        return mUniformBlockIndexToDescriptorDescIndex[bindingIndex];
    }

    uint32_t getDescriptorDescIndexForStorageBufferBlockIndex(size_t bindingIndex) const
    {
        ASSERT(bindingIndex < mStorageBlockIndexToDescriptorDescIndex.size());

        return mStorageBlockIndexToDescriptorDescIndex[bindingIndex];
    }

  private:
    bool hasWriteDescAtIndex(uint32_t bindingIndex) const
    {
        return bindingIndex < mDescs.size() && mDescs[bindingIndex].descriptorCount > 0;
    }

    void incrementDescriptorCount(uint32_t bindingIndex, uint32_t count)
    {
        // Validate we have no subsequent writes.
        ASSERT(hasWriteDescAtIndex(bindingIndex));
        mDescs[bindingIndex].descriptorCount += count;
    }

    void updateWriteDesc(uint32_t bindingIndex,
                         VkDescriptorType descriptorType,
                         uint32_t descriptorCount);

    // After a preliminary minimum size, use heap memory.
    angle::FastMap<WriteDescriptorDesc, kFastDescriptorSetDescLimit> mDescs;
    size_t mDynamicDescriptorSetCount = 0;
    uint32_t mCurrentInfoIndex        = 0;

    // A map of { uniform block index, mDescs index }
    std::vector<uint32_t> mUniformBlockIndexToDescriptorDescIndex;
    // A map of { storage block index, mDescs index }
    std::vector<uint32_t> mStorageBlockIndexToDescriptorDescIndex;
};
std::ostream &operator<<(std::ostream &os, const WriteDescriptorDescs &desc);

class DescriptorSetDesc
{
  public:
    DescriptorSetDesc()  = default;
    ~DescriptorSetDesc() = default;

    DescriptorSetDesc(const DescriptorSetDesc &other) : mDescriptorInfos(other.mDescriptorInfos) {}

    DescriptorSetDesc &operator=(const DescriptorSetDesc &other)
    {
        mDescriptorInfos = other.mDescriptorInfos;
        return *this;
    }

    size_t hash() const;

    size_t size() const { return mDescriptorInfos.size(); }
    void resize(size_t count) { mDescriptorInfos.resize(count); }

    size_t getKeySizeBytes() const { return mDescriptorInfos.size() * sizeof(DescriptorInfoDesc); }

    bool operator==(const DescriptorSetDesc &other) const
    {
        return mDescriptorInfos.size() == other.mDescriptorInfos.size() &&
               ANGLE_UNSAFE_TODO(memcmp(mDescriptorInfos.data(), other.mDescriptorInfos.data(),
                                        mDescriptorInfos.size() * sizeof(DescriptorInfoDesc))) == 0;
    }

    DescriptorInfoDesc &getInfoDesc(uint32_t infoDescIndex)
    {
        return mDescriptorInfos[infoDescIndex];
    }

    const DescriptorInfoDesc &getInfoDesc(uint32_t infoDescIndex) const
    {
        return mDescriptorInfos[infoDescIndex];
    }

    const DescriptorInfoDesc *getInfoDescs() const { return mDescriptorInfos.data(); }

  private:
    // After a preliminary minimum size, use heap memory.
    angle::FastVector<DescriptorInfoDesc, kFastDescriptorSetDescLimit> mDescriptorInfos;
};
std::ostream &operator<<(std::ostream &os, const DescriptorSetDesc &desc);

class DescriptorPoolHelper;

// SharedDescriptorSetCacheKey.
// Because DescriptorSet must associate with a pool, we need to define a structure that wraps both.
class DescriptorSetDescAndPool final
{
  public:
    DescriptorSetDescAndPool() : mPool(nullptr) {}
    DescriptorSetDescAndPool(const DescriptorSetDesc &desc, DynamicDescriptorPool *pool)
        : mDesc(desc), mPool(pool)
    {}
    DescriptorSetDescAndPool(DescriptorSetDescAndPool &&other)
        : mDesc(other.mDesc), mPool(other.mPool)
    {
        other.mPool = nullptr;
    }
    ~DescriptorSetDescAndPool() { ASSERT(!valid()); }
    void destroy(VkDevice /*device*/) { mPool = nullptr; }

    void destroyCachedObject(Renderer *renderer);
    void releaseCachedObject(ContextVk *contextVk) { UNREACHABLE(); }
    void releaseCachedObject(Renderer *renderer);
    bool valid() const { return mPool != nullptr; }
    const DescriptorSetDesc &getDesc() const
    {
        ASSERT(valid());
        return mDesc;
    }
    bool operator==(const DescriptorSetDescAndPool &other) const
    {
        return mDesc == other.mDesc && mPool == other.mPool;
    }

    // Returns true if the key/value can be found in the cache.
    bool hasValidCachedObject(ContextVk *contextVk) const;

  private:
    DescriptorSetDesc mDesc;
    DynamicDescriptorPool *mPool;
};
using SharedDescriptorSetCacheKey = SharedPtr<DescriptorSetDescAndPool>;
ANGLE_INLINE const SharedDescriptorSetCacheKey
CreateSharedDescriptorSetCacheKey(const DescriptorSetDesc &desc, DynamicDescriptorPool *pool)
{
    return SharedDescriptorSetCacheKey::MakeShared(VK_NULL_HANDLE, desc, pool);
}

constexpr VkDescriptorType kStorageBufferDescriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;

// Manages a descriptor set desc with a few helper routines and also stores object handles.
class DescriptorSetDescBuilder final
{
  public:
    DescriptorSetDescBuilder();
    DescriptorSetDescBuilder(size_t descriptorCount);
    ~DescriptorSetDescBuilder();

    DescriptorSetDescBuilder(const DescriptorSetDescBuilder &other);
    DescriptorSetDescBuilder &operator=(const DescriptorSetDescBuilder &other);

    const DescriptorSetDesc &getDesc() const { return mDesc; }

    void resize(size_t descriptorCount)
    {
        mDesc.resize(descriptorCount);
        mHandles.resize(descriptorCount);
        mDynamicOffsets.resize(descriptorCount);
    }

    // Specific helpers for uniforms/xfb descriptors.
    void updateUniformBuffer(uint32_t shaderIndex,
                             const WriteDescriptorDescs &writeDescriptorDescs,
                             const BufferHelper &bufferHelper,
                             VkDeviceSize bufferRange);

    void updateTransformFeedbackBuffer(const Context *context,
                                       const ShaderInterfaceVariableInfoMap &variableInfoMap,
                                       const WriteDescriptorDescs &writeDescriptorDescs,
                                       uint32_t xfbBufferIndex,
                                       const BufferHelper &bufferHelper,
                                       VkDeviceSize bufferOffset,
                                       VkDeviceSize bufferRange);

    void updateUniformsAndXfb(Context *context,
                              const gl::ProgramExecutable &executable,
                              const WriteDescriptorDescs &writeDescriptorDescs,
                              const BufferHelper *currentUniformBuffer,
                              const BufferHelper &emptyBuffer,
                              bool activeUnpaused,
                              TransformFeedbackVk *transformFeedbackVk);

    // Specific helpers for shader resource descriptors.
    void updateOneUniformBuffer(Context *context,
                                CommandBufferHelperCommon *commandBufferHelper,
                                const size_t blockIndex,
                                const gl::InterfaceBlock &block,
                                const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding,
                                VkDescriptorType descriptorType,
                                VkDeviceSize maxBoundBufferRange,
                                const BufferHelper &emptyBuffer,
                                const WriteDescriptorDescs &writeDescriptorDescs);
    void updateOneStorageBuffer(Context *context,
                                CommandBufferHelperCommon *commandBufferHelper,
                                const size_t blockIndex,
                                const gl::InterfaceBlock &block,
                                const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding,
                                VkDescriptorType descriptorType,
                                VkDeviceSize maxBoundBufferRange,
                                const BufferHelper &emptyBuffer,
                                const WriteDescriptorDescs &writeDescriptorDescs,
                                const GLbitfield memoryBarrierBits);
    void updateOneShaderBuffer(Context *context,
                               CommandBufferHelperCommon *commandBufferHelper,
                               const uint32_t infoDescIndex,
                               const gl::InterfaceBlock &block,
                               const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding,
                               VkDescriptorType descriptorType,
                               VkDeviceSize maxBoundBufferRange,
                               const BufferHelper &emptyBuffer);
    void updateStorageBuffers(Context *context,
                              CommandBufferHelperCommon *commandBufferHelper,
                              const gl::ProgramExecutable &executable,
                              const gl::BufferVector &buffers,
                              const std::vector<gl::InterfaceBlock> &blocks,
                              VkDescriptorType descriptorType,
                              VkDeviceSize maxBoundBufferRange,
                              const BufferHelper &emptyBuffer,
                              const WriteDescriptorDescs &writeDescriptorDescs,
                              const GLbitfield memoryBarrierBits);
    void updateAtomicCounters(Context *context,
                              CommandBufferHelperCommon *commandBufferHelper,
                              const gl::ProgramExecutable &executable,
                              const ShaderInterfaceVariableInfoMap &variableInfoMap,
                              const gl::BufferVector &buffers,
                              const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers,
                              const VkDeviceSize requiredOffsetAlignment,
                              const BufferHelper &emptyBuffer,
                              const WriteDescriptorDescs &writeDescriptorDescs);
    void updateOneUniformBufferOffset(const size_t blockIndex,
                                      const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding,
                                      const WriteDescriptorDescs &writeDescriptorDescs);
    angle::Result updateImages(ContextVk *contextVk,
                               const gl::ProgramExecutable &executable,
                               const ShaderInterfaceVariableInfoMap &variableInfoMap,
                               const gl::ActiveTextureArray<TextureVk *> &activeImages,
                               const std::vector<gl::ImageUnit> &imageUnits,
                               const WriteDescriptorDescs &writeDescriptorDescs);

    angle::Result updateInputAttachments(ContextVk *contextVk,
                                         const gl::ProgramExecutable &executable,
                                         const ShaderInterfaceVariableInfoMap &variableInfoMap,
                                         const FramebufferVk *framebufferVk,
                                         WriteDescriptorDescs &writeDescriptorDescs,
                                         gl::AttachmentsMask *currentMaskOut);

    // Specialized update for textures.
    void updatePreCacheActiveTextures(Context *context,
                                      const gl::ProgramExecutable &executable,
                                      const gl::ActiveTextureArray<TextureVk *> &textures,
                                      const gl::SamplerBindingVector &samplers,
                                      const WriteDescriptorDescs &writeDescriptorDescs);

    const uint32_t *getDynamicOffsets() const { return mDynamicOffsets.data(); }

    const DescriptorDescHandles *getHandles() const { return mHandles.data(); }

    void resetDescriptor(uint32_t infoIndex)
    {
        mDesc.getInfoDesc(infoIndex) = {};
        mHandles[infoIndex]          = {};
    }

  private:
    void updateInputAttachment(Context *context,
                               uint32_t binding,
                               VkImageLayout layout,
                               const vk::ImageView *imageView,
                               ImageOrBufferViewSubresourceSerial serial,
                               const WriteDescriptorDescs &writeDescriptorDescs);

    void setEmptyBuffer(uint32_t infoDescIndex,
                        VkDescriptorType descriptorType,
                        const BufferHelper &emptyBuffer);

    DescriptorSetDesc mDesc;
    angle::FastVector<DescriptorDescHandles, kFastDescriptorSetDescLimit> mHandles;
    angle::FastVector<uint32_t, kFastDescriptorSetDescLimit> mDynamicOffsets;
};

// In the FramebufferDesc object:
//  - Depth/stencil serial is at index 0
//  - Color serials are at indices [1, gl::IMPLEMENTATION_MAX_DRAW_BUFFERS]
//  - Depth/stencil resolve attachment is at index gl::IMPLEMENTATION_MAX_DRAW_BUFFERS+1
//  - Resolve attachments are at indices [gl::IMPLEMENTATION_MAX_DRAW_BUFFERS+2,
//                                        gl::IMPLEMENTATION_MAX_DRAW_BUFFERS*2+1]
//    Fragment shading rate attachment serial is at index
//    (gl::IMPLEMENTATION_MAX_DRAW_BUFFERS*2+1)+1
constexpr size_t kFramebufferDescDepthStencilIndex = 0;
constexpr size_t kFramebufferDescColorIndexOffset  = kFramebufferDescDepthStencilIndex + 1;
constexpr size_t kFramebufferDescDepthStencilResolveIndexOffset =
    kFramebufferDescColorIndexOffset + gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
constexpr size_t kFramebufferDescColorResolveIndexOffset =
    kFramebufferDescDepthStencilResolveIndexOffset + 1;
constexpr size_t kFramebufferDescFragmentShadingRateAttachmentIndexOffset =
    kFramebufferDescColorResolveIndexOffset + gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;

// Enable struct padding warnings for the code below since it is used in caches.
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS

class FramebufferDesc
{
  public:
    FramebufferDesc();
    ~FramebufferDesc();

    FramebufferDesc(const FramebufferDesc &other);
    FramebufferDesc &operator=(const FramebufferDesc &other);

    void updateColor(uint32_t index, ImageOrBufferViewSubresourceSerial serial);
    void updateColorResolve(uint32_t index, ImageOrBufferViewSubresourceSerial serial);
    void updateUnresolveMask(FramebufferNonResolveAttachmentMask unresolveMask);
    void updateDepthStencil(ImageOrBufferViewSubresourceSerial serial);
    void updateDepthStencilResolve(ImageOrBufferViewSubresourceSerial serial);
    ANGLE_INLINE void setWriteControlMode(gl::SrgbWriteControlMode mode)
    {
        mSrgbWriteControlMode = static_cast<uint16_t>(mode);
    }
    void updateIsMultiview(bool isMultiview) { mIsMultiview = isMultiview; }
    size_t hash() const;

    bool operator==(const FramebufferDesc &other) const;

    uint32_t attachmentCount() const;

    ImageOrBufferViewSubresourceSerial getColorImageViewSerial(uint32_t index)
    {
        ASSERT(kFramebufferDescColorIndexOffset + index < mSerials.size());
        return mSerials[kFramebufferDescColorIndexOffset + index];
    }

    FramebufferNonResolveAttachmentMask getUnresolveAttachmentMask() const;
    ANGLE_INLINE gl::SrgbWriteControlMode getWriteControlMode() const
    {
        return (mSrgbWriteControlMode == 1) ? gl::SrgbWriteControlMode::Linear
                                            : gl::SrgbWriteControlMode::Default;
    }

    void updateLayerCount(uint32_t layerCount);
    uint32_t getLayerCount() const { return mLayerCount; }
    void setColorFramebufferFetchMode(bool hasColorFramebufferFetch);
    bool hasColorFramebufferFetch() const { return mHasColorFramebufferFetch; }

    bool isMultiview() const { return mIsMultiview; }

    void updateRenderToTexture(bool isRenderToTexture);

    void updateFragmentShadingRate(ImageOrBufferViewSubresourceSerial serial);
    bool hasFragmentShadingRateAttachment() const;

    // Used by SharedFramebufferCacheKey
    void destroy(VkDevice /*device*/) { SetBitField(mIsValid, 0); }
    void destroyCachedObject(Renderer *renderer);
    void releaseCachedObject(Renderer *renderer) { UNREACHABLE(); }
    void releaseCachedObject(ContextVk *contextVk);
    bool valid() const { return mIsValid; }
    bool hasValidCachedObject(ContextVk *contextVk) const;

  private:
    void reset();
    void update(uint32_t index, ImageOrBufferViewSubresourceSerial serial);

    // Note: this is an exclusive index. If there is one index it will be "1".
    // Maximum value is 18
    uint16_t mMaxIndex : 5;

    // Whether the render pass has input attachments or not.
    // Note that depth/stencil framebuffer fetch is only implemented for dynamic rendering, and so
    // does not interact with this class.
    uint16_t mHasColorFramebufferFetch : 1;
    static_assert(gl::IMPLEMENTATION_MAX_FRAMEBUFFER_LAYERS < (1 << 9) - 1,
                  "Not enough bits for mLayerCount");

    uint16_t mLayerCount : 9;

    uint16_t mSrgbWriteControlMode : 1;

    // If the render pass contains an initial subpass to unresolve a number of attachments, the
    // subpass description is derived from the following mask, specifying which attachments need
    // to be unresolved.  Includes both color and depth/stencil attachments.
    uint16_t mUnresolveAttachmentMask : kMaxFramebufferNonResolveAttachments;

    // Whether this is a multisampled-render-to-single-sampled framebuffer.  Only used when using
    // VK_EXT_multisampled_render_to_single_sampled.  Only one bit is used and the rest is padding.
    uint16_t mIsRenderToTexture : 14 - kMaxFramebufferNonResolveAttachments;

    uint16_t mIsMultiview : 1;
    // Used by SharedFramebufferCacheKey to indicate if this cache key is valid or not.
    uint16_t mIsValid : 1;

    uint32_t mPadding;

    FramebufferAttachmentArray<ImageOrBufferViewSubresourceSerial> mSerials;
};

static_assert(sizeof(FramebufferDesc) == 312, "Size check failed");

// Disable warnings about struct padding.
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

using SharedFramebufferCacheKey = SharedPtr<FramebufferDesc>;
ANGLE_INLINE const SharedFramebufferCacheKey
CreateSharedFramebufferCacheKey(const FramebufferDesc &desc)
{
    return SharedFramebufferCacheKey::MakeShared(VK_NULL_HANDLE, desc);
}

// The SamplerHelper allows a Sampler to be coupled with a serial.
// Must be included before we declare SamplerCache.
class SamplerHelper final : angle::NonCopyable
{
  public:
    SamplerHelper() = default;
    ~SamplerHelper() { ASSERT(!valid()); }

    explicit SamplerHelper(SamplerHelper &&samplerHelper);
    SamplerHelper &operator=(SamplerHelper &&rhs);

    angle::Result init(ErrorContext *context, const VkSamplerCreateInfo &createInfo);
    angle::Result init(ContextVk *contextVk, const SamplerDesc &desc);
    void destroy(VkDevice device) { mSampler.destroy(device); }
    void destroy() { ASSERT(!valid()); }
    bool valid() const { return mSampler.valid(); }
    const Sampler &get() const { return mSampler; }
    SamplerSerial getSamplerSerial() const { return mSamplerSerial; }

  private:
    Sampler mSampler;
    SamplerSerial mSamplerSerial;
};

using SharedSamplerPtr = SharedPtr<SamplerHelper>;

class RenderPassHelper final : angle::NonCopyable
{
  public:
    RenderPassHelper();
    ~RenderPassHelper();

    RenderPassHelper(RenderPassHelper &&other);
    RenderPassHelper &operator=(RenderPassHelper &&other);

    void destroy(VkDevice device);
    void release(ContextVk *contextVk);

    const RenderPass &getRenderPass() const;
    RenderPass &getRenderPass();

    const RenderPassPerfCounters &getPerfCounters() const;
    RenderPassPerfCounters &getPerfCounters();

  private:
    RenderPass mRenderPass;
    RenderPassPerfCounters mPerfCounters;
};

// Helper class manages the lifetime of various cache objects so that the cache entry can be
// destroyed when one of the components becomes invalid.
template <class SharedCacheKeyT>
class SharedCacheKeyManager
{
  public:
    SharedCacheKeyManager() = default;
    ~SharedCacheKeyManager() { ASSERT(empty()); }
    // Store the pointer to the cache key and retains it
    void addKey(const SharedCacheKeyT &key);
    // Iterate over the descriptor array and release the descriptor and cache.
    void releaseKeys(ContextVk *contextVk);
    void releaseKeys(Renderer *renderer);
    // Iterate over the descriptor array and destroy the descriptor and cache.
    void destroyKeys(Renderer *renderer);
    void clear();

    // The following APIs are expected to be used for assertion only
    bool empty() const { return mSharedCacheKeys.empty(); }
    bool allValidEntriesAreCached(ContextVk *contextVk) const;

  private:
    bool addKeyToEmptySlot(const SharedCacheKeyT &key);
    bool releaseUnusedKeysAndReplaceWithKey(const SharedCacheKeyT &key);
    void addKeyToNewSlot(const SharedCacheKeyT &key);

    bool containsKeyWithOwnerEqual(const SharedCacheKeyT &key) const;
    void assertAllEntriesDestroyed() const;

    // Tracks an array of cache keys with refcounting. Note this owns one refcount of
    // SharedCacheKeyT object.
    std::deque<SharedCacheKeyT> mSharedCacheKeys;
    SharedCacheKeyT mLastAddedSharedCacheKey;

    // To speed up searching for available slot in the mSharedCacheKeys, we use bitset to track
    // available (i.e, empty) slot
    static constexpr size_t kInvalidSlot  = -1;
    static constexpr size_t kSlotBitCount = 64;
    using SlotBitMask                     = angle::BitSet64<kSlotBitCount>;
    std::vector<SlotBitMask> mEmptySlotBits;
};

using FramebufferCacheManager   = SharedCacheKeyManager<SharedFramebufferCacheKey>;
template <>
void FramebufferCacheManager::addKey(const SharedFramebufferCacheKey &key);

using DescriptorSetCacheManager = SharedCacheKeyManager<SharedDescriptorSetCacheKey>;
template <>
void DescriptorSetCacheManager::addKey(const SharedDescriptorSetCacheKey &key);
}  // namespace vk
}  // namespace rx

// Introduce std::hash for the above classes.
namespace std
{
template <>
struct hash<rx::vk::RenderPassDesc>
{
    size_t operator()(const rx::vk::RenderPassDesc &key) const { return key.hash(); }
};

template <>
struct hash<rx::vk::AttachmentOpsArray>
{
    size_t operator()(const rx::vk::AttachmentOpsArray &key) const { return key.hash(); }
};

template <>
struct hash<rx::vk::DescriptorSetLayoutDesc>
{
    size_t operator()(const rx::vk::DescriptorSetLayoutDesc &key) const { return key.hash(); }
};

template <>
struct hash<rx::vk::PipelineLayoutDesc>
{
    size_t operator()(const rx::vk::PipelineLayoutDesc &key) const { return key.hash(); }
};

template <>
struct hash<rx::vk::ImageSubresourceRange>
{
    size_t operator()(const rx::vk::ImageSubresourceRange &key) const
    {
        return *reinterpret_cast<const uint32_t *>(&key);
    }
};

template <>
struct hash<rx::vk::DescriptorSetDesc>
{
    size_t operator()(const rx::vk::DescriptorSetDesc &key) const { return key.hash(); }
};

template <>
struct hash<rx::vk::FramebufferDesc>
{
    size_t operator()(const rx::vk::FramebufferDesc &key) const { return key.hash(); }
};

template <>
struct hash<rx::vk::YcbcrConversionDesc>
{
    size_t operator()(const rx::vk::YcbcrConversionDesc &key) const { return key.hash(); }
};

template <>
struct hash<rx::vk::SamplerDesc>
{
    size_t operator()(const rx::vk::SamplerDesc &key) const { return key.hash(); }
};

// See Resource Serial types defined in vk_utils.h.
#define ANGLE_HASH_VK_SERIAL(Type)                               \
    template <>                                                  \
    struct hash<rx::vk::Type##Serial>                            \
    {                                                            \
        size_t operator()(const rx::vk::Type##Serial &key) const \
        {                                                        \
            return std::hash<uint64_t>()(key.getValue());        \
        }                                                        \
    };

ANGLE_VK_SERIAL_OP(ANGLE_HASH_VK_SERIAL)

}  // namespace std

namespace rx
{
// Cache types for various Vulkan objects
enum class VulkanCacheType
{
    CompatibleRenderPass,
    RenderPassWithOps,
    GraphicsPipeline,
    ComputePipeline,
    PipelineLayout,
    Sampler,
    SamplerYcbcrConversion,
    DescriptorSetLayout,
    UniformsAndXfbDescriptors,
    TextureDescriptors,
    UniformBuffersDescriptors,
    ShaderResourcesDescriptors,
    Framebuffer,
    DescriptorMetaCache,
    EnumCount
};

// Base class for all caches. Provides cache hit and miss counters.
class CacheStats final : angle::NonCopyable
{
  public:
    CacheStats() { reset(); }
    ~CacheStats() {}

    CacheStats(const CacheStats &rhs)
        : mHitCount(rhs.mHitCount), mMissCount(rhs.mMissCount), mSize(rhs.mSize)
    {}

    CacheStats &operator=(const CacheStats &rhs)
    {
        mHitCount  = rhs.mHitCount;
        mMissCount = rhs.mMissCount;
        mSize      = rhs.mSize;
        return *this;
    }

    ANGLE_INLINE void hit() { mHitCount++; }
    ANGLE_INLINE void miss() { mMissCount++; }
    ANGLE_INLINE void incrementSize() { mSize++; }
    ANGLE_INLINE void decrementSize() { mSize--; }
    ANGLE_INLINE void missAndIncrementSize()
    {
        mMissCount++;
        mSize++;
    }
    ANGLE_INLINE void accumulate(const CacheStats &stats)
    {
        mHitCount += stats.mHitCount;
        mMissCount += stats.mMissCount;
        mSize += stats.mSize;
    }

    uint32_t getHitCount() const { return mHitCount; }
    uint32_t getMissCount() const { return mMissCount; }

    ANGLE_INLINE double getHitRatio() const
    {
        if (mHitCount + mMissCount == 0)
        {
            return 0;
        }
        else
        {
            return static_cast<double>(mHitCount) / (mHitCount + mMissCount);
        }
    }

    ANGLE_INLINE uint32_t getSize() const { return mSize; }
    ANGLE_INLINE void setSize(uint32_t size) { mSize = size; }

    void reset()
    {
        mHitCount  = 0;
        mMissCount = 0;
        mSize      = 0;
    }

    void resetHitAndMissCount()
    {
        mHitCount  = 0;
        mMissCount = 0;
    }

    void accumulateCacheStats(VulkanCacheType cacheType, const CacheStats &cacheStats)
    {
        mHitCount += cacheStats.getHitCount();
        mMissCount += cacheStats.getMissCount();
    }

  private:
    uint32_t mHitCount;
    uint32_t mMissCount;
    uint32_t mSize;
};

template <VulkanCacheType CacheType>
class HasCacheStats : angle::NonCopyable
{
  public:
    template <typename Accumulator>
    void accumulateCacheStats(Accumulator *accum)
    {
        accum->accumulateCacheStats(CacheType, mCacheStats);
        mCacheStats.reset();
    }

    void getCacheStats(CacheStats *accum) const { accum->accumulate(mCacheStats); }

  protected:
    HasCacheStats()          = default;
    virtual ~HasCacheStats() = default;

    CacheStats mCacheStats;
};

using VulkanCacheStats = angle::PackedEnumMap<VulkanCacheType, CacheStats>;

// FramebufferVk Cache
class FramebufferCache final : angle::NonCopyable
{
  public:
    FramebufferCache() = default;
    ~FramebufferCache() { ASSERT(mPayload.empty()); }

    void destroy(vk::Renderer *renderer);

    bool get(ContextVk *contextVk, const vk::FramebufferDesc &desc, vk::Framebuffer &framebuffer);
    void insert(ContextVk *contextVk,
                const vk::FramebufferDesc &desc,
                vk::Framebuffer &&framebuffer);
    void erase(ContextVk *contextVk, const vk::FramebufferDesc &desc);

    size_t getSize() const { return mPayload.size(); }
    bool empty() const { return mPayload.empty(); }

  private:
    angle::HashMap<vk::FramebufferDesc, vk::Framebuffer> mPayload;
    CacheStats mCacheStats;
};

// TODO(jmadill): Add cache trimming/eviction.
class RenderPassCache final : angle::NonCopyable
{
  public:
    RenderPassCache();
    ~RenderPassCache();

    void destroy(ContextVk *contextVk);
    void clear(ContextVk *contextVk);

    ANGLE_INLINE angle::Result getCompatibleRenderPass(ContextVk *contextVk,
                                                       const vk::RenderPassDesc &desc,
                                                       const vk::RenderPass **renderPassOut)
    {
        auto outerIt = mPayload.find(desc);
        if (outerIt != mPayload.end())
        {
            InnerCache &innerCache = outerIt->second;
            ASSERT(!innerCache.empty());

            // Find the first element and return it.
            *renderPassOut = &innerCache.begin()->second.getRenderPass();
            mCompatibleRenderPassCacheStats.hit();
            return angle::Result::Continue;
        }

        mCompatibleRenderPassCacheStats.missAndIncrementSize();
        return addCompatibleRenderPass(contextVk, desc, renderPassOut);
    }

    angle::Result getRenderPassWithOps(ContextVk *contextVk,
                                       const vk::RenderPassDesc &desc,
                                       const vk::AttachmentOpsArray &attachmentOps,
                                       const vk::RenderPass **renderPassOut);

    static void InitializeOpsForCompatibleRenderPass(const vk::RenderPassDesc &desc,
                                                     vk::AttachmentOpsArray *opsOut);
    static angle::Result MakeRenderPass(vk::ErrorContext *context,
                                        const vk::RenderPassDesc &desc,
                                        const vk::AttachmentOpsArray &ops,
                                        vk::RenderPass *renderPass,
                                        vk::RenderPassPerfCounters *renderPassCounters);

  private:
    angle::Result getRenderPassWithOpsImpl(ContextVk *contextVk,
                                           const vk::RenderPassDesc &desc,
                                           const vk::AttachmentOpsArray &attachmentOps,
                                           bool updatePerfCounters,
                                           const vk::RenderPass **renderPassOut);

    angle::Result addCompatibleRenderPass(ContextVk *contextVk,
                                          const vk::RenderPassDesc &desc,
                                          const vk::RenderPass **renderPassOut);

    // Use a two-layer caching scheme. The top level matches the "compatible" RenderPass elements.
    // The second layer caches the attachment load/store ops and initial/final layout.
    // Switch to `std::unordered_map` to retain pointer stability.
    using InnerCache = std::unordered_map<vk::AttachmentOpsArray, vk::RenderPassHelper>;
    using OuterCache = std::unordered_map<vk::RenderPassDesc, InnerCache>;

    OuterCache mPayload;
    CacheStats mCompatibleRenderPassCacheStats;
    CacheStats mRenderPassWithOpsCacheStats;
};

enum class PipelineSource
{
    // Pipeline created when warming up the program's pipeline cache
    WarmUp,
    // Monolithic pipeline created at draw time
    Draw,
    // Pipeline created at draw time by linking partial pipeline libraries
    DrawLinked,
    // Pipeline created for UtilsVk
    Utils,
    // Pipeline created at dispatch time
    Dispatch
};

struct ComputePipelineDescHash
{
    size_t operator()(const rx::vk::ComputePipelineDesc &key) const { return key.hash(); }
};
struct GraphicsPipelineDescCompleteHash
{
    size_t operator()(const rx::vk::GraphicsPipelineDesc &key) const
    {
        return key.hash(vk::GraphicsPipelineSubset::Complete);
    }
};
struct GraphicsPipelineDescShadersHash
{
    size_t operator()(const rx::vk::GraphicsPipelineDesc &key) const
    {
        return key.hash(vk::GraphicsPipelineSubset::Shaders);
    }
};

struct ComputePipelineDescKeyEqual
{
    size_t operator()(const rx::vk::ComputePipelineDesc &first,
                      const rx::vk::ComputePipelineDesc &second) const
    {
        return first.keyEqual(second);
    }
};
struct GraphicsPipelineDescCompleteKeyEqual
{
    size_t operator()(const rx::vk::GraphicsPipelineDesc &first,
                      const rx::vk::GraphicsPipelineDesc &second) const
    {
        return first.keyEqual(second, vk::GraphicsPipelineSubset::Complete);
    }
};
struct GraphicsPipelineDescShadersKeyEqual
{
    size_t operator()(const rx::vk::GraphicsPipelineDesc &first,
                      const rx::vk::GraphicsPipelineDesc &second) const
    {
        return first.keyEqual(second, vk::GraphicsPipelineSubset::Shaders);
    }
};

// Derive the KeyEqual and GraphicsPipelineSubset enum from the Hash struct
template <typename Hash>
struct GraphicsPipelineCacheTypeHelper
{
    using KeyEqual                                      = GraphicsPipelineDescCompleteKeyEqual;
    static constexpr vk::GraphicsPipelineSubset kSubset = vk::GraphicsPipelineSubset::Complete;
};

template <>
struct GraphicsPipelineCacheTypeHelper<GraphicsPipelineDescShadersHash>
{
    using KeyEqual                                      = GraphicsPipelineDescShadersKeyEqual;
    static constexpr vk::GraphicsPipelineSubset kSubset = vk::GraphicsPipelineSubset::Shaders;
};

// Compute Pipeline Cache implementation
// TODO(aannestrand): Add cache trimming/eviction.
// http://anglebug.com/391672281
class ComputePipelineCache final : HasCacheStats<rx::VulkanCacheType::ComputePipeline>
{
  public:
    ComputePipelineCache() = default;
    ~ComputePipelineCache() override { ASSERT(mPayload.empty()); }

    void destroy(vk::ErrorContext *context);
    void release(vk::ErrorContext *context);

    angle::Result getOrCreatePipeline(vk::ErrorContext *context,
                                      vk::PipelineCacheAccess *pipelineCache,
                                      const vk::PipelineLayout &pipelineLayout,
                                      vk::ComputePipelineOptions &pipelineOptions,
                                      PipelineSource source,
                                      vk::PipelineHelper **pipelineOut,
                                      const char *shaderName,
                                      VkSpecializationInfo *specializationInfo,
                                      const vk::ShaderModuleMap &shaderModuleMap);

  private:
    angle::Result createPipeline(vk::ErrorContext *context,
                                 vk::PipelineCacheAccess *pipelineCache,
                                 const vk::PipelineLayout &pipelineLayout,
                                 vk::ComputePipelineOptions &pipelineOptions,
                                 PipelineSource source,
                                 const char *shaderName,
                                 const vk::ShaderModule &shaderModule,
                                 VkSpecializationInfo *specializationInfo,
                                 const vk::ComputePipelineDesc &desc,
                                 vk::PipelineHelper **pipelineOut);

    std::unordered_map<vk::ComputePipelineDesc,
                       vk::PipelineHelper,
                       ComputePipelineDescHash,
                       ComputePipelineDescKeyEqual>
        mPayload;
};

// TODO(jmadill): Add cache trimming/eviction.
template <typename Hash>
class GraphicsPipelineCache final : public HasCacheStats<VulkanCacheType::GraphicsPipeline>
{
  public:
    GraphicsPipelineCache() = default;
    ~GraphicsPipelineCache() override { ASSERT(mPayload.empty()); }

    void destroy(vk::ErrorContext *context);
    void release(vk::ErrorContext *context);

    void populate(const vk::GraphicsPipelineDesc &desc,
                  vk::Pipeline &&pipeline,
                  vk::PipelineHelper **pipelineHelperOut);

    void remove(const vk::GraphicsPipelineDesc &desc) { mPayload.erase(desc); }

    // Get a pipeline from the cache, if it exists
    ANGLE_INLINE bool getPipeline(const vk::GraphicsPipelineDesc &desc,
                                  const vk::GraphicsPipelineDesc **descPtrOut,
                                  vk::PipelineHelper **pipelineOut)
    {
        auto item = mPayload.find(desc);
        if (item == mPayload.end())
        {
            return false;
        }

        *descPtrOut  = &item->first;
        *pipelineOut = &item->second;

        mCacheStats.hit();

        return true;
    }

    angle::Result createPipeline(vk::ErrorContext *context,
                                 vk::PipelineCacheAccess *pipelineCache,
                                 const vk::RenderPass &compatibleRenderPass,
                                 const vk::PipelineLayout &pipelineLayout,
                                 const vk::GraphicsPipelineShadersInfo &shaders,
                                 PipelineSource source,
                                 const vk::GraphicsPipelineDesc &desc,
                                 const vk::GraphicsPipelineDesc **descPtrOut,
                                 vk::PipelineHelper **pipelineOut);

    // Helper for VulkanPipelineCachePerf that resets the object without destroying any object.
    void reset() { mPayload.clear(); }

  private:
    void addToCache(PipelineSource source,
                    const vk::GraphicsPipelineDesc &desc,
                    vk::Pipeline &&pipeline,
                    vk::CacheLookUpFeedback feedback,
                    const vk::GraphicsPipelineDesc **descPtrOut,
                    vk::PipelineHelper **pipelineOut);

    using KeyEqual = typename GraphicsPipelineCacheTypeHelper<Hash>::KeyEqual;
    std::unordered_map<vk::GraphicsPipelineDesc, vk::PipelineHelper, Hash, KeyEqual> mPayload;
};

using CompleteGraphicsPipelineCache    = GraphicsPipelineCache<GraphicsPipelineDescCompleteHash>;
using ShadersGraphicsPipelineCache     = GraphicsPipelineCache<GraphicsPipelineDescShadersHash>;

class DescriptorSetLayoutCache final : angle::NonCopyable
{
  public:
    DescriptorSetLayoutCache();
    ~DescriptorSetLayoutCache();

    void destroy(vk::Renderer *renderer);

    angle::Result getDescriptorSetLayout(vk::ErrorContext *context,
                                         const vk::DescriptorSetLayoutDesc &desc,
                                         vk::DescriptorSetLayoutPtr *descriptorSetLayoutOut);

    // Helpers for white box tests
    size_t getCacheHitCount() const { return mCacheStats.getHitCount(); }
    size_t getCacheMissCount() const { return mCacheStats.getMissCount(); }

  private:
    mutable angle::SimpleMutex mMutex;
    std::unordered_map<vk::DescriptorSetLayoutDesc, vk::DescriptorSetLayoutPtr> mPayload;
    CacheStats mCacheStats;
};

class PipelineLayoutCache final : public HasCacheStats<VulkanCacheType::PipelineLayout>
{
  public:
    PipelineLayoutCache();
    ~PipelineLayoutCache() override;

    void destroy(vk::Renderer *renderer);

    angle::Result getPipelineLayout(vk::ErrorContext *context,
                                    const vk::PipelineLayoutDesc &desc,
                                    const vk::DescriptorSetLayoutPointerArray &descriptorSetLayouts,
                                    vk::PipelineLayoutPtr *pipelineLayoutOut);

  private:
    mutable angle::SimpleMutex mMutex;
    std::unordered_map<vk::PipelineLayoutDesc, vk::PipelineLayoutPtr> mPayload;
};

class SamplerCache final : public HasCacheStats<VulkanCacheType::Sampler>
{
  public:
    SamplerCache();
    ~SamplerCache() override;

    void destroy(vk::Renderer *renderer);

    angle::Result getSampler(ContextVk *contextVk,
                             const vk::SamplerDesc &desc,
                             vk::SharedSamplerPtr *samplerOut);

  private:
    std::unordered_map<vk::SamplerDesc, vk::SharedSamplerPtr> mPayload;
};

// YuvConversion Cache
class SamplerYcbcrConversionCache final
    : public HasCacheStats<VulkanCacheType::SamplerYcbcrConversion>
{
  public:
    SamplerYcbcrConversionCache();
    ~SamplerYcbcrConversionCache() override;

    void destroy(vk::Renderer *renderer);

    angle::Result getSamplerYcbcrConversion(vk::ErrorContext *context,
                                            const vk::YcbcrConversionDesc &ycbcrConversionDesc,
                                            VkSamplerYcbcrConversion *vkSamplerYcbcrConversionOut);

  private:
    using SamplerYcbcrConversionMap =
        std::unordered_map<vk::YcbcrConversionDesc, vk::SamplerYcbcrConversion>;
    SamplerYcbcrConversionMap mExternalFormatPayload;
    SamplerYcbcrConversionMap mVkFormatPayload;
};

// Descriptor Set Cache
template <typename T>
class DescriptorSetCache final : angle::NonCopyable
{
  public:
    DescriptorSetCache() = default;
    ~DescriptorSetCache() { ASSERT(mPayload.empty()); }

    DescriptorSetCache(DescriptorSetCache &&other) : DescriptorSetCache()
    {
        *this = std::move(other);
    }

    DescriptorSetCache &operator=(DescriptorSetCache &&other)
    {
        std::swap(mPayload, other.mPayload);
        return *this;
    }

    void clear() { mPayload.clear(); }

    bool getDescriptorSet(const vk::DescriptorSetDesc &desc, T *descriptorSetOut) const
    {
        auto iter = mPayload.find(desc);
        if (iter != mPayload.end())
        {
            *descriptorSetOut = iter->second;
            return true;
        }
        return false;
    }

    void insertDescriptorSet(const vk::DescriptorSetDesc &desc, const T &descriptorSetHelper)
    {
        mPayload.emplace(desc, descriptorSetHelper);
    }

    bool eraseDescriptorSet(const vk::DescriptorSetDesc &desc, T *descriptorSetOut)
    {
        auto iter = mPayload.find(desc);
        if (iter != mPayload.end())
        {
            *descriptorSetOut = std::move(iter->second);
            mPayload.erase(iter);
            return true;
        }
        return false;
    }

    bool eraseDescriptorSet(const vk::DescriptorSetDesc &desc)
    {
        auto iter = mPayload.find(desc);
        if (iter != mPayload.end())
        {
            mPayload.erase(iter);
            return true;
        }
        return false;
    }

    size_t getTotalCacheSize() const { return mPayload.size(); }

    size_t getTotalCacheKeySizeBytes() const
    {
        size_t totalSize = 0;
        for (const auto &iter : mPayload)
        {
            const vk::DescriptorSetDesc &desc = iter.first;
            totalSize += desc.getKeySizeBytes();
        }
        return totalSize;
    }
    bool empty() const { return mPayload.empty(); }

  private:
    angle::HashMap<vk::DescriptorSetDesc, T> mPayload;
};

// There is 1 default uniform binding used per stage.
constexpr uint32_t kReservedPerStageDefaultUniformBindingCount = 1;

class UpdateDescriptorSetsBuilder final : angle::NonCopyable
{
  public:
    UpdateDescriptorSetsBuilder();
    ~UpdateDescriptorSetsBuilder();

    VkDescriptorBufferInfo *allocDescriptorBufferInfos(uint32_t count)
    {
        return mDescriptorBufferInfos.allocate(count);
    }
    VkDescriptorImageInfo *allocDescriptorImageInfos(uint32_t count)
    {
        return mDescriptorImageInfos.allocate(count);
    }
    VkWriteDescriptorSet *allocWriteDescriptorSets(uint32_t count)
    {
        return mWriteDescriptorSets.allocate(count);
    }
    VkBufferView *allocBufferViews(uint32_t count) { return mBufferViews.allocate(count); }

    VkDescriptorBufferInfo &allocDescriptorBufferInfo() { return *allocDescriptorBufferInfos(1); }
    VkDescriptorImageInfo &allocDescriptorImageInfo() { return *allocDescriptorImageInfos(1); }
    VkWriteDescriptorSet &allocWriteDescriptorSet() { return *allocWriteDescriptorSets(1); }
    VkBufferView &allocBufferView() { return *allocBufferViews(1); }

    // Returns the number of written descriptor sets.
    uint32_t flushDescriptorSetUpdates(VkDevice device);

    void updateWriteDescriptorSet(vk::Renderer *renderer,
                                  const vk::DescriptorSetDescBuilder &descriptorSetDescBuilder,
                                  const vk::WriteDescriptorDescs &writeDescriptorDescs,
                                  const VkDescriptorSet descriptorSet);

  private:
    // Manage the storage for VkDescriptorBufferInfo and VkDescriptorImageInfo. The storage is not
    // required to be continuous, but the requested allocation from allocate() call must be
    // continuous. The actual storage will grow as needed.
    template <typename T>
    class DescriptorInfoAllocator : angle::NonCopyable
    {
      public:
        void init(uint32_t initialVectorCapacity)
        {
            mVectorCapacity = initialVectorCapacity;
            mDescriptorInfos.emplace_back();
            mDescriptorInfos.back().reserve(mVectorCapacity);
            mCurrentVector = mDescriptorInfos.begin();
            mTotalSize     = 0;
        }
        void clear()
        {
            mDescriptorInfos.resize(1);
            mDescriptorInfos.front().clear();
            // Grow the first vector's capacity big enough to hold all of them
            mVectorCapacity = std::max(mTotalSize, mVectorCapacity);
            mDescriptorInfos.front().reserve(mVectorCapacity);
            mCurrentVector = mDescriptorInfos.begin();
            mTotalSize     = 0;
        }
        T *allocate(uint32_t count);

        bool empty() const { return mTotalSize == 0; }

      protected:
        uint32_t mVectorCapacity = 16;
        std::deque<std::vector<T>> mDescriptorInfos;
        typename std::deque<std::vector<T>>::iterator mCurrentVector;
        uint32_t mTotalSize;
    };

    class WriteDescriptorSetAllocator final : public DescriptorInfoAllocator<VkWriteDescriptorSet>
    {
      public:
        uint32_t updateDescriptorSets(VkDevice device) const;
    };

    DescriptorInfoAllocator<VkDescriptorBufferInfo> mDescriptorBufferInfos;
    DescriptorInfoAllocator<VkDescriptorImageInfo> mDescriptorImageInfos;
    DescriptorInfoAllocator<VkBufferView> mBufferViews;
    WriteDescriptorSetAllocator mWriteDescriptorSets;
};

}  // namespace rx

#endif  // LIBANGLE_RENDERER_VULKAN_VK_CACHE_UTILS_H_
