//
// 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.cpp:
//    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.
//

#include "libANGLE/renderer/vulkan/vk_cache_utils.h"

#include "common/aligned_memory.h"
#include "common/vulkan/vk_google_filtering_precision.h"
#include "libANGLE/BlobCache.h"
#include "libANGLE/VertexAttribute.h"
#include "libANGLE/renderer/vulkan/DisplayVk.h"
#include "libANGLE/renderer/vulkan/FramebufferVk.h"
#include "libANGLE/renderer/vulkan/ProgramVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "libANGLE/renderer/vulkan/TextureVk.h"
#include "libANGLE/renderer/vulkan/VertexArrayVk.h"
#include "libANGLE/renderer/vulkan/vk_format_utils.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"

#include <type_traits>

namespace rx
{
constexpr bool kDumpPipelineCacheGraph = false;

namespace vk
{

namespace
{
static_assert(static_cast<uint32_t>(RenderPassLoadOp::Load) == VK_ATTACHMENT_LOAD_OP_LOAD,
              "ConvertRenderPassLoadOpToVkLoadOp must be updated");
static_assert(static_cast<uint32_t>(RenderPassLoadOp::Clear) == VK_ATTACHMENT_LOAD_OP_CLEAR,
              "ConvertRenderPassLoadOpToVkLoadOp must be updated");
static_assert(static_cast<uint32_t>(RenderPassLoadOp::DontCare) == VK_ATTACHMENT_LOAD_OP_DONT_CARE,
              "ConvertRenderPassLoadOpToVkLoadOp must be updated");
static_assert(static_cast<uint32_t>(RenderPassLoadOp::None) == 3,
              "ConvertRenderPassLoadOpToVkLoadOp must be updated");

static_assert(static_cast<uint32_t>(RenderPassStoreOp::Store) == VK_ATTACHMENT_STORE_OP_STORE,
              "ConvertRenderPassStoreOpToVkStoreOp must be updated");
static_assert(static_cast<uint32_t>(RenderPassStoreOp::DontCare) ==
                  VK_ATTACHMENT_STORE_OP_DONT_CARE,
              "ConvertRenderPassStoreOpToVkStoreOp must be updated");
static_assert(static_cast<uint32_t>(RenderPassStoreOp::None) == 2,
              "ConvertRenderPassStoreOpToVkStoreOp must be updated");

constexpr uint16_t kMinSampleShadingScale = angle::BitMask<uint16_t>(11);

VkAttachmentLoadOp ConvertRenderPassLoadOpToVkLoadOp(RenderPassLoadOp loadOp)
{
    return loadOp == RenderPassLoadOp::None ? VK_ATTACHMENT_LOAD_OP_NONE_EXT
                                            : static_cast<VkAttachmentLoadOp>(loadOp);
}
VkAttachmentStoreOp ConvertRenderPassStoreOpToVkStoreOp(RenderPassStoreOp storeOp)
{
    return storeOp == RenderPassStoreOp::None ? VK_ATTACHMENT_STORE_OP_NONE_EXT
                                              : static_cast<VkAttachmentStoreOp>(storeOp);
}

uint8_t PackGLBlendOp(GLenum blendOp)
{
    switch (blendOp)
    {
        case GL_FUNC_ADD:
            return static_cast<uint8_t>(VK_BLEND_OP_ADD);
        case GL_FUNC_SUBTRACT:
            return static_cast<uint8_t>(VK_BLEND_OP_SUBTRACT);
        case GL_FUNC_REVERSE_SUBTRACT:
            return static_cast<uint8_t>(VK_BLEND_OP_REVERSE_SUBTRACT);
        case GL_MIN:
            return static_cast<uint8_t>(VK_BLEND_OP_MIN);
        case GL_MAX:
            return static_cast<uint8_t>(VK_BLEND_OP_MAX);
        case GL_MULTIPLY_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_MULTIPLY_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_SCREEN_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_SCREEN_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_OVERLAY_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_OVERLAY_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_DARKEN_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_DARKEN_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_LIGHTEN_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_LIGHTEN_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_COLORDODGE_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_COLORDODGE_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_COLORBURN_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_COLORBURN_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_HARDLIGHT_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_HARDLIGHT_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_SOFTLIGHT_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_SOFTLIGHT_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_DIFFERENCE_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_DIFFERENCE_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_EXCLUSION_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_EXCLUSION_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_HSL_HUE_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_HSL_HUE_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_HSL_SATURATION_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_HSL_SATURATION_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_HSL_COLOR_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_HSL_COLOR_EXT - VK_BLEND_OP_ZERO_EXT);
        case GL_HSL_LUMINOSITY_KHR:
            return static_cast<uint8_t>(VK_BLEND_OP_HSL_LUMINOSITY_EXT - VK_BLEND_OP_ZERO_EXT);
        default:
            UNREACHABLE();
            return 0;
    }
}

VkBlendOp UnpackBlendOp(uint8_t packedBlendOp)
{
    if (packedBlendOp <= VK_BLEND_OP_MAX)
    {
        return static_cast<VkBlendOp>(packedBlendOp);
    }
    return static_cast<VkBlendOp>(packedBlendOp + VK_BLEND_OP_ZERO_EXT);
}

uint8_t PackGLBlendFactor(GLenum blendFactor)
{
    switch (blendFactor)
    {
        case GL_ZERO:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ZERO);
        case GL_ONE:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE);
        case GL_SRC_COLOR:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_SRC_COLOR);
        case GL_DST_COLOR:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_DST_COLOR);
        case GL_ONE_MINUS_SRC_COLOR:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR);
        case GL_SRC_ALPHA:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_SRC_ALPHA);
        case GL_ONE_MINUS_SRC_ALPHA:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA);
        case GL_DST_ALPHA:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_DST_ALPHA);
        case GL_ONE_MINUS_DST_ALPHA:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA);
        case GL_ONE_MINUS_DST_COLOR:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR);
        case GL_SRC_ALPHA_SATURATE:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_SRC_ALPHA_SATURATE);
        case GL_CONSTANT_COLOR:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_CONSTANT_COLOR);
        case GL_CONSTANT_ALPHA:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_CONSTANT_ALPHA);
        case GL_ONE_MINUS_CONSTANT_COLOR:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR);
        case GL_ONE_MINUS_CONSTANT_ALPHA:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA);
        case GL_SRC1_COLOR_EXT:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_SRC1_COLOR);
        case GL_SRC1_ALPHA_EXT:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_SRC1_ALPHA);
        case GL_ONE_MINUS_SRC1_COLOR_EXT:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR);
        case GL_ONE_MINUS_SRC1_ALPHA_EXT:
            return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA);
        default:
            UNREACHABLE();
            return 0;
    }
}

void UnpackAttachmentDesc(VkAttachmentDescription *desc,
                          angle::FormatID formatID,
                          uint8_t samples,
                          const PackedAttachmentOpsDesc &ops)
{
    desc->flags   = 0;
    desc->format  = GetVkFormatFromFormatID(formatID);
    desc->samples = gl_vk::GetSamples(samples);
    desc->loadOp  = ConvertRenderPassLoadOpToVkLoadOp(static_cast<RenderPassLoadOp>(ops.loadOp));
    desc->storeOp =
        ConvertRenderPassStoreOpToVkStoreOp(static_cast<RenderPassStoreOp>(ops.storeOp));
    desc->stencilLoadOp =
        ConvertRenderPassLoadOpToVkLoadOp(static_cast<RenderPassLoadOp>(ops.stencilLoadOp));
    desc->stencilStoreOp =
        ConvertRenderPassStoreOpToVkStoreOp(static_cast<RenderPassStoreOp>(ops.stencilStoreOp));
    desc->initialLayout =
        ConvertImageLayoutToVkImageLayout(static_cast<ImageLayout>(ops.initialLayout));
    desc->finalLayout =
        ConvertImageLayoutToVkImageLayout(static_cast<ImageLayout>(ops.finalLayout));
}

void UnpackColorResolveAttachmentDesc(VkAttachmentDescription *desc,
                                      angle::FormatID formatID,
                                      bool usedAsInputAttachment,
                                      bool isInvalidated)
{
    desc->flags  = 0;
    desc->format = GetVkFormatFromFormatID(formatID);

    // This function is for color resolve attachments.
    const angle::Format &angleFormat = angle::Format::Get(formatID);
    ASSERT(angleFormat.depthBits == 0 && angleFormat.stencilBits == 0);

    // Resolve attachments always have a sample count of 1.
    //
    // If the corresponding color attachment needs to take its initial value from the resolve
    // attachment (i.e. needs to be unresolved), loadOp needs to be set to LOAD, otherwise it should
    // be DONT_CARE as it gets overwritten during resolve.
    //
    // storeOp should be STORE.  If the attachment is invalidated, it is set to DONT_CARE.
    desc->samples = VK_SAMPLE_COUNT_1_BIT;
    desc->loadOp =
        usedAsInputAttachment ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    desc->storeOp = isInvalidated ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
    desc->stencilLoadOp  = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    desc->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    desc->initialLayout  = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    desc->finalLayout    = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}

void UnpackDepthStencilResolveAttachmentDesc(VkAttachmentDescription *desc,
                                             angle::FormatID formatID,
                                             bool usedAsDepthInputAttachment,
                                             bool usedAsStencilInputAttachment,
                                             bool isDepthInvalidated,
                                             bool isStencilInvalidated)
{
    // There cannot be simultaneous usages of the depth/stencil resolve image, as depth/stencil
    // resolve currently only comes from depth/stencil renderbuffers.
    desc->flags  = 0;
    desc->format = GetVkFormatFromFormatID(formatID);

    // This function is for depth/stencil resolve attachment.
    const angle::Format &angleFormat = angle::Format::Get(formatID);
    ASSERT(angleFormat.depthBits != 0 || angleFormat.stencilBits != 0);

    // Missing aspects are folded in is*Invalidated parameters, so no need to double check.
    ASSERT(angleFormat.depthBits > 0 || isDepthInvalidated);
    ASSERT(angleFormat.stencilBits > 0 || isStencilInvalidated);

    // Similarly to color resolve attachments, sample count is 1, loadOp is LOAD or DONT_CARE based
    // on whether unresolve is required, and storeOp is STORE or DONT_CARE based on whether the
    // attachment is invalidated or the aspect exists.
    desc->samples = VK_SAMPLE_COUNT_1_BIT;
    desc->loadOp =
        usedAsDepthInputAttachment ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    desc->storeOp =
        isDepthInvalidated ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
    desc->stencilLoadOp =
        usedAsStencilInputAttachment ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    desc->stencilStoreOp =
        isStencilInvalidated ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
    desc->initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
    desc->finalLayout   = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
}

void UnpackStencilState(const PackedStencilOpState &packedState, VkStencilOpState *stateOut)
{
    stateOut->failOp      = static_cast<VkStencilOp>(packedState.ops.fail);
    stateOut->passOp      = static_cast<VkStencilOp>(packedState.ops.pass);
    stateOut->depthFailOp = static_cast<VkStencilOp>(packedState.ops.depthFail);
    stateOut->compareOp   = static_cast<VkCompareOp>(packedState.ops.compare);
    stateOut->compareMask = 0;
    stateOut->writeMask   = 0;
    stateOut->reference   = 0;
}

void UnpackBlendAttachmentState(const PackedColorBlendAttachmentState &packedState,
                                VkPipelineColorBlendAttachmentState *stateOut)
{
    stateOut->srcColorBlendFactor = static_cast<VkBlendFactor>(packedState.srcColorBlendFactor);
    stateOut->dstColorBlendFactor = static_cast<VkBlendFactor>(packedState.dstColorBlendFactor);
    stateOut->colorBlendOp        = UnpackBlendOp(packedState.colorBlendOp);
    stateOut->srcAlphaBlendFactor = static_cast<VkBlendFactor>(packedState.srcAlphaBlendFactor);
    stateOut->dstAlphaBlendFactor = static_cast<VkBlendFactor>(packedState.dstAlphaBlendFactor);
    stateOut->alphaBlendOp        = UnpackBlendOp(packedState.alphaBlendOp);
}

void SetPipelineShaderStageInfo(const VkStructureType type,
                                const VkShaderStageFlagBits stage,
                                const VkShaderModule module,
                                const VkSpecializationInfo &specializationInfo,
                                VkPipelineShaderStageCreateInfo *shaderStage)
{
    shaderStage->sType               = type;
    shaderStage->flags               = 0;
    shaderStage->stage               = stage;
    shaderStage->module              = module;
    shaderStage->pName               = "main";
    shaderStage->pSpecializationInfo = &specializationInfo;
}

// Defines a subpass that uses the resolve attachments as input attachments to initialize color and
// depth/stencil attachments that need to be "unresolved" at the start of the render pass.  The
// subpass will only contain the attachments that need to be unresolved to simplify the shader that
// performs the operations.
void InitializeUnresolveSubpass(
    const RenderPassDesc &desc,
    const gl::DrawBuffersVector<VkAttachmentReference> &drawSubpassColorAttachmentRefs,
    const gl::DrawBuffersVector<VkAttachmentReference> &drawSubpassResolveAttachmentRefs,
    const VkAttachmentReference &depthStencilAttachmentRef,
    const VkAttachmentReference2KHR &depthStencilResolveAttachmentRef,
    gl::DrawBuffersVector<VkAttachmentReference> *unresolveColorAttachmentRefs,
    VkAttachmentReference *unresolveDepthStencilAttachmentRef,
    FramebufferAttachmentsVector<VkAttachmentReference> *unresolveInputAttachmentRefs,
    FramebufferAttachmentsVector<uint32_t> *unresolvePreserveAttachmentRefs,
    VkSubpassDescription *subpassDesc)
{
    for (uint32_t colorIndexGL = 0; colorIndexGL < desc.colorAttachmentRange(); ++colorIndexGL)
    {
        // Assume the GL Framebuffer has the following attachments enabled:
        //
        //     GL Color 0
        //     GL Color 3
        //     GL Color 4
        //     GL Color 6
        //     GL Color 7
        //     GL Depth/Stencil
        //
        // Additionally, assume Color 0, 4 and 6 are multisampled-render-to-texture (or for any
        // other reason) have corresponding resolve attachments.  Furthermore, say Color 4 and 6
        // require an initial unresolve operation.
        //
        // In the above example, the render pass is created with the following attachments:
        //
        //     RP Attachment[0] <- corresponding to GL Color 0
        //     RP Attachment[1] <- corresponding to GL Color 3
        //     RP Attachment[2] <- corresponding to GL Color 4
        //     RP Attachment[3] <- corresponding to GL Color 6
        //     RP Attachment[4] <- corresponding to GL Color 7
        //     RP Attachment[5] <- corresponding to GL Depth/Stencil
        //     RP Attachment[6] <- corresponding to resolve attachment of GL Color 0
        //     RP Attachment[7] <- corresponding to resolve attachment of GL Color 4
        //     RP Attachment[8] <- corresponding to resolve attachment of GL Color 6
        //
        // If the depth/stencil attachment is to be resolved, the following attachment would also be
        // present:
        //
        //     RP Attachment[9] <- corresponding to resolve attachment of GL Depth/Stencil
        //
        // The subpass that takes the application draw calls has the following attachments, creating
        // the mapping from the Vulkan attachment indices (i.e. RP attachment indices) to GL indices
        // as indicated by the GL shaders:
        //
        //     Subpass[1] Color[0] -> RP Attachment[0]
        //     Subpass[1] Color[1] -> VK_ATTACHMENT_UNUSED
        //     Subpass[1] Color[2] -> VK_ATTACHMENT_UNUSED
        //     Subpass[1] Color[3] -> RP Attachment[1]
        //     Subpass[1] Color[4] -> RP Attachment[2]
        //     Subpass[1] Color[5] -> VK_ATTACHMENT_UNUSED
        //     Subpass[1] Color[6] -> RP Attachment[3]
        //     Subpass[1] Color[7] -> RP Attachment[4]
        //     Subpass[1] Depth/Stencil -> RP Attachment[5]
        //     Subpass[1] Resolve[0] -> RP Attachment[6]
        //     Subpass[1] Resolve[1] -> VK_ATTACHMENT_UNUSED
        //     Subpass[1] Resolve[2] -> VK_ATTACHMENT_UNUSED
        //     Subpass[1] Resolve[3] -> VK_ATTACHMENT_UNUSED
        //     Subpass[1] Resolve[4] -> RP Attachment[7]
        //     Subpass[1] Resolve[5] -> VK_ATTACHMENT_UNUSED
        //     Subpass[1] Resolve[6] -> RP Attachment[8]
        //     Subpass[1] Resolve[7] -> VK_ATTACHMENT_UNUSED
        //
        // With depth/stencil resolve attachment:
        //
        //     Subpass[1] Depth/Stencil Resolve -> RP Attachment[9]
        //
        // The initial subpass that's created here is (remember that in the above example Color 4
        // and 6 need to be unresolved):
        //
        //     Subpass[0] Input[0] -> RP Attachment[7] = Subpass[1] Resolve[4]
        //     Subpass[0] Input[1] -> RP Attachment[8] = Subpass[1] Resolve[6]
        //     Subpass[0] Color[0] -> RP Attachment[2] = Subpass[1] Color[4]
        //     Subpass[0] Color[1] -> RP Attachment[3] = Subpass[1] Color[6]
        //
        // The trick here therefore is to use the color attachment refs already created for the
        // application draw subpass indexed with colorIndexGL.
        //
        // If depth/stencil needs to be unresolved:
        //
        //     Subpass[0] Input[2] -> RP Attachment[9] = Subpass[1] Depth/Stencil Resolve
        //     Subpass[0] Color[2] -> RP Attachment[5] = Subpass[1] Depth/Stencil
        //
        // As an additional note, the attachments that are not used in the unresolve subpass must be
        // preserved.  That is color attachments and the depth/stencil attachment if any.  Resolve
        // attachments are rewritten by the next subpass, so they don't need to be preserved.  Note
        // that there's no need to preserve attachments whose loadOp is DONT_CARE.  For simplicity,
        // we preserve those as well.  The driver would ideally avoid preserving attachments with
        // loadOp=DONT_CARE.
        //
        // With the above example:
        //
        //     Subpass[0] Preserve[0] -> RP Attachment[0] = Subpass[1] Color[0]
        //     Subpass[0] Preserve[1] -> RP Attachment[1] = Subpass[1] Color[3]
        //     Subpass[0] Preserve[2] -> RP Attachment[4] = Subpass[1] Color[7]
        //
        // If depth/stencil is not unresolved:
        //
        //     Subpass[0] Preserve[3] -> RP Attachment[5] = Subpass[1] Depth/Stencil
        //
        // Again, the color attachment refs already created for the application draw subpass can be
        // used indexed with colorIndexGL.

        if (!desc.hasColorUnresolveAttachment(colorIndexGL))
        {
            if (desc.isColorAttachmentEnabled(colorIndexGL))
            {
                unresolvePreserveAttachmentRefs->push_back(
                    drawSubpassColorAttachmentRefs[colorIndexGL].attachment);
            }
            continue;
        }
        ASSERT(desc.isColorAttachmentEnabled(colorIndexGL));
        ASSERT(desc.hasColorResolveAttachment(colorIndexGL));
        ASSERT(drawSubpassColorAttachmentRefs[colorIndexGL].attachment != VK_ATTACHMENT_UNUSED);
        ASSERT(drawSubpassResolveAttachmentRefs[colorIndexGL].attachment != VK_ATTACHMENT_UNUSED);

        unresolveColorAttachmentRefs->push_back(drawSubpassColorAttachmentRefs[colorIndexGL]);
        unresolveInputAttachmentRefs->push_back(drawSubpassResolveAttachmentRefs[colorIndexGL]);

        // Note the input attachment layout should be shader read-only.  The subpass dependency
        // will take care of transitioning the layout of the resolve attachment to color attachment
        // automatically.
        unresolveInputAttachmentRefs->back().layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    }

    if (desc.hasDepthStencilUnresolveAttachment())
    {
        ASSERT(desc.hasDepthStencilAttachment());
        ASSERT(desc.hasDepthStencilResolveAttachment());

        *unresolveDepthStencilAttachmentRef = depthStencilAttachmentRef;

        VkAttachmentReference unresolveDepthStencilInputAttachmentRef = {};
        unresolveDepthStencilInputAttachmentRef.attachment =
            depthStencilResolveAttachmentRef.attachment;
        unresolveDepthStencilInputAttachmentRef.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

        unresolveInputAttachmentRefs->push_back(unresolveDepthStencilInputAttachmentRef);
    }
    else if (desc.hasDepthStencilAttachment())
    {
        // Preserve the depth/stencil attachment if not unresolved.  Again, there's no need to
        // preserve this attachment if loadOp=DONT_CARE, but we do for simplicity.
        unresolvePreserveAttachmentRefs->push_back(depthStencilAttachmentRef.attachment);
    }

    ASSERT(!unresolveColorAttachmentRefs->empty() ||
           unresolveDepthStencilAttachmentRef->attachment != VK_ATTACHMENT_UNUSED);
    ASSERT(unresolveColorAttachmentRefs->size() +
               (desc.hasDepthStencilUnresolveAttachment() ? 1 : 0) ==
           unresolveInputAttachmentRefs->size());

    subpassDesc->flags                = 0;
    subpassDesc->pipelineBindPoint    = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpassDesc->inputAttachmentCount = static_cast<uint32_t>(unresolveInputAttachmentRefs->size());
    subpassDesc->pInputAttachments    = unresolveInputAttachmentRefs->data();
    subpassDesc->colorAttachmentCount = static_cast<uint32_t>(unresolveColorAttachmentRefs->size());
    subpassDesc->pColorAttachments    = unresolveColorAttachmentRefs->data();
    subpassDesc->pResolveAttachments  = nullptr;
    subpassDesc->pDepthStencilAttachment = unresolveDepthStencilAttachmentRef;
    subpassDesc->preserveAttachmentCount =
        static_cast<uint32_t>(unresolvePreserveAttachmentRefs->size());
    subpassDesc->pPreserveAttachments = unresolvePreserveAttachmentRefs->data();
}

// There is normally one subpass, and occasionally another for the unresolve operation.
constexpr size_t kSubpassFastVectorSize = 2;
template <typename T>
using SubpassVector = angle::FastVector<T, kSubpassFastVectorSize>;

void InitializeUnresolveSubpassDependencies(const SubpassVector<VkSubpassDescription> &subpassDesc,
                                            bool unresolveColor,
                                            bool unresolveDepthStencil,
                                            std::vector<VkSubpassDependency> *subpassDependencies)
{
    ASSERT(subpassDesc.size() >= 2);
    ASSERT(unresolveColor || unresolveDepthStencil);

    // The unresolve subpass is the first subpass.  The application draw subpass is the next one.
    constexpr uint32_t kUnresolveSubpassIndex = 0;
    constexpr uint32_t kDrawSubpassIndex      = 1;

    // A subpass dependency is needed between the unresolve and draw subpasses.  There are two
    // hazards here:
    //
    // - Subpass 0 writes to color/depth/stencil attachments, subpass 1 writes to the same
    //   attachments.  This is a WaW hazard (color/depth/stencil write -> color/depth/stencil write)
    //   similar to when two subsequent render passes write to the same images.
    // - Subpass 0 reads from resolve attachments, subpass 1 writes to the same resolve attachments.
    //   This is a WaR hazard (fragment shader read -> color write) which only requires an execution
    //   barrier.
    //
    // Note: the DEPENDENCY_BY_REGION flag is necessary to create a "framebuffer-local" dependency,
    // as opposed to "framebuffer-global".  The latter is effectively a render pass break.  The
    // former creates a dependency per framebuffer region.  If dependency scopes correspond to
    // attachments with:
    //
    // - Same sample count: dependency is at sample granularity
    // - Different sample count: dependency is at pixel granularity
    //
    // The latter is clarified by the spec as such:
    //
    // > Practically, the pixel vs sample granularity dependency means that if an input attachment
    // > has a different number of samples than the pipeline's rasterizationSamples, then a fragment
    // > can access any sample in the input attachment's pixel even if it only uses
    // > framebuffer-local dependencies.
    //
    // The dependency for the first hazard above (attachment write -> attachment write) is on
    // same-sample attachments, so it will not allow the use of input attachments as required by the
    // unresolve subpass.  As a result, even though the second hazard seems to be subsumed by the
    // first (its src stage is earlier and its dst stage is the same), a separate dependency is
    // created for it just to obtain a pixel granularity dependency.
    //
    // Note: depth/stencil resolve is considered to be done in the color write stage:
    //
    // > Moving to the next subpass automatically performs any multisample resolve operations in the
    // > subpass being ended. End-of-subpass multisample resolves are treated as color attachment
    // > writes for the purposes of synchronization. This applies to resolve operations for both
    // > color and depth/stencil attachments. That is, they are considered to execute in the
    // > VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT pipeline stage and their writes are
    // > synchronized with VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT.

    subpassDependencies->emplace_back();
    VkSubpassDependency *dependency = &subpassDependencies->back();

    constexpr VkPipelineStageFlags kColorWriteStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    constexpr VkPipelineStageFlags kColorReadWriteStage =
        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    constexpr VkAccessFlags kColorWriteFlags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    constexpr VkAccessFlags kColorReadWriteFlags =
        kColorWriteFlags | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;

    constexpr VkPipelineStageFlags kDepthStencilWriteStage =
        VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
    constexpr VkPipelineStageFlags kDepthStencilReadWriteStage =
        VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
    constexpr VkAccessFlags kDepthStencilWriteFlags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
    constexpr VkAccessFlags kDepthStencilReadWriteFlags =
        kDepthStencilWriteFlags | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;

    VkPipelineStageFlags attachmentWriteStages     = 0;
    VkPipelineStageFlags attachmentReadWriteStages = 0;
    VkAccessFlags attachmentWriteFlags             = 0;
    VkAccessFlags attachmentReadWriteFlags         = 0;

    if (unresolveColor)
    {
        attachmentWriteStages |= kColorWriteStage;
        attachmentReadWriteStages |= kColorReadWriteStage;
        attachmentWriteFlags |= kColorWriteFlags;
        attachmentReadWriteFlags |= kColorReadWriteFlags;
    }

    if (unresolveDepthStencil)
    {
        attachmentWriteStages |= kDepthStencilWriteStage;
        attachmentReadWriteStages |= kDepthStencilReadWriteStage;
        attachmentWriteFlags |= kDepthStencilWriteFlags;
        attachmentReadWriteFlags |= kDepthStencilReadWriteFlags;
    }

    dependency->srcSubpass      = kUnresolveSubpassIndex;
    dependency->dstSubpass      = kDrawSubpassIndex;
    dependency->srcStageMask    = attachmentWriteStages;
    dependency->dstStageMask    = attachmentReadWriteStages;
    dependency->srcAccessMask   = attachmentWriteFlags;
    dependency->dstAccessMask   = attachmentReadWriteFlags;
    dependency->dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;

    subpassDependencies->emplace_back();
    dependency = &subpassDependencies->back();

    // Note again that depth/stencil resolve is considered to be done in the color output stage.
    dependency->srcSubpass      = kUnresolveSubpassIndex;
    dependency->dstSubpass      = kDrawSubpassIndex;
    dependency->srcStageMask    = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    dependency->dstStageMask    = kColorWriteStage;
    dependency->srcAccessMask   = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
    dependency->dstAccessMask   = kColorWriteFlags;
    dependency->dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
}

// glFramebufferFetchBarrierEXT and glBlendBarrierKHR require a pipeline barrier to be inserted in
// the render pass.  This requires a subpass self-dependency.
//
// For framebuffer fetch:
//
//     srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
//     dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
//     srcAccess = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
//     dstAccess = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT
//
// For advanced blend:
//
//     srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
//     dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
//     srcAccess = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
//     dstAccess = VK_ACCESS_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT
//
// Subpass dependencies cannot be added after the fact at the end of the render pass due to render
// pass compatibility rules.  ANGLE specifies a subpass self-dependency with the above stage/access
// masks in preparation of potential framebuffer fetch and advanced blend barriers.  This is known
// not to add any overhead on any hardware we have been able to gather information from.
void InitializeDefaultSubpassSelfDependencies(vk::Context *context,
                                              const RenderPassDesc &desc,
                                              uint32_t subpassIndex,
                                              std::vector<VkSubpassDependency> *subpassDependencies)
{
    subpassDependencies->emplace_back();
    VkSubpassDependency *dependency = &subpassDependencies->back();

    dependency->srcSubpass   = subpassIndex;
    dependency->dstSubpass   = subpassIndex;
    dependency->srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency->dstStageMask =
        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency->srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    dependency->dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
    if (context->getRenderer()->getFeatures().supportsBlendOperationAdvanced.enabled)
    {
        dependency->dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT;
    }
    dependency->dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
    if (desc.viewCount() > 0)
    {
        dependency->dependencyFlags |= VK_DEPENDENCY_VIEW_LOCAL_BIT;
    }
}

void ToAttachmentDesciption2(const VkAttachmentDescription &desc,
                             VkAttachmentDescription2KHR *desc2Out)
{
    *desc2Out                = {};
    desc2Out->sType          = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR;
    desc2Out->flags          = desc.flags;
    desc2Out->format         = desc.format;
    desc2Out->samples        = desc.samples;
    desc2Out->loadOp         = desc.loadOp;
    desc2Out->storeOp        = desc.storeOp;
    desc2Out->stencilLoadOp  = desc.stencilLoadOp;
    desc2Out->stencilStoreOp = desc.stencilStoreOp;
    desc2Out->initialLayout  = desc.initialLayout;
    desc2Out->finalLayout    = desc.finalLayout;
}

void ToAttachmentReference2(const VkAttachmentReference &ref,
                            VkImageAspectFlags aspectMask,
                            VkAttachmentReference2KHR *ref2Out)
{
    *ref2Out            = {};
    ref2Out->sType      = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
    ref2Out->attachment = ref.attachment;
    ref2Out->layout     = ref.layout;
    ref2Out->aspectMask = aspectMask;
}

void ToSubpassDescription2(const VkSubpassDescription &desc,
                           const FramebufferAttachmentsVector<VkAttachmentReference2KHR> &inputRefs,
                           const gl::DrawBuffersVector<VkAttachmentReference2KHR> &colorRefs,
                           const gl::DrawBuffersVector<VkAttachmentReference2KHR> &resolveRefs,
                           const VkAttachmentReference2KHR &depthStencilRef,
                           uint32_t viewMask,
                           VkSubpassDescription2KHR *desc2Out)
{
    *desc2Out                         = {};
    desc2Out->sType                   = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
    desc2Out->flags                   = desc.flags;
    desc2Out->pipelineBindPoint       = desc.pipelineBindPoint;
    desc2Out->viewMask                = viewMask;
    desc2Out->inputAttachmentCount    = static_cast<uint32_t>(inputRefs.size());
    desc2Out->pInputAttachments       = !inputRefs.empty() ? inputRefs.data() : nullptr;
    desc2Out->colorAttachmentCount    = static_cast<uint32_t>(colorRefs.size());
    desc2Out->pColorAttachments       = !colorRefs.empty() ? colorRefs.data() : nullptr;
    desc2Out->pResolveAttachments     = !resolveRefs.empty() ? resolveRefs.data() : nullptr;
    desc2Out->pDepthStencilAttachment = desc.pDepthStencilAttachment ? &depthStencilRef : nullptr;
    desc2Out->preserveAttachmentCount = desc.preserveAttachmentCount;
    desc2Out->pPreserveAttachments    = desc.pPreserveAttachments;
}

void ToSubpassDependency2(const VkSubpassDependency &dep, VkSubpassDependency2KHR *dep2Out)
{
    *dep2Out                 = {};
    dep2Out->sType           = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2_KHR;
    dep2Out->srcSubpass      = dep.srcSubpass;
    dep2Out->dstSubpass      = dep.dstSubpass;
    dep2Out->srcStageMask    = dep.srcStageMask;
    dep2Out->dstStageMask    = dep.dstStageMask;
    dep2Out->srcAccessMask   = dep.srcAccessMask;
    dep2Out->dstAccessMask   = dep.dstAccessMask;
    dep2Out->dependencyFlags = dep.dependencyFlags;
}

angle::Result CreateRenderPass2(Context *context,
                                const VkRenderPassCreateInfo &createInfo,
                                const VkSubpassDescriptionDepthStencilResolve &depthStencilResolve,
                                const VkRenderPassMultiviewCreateInfo &multiviewInfo,
                                bool unresolveDepth,
                                bool unresolveStencil,
                                bool isRenderToTextureThroughExtension,
                                uint8_t renderToTextureSamples,
                                RenderPass *renderPass)
{
    // Convert the attachments to VkAttachmentDescription2.
    FramebufferAttachmentArray<VkAttachmentDescription2KHR> attachmentDescs;
    for (uint32_t index = 0; index < createInfo.attachmentCount; ++index)
    {
        ToAttachmentDesciption2(createInfo.pAttachments[index], &attachmentDescs[index]);
    }

    // Convert subpass attachments to VkAttachmentReference2 and the subpass description to
    // VkSubpassDescription2.
    SubpassVector<FramebufferAttachmentsVector<VkAttachmentReference2KHR>>
        subpassInputAttachmentRefs(createInfo.subpassCount);
    SubpassVector<gl::DrawBuffersVector<VkAttachmentReference2KHR>> subpassColorAttachmentRefs(
        createInfo.subpassCount);
    SubpassVector<gl::DrawBuffersVector<VkAttachmentReference2KHR>> subpassResolveAttachmentRefs(
        createInfo.subpassCount);
    SubpassVector<VkAttachmentReference2KHR> subpassDepthStencilAttachmentRefs(
        createInfo.subpassCount);
    SubpassVector<VkSubpassDescription2KHR> subpassDescriptions(createInfo.subpassCount);
    for (uint32_t subpass = 0; subpass < createInfo.subpassCount; ++subpass)
    {
        const VkSubpassDescription &desc = createInfo.pSubpasses[subpass];
        FramebufferAttachmentsVector<VkAttachmentReference2KHR> &inputRefs =
            subpassInputAttachmentRefs[subpass];
        gl::DrawBuffersVector<VkAttachmentReference2KHR> &colorRefs =
            subpassColorAttachmentRefs[subpass];
        gl::DrawBuffersVector<VkAttachmentReference2KHR> &resolveRefs =
            subpassResolveAttachmentRefs[subpass];
        VkAttachmentReference2KHR &depthStencilRef = subpassDepthStencilAttachmentRefs[subpass];

        inputRefs.resize(desc.inputAttachmentCount);
        colorRefs.resize(desc.colorAttachmentCount);

        // Convert subpass attachment references.
        for (uint32_t index = 0; index < desc.inputAttachmentCount; ++index)
        {
            VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            if (index >= desc.colorAttachmentCount)
            {
                // Set the aspect of the depth/stencil input attachment (of which there can be only
                // one).
                ASSERT(index + 1 == desc.inputAttachmentCount);
                aspectMask = 0;
                if (unresolveDepth)
                {
                    aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
                }
                if (unresolveStencil)
                {
                    aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
                }
                ASSERT(aspectMask != 0);
            }

            ToAttachmentReference2(desc.pInputAttachments[index], aspectMask, &inputRefs[index]);
        }

        for (uint32_t index = 0; index < desc.colorAttachmentCount; ++index)
        {
            ToAttachmentReference2(desc.pColorAttachments[index], VK_IMAGE_ASPECT_COLOR_BIT,
                                   &colorRefs[index]);
        }
        if (desc.pResolveAttachments)
        {
            resolveRefs.resize(desc.colorAttachmentCount);
            for (uint32_t index = 0; index < desc.colorAttachmentCount; ++index)
            {
                ToAttachmentReference2(desc.pResolveAttachments[index], VK_IMAGE_ASPECT_COLOR_BIT,
                                       &resolveRefs[index]);
            }
        }
        if (desc.pDepthStencilAttachment)
        {
            ToAttachmentReference2(*desc.pDepthStencilAttachment,
                                   VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
                                   &depthStencilRef);
        }

        // Convert subpass itself.
        ToSubpassDescription2(desc, inputRefs, colorRefs, resolveRefs, depthStencilRef,
                              multiviewInfo.pViewMasks[subpass], &subpassDescriptions[subpass]);
    }

    VkMultisampledRenderToSingleSampledInfoEXT renderToTextureInfo = {};
    renderToTextureInfo.sType = VK_STRUCTURE_TYPE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_INFO_EXT;
    renderToTextureInfo.multisampledRenderToSingleSampledEnable = true;
    renderToTextureInfo.rasterizationSamples = gl_vk::GetSamples(renderToTextureSamples);
    renderToTextureInfo.depthResolveMode     = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
    renderToTextureInfo.stencilResolveMode   = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;

    // Append the depth/stencil resolve attachment to the pNext chain of last subpass, if any.
    if (depthStencilResolve.pDepthStencilResolveAttachment != nullptr)
    {
        ASSERT(!isRenderToTextureThroughExtension);
        subpassDescriptions.back().pNext = &depthStencilResolve;
    }
    else
    {
        RendererVk *renderer = context->getRenderer();

        ASSERT(isRenderToTextureThroughExtension);
        ASSERT(renderer->getFeatures().supportsMultisampledRenderToSingleSampled.enabled);
        ASSERT(subpassDescriptions.size() == 1);

        subpassDescriptions.back().pNext = &renderToTextureInfo;
    }

    // Convert subpass dependencies to VkSubpassDependency2.
    std::vector<VkSubpassDependency2KHR> subpassDependencies(createInfo.dependencyCount);
    for (uint32_t index = 0; index < createInfo.dependencyCount; ++index)
    {
        ToSubpassDependency2(createInfo.pDependencies[index], &subpassDependencies[index]);
    }

    // Convert CreateInfo itself
    VkRenderPassCreateInfo2KHR createInfo2 = {};
    createInfo2.sType                      = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
    createInfo2.flags                      = createInfo.flags;
    createInfo2.attachmentCount            = createInfo.attachmentCount;
    createInfo2.pAttachments               = attachmentDescs.data();
    createInfo2.subpassCount               = createInfo.subpassCount;
    createInfo2.pSubpasses                 = subpassDescriptions.data();
    createInfo2.dependencyCount            = static_cast<uint32_t>(subpassDependencies.size());
    createInfo2.pDependencies = !subpassDependencies.empty() ? subpassDependencies.data() : nullptr;
    createInfo2.correlatedViewMaskCount = multiviewInfo.correlationMaskCount;
    createInfo2.pCorrelatedViewMasks    = multiviewInfo.pCorrelationMasks;

    // Initialize the render pass.
    ANGLE_VK_TRY(context, renderPass->init2(context->getDevice(), createInfo2));

    return angle::Result::Continue;
}

void UpdateRenderPassColorPerfCounters(const VkRenderPassCreateInfo &createInfo,
                                       FramebufferAttachmentMask depthStencilAttachmentIndices,
                                       RenderPassPerfCounters *countersOut)
{
    for (uint32_t index = 0; index < createInfo.attachmentCount; index++)
    {
        if (depthStencilAttachmentIndices.test(index))
        {
            continue;
        }

        VkAttachmentLoadOp loadOp   = createInfo.pAttachments[index].loadOp;
        VkAttachmentStoreOp storeOp = createInfo.pAttachments[index].storeOp;
        countersOut->colorLoadOpClears += loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
        countersOut->colorLoadOpLoads += loadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
        countersOut->colorLoadOpNones += loadOp == VK_ATTACHMENT_LOAD_OP_NONE_EXT ? 1 : 0;
        countersOut->colorStoreOpStores += storeOp == VK_ATTACHMENT_STORE_OP_STORE ? 1 : 0;
        countersOut->colorStoreOpNones += storeOp == VK_ATTACHMENT_STORE_OP_NONE_EXT ? 1 : 0;
    }
}

void UpdateSubpassColorPerfCounters(const VkRenderPassCreateInfo &createInfo,
                                    const VkSubpassDescription &subpass,
                                    RenderPassPerfCounters *countersOut)
{
    // Color resolve counters.
    if (subpass.pResolveAttachments == nullptr)
    {
        return;
    }

    for (uint32_t colorSubpassIndex = 0; colorSubpassIndex < subpass.colorAttachmentCount;
         ++colorSubpassIndex)
    {
        uint32_t resolveRenderPassIndex = subpass.pResolveAttachments[colorSubpassIndex].attachment;

        if (resolveRenderPassIndex == VK_ATTACHMENT_UNUSED)
        {
            continue;
        }

        ++countersOut->colorAttachmentResolves;
    }
}

void UpdateRenderPassDepthStencilPerfCounters(const VkRenderPassCreateInfo &createInfo,
                                              size_t renderPassIndex,
                                              RenderPassPerfCounters *countersOut)
{
    ASSERT(renderPassIndex != VK_ATTACHMENT_UNUSED);

    // Depth/stencil ops counters.
    const VkAttachmentDescription &ds = createInfo.pAttachments[renderPassIndex];

    countersOut->depthLoadOpClears += ds.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
    countersOut->depthLoadOpLoads += ds.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
    countersOut->depthLoadOpNones += ds.loadOp == VK_ATTACHMENT_LOAD_OP_NONE_EXT ? 1 : 0;
    countersOut->depthStoreOpStores += ds.storeOp == VK_ATTACHMENT_STORE_OP_STORE ? 1 : 0;
    countersOut->depthStoreOpNones += ds.storeOp == VK_ATTACHMENT_STORE_OP_NONE_EXT ? 1 : 0;

    countersOut->stencilLoadOpClears += ds.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
    countersOut->stencilLoadOpLoads += ds.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
    countersOut->stencilLoadOpNones += ds.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_NONE_EXT ? 1 : 0;
    countersOut->stencilStoreOpStores += ds.stencilStoreOp == VK_ATTACHMENT_STORE_OP_STORE ? 1 : 0;
    countersOut->stencilStoreOpNones +=
        ds.stencilStoreOp == VK_ATTACHMENT_STORE_OP_NONE_EXT ? 1 : 0;

    // Depth/stencil read-only mode.
    countersOut->readOnlyDepthStencil +=
        ds.finalLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL ? 1 : 0;
}

void UpdateRenderPassDepthStencilResolvePerfCounters(
    const VkRenderPassCreateInfo &createInfo,
    const VkSubpassDescriptionDepthStencilResolve &depthStencilResolve,
    RenderPassPerfCounters *countersOut)
{
    if (depthStencilResolve.pDepthStencilResolveAttachment == nullptr)
    {
        return;
    }

    uint32_t resolveRenderPassIndex =
        depthStencilResolve.pDepthStencilResolveAttachment->attachment;

    if (resolveRenderPassIndex == VK_ATTACHMENT_UNUSED)
    {
        return;
    }

    const VkAttachmentDescription &dsResolve = createInfo.pAttachments[resolveRenderPassIndex];

    // Resolve depth/stencil ops counters.
    countersOut->depthLoadOpClears += dsResolve.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
    countersOut->depthLoadOpLoads += dsResolve.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
    countersOut->depthStoreOpStores +=
        dsResolve.storeOp == static_cast<uint16_t>(RenderPassStoreOp::Store) ? 1 : 0;

    countersOut->stencilLoadOpClears +=
        dsResolve.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
    countersOut->stencilLoadOpLoads +=
        dsResolve.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
    countersOut->stencilStoreOpStores +=
        dsResolve.stencilStoreOp == static_cast<uint16_t>(RenderPassStoreOp::Store) ? 1 : 0;

    // Depth/stencil resolve counters.
    countersOut->depthAttachmentResolves +=
        depthStencilResolve.depthResolveMode != VK_RESOLVE_MODE_NONE ? 1 : 0;
    countersOut->stencilAttachmentResolves +=
        depthStencilResolve.stencilResolveMode != VK_RESOLVE_MODE_NONE ? 1 : 0;
}

void UpdateRenderPassPerfCounters(
    const RenderPassDesc &desc,
    const VkRenderPassCreateInfo &createInfo,
    const VkSubpassDescriptionDepthStencilResolve &depthStencilResolve,
    RenderPassPerfCounters *countersOut)
{
    // Accumulate depth/stencil attachment indices in all subpasses to avoid double-counting
    // counters.
    FramebufferAttachmentMask depthStencilAttachmentIndices;

    for (uint32_t subpassIndex = 0; subpassIndex < createInfo.subpassCount; ++subpassIndex)
    {
        const VkSubpassDescription &subpass = createInfo.pSubpasses[subpassIndex];

        // Color counters.
        // NOTE: For simplicity, this will accumulate counts for all subpasses in the renderpass.
        UpdateSubpassColorPerfCounters(createInfo, subpass, countersOut);

        // Record index of depth/stencil attachment.
        if (subpass.pDepthStencilAttachment != nullptr)
        {
            uint32_t attachmentRenderPassIndex = subpass.pDepthStencilAttachment->attachment;
            if (attachmentRenderPassIndex != VK_ATTACHMENT_UNUSED)
            {
                depthStencilAttachmentIndices.set(attachmentRenderPassIndex);
            }
        }
    }

    UpdateRenderPassColorPerfCounters(createInfo, depthStencilAttachmentIndices, countersOut);

    // Depth/stencil counters.  Currently, both subpasses use the same depth/stencil attachment (if
    // any).
    ASSERT(depthStencilAttachmentIndices.count() <= 1);
    for (size_t attachmentRenderPassIndex : depthStencilAttachmentIndices)
    {
        UpdateRenderPassDepthStencilPerfCounters(createInfo, attachmentRenderPassIndex,
                                                 countersOut);
    }

    UpdateRenderPassDepthStencilResolvePerfCounters(createInfo, depthStencilResolve, countersOut);

    // Determine unresolve counters from the render pass desc, to avoid making guesses from subpass
    // count etc.
    countersOut->colorAttachmentUnresolves += desc.getColorUnresolveAttachmentMask().count();
    countersOut->depthAttachmentUnresolves += desc.hasDepthUnresolveAttachment() ? 1 : 0;
    countersOut->stencilAttachmentUnresolves += desc.hasStencilUnresolveAttachment() ? 1 : 0;
}

angle::Result InitializeRenderPassFromDesc(ContextVk *contextVk,
                                           const RenderPassDesc &desc,
                                           const AttachmentOpsArray &ops,
                                           RenderPassHelper *renderPassHelper)
{
    constexpr VkAttachmentReference kUnusedAttachment   = {VK_ATTACHMENT_UNUSED,
                                                           VK_IMAGE_LAYOUT_UNDEFINED};
    constexpr VkAttachmentReference2 kUnusedAttachment2 = {
        VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR, nullptr, VK_ATTACHMENT_UNUSED,
        VK_IMAGE_LAYOUT_UNDEFINED, 0};

    const bool needInputAttachments = desc.getFramebufferFetchMode();
    const bool isRenderToTextureThroughExtension =
        desc.isRenderToTexture() &&
        contextVk->getFeatures().supportsMultisampledRenderToSingleSampled.enabled;
    const bool isRenderToTextureThroughEmulation =
        desc.isRenderToTexture() && !isRenderToTextureThroughExtension;

    const uint8_t descSamples            = desc.samples();
    const uint8_t attachmentSamples      = isRenderToTextureThroughExtension ? 1 : descSamples;
    const uint8_t renderToTextureSamples = isRenderToTextureThroughExtension ? descSamples : 1;

    // Unpack the packed and split representation into the format required by Vulkan.
    gl::DrawBuffersVector<VkAttachmentReference> colorAttachmentRefs;
    gl::DrawBuffersVector<VkAttachmentReference> colorResolveAttachmentRefs;
    VkAttachmentReference depthStencilAttachmentRef            = kUnusedAttachment;
    VkAttachmentReference2KHR depthStencilResolveAttachmentRef = kUnusedAttachment2;

    // The list of attachments includes all non-resolve and resolve attachments.
    FramebufferAttachmentArray<VkAttachmentDescription> attachmentDescs;

    // Track invalidated attachments so their resolve attachments can be invalidated as well.
    // Resolve attachments can be removed in that case if the render pass has only one subpass
    // (which is the case if there are no unresolve attachments).
    gl::DrawBufferMask isColorInvalidated;
    bool isDepthInvalidated   = false;
    bool isStencilInvalidated = false;
    const bool hasUnresolveAttachments =
        desc.getColorUnresolveAttachmentMask().any() || desc.hasDepthStencilUnresolveAttachment();
    const bool canRemoveResolveAttachments = !hasUnresolveAttachments;

    // Pack color attachments
    PackedAttachmentIndex attachmentCount(0);
    for (uint32_t colorIndexGL = 0; colorIndexGL < desc.colorAttachmentRange(); ++colorIndexGL)
    {
        // Vulkan says:
        //
        // > Each element of the pColorAttachments array corresponds to an output location in the
        // > shader, i.e. if the shader declares an output variable decorated with a Location value
        // > of X, then it uses the attachment provided in pColorAttachments[X].
        //
        // This means that colorAttachmentRefs is indexed by colorIndexGL.  Where the color
        // attachment is disabled, a reference with VK_ATTACHMENT_UNUSED is given.

        if (!desc.isColorAttachmentEnabled(colorIndexGL))
        {
            colorAttachmentRefs.push_back(kUnusedAttachment);
            continue;
        }

        angle::FormatID attachmentFormatID = desc[colorIndexGL];
        ASSERT(attachmentFormatID != angle::FormatID::NONE);

        VkAttachmentReference colorRef;
        colorRef.attachment = attachmentCount.get();
        colorRef.layout     = needInputAttachments
                                  ? VK_IMAGE_LAYOUT_GENERAL
                                  : ConvertImageLayoutToVkImageLayout(
                                        static_cast<ImageLayout>(ops[attachmentCount].initialLayout));
        colorAttachmentRefs.push_back(colorRef);

        UnpackAttachmentDesc(&attachmentDescs[attachmentCount.get()], attachmentFormatID,
                             attachmentSamples, ops[attachmentCount]);

        // If this renderpass uses EXT_srgb_write_control, we need to override the format to its
        // linear counterpart. Formats that cannot be reinterpreted are exempt from this
        // requirement.
        angle::FormatID linearFormat = rx::ConvertToLinear(attachmentFormatID);
        if (linearFormat != angle::FormatID::NONE)
        {
            if (desc.getSRGBWriteControlMode() == gl::SrgbWriteControlMode::Linear)
            {
                attachmentFormatID = linearFormat;
            }
        }
        attachmentDescs[attachmentCount.get()].format = GetVkFormatFromFormatID(attachmentFormatID);
        ASSERT(attachmentDescs[attachmentCount.get()].format != VK_FORMAT_UNDEFINED);

        isColorInvalidated.set(colorIndexGL, ops[attachmentCount].isInvalidated);

        ++attachmentCount;
    }

    // Pack depth/stencil attachment, if any
    if (desc.hasDepthStencilAttachment())
    {
        uint32_t depthStencilIndexGL = static_cast<uint32_t>(desc.depthStencilAttachmentIndex());

        angle::FormatID attachmentFormatID = desc[depthStencilIndexGL];
        ASSERT(attachmentFormatID != angle::FormatID::NONE);

        depthStencilAttachmentRef.attachment = attachmentCount.get();
        depthStencilAttachmentRef.layout     = ConvertImageLayoutToVkImageLayout(
                static_cast<ImageLayout>(ops[attachmentCount].initialLayout));

        UnpackAttachmentDesc(&attachmentDescs[attachmentCount.get()], attachmentFormatID,
                             attachmentSamples, ops[attachmentCount]);

        isDepthInvalidated   = ops[attachmentCount].isInvalidated;
        isStencilInvalidated = ops[attachmentCount].isStencilInvalidated;

        ++attachmentCount;
    }

    // Pack color resolve attachments
    const uint32_t nonResolveAttachmentCount = attachmentCount.get();
    for (uint32_t colorIndexGL = 0; colorIndexGL < desc.colorAttachmentRange(); ++colorIndexGL)
    {
        if (!desc.hasColorResolveAttachment(colorIndexGL))
        {
            colorResolveAttachmentRefs.push_back(kUnusedAttachment);
            continue;
        }

        ASSERT(desc.isColorAttachmentEnabled(colorIndexGL));

        angle::FormatID attachmentFormatID = desc[colorIndexGL];

        VkAttachmentReference colorRef;
        colorRef.attachment = attachmentCount.get();
        colorRef.layout     = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

        // If color attachment is invalidated, try to remove its resolve attachment altogether.
        if (canRemoveResolveAttachments && isColorInvalidated.test(colorIndexGL))
        {
            colorResolveAttachmentRefs.push_back(kUnusedAttachment);
        }
        else
        {
            colorResolveAttachmentRefs.push_back(colorRef);
        }

        // When multisampled-render-to-texture is used, invalidating an attachment invalidates both
        // the multisampled and the resolve attachments.  Otherwise, the resolve attachment is
        // independent of the multisampled attachment, and is never invalidated.
        UnpackColorResolveAttachmentDesc(
            &attachmentDescs[attachmentCount.get()], attachmentFormatID,
            desc.hasColorUnresolveAttachment(colorIndexGL),
            isColorInvalidated.test(colorIndexGL) && isRenderToTextureThroughEmulation);

        ++attachmentCount;
    }

    // Pack depth/stencil resolve attachment, if any
    if (desc.hasDepthStencilResolveAttachment())
    {
        ASSERT(desc.hasDepthStencilAttachment());

        uint32_t depthStencilIndexGL = static_cast<uint32_t>(desc.depthStencilAttachmentIndex());

        angle::FormatID attachmentFormatID = desc[depthStencilIndexGL];
        const angle::Format &angleFormat   = angle::Format::Get(attachmentFormatID);

        // Treat missing aspect as invalidated for the purpose of the resolve attachment.
        if (angleFormat.depthBits == 0)
        {
            isDepthInvalidated = true;
        }
        if (angleFormat.stencilBits == 0)
        {
            isStencilInvalidated = true;
        }

        depthStencilResolveAttachmentRef.attachment = attachmentCount.get();
        depthStencilResolveAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
        depthStencilResolveAttachmentRef.aspectMask = 0;

        if (!isDepthInvalidated)
        {
            depthStencilResolveAttachmentRef.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
        }
        if (!isStencilInvalidated)
        {
            depthStencilResolveAttachmentRef.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
        }

        UnpackDepthStencilResolveAttachmentDesc(
            &attachmentDescs[attachmentCount.get()], attachmentFormatID,
            desc.hasDepthUnresolveAttachment(), desc.hasStencilUnresolveAttachment(),
            isDepthInvalidated, isStencilInvalidated);

        ++attachmentCount;
    }

    SubpassVector<VkSubpassDescription> subpassDesc;

    // If any attachment needs to be unresolved, create an initial subpass for this purpose.  Note
    // that the following arrays are used in initializing a VkSubpassDescription in subpassDesc,
    // which is in turn used in VkRenderPassCreateInfo below.  That is why they are declared in the
    // same scope.
    gl::DrawBuffersVector<VkAttachmentReference> unresolveColorAttachmentRefs;
    VkAttachmentReference unresolveDepthStencilAttachmentRef = kUnusedAttachment;
    FramebufferAttachmentsVector<VkAttachmentReference> unresolveInputAttachmentRefs;
    FramebufferAttachmentsVector<uint32_t> unresolvePreserveAttachmentRefs;
    if (hasUnresolveAttachments)
    {
        subpassDesc.push_back({});
        InitializeUnresolveSubpass(
            desc, colorAttachmentRefs, colorResolveAttachmentRefs, depthStencilAttachmentRef,
            depthStencilResolveAttachmentRef, &unresolveColorAttachmentRefs,
            &unresolveDepthStencilAttachmentRef, &unresolveInputAttachmentRefs,
            &unresolvePreserveAttachmentRefs, &subpassDesc.back());
    }

    subpassDesc.push_back({});
    VkSubpassDescription *applicationSubpass = &subpassDesc.back();

    applicationSubpass->flags             = 0;
    applicationSubpass->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    applicationSubpass->inputAttachmentCount =
        needInputAttachments ? static_cast<uint32_t>(colorAttachmentRefs.size()) : 0;
    applicationSubpass->pInputAttachments =
        needInputAttachments ? colorAttachmentRefs.data() : nullptr;
    applicationSubpass->colorAttachmentCount = static_cast<uint32_t>(colorAttachmentRefs.size());
    applicationSubpass->pColorAttachments    = colorAttachmentRefs.data();
    applicationSubpass->pResolveAttachments  = attachmentCount.get() > nonResolveAttachmentCount
                                                   ? colorResolveAttachmentRefs.data()
                                                   : nullptr;
    applicationSubpass->pDepthStencilAttachment =
        (depthStencilAttachmentRef.attachment != VK_ATTACHMENT_UNUSED ? &depthStencilAttachmentRef
                                                                      : nullptr);
    applicationSubpass->preserveAttachmentCount = 0;
    applicationSubpass->pPreserveAttachments    = nullptr;

    // If depth/stencil is to be resolved, add a VkSubpassDescriptionDepthStencilResolve to the
    // pNext chain of the subpass description.  Note that we need a VkSubpassDescription2KHR to have
    // a pNext pointer.  CreateRenderPass2 is called to convert the data structures here to those
    // specified by VK_KHR_create_renderpass2 for this purpose.
    VkSubpassDescriptionDepthStencilResolve depthStencilResolve = {};
    if (desc.hasDepthStencilResolveAttachment())
    {
        depthStencilResolve.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE;
        depthStencilResolve.depthResolveMode   = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
        depthStencilResolve.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;

        // If depth/stencil attachment is invalidated, try to remove its resolve attachment
        // altogether.
        const bool removeDepthStencilResolve =
            canRemoveResolveAttachments && isDepthInvalidated && isStencilInvalidated;
        if (!removeDepthStencilResolve)
        {
            depthStencilResolve.pDepthStencilResolveAttachment = &depthStencilResolveAttachmentRef;
        }
    }

    std::vector<VkSubpassDependency> subpassDependencies;
    if (hasUnresolveAttachments)
    {
        InitializeUnresolveSubpassDependencies(
            subpassDesc, desc.getColorUnresolveAttachmentMask().any(),
            desc.hasDepthStencilUnresolveAttachment(), &subpassDependencies);
    }

    const uint32_t drawSubpassIndex = static_cast<uint32_t>(subpassDesc.size()) - 1;
    InitializeDefaultSubpassSelfDependencies(contextVk, desc, drawSubpassIndex,
                                             &subpassDependencies);

    VkRenderPassCreateInfo createInfo = {};
    createInfo.sType                  = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    createInfo.flags                  = 0;
    createInfo.attachmentCount        = attachmentCount.get();
    createInfo.pAttachments           = attachmentDescs.data();
    createInfo.subpassCount           = static_cast<uint32_t>(subpassDesc.size());
    createInfo.pSubpasses             = subpassDesc.data();
    createInfo.dependencyCount        = 0;
    createInfo.pDependencies          = nullptr;

    if (!subpassDependencies.empty())
    {
        createInfo.dependencyCount = static_cast<uint32_t>(subpassDependencies.size());
        createInfo.pDependencies   = subpassDependencies.data();
    }

    SubpassVector<uint32_t> viewMasks(subpassDesc.size(),
                                      angle::BitMask<uint32_t>(desc.viewCount()));
    VkRenderPassMultiviewCreateInfo multiviewInfo = {};
    multiviewInfo.sType        = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
    multiviewInfo.subpassCount = createInfo.subpassCount;
    multiviewInfo.pViewMasks   = viewMasks.data();

    if (desc.viewCount() > 0)
    {
        // For VR, the views are correlated, so this would be an optimization.  However, an
        // application can also use multiview for example to render to all 6 faces of a cubemap, in
        // which case the views are actually not so correlated.  In the absence of any hints from
        // the application, we have to decide on one or the other.  Since VR is more expensive, the
        // views are marked as correlated to optimize that use case.
        multiviewInfo.correlationMaskCount = 1;
        multiviewInfo.pCorrelationMasks    = viewMasks.data();

        createInfo.pNext = &multiviewInfo;
    }

    // If depth/stencil resolve is used, we need to create the render pass with
    // vkCreateRenderPass2KHR.  Same when using the VK_EXT_multisampled_render_to_single_sampled
    // extension.
    if (depthStencilResolve.pDepthStencilResolveAttachment != nullptr ||
        isRenderToTextureThroughExtension)
    {
        ANGLE_TRY(CreateRenderPass2(contextVk, createInfo, depthStencilResolve, multiviewInfo,
                                    desc.hasDepthUnresolveAttachment(),
                                    desc.hasStencilUnresolveAttachment(),
                                    isRenderToTextureThroughExtension, renderToTextureSamples,
                                    &renderPassHelper->getRenderPass()));
    }
    else
    {
        ANGLE_VK_TRY(contextVk,
                     renderPassHelper->getRenderPass().init(contextVk->getDevice(), createInfo));
    }

    // Calculate perf counters associated with this render pass, such as load/store ops, unresolve
    // and resolve operations etc.  This information is taken out of the render pass create info.
    // Depth/stencil resolve attachment uses RenderPass2 structures, so it's passed in separately.
    UpdateRenderPassPerfCounters(desc, createInfo, depthStencilResolve,
                                 &renderPassHelper->getPerfCounters());

    return angle::Result::Continue;
}

void GetRenderPassAndUpdateCounters(ContextVk *contextVk,
                                    bool updatePerfCounters,
                                    RenderPassHelper *renderPassHelper,
                                    RenderPass **renderPassOut)
{
    *renderPassOut = &renderPassHelper->getRenderPass();
    if (updatePerfCounters)
    {
        angle::VulkanPerfCounters &counters      = contextVk->getPerfCounters();
        const RenderPassPerfCounters &rpCounters = renderPassHelper->getPerfCounters();

        counters.colorLoadOpClears += rpCounters.colorLoadOpClears;
        counters.colorLoadOpLoads += rpCounters.colorLoadOpLoads;
        counters.colorLoadOpNones += rpCounters.colorLoadOpNones;
        counters.colorStoreOpStores += rpCounters.colorStoreOpStores;
        counters.colorStoreOpNones += rpCounters.colorStoreOpNones;
        counters.depthLoadOpClears += rpCounters.depthLoadOpClears;
        counters.depthLoadOpLoads += rpCounters.depthLoadOpLoads;
        counters.depthLoadOpNones += rpCounters.depthLoadOpNones;
        counters.depthStoreOpStores += rpCounters.depthStoreOpStores;
        counters.depthStoreOpNones += rpCounters.depthStoreOpNones;
        counters.stencilLoadOpClears += rpCounters.stencilLoadOpClears;
        counters.stencilLoadOpLoads += rpCounters.stencilLoadOpLoads;
        counters.stencilLoadOpNones += rpCounters.stencilLoadOpNones;
        counters.stencilStoreOpStores += rpCounters.stencilStoreOpStores;
        counters.stencilStoreOpNones += rpCounters.stencilStoreOpNones;
        counters.colorAttachmentUnresolves += rpCounters.colorAttachmentUnresolves;
        counters.colorAttachmentResolves += rpCounters.colorAttachmentResolves;
        counters.depthAttachmentUnresolves += rpCounters.depthAttachmentUnresolves;
        counters.depthAttachmentResolves += rpCounters.depthAttachmentResolves;
        counters.stencilAttachmentUnresolves += rpCounters.stencilAttachmentUnresolves;
        counters.stencilAttachmentResolves += rpCounters.stencilAttachmentResolves;
        counters.readOnlyDepthStencilRenderPasses += rpCounters.readOnlyDepthStencil;
    }
}

void InitializeSpecializationInfo(
    const SpecializationConstants &specConsts,
    SpecializationConstantMap<VkSpecializationMapEntry> *specializationEntriesOut,
    VkSpecializationInfo *specializationInfoOut)
{
    // Collect specialization constants.
    for (const sh::vk::SpecializationConstantId id :
         angle::AllEnums<sh::vk::SpecializationConstantId>())
    {
        (*specializationEntriesOut)[id].constantID = static_cast<uint32_t>(id);
        switch (id)
        {
            case sh::vk::SpecializationConstantId::LineRasterEmulation:
                (*specializationEntriesOut)[id].offset =
                    offsetof(SpecializationConstants, lineRasterEmulation);
                (*specializationEntriesOut)[id].size = sizeof(specConsts.lineRasterEmulation);
                break;
            case sh::vk::SpecializationConstantId::SurfaceRotation:
                (*specializationEntriesOut)[id].offset =
                    offsetof(SpecializationConstants, surfaceRotation);
                (*specializationEntriesOut)[id].size = sizeof(specConsts.surfaceRotation);
                break;
            case sh::vk::SpecializationConstantId::Dither:
                (*specializationEntriesOut)[id].offset =
                    offsetof(vk::SpecializationConstants, dither);
                (*specializationEntriesOut)[id].size = sizeof(specConsts.dither);
                break;
            default:
                UNREACHABLE();
                break;
        }
    }

    specializationInfoOut->mapEntryCount = static_cast<uint32_t>(specializationEntriesOut->size());
    specializationInfoOut->pMapEntries   = specializationEntriesOut->data();
    specializationInfoOut->dataSize      = sizeof(specConsts);
    specializationInfoOut->pData         = &specConsts;
}

// Adjust border color value according to intended format.
gl::ColorGeneric AdjustBorderColor(const angle::ColorGeneric &borderColorGeneric,
                                   const angle::Format &format,
                                   bool stencilMode)
{
    gl::ColorGeneric adjustedBorderColor = borderColorGeneric;

    // Handle depth formats
    if (format.hasDepthOrStencilBits())
    {
        if (stencilMode)
        {
            // Stencil component
            adjustedBorderColor.colorUI.red = gl::clampForBitCount<unsigned int>(
                borderColorGeneric.colorUI.red, format.stencilBits);
        }
        else
        {
            // Depth component
            if (format.isUnorm())
            {
                adjustedBorderColor.colorF.red = gl::clamp01(borderColorGeneric.colorF.red);
            }
        }

        return adjustedBorderColor;
    }

    // Handle LUMA formats
    if (format.isLUMA() && format.alphaBits > 0)
    {
        if (format.luminanceBits > 0)
        {
            adjustedBorderColor.colorF.green = borderColorGeneric.colorF.alpha;
        }
        else
        {
            adjustedBorderColor.colorF.red = borderColorGeneric.colorF.alpha;
        }

        return adjustedBorderColor;
    }

    // Handle all other formats
    if (format.isSint())
    {
        adjustedBorderColor.colorI.red =
            gl::clampForBitCount<int>(borderColorGeneric.colorI.red, format.redBits);
        adjustedBorderColor.colorI.green =
            gl::clampForBitCount<int>(borderColorGeneric.colorI.green, format.greenBits);
        adjustedBorderColor.colorI.blue =
            gl::clampForBitCount<int>(borderColorGeneric.colorI.blue, format.blueBits);
        adjustedBorderColor.colorI.alpha =
            gl::clampForBitCount<int>(borderColorGeneric.colorI.alpha, format.alphaBits);
    }
    else if (format.isUint())
    {
        adjustedBorderColor.colorUI.red =
            gl::clampForBitCount<unsigned int>(borderColorGeneric.colorUI.red, format.redBits);
        adjustedBorderColor.colorUI.green =
            gl::clampForBitCount<unsigned int>(borderColorGeneric.colorUI.green, format.greenBits);
        adjustedBorderColor.colorUI.blue =
            gl::clampForBitCount<unsigned int>(borderColorGeneric.colorUI.blue, format.blueBits);
        adjustedBorderColor.colorUI.alpha =
            gl::clampForBitCount<unsigned int>(borderColorGeneric.colorUI.alpha, format.alphaBits);
    }
    else if (format.isSnorm())
    {
        // clamp between -1.0f and 1.0f
        adjustedBorderColor.colorF.red   = gl::clamp(borderColorGeneric.colorF.red, -1.0f, 1.0f);
        adjustedBorderColor.colorF.green = gl::clamp(borderColorGeneric.colorF.green, -1.0f, 1.0f);
        adjustedBorderColor.colorF.blue  = gl::clamp(borderColorGeneric.colorF.blue, -1.0f, 1.0f);
        adjustedBorderColor.colorF.alpha = gl::clamp(borderColorGeneric.colorF.alpha, -1.0f, 1.0f);
    }
    else if (format.isUnorm())
    {
        // clamp between 0.0f and 1.0f
        adjustedBorderColor.colorF.red   = gl::clamp01(borderColorGeneric.colorF.red);
        adjustedBorderColor.colorF.green = gl::clamp01(borderColorGeneric.colorF.green);
        adjustedBorderColor.colorF.blue  = gl::clamp01(borderColorGeneric.colorF.blue);
        adjustedBorderColor.colorF.alpha = gl::clamp01(borderColorGeneric.colorF.alpha);
    }

    return adjustedBorderColor;
}

// Utility for setting a value on a packed 4-bit integer array.
template <typename SrcT>
void Int4Array_Set(uint8_t *arrayBytes, uint32_t arrayIndex, SrcT value)
{
    uint32_t byteIndex = arrayIndex >> 1;
    ASSERT(value < 16);

    if ((arrayIndex & 1) == 0)
    {
        arrayBytes[byteIndex] &= 0xF0;
        arrayBytes[byteIndex] |= static_cast<uint8_t>(value);
    }
    else
    {
        arrayBytes[byteIndex] &= 0x0F;
        arrayBytes[byteIndex] |= static_cast<uint8_t>(value) << 4;
    }
}

// Utility for getting a value from a packed 4-bit integer array.
template <typename DestT>
DestT Int4Array_Get(const uint8_t *arrayBytes, uint32_t arrayIndex)
{
    uint32_t byteIndex = arrayIndex >> 1;

    if ((arrayIndex & 1) == 0)
    {
        return static_cast<DestT>(arrayBytes[byteIndex] & 0xF);
    }
    else
    {
        return static_cast<DestT>(arrayBytes[byteIndex] >> 4);
    }
}

// When converting a byte number to a transition bit index we can shift instead of divide.
constexpr size_t kTransitionByteShift = Log2(kGraphicsPipelineDirtyBitBytes);

// When converting a number of bits offset to a transition bit index we can also shift.
constexpr size_t kBitsPerByte        = 8;
constexpr size_t kTransitionBitShift = kTransitionByteShift + Log2(kBitsPerByte);

// Helper macro to map from a PipelineDesc struct and field to a dirty bit index.
// Uses the 'offsetof' macro to compute the offset 'Member' within the PipelineDesc
// and the offset of 'Field' within 'Member'. We can optimize the dirty bit setting by computing
// the shifted dirty bit at compile time instead of calling "set".
#define ANGLE_GET_TRANSITION_BIT(Member, Field)                                      \
    ((offsetof(GraphicsPipelineDesc, Member) + offsetof(decltype(Member), Field)) >> \
     kTransitionByteShift)

// Indexed dirty bits cannot be entirely computed at compile time since the index is passed to
// the update function.
#define ANGLE_GET_INDEXED_TRANSITION_BIT(Member, Field, Index, BitWidth) \
    (((BitWidth * Index) >> kTransitionBitShift) + ANGLE_GET_TRANSITION_BIT(Member, Field))

constexpr angle::PackedEnumMap<gl::ComponentType, VkFormat> kMismatchedComponentTypeMap = {{
    {gl::ComponentType::Float, VK_FORMAT_R32G32B32A32_SFLOAT},
    {gl::ComponentType::Int, VK_FORMAT_R32G32B32A32_SINT},
    {gl::ComponentType::UnsignedInt, VK_FORMAT_R32G32B32A32_UINT},
}};

constexpr char kDescriptorTypeNameMap[][30] = {"sampler",
                                               "combined image sampler",
                                               "sampled image",
                                               "storage image",
                                               "uniform texel buffer",
                                               "storage texel buffer",
                                               "uniform buffer",
                                               "storage buffer",
                                               "uniform buffer dynamic",
                                               "storage buffer dynamic",
                                               "input attachment"};

void DumpPipelineCacheGraph(
    std::ostream &out,
    const std::unordered_map<vk::GraphicsPipelineDesc, vk::PipelineHelper> &cache)
{
    static std::atomic<uint32_t> sCacheSerial(0);
    angle::HashMap<vk::GraphicsPipelineDesc, uint32_t> descToId;

    uint32_t cacheSerial = sCacheSerial.fetch_add(1);
    uint32_t descId      = 0;

    out << " subgraph cluster_" << cacheSerial << "{\n";
    for (const auto &descAndPipeline : cache)
    {
        const vk::GraphicsPipelineDesc &desc = descAndPipeline.first;

        out << "  p" << cacheSerial << "_" << descId << ";\n";

        descToId[desc] = descId++;
    }
    for (const auto &descAndPipeline : cache)
    {
        const vk::GraphicsPipelineDesc &desc     = descAndPipeline.first;
        const vk::PipelineHelper &pipelineHelper = descAndPipeline.second;
        const std::vector<GraphicsPipelineTransition> &transitions =
            pipelineHelper.getTransitions();

        for (const GraphicsPipelineTransition &transition : transitions)
        {
#if defined(ANGLE_IS_64_BIT_CPU)
            const uint64_t transitionBits = transition.bits.bits();
#else
            const uint64_t transitionBits =
                static_cast<uint64_t>(transition.bits.bits(1)) << 32 | transition.bits.bits(0);
#endif
            out << "  p" << cacheSerial << "_" << descToId[desc] << " -> p" << cacheSerial << "_"
                << descToId[*transition.desc] << " [label=\"'0x" << std::hex << transitionBits
                << "'\"];\n";
        }
    }
    out << " }\n";
}
}  // anonymous namespace

// RenderPassDesc implementation.
RenderPassDesc::RenderPassDesc()
{
    memset(this, 0, sizeof(RenderPassDesc));
}

RenderPassDesc::~RenderPassDesc() = default;

RenderPassDesc::RenderPassDesc(const RenderPassDesc &other)
{
    memcpy(this, &other, sizeof(RenderPassDesc));
}

void RenderPassDesc::packColorAttachment(size_t colorIndexGL, angle::FormatID formatID)
{
    ASSERT(colorIndexGL < mAttachmentFormats.size());
    static_assert(angle::kNumANGLEFormats < std::numeric_limits<uint8_t>::max(),
                  "Too many ANGLE formats to fit in uint8_t");
    // Force the user to pack the depth/stencil attachment last.
    ASSERT(!hasDepthStencilAttachment());
    // This function should only be called for enabled GL color attachments.
    ASSERT(formatID != angle::FormatID::NONE);

    uint8_t &packedFormat = mAttachmentFormats[colorIndexGL];
    SetBitField(packedFormat, formatID);

    // Set color attachment range such that it covers the range from index 0 through last active
    // index.  This is the reasons why we need depth/stencil to be packed last.
    SetBitField(mColorAttachmentRange, std::max<size_t>(mColorAttachmentRange, colorIndexGL + 1));
}

void RenderPassDesc::packColorAttachmentGap(size_t colorIndexGL)
{
    ASSERT(colorIndexGL < mAttachmentFormats.size());
    static_assert(angle::kNumANGLEFormats < std::numeric_limits<uint8_t>::max(),
                  "Too many ANGLE formats to fit in uint8_t");
    // Force the user to pack the depth/stencil attachment last.
    ASSERT(!hasDepthStencilAttachment());

    // Use NONE as a flag for gaps in GL color attachments.
    uint8_t &packedFormat = mAttachmentFormats[colorIndexGL];
    SetBitField(packedFormat, angle::FormatID::NONE);
}

void RenderPassDesc::packDepthStencilAttachment(angle::FormatID formatID)
{
    ASSERT(!hasDepthStencilAttachment());

    size_t index = depthStencilAttachmentIndex();
    ASSERT(index < mAttachmentFormats.size());

    uint8_t &packedFormat = mAttachmentFormats[index];
    SetBitField(packedFormat, formatID);
}

void RenderPassDesc::packColorResolveAttachment(size_t colorIndexGL)
{
    ASSERT(isColorAttachmentEnabled(colorIndexGL));
    ASSERT(!mColorResolveAttachmentMask.test(colorIndexGL));
    ASSERT(mSamples > 1);
    mColorResolveAttachmentMask.set(colorIndexGL);
}

void RenderPassDesc::removeColorResolveAttachment(size_t colorIndexGL)
{
    ASSERT(mColorResolveAttachmentMask.test(colorIndexGL));
    mColorResolveAttachmentMask.reset(colorIndexGL);
}

void RenderPassDesc::packColorUnresolveAttachment(size_t colorIndexGL)
{
    mColorUnresolveAttachmentMask.set(colorIndexGL);
}

void RenderPassDesc::removeColorUnresolveAttachment(size_t colorIndexGL)
{
    mColorUnresolveAttachmentMask.reset(colorIndexGL);
}

void RenderPassDesc::packDepthStencilResolveAttachment()
{
    ASSERT(hasDepthStencilAttachment());
    ASSERT(!hasDepthStencilResolveAttachment());

    mResolveDepthStencil = true;
}

void RenderPassDesc::packDepthStencilUnresolveAttachment(bool unresolveDepth, bool unresolveStencil)
{
    ASSERT(hasDepthStencilAttachment());

    mUnresolveDepth   = unresolveDepth;
    mUnresolveStencil = unresolveStencil;
}

void RenderPassDesc::removeDepthStencilUnresolveAttachment()
{
    mUnresolveDepth   = false;
    mUnresolveStencil = false;
}

RenderPassDesc &RenderPassDesc::operator=(const RenderPassDesc &other)
{
    memcpy(this, &other, sizeof(RenderPassDesc));
    return *this;
}

void RenderPassDesc::setWriteControlMode(gl::SrgbWriteControlMode mode)
{
    SetBitField(mSrgbWriteControl, mode);
}

size_t RenderPassDesc::hash() const
{
    return angle::ComputeGenericHash(*this);
}

bool RenderPassDesc::isColorAttachmentEnabled(size_t colorIndexGL) const
{
    angle::FormatID formatID = operator[](colorIndexGL);
    return formatID != angle::FormatID::NONE;
}

bool RenderPassDesc::hasDepthStencilAttachment() const
{
    angle::FormatID formatID = operator[](depthStencilAttachmentIndex());
    return formatID != angle::FormatID::NONE;
}

size_t RenderPassDesc::attachmentCount() const
{
    size_t colorAttachmentCount = 0;
    for (size_t i = 0; i < mColorAttachmentRange; ++i)
    {
        colorAttachmentCount += isColorAttachmentEnabled(i);
    }

    // Note that there are no gaps in depth/stencil attachments.  In fact there is a maximum of 1 of
    // it + 1 for its resolve attachment.
    size_t depthStencilCount        = hasDepthStencilAttachment() ? 1 : 0;
    size_t depthStencilResolveCount = hasDepthStencilResolveAttachment() ? 1 : 0;
    return colorAttachmentCount + mColorResolveAttachmentMask.count() + depthStencilCount +
           depthStencilResolveCount;
}

bool operator==(const RenderPassDesc &lhs, const RenderPassDesc &rhs)
{
    return (memcmp(&lhs, &rhs, sizeof(RenderPassDesc)) == 0);
}

// GraphicsPipelineDesc implementation.
// Use aligned allocation and free so we can use the alignas keyword.
void *GraphicsPipelineDesc::operator new(std::size_t size)
{
    return angle::AlignedAlloc(size, 32);
}

void GraphicsPipelineDesc::operator delete(void *ptr)
{
    return angle::AlignedFree(ptr);
}

GraphicsPipelineDesc::GraphicsPipelineDesc()
{
    memset(this, 0, sizeof(GraphicsPipelineDesc));
}

GraphicsPipelineDesc::~GraphicsPipelineDesc() = default;

GraphicsPipelineDesc::GraphicsPipelineDesc(const GraphicsPipelineDesc &other)
{
    memcpy(this, &other, sizeof(GraphicsPipelineDesc));
}

GraphicsPipelineDesc &GraphicsPipelineDesc::operator=(const GraphicsPipelineDesc &other)
{
    memcpy(this, &other, sizeof(GraphicsPipelineDesc));
    return *this;
}

size_t GraphicsPipelineDesc::hash() const
{
    size_t keySize = sizeof(*this);
    if (mDynamicState.ds1And2.supportsDynamicState1)
    {
        keySize -= kPackedDynamicState1Size;

        if (mDynamicState.ds1And2.supportsDynamicState2)
        {
            keySize -= kPackedDynamicState1And2Size;
        }
    }
    return angle::ComputeGenericHash(this, keySize);
}

bool GraphicsPipelineDesc::operator==(const GraphicsPipelineDesc &other) const
{
    return (memcmp(this, &other, sizeof(GraphicsPipelineDesc)) == 0);
}

// TODO(jmadill): We should prefer using Packed GLenums. http://anglebug.com/2169

// Initialize PSO states, it is consistent with initial value of gl::State
void GraphicsPipelineDesc::initDefaults(const ContextVk *contextVk)
{
    // Set all vertex input attributes to default, the default format is Float
    angle::FormatID defaultFormat = GetCurrentValueFormatID(gl::VertexAttribType::Float);
    for (PackedAttribDesc &packedAttrib : mVertexInputAttribs.attribs)
    {
        SetBitField(packedAttrib.divisor, 0);
        SetBitField(packedAttrib.format, defaultFormat);
        SetBitField(packedAttrib.compressed, 0);
        SetBitField(packedAttrib.offset, 0);
    }

    mInputAssemblyAndRasterizationStateInfo.bits.subpass = 0;
    mInputAssemblyAndRasterizationStateInfo.bits.depthClampEnable =
        contextVk->getFeatures().depthClamping.enabled ? VK_TRUE : VK_FALSE;
    mInputAssemblyAndRasterizationStateInfo.bits.rasterizationSamples  = 1;
    mInputAssemblyAndRasterizationStateInfo.bits.sampleShadingEnable   = 0;
    mInputAssemblyAndRasterizationStateInfo.bits.alphaToCoverageEnable = 0;
    mInputAssemblyAndRasterizationStateInfo.bits.alphaToOneEnable      = 0;
    mInputAssemblyAndRasterizationStateInfo.bits.logicOpEnable         = 0;
    SetBitField(mInputAssemblyAndRasterizationStateInfo.bits.logicOp, VK_LOGIC_OP_CLEAR);

    mInputAssemblyAndRasterizationStateInfo.sampleMask = std::numeric_limits<uint16_t>::max();

    SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.topology,
                VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
    SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.patchVertices, 3);
    mInputAssemblyAndRasterizationStateInfo.misc.surfaceRotation = 0;
    mInputAssemblyAndRasterizationStateInfo.misc.viewportNegativeOneToOne =
        contextVk->getFeatures().supportsDepthClipControl.enabled;
    mInputAssemblyAndRasterizationStateInfo.misc.depthBoundsTest  = 0;
    mInputAssemblyAndRasterizationStateInfo.misc.minSampleShading = kMinSampleShadingScale;
    mInputAssemblyAndRasterizationStateInfo.misc.blendEnableMask  = 0;

    VkFlags allColorBits = (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
                            VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);

    for (uint32_t colorIndexGL = 0; colorIndexGL < gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
         ++colorIndexGL)
    {
        Int4Array_Set(mColorBlendStateInfo.colorWriteMaskBits, colorIndexGL, allColorBits);
    }

    PackedColorBlendAttachmentState blendAttachmentState;
    SetBitField(blendAttachmentState.srcColorBlendFactor, VK_BLEND_FACTOR_ONE);
    SetBitField(blendAttachmentState.dstColorBlendFactor, VK_BLEND_FACTOR_ZERO);
    SetBitField(blendAttachmentState.colorBlendOp, VK_BLEND_OP_ADD);
    SetBitField(blendAttachmentState.srcAlphaBlendFactor, VK_BLEND_FACTOR_ONE);
    SetBitField(blendAttachmentState.dstAlphaBlendFactor, VK_BLEND_FACTOR_ZERO);
    SetBitField(blendAttachmentState.alphaBlendOp, VK_BLEND_OP_ADD);

    std::fill(&mColorBlendStateInfo.attachments[0],
              &mColorBlendStateInfo.attachments[gl::IMPLEMENTATION_MAX_DRAW_BUFFERS],
              blendAttachmentState);

    mDither.emulatedDitherControl = 0;
    mDither.unused                = 0;

    SetBitField(mDynamicState.ds1And2.cullMode, VK_CULL_MODE_NONE);
    SetBitField(mDynamicState.ds1And2.frontFace, VK_FRONT_FACE_COUNTER_CLOCKWISE);
    SetBitField(mDynamicState.ds1And2.depthCompareOp, VK_COMPARE_OP_LESS);
    mDynamicState.ds1And2.depthTest   = 0;
    mDynamicState.ds1And2.depthWrite  = 0;
    mDynamicState.ds1And2.stencilTest = 0;

    mDynamicState.ds1And2.rasterizerDiscardEnable = 0;
    mDynamicState.ds1And2.depthBiasEnable         = 0;
    mDynamicState.ds1And2.restartEnable           = 0;

    mDynamicState.ds1And2.supportsDynamicState1 =
        contextVk->getFeatures().supportsExtendedDynamicState.enabled;
    mDynamicState.ds1And2.supportsDynamicState2 =
        contextVk->getFeatures().supportsExtendedDynamicState2.enabled;
    mDynamicState.ds1And2.padding = 0;

    SetBitField(mDynamicState.ds1.front.ops.fail, VK_STENCIL_OP_KEEP);
    SetBitField(mDynamicState.ds1.front.ops.pass, VK_STENCIL_OP_KEEP);
    SetBitField(mDynamicState.ds1.front.ops.depthFail, VK_STENCIL_OP_KEEP);
    SetBitField(mDynamicState.ds1.front.ops.compare, VK_COMPARE_OP_ALWAYS);
    SetBitField(mDynamicState.ds1.back.ops.fail, VK_STENCIL_OP_KEEP);
    SetBitField(mDynamicState.ds1.back.ops.pass, VK_STENCIL_OP_KEEP);
    SetBitField(mDynamicState.ds1.back.ops.depthFail, VK_STENCIL_OP_KEEP);
    SetBitField(mDynamicState.ds1.back.ops.compare, VK_COMPARE_OP_ALWAYS);
    memset(mDynamicState.ds1.vertexStrides, 0, sizeof(mDynamicState.ds1.vertexStrides));
}

angle::Result GraphicsPipelineDesc::initializePipeline(
    ContextVk *contextVk,
    const PipelineCache &pipelineCacheVk,
    const RenderPass &compatibleRenderPass,
    const PipelineLayout &pipelineLayout,
    const gl::AttributesMask &activeAttribLocationsMask,
    const gl::ComponentTypeMask &programAttribsTypeMask,
    const gl::DrawBufferMask &missingOutputsMask,
    const ShaderAndSerialMap &shaders,
    const SpecializationConstants &specConsts,
    Pipeline *pipelineOut) const
{
    angle::FixedVector<VkPipelineShaderStageCreateInfo, 5> shaderStages;
    VkPipelineVertexInputStateCreateInfo vertexInputState     = {};
    VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {};
    VkPipelineViewportStateCreateInfo viewportState           = {};
    VkPipelineRasterizationStateCreateInfo rasterState        = {};
    VkPipelineMultisampleStateCreateInfo multisampleState     = {};
    VkPipelineDepthStencilStateCreateInfo depthStencilState   = {};
    gl::DrawBuffersArray<VkPipelineColorBlendAttachmentState> blendAttachmentState;
    VkPipelineTessellationStateCreateInfo tessellationState             = {};
    VkPipelineTessellationDomainOriginStateCreateInfo domainOriginState = {};
    VkPipelineColorBlendStateCreateInfo blendState                      = {};
    VkSpecializationInfo specializationInfo                             = {};
    VkGraphicsPipelineCreateInfo createInfo                             = {};

    SpecializationConstantMap<VkSpecializationMapEntry> specializationEntries;
    InitializeSpecializationInfo(specConsts, &specializationEntries, &specializationInfo);

    // Vertex shader is always expected to be present.
    const ShaderModule &vertexModule = shaders[gl::ShaderType::Vertex].get().get();
    ASSERT(vertexModule.valid());
    VkPipelineShaderStageCreateInfo vertexStage = {};
    SetPipelineShaderStageInfo(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                               VK_SHADER_STAGE_VERTEX_BIT, vertexModule.getHandle(),
                               specializationInfo, &vertexStage);
    shaderStages.push_back(vertexStage);

    const ShaderAndSerialPointer &tessControlPointer = shaders[gl::ShaderType::TessControl];
    if (tessControlPointer.valid())
    {
        const ShaderModule &tessControlModule            = tessControlPointer.get().get();
        VkPipelineShaderStageCreateInfo tessControlStage = {};
        SetPipelineShaderStageInfo(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                                   VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
                                   tessControlModule.getHandle(), specializationInfo,
                                   &tessControlStage);
        shaderStages.push_back(tessControlStage);
    }

    const ShaderAndSerialPointer &tessEvaluationPointer = shaders[gl::ShaderType::TessEvaluation];
    if (tessEvaluationPointer.valid())
    {
        const ShaderModule &tessEvaluationModule            = tessEvaluationPointer.get().get();
        VkPipelineShaderStageCreateInfo tessEvaluationStage = {};
        SetPipelineShaderStageInfo(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                                   VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
                                   tessEvaluationModule.getHandle(), specializationInfo,
                                   &tessEvaluationStage);
        shaderStages.push_back(tessEvaluationStage);
    }

    const ShaderAndSerialPointer &geometryPointer = shaders[gl::ShaderType::Geometry];
    if (geometryPointer.valid())
    {
        const ShaderModule &geometryModule            = geometryPointer.get().get();
        VkPipelineShaderStageCreateInfo geometryStage = {};
        SetPipelineShaderStageInfo(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                                   VK_SHADER_STAGE_GEOMETRY_BIT, geometryModule.getHandle(),
                                   specializationInfo, &geometryStage);
        shaderStages.push_back(geometryStage);
    }

    // Fragment shader is optional.
    // anglebug.com/3509 - Don't compile the fragment shader if rasterizerDiscardEnable = true
    const ShaderAndSerialPointer &fragmentPointer = shaders[gl::ShaderType::Fragment];
    if (fragmentPointer.valid() && !mDynamicState.ds1And2.rasterizerDiscardEnable)
    {
        const ShaderModule &fragmentModule            = fragmentPointer.get().get();
        VkPipelineShaderStageCreateInfo fragmentStage = {};
        SetPipelineShaderStageInfo(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                                   VK_SHADER_STAGE_FRAGMENT_BIT, fragmentModule.getHandle(),
                                   specializationInfo, &fragmentStage);
        shaderStages.push_back(fragmentStage);
    }

    // TODO(jmadill): Possibly use different path for ES 3.1 split bindings/attribs.
    gl::AttribArray<VkVertexInputBindingDescription> bindingDescs;
    gl::AttribArray<VkVertexInputAttributeDescription> attributeDescs;

    uint32_t vertexAttribCount = 0;

    size_t unpackedSize = sizeof(shaderStages) + sizeof(vertexInputState) +
                          sizeof(inputAssemblyState) + sizeof(viewportState) + sizeof(rasterState) +
                          sizeof(multisampleState) + sizeof(depthStencilState) +
                          sizeof(tessellationState) + sizeof(blendAttachmentState) +
                          sizeof(blendState) + sizeof(bindingDescs) + sizeof(attributeDescs);
    ANGLE_UNUSED_VARIABLE(unpackedSize);

    gl::AttribArray<VkVertexInputBindingDivisorDescriptionEXT> divisorDesc;
    VkPipelineVertexInputDivisorStateCreateInfoEXT divisorState = {};
    divisorState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT;
    divisorState.pVertexBindingDivisors = divisorDesc.data();
    for (size_t attribIndexSizeT : activeAttribLocationsMask)
    {
        const uint32_t attribIndex = static_cast<uint32_t>(attribIndexSizeT);

        VkVertexInputBindingDescription &bindingDesc  = bindingDescs[vertexAttribCount];
        VkVertexInputAttributeDescription &attribDesc = attributeDescs[vertexAttribCount];
        const PackedAttribDesc &packedAttrib          = mVertexInputAttribs.attribs[attribIndex];

        bindingDesc.binding = attribIndex;
        bindingDesc.stride  = static_cast<uint32_t>(mDynamicState.ds1.vertexStrides[attribIndex]);
        if (packedAttrib.divisor != 0)
        {
            bindingDesc.inputRate = static_cast<VkVertexInputRate>(VK_VERTEX_INPUT_RATE_INSTANCE);
            divisorDesc[divisorState.vertexBindingDivisorCount].binding = bindingDesc.binding;
            divisorDesc[divisorState.vertexBindingDivisorCount].divisor = packedAttrib.divisor;
            ++divisorState.vertexBindingDivisorCount;
        }
        else
        {
            bindingDesc.inputRate = static_cast<VkVertexInputRate>(VK_VERTEX_INPUT_RATE_VERTEX);
        }

        // Get the corresponding VkFormat for the attrib's format.
        angle::FormatID formatID            = static_cast<angle::FormatID>(packedAttrib.format);
        const Format &format                = contextVk->getRenderer()->getFormat(formatID);
        const angle::Format &intendedFormat = format.getIntendedFormat();
        VkFormat vkFormat = format.getActualBufferVkFormat(packedAttrib.compressed);

        const gl::ComponentType attribType = GetVertexAttributeComponentType(
            intendedFormat.isPureInt(), intendedFormat.vertexAttribType);
        const gl::ComponentType programAttribType =
            gl::GetComponentTypeMask(programAttribsTypeMask, attribIndex);

        // This forces stride to 0 when glVertexAttribPointer specifies a different type from the
        // program's attribute type except when the type mismatch is a mismatched integer sign.
        if (bindingDesc.stride > 0 && attribType != programAttribType)
        {
            if (attribType == gl::ComponentType::Float ||
                programAttribType == gl::ComponentType::Float)
            {
                // When dealing with float to int or unsigned int or vice versa, just override the
                // format with a compatible one.
                vkFormat = kMismatchedComponentTypeMap[programAttribType];
            }
            else
            {
                // When converting from an unsigned to a signed format or vice versa, attempt to
                // match the bit width.
                angle::FormatID convertedFormatID = gl::ConvertFormatSignedness(intendedFormat);
                const Format &convertedFormat =
                    contextVk->getRenderer()->getFormat(convertedFormatID);
                ASSERT(intendedFormat.channelCount ==
                       convertedFormat.getIntendedFormat().channelCount);
                ASSERT(intendedFormat.redBits == convertedFormat.getIntendedFormat().redBits);
                ASSERT(intendedFormat.greenBits == convertedFormat.getIntendedFormat().greenBits);
                ASSERT(intendedFormat.blueBits == convertedFormat.getIntendedFormat().blueBits);
                ASSERT(intendedFormat.alphaBits == convertedFormat.getIntendedFormat().alphaBits);

                vkFormat = convertedFormat.getActualBufferVkFormat(packedAttrib.compressed);
            }

            ASSERT(contextVk->getNativeExtensions().relaxedVertexAttributeTypeANGLE);
            // If using dynamic state for stride, the value for stride is unconditionally 0 here.
            // |ContextVk::handleDirtyGraphicsVertexBuffers| implements the same fix when setting
            // stride dynamically.
            ASSERT(!contextVk->getFeatures().supportsExtendedDynamicState.enabled ||
                   bindingDesc.stride == 0);

            if (programAttribType == gl::ComponentType::Float ||
                attribType == gl::ComponentType::Float)
            {
                bindingDesc.stride = 0;  // Prevent out-of-bounds accesses.
            }
        }

        attribDesc.binding  = attribIndex;
        attribDesc.format   = vkFormat;
        attribDesc.location = static_cast<uint32_t>(attribIndex);
        attribDesc.offset   = packedAttrib.offset;

        vertexAttribCount++;
    }

    // The binding descriptions are filled in at draw time.
    vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    vertexInputState.flags = 0;
    vertexInputState.vertexBindingDescriptionCount   = vertexAttribCount;
    vertexInputState.pVertexBindingDescriptions      = bindingDescs.data();
    vertexInputState.vertexAttributeDescriptionCount = vertexAttribCount;
    vertexInputState.pVertexAttributeDescriptions    = attributeDescs.data();
    if (divisorState.vertexBindingDivisorCount)
    {
        vertexInputState.pNext = &divisorState;
    }

    const PackedInputAssemblyAndRasterizationStateInfo &inputAndRaster =
        mInputAssemblyAndRasterizationStateInfo;

    // Primitive topology is filled in at draw time.
    inputAssemblyState.sType    = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
    inputAssemblyState.flags    = 0;
    inputAssemblyState.topology = static_cast<VkPrimitiveTopology>(inputAndRaster.misc.topology);
    // http://anglebug.com/3832
    // We currently hit a VK Validation here where VUID
    // VUID-VkPipelineInputAssemblyStateCreateInfo-topology-00428 is flagged because we allow
    // primitiveRestartEnable to be true for topologies VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
    // VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
    // VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,
    // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY and VK_PRIMITIVE_TOPOLOGY_PATCH_LIST
    // However if we force primiteRestartEnable to FALSE we fail tests.
    // Need to identify alternate fix.
    inputAssemblyState.primitiveRestartEnable =
        static_cast<VkBool32>(mDynamicState.ds1And2.restartEnable);

    // Set initial viewport and scissor state.
    viewportState.sType         = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewportState.flags         = 0;
    viewportState.viewportCount = 1;
    viewportState.pViewports    = nullptr;
    viewportState.scissorCount  = 1;
    viewportState.pScissors     = nullptr;

    VkPipelineViewportDepthClipControlCreateInfoEXT depthClipControl = {};
    if (contextVk->getFeatures().supportsDepthClipControl.enabled)
    {
        depthClipControl.sType =
            VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_DEPTH_CLIP_CONTROL_CREATE_INFO_EXT;
        depthClipControl.negativeOneToOne =
            static_cast<VkBool32>(inputAndRaster.misc.viewportNegativeOneToOne);

        viewportState.pNext = &depthClipControl;
    }

    // Rasterizer state.
    rasterState.sType            = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rasterState.flags            = 0;
    rasterState.depthClampEnable = static_cast<VkBool32>(inputAndRaster.bits.depthClampEnable);
    rasterState.rasterizerDiscardEnable =
        static_cast<VkBool32>(mDynamicState.ds1And2.rasterizerDiscardEnable);
    rasterState.polygonMode     = VK_POLYGON_MODE_FILL;
    rasterState.cullMode        = static_cast<VkCullModeFlags>(mDynamicState.ds1And2.cullMode);
    rasterState.frontFace       = static_cast<VkFrontFace>(mDynamicState.ds1And2.frontFace);
    rasterState.depthBiasEnable = static_cast<VkBool32>(mDynamicState.ds1And2.depthBiasEnable);
    rasterState.lineWidth       = 0;
    const void **pNextPtr       = &rasterState.pNext;

    VkPipelineRasterizationLineStateCreateInfoEXT rasterLineState = {};
    rasterLineState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT;
    // Enable Bresenham line rasterization if available and the following conditions are met:
    // 1.) not multisampling
    // 2.) VUID-VkGraphicsPipelineCreateInfo-lineRasterizationMode-02766:
    // The Vulkan spec states: If the lineRasterizationMode member of a
    // VkPipelineRasterizationLineStateCreateInfoEXT structure included in the pNext chain of
    // pRasterizationState is VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT or
    // VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT and if rasterization is enabled, then the
    // alphaToCoverageEnable, alphaToOneEnable, and sampleShadingEnable members of pMultisampleState
    // must all be VK_FALSE.
    if (inputAndRaster.bits.rasterizationSamples <= 1 &&
        !mDynamicState.ds1And2.rasterizerDiscardEnable &&
        !inputAndRaster.bits.alphaToCoverageEnable && !inputAndRaster.bits.alphaToOneEnable &&
        !inputAndRaster.bits.sampleShadingEnable &&
        contextVk->getFeatures().bresenhamLineRasterization.enabled)
    {
        rasterLineState.lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT;
        *pNextPtr                             = &rasterLineState;
        pNextPtr                              = &rasterLineState.pNext;
    }

    VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provokingVertexState = {};
    provokingVertexState.sType =
        VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT;
    // Always set provoking vertex mode to last if available.
    if (contextVk->getFeatures().provokingVertex.enabled)
    {
        provokingVertexState.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT;
        *pNextPtr                                = &provokingVertexState;
        pNextPtr                                 = &provokingVertexState.pNext;
    }

    // When depth clamping is used, depth clipping is automatically disabled.
    // When the 'depthClamping' feature is enabled, we'll be using depth clamping
    // to work around a driver issue, not as an alternative to depth clipping. Therefore we need to
    // explicitly re-enable depth clipping.
    VkPipelineRasterizationDepthClipStateCreateInfoEXT depthClipState = {};
    depthClipState.sType =
        VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT;
    if (contextVk->getFeatures().depthClamping.enabled)
    {
        depthClipState.depthClipEnable = VK_TRUE;
        *pNextPtr                      = &depthClipState;
        pNextPtr                       = &depthClipState.pNext;
    }

    VkPipelineRasterizationStateStreamCreateInfoEXT rasterStreamState = {};
    rasterStreamState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_STREAM_CREATE_INFO_EXT;
    if (contextVk->getFeatures().supportsGeometryStreamsCapability.enabled)
    {
        rasterStreamState.rasterizationStream = 0;
        *pNextPtr                             = &rasterStreamState;
        pNextPtr                              = &rasterStreamState.pNext;
    }

    uint32_t sampleMask = inputAndRaster.sampleMask;

    // Multisample state.
    multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    multisampleState.flags = 0;
    multisampleState.rasterizationSamples =
        gl_vk::GetSamples(inputAndRaster.bits.rasterizationSamples);
    multisampleState.sampleShadingEnable =
        static_cast<VkBool32>(inputAndRaster.bits.sampleShadingEnable);
    multisampleState.minSampleShading =
        static_cast<float>(inputAndRaster.misc.minSampleShading) / kMinSampleShadingScale;
    multisampleState.pSampleMask = &sampleMask;
    multisampleState.alphaToCoverageEnable =
        static_cast<VkBool32>(inputAndRaster.bits.alphaToCoverageEnable);
    multisampleState.alphaToOneEnable = static_cast<VkBool32>(inputAndRaster.bits.alphaToOneEnable);

    // Depth/stencil state.
    depthStencilState.sType            = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
    depthStencilState.flags            = 0;
    depthStencilState.depthTestEnable  = static_cast<VkBool32>(mDynamicState.ds1And2.depthTest);
    depthStencilState.depthWriteEnable = static_cast<VkBool32>(mDynamicState.ds1And2.depthWrite);
    depthStencilState.depthCompareOp =
        static_cast<VkCompareOp>(mDynamicState.ds1And2.depthCompareOp);
    depthStencilState.depthBoundsTestEnable =
        static_cast<VkBool32>(inputAndRaster.misc.depthBoundsTest);
    depthStencilState.stencilTestEnable = static_cast<VkBool32>(mDynamicState.ds1And2.stencilTest);
    UnpackStencilState(mDynamicState.ds1.front, &depthStencilState.front);
    UnpackStencilState(mDynamicState.ds1.back, &depthStencilState.back);
    depthStencilState.minDepthBounds = 0;
    depthStencilState.maxDepthBounds = 0;

    blendState.sType           = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    blendState.flags           = 0;
    blendState.logicOpEnable   = static_cast<VkBool32>(inputAndRaster.bits.logicOpEnable);
    blendState.logicOp         = static_cast<VkLogicOp>(inputAndRaster.bits.logicOp);
    blendState.attachmentCount = static_cast<uint32_t>(mRenderPassDesc.colorAttachmentRange());
    blendState.pAttachments    = blendAttachmentState.data();

    // If this graphics pipeline is for the unresolve operation, correct the color attachment count
    // for that subpass.
    if ((mRenderPassDesc.getColorUnresolveAttachmentMask().any() ||
         mRenderPassDesc.hasDepthStencilUnresolveAttachment()) &&
        mInputAssemblyAndRasterizationStateInfo.bits.subpass == 0)
    {
        blendState.attachmentCount =
            static_cast<uint32_t>(mRenderPassDesc.getColorUnresolveAttachmentMask().count());
    }

    const gl::DrawBufferMask blendEnableMask(inputAndRaster.misc.blendEnableMask);

    // Zero-init all states.
    blendAttachmentState = {};

    const PackedColorBlendStateInfo &colorBlend = mColorBlendStateInfo;

    for (uint32_t colorIndexGL = 0; colorIndexGL < blendState.attachmentCount; ++colorIndexGL)
    {
        VkPipelineColorBlendAttachmentState &state = blendAttachmentState[colorIndexGL];

        if (blendEnableMask[colorIndexGL])
        {
            // To avoid triggering valid usage error, blending must be disabled for formats that do
            // not have VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT feature bit set.
            // From OpenGL ES clients, this means disabling blending for integer formats.
            if (!angle::Format::Get(mRenderPassDesc[colorIndexGL]).isInt())
            {
                ASSERT(!contextVk->getRenderer()
                            ->getFormat(mRenderPassDesc[colorIndexGL])
                            .getActualRenderableImageFormat()
                            .isInt());

                // The blend fixed-function is enabled with normal blend as well as advanced blend
                // when the Vulkan extension is present.  When emulating advanced blend in the
                // shader, the blend fixed-function must be disabled.
                const PackedColorBlendAttachmentState &packedBlendState =
                    colorBlend.attachments[colorIndexGL];
                if (packedBlendState.colorBlendOp <= static_cast<uint8_t>(VK_BLEND_OP_MAX) ||
                    contextVk->getFeatures().supportsBlendOperationAdvanced.enabled)
                {
                    state.blendEnable = VK_TRUE;
                    UnpackBlendAttachmentState(packedBlendState, &state);
                }
            }
        }

        if (contextVk->getExtensions().robustFragmentShaderOutputANGLE &&
            missingOutputsMask[colorIndexGL])
        {
            state.colorWriteMask = 0;
        }
        else
        {
            state.colorWriteMask =
                Int4Array_Get<VkColorComponentFlags>(colorBlend.colorWriteMaskBits, colorIndexGL);
        }
    }

    // Dynamic state
    angle::FixedVector<VkDynamicState, 21> dynamicStateList;
    dynamicStateList.push_back(VK_DYNAMIC_STATE_VIEWPORT);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_SCISSOR);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_LINE_WIDTH);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_BLEND_CONSTANTS);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_DEPTH_BOUNDS);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK);
    dynamicStateList.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE);
    if (contextVk->getFeatures().supportsExtendedDynamicState.enabled)
    {
        dynamicStateList.push_back(VK_DYNAMIC_STATE_CULL_MODE_EXT);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_FRONT_FACE_EXT);
        if (vertexAttribCount > 0)
        {
            dynamicStateList.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE);
        }
        dynamicStateList.push_back(VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_DEPTH_COMPARE_OP);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_STENCIL_OP);
    }
    if (contextVk->getFeatures().supportsExtendedDynamicState2.enabled)
    {
        dynamicStateList.push_back(VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE);
    }
    if (contextVk->getFeatures().supportsFragmentShadingRate.enabled)
    {
        dynamicStateList.push_back(VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR);
    }

    VkPipelineDynamicStateCreateInfo dynamicState = {};
    dynamicState.sType             = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStateList.size());
    dynamicState.pDynamicStates    = dynamicStateList.data();

    // tessellation State
    if (tessControlPointer.valid() && tessEvaluationPointer.valid())
    {
        domainOriginState.sType =
            VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO;
        domainOriginState.pNext        = NULL;
        domainOriginState.domainOrigin = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT;

        tessellationState.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
        tessellationState.flags = 0;
        tessellationState.pNext = &domainOriginState;
        tessellationState.patchControlPoints =
            static_cast<uint32_t>(inputAndRaster.misc.patchVertices);
    }

    createInfo.sType               = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    createInfo.flags               = 0;
    createInfo.stageCount          = static_cast<uint32_t>(shaderStages.size());
    createInfo.pStages             = shaderStages.data();
    createInfo.pVertexInputState   = &vertexInputState;
    createInfo.pInputAssemblyState = &inputAssemblyState;
    createInfo.pTessellationState  = &tessellationState;
    createInfo.pViewportState      = &viewportState;
    createInfo.pRasterizationState = &rasterState;
    createInfo.pMultisampleState   = &multisampleState;
    createInfo.pDepthStencilState  = &depthStencilState;
    createInfo.pColorBlendState    = &blendState;
    createInfo.pDynamicState       = dynamicStateList.empty() ? nullptr : &dynamicState;
    createInfo.layout              = pipelineLayout.getHandle();
    createInfo.renderPass          = compatibleRenderPass.getHandle();
    createInfo.subpass             = mInputAssemblyAndRasterizationStateInfo.bits.subpass;
    createInfo.basePipelineHandle  = VK_NULL_HANDLE;
    createInfo.basePipelineIndex   = 0;

    ANGLE_VK_TRY(contextVk,
                 pipelineOut->initGraphics(contextVk->getDevice(), createInfo, pipelineCacheVk));
    return angle::Result::Continue;
}

void GraphicsPipelineDesc::updateVertexInput(ContextVk *contextVk,
                                             GraphicsPipelineTransitionBits *transition,
                                             uint32_t attribIndex,
                                             GLuint stride,
                                             GLuint divisor,
                                             angle::FormatID format,
                                             bool compressed,
                                             GLuint relativeOffset)
{
    PackedAttribDesc &packedAttrib = mVertexInputAttribs.attribs[attribIndex];

    SetBitField(packedAttrib.divisor, divisor);

    if (format == angle::FormatID::NONE)
    {
        UNIMPLEMENTED();
    }

    SetBitField(packedAttrib.format, format);
    SetBitField(packedAttrib.compressed, compressed);
    SetBitField(packedAttrib.offset, relativeOffset);

    constexpr size_t kAttribBits = kPackedAttribDescSize * kBitsPerByte;
    const size_t kBit =
        ANGLE_GET_INDEXED_TRANSITION_BIT(mVertexInputAttribs, attribs, attribIndex, kAttribBits);

    // Each attribute is 4 bytes, so only one transition bit needs to be set.
    static_assert(kPackedAttribDescSize == kGraphicsPipelineDirtyBitBytes,
                  "Adjust transition bits");
    transition->set(kBit);

    if (!contextVk->getFeatures().supportsExtendedDynamicState.enabled)
    {
        SetBitField(mDynamicState.ds1.vertexStrides[attribIndex], stride);
        transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(
            mDynamicState.ds1, vertexStrides, attribIndex,
            sizeof(mDynamicState.ds1.vertexStrides[0]) * kBitsPerByte));
    }
}

void GraphicsPipelineDesc::setTopology(gl::PrimitiveMode drawMode)
{
    VkPrimitiveTopology vkTopology = gl_vk::GetPrimitiveTopology(drawMode);
    SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.topology, vkTopology);
}

void GraphicsPipelineDesc::updateTopology(GraphicsPipelineTransitionBits *transition,
                                          gl::PrimitiveMode drawMode)
{
    setTopology(drawMode);
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, misc));
}

void GraphicsPipelineDesc::updateDepthClipControl(GraphicsPipelineTransitionBits *transition,
                                                  bool negativeOneToOne)
{
    SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.viewportNegativeOneToOne,
                negativeOneToOne);
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, misc));
}

void GraphicsPipelineDesc::updatePrimitiveRestartEnabled(GraphicsPipelineTransitionBits *transition,
                                                         bool primitiveRestartEnabled)
{
    mDynamicState.ds1And2.restartEnable = static_cast<uint16_t>(primitiveRestartEnabled);
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

void GraphicsPipelineDesc::updateCullMode(GraphicsPipelineTransitionBits *transition,
                                          const gl::RasterizerState &rasterState)
{
    SetBitField(mDynamicState.ds1And2.cullMode, gl_vk::GetCullMode(rasterState));
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

void GraphicsPipelineDesc::updateFrontFace(GraphicsPipelineTransitionBits *transition,
                                           const gl::RasterizerState &rasterState,
                                           bool invertFrontFace)
{
    SetBitField(mDynamicState.ds1And2.frontFace,
                gl_vk::GetFrontFace(rasterState.frontFace, invertFrontFace));
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

void GraphicsPipelineDesc::updateRasterizerDiscardEnabled(
    GraphicsPipelineTransitionBits *transition,
    bool rasterizerDiscardEnabled)
{
    mDynamicState.ds1And2.rasterizerDiscardEnable = static_cast<uint32_t>(rasterizerDiscardEnabled);
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

uint32_t GraphicsPipelineDesc::getRasterizationSamples() const
{
    return mInputAssemblyAndRasterizationStateInfo.bits.rasterizationSamples;
}

void GraphicsPipelineDesc::setRasterizationSamples(uint32_t rasterizationSamples)
{
    mInputAssemblyAndRasterizationStateInfo.bits.rasterizationSamples = rasterizationSamples;
}

void GraphicsPipelineDesc::updateRasterizationSamples(GraphicsPipelineTransitionBits *transition,
                                                      uint32_t rasterizationSamples)
{
    setRasterizationSamples(rasterizationSamples);
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, bits));
}

void GraphicsPipelineDesc::updateAlphaToCoverageEnable(GraphicsPipelineTransitionBits *transition,
                                                       bool enable)
{
    mInputAssemblyAndRasterizationStateInfo.bits.alphaToCoverageEnable = enable;
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, bits));
}

void GraphicsPipelineDesc::updateAlphaToOneEnable(GraphicsPipelineTransitionBits *transition,
                                                  bool enable)
{
    mInputAssemblyAndRasterizationStateInfo.bits.alphaToOneEnable = enable;
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, bits));
}

void GraphicsPipelineDesc::updateSampleMask(GraphicsPipelineTransitionBits *transition,
                                            uint32_t maskNumber,
                                            uint32_t mask)
{
    ASSERT(maskNumber == 0);
    SetBitField(mInputAssemblyAndRasterizationStateInfo.sampleMask, mask);

    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, sampleMask));
}

void GraphicsPipelineDesc::updateSampleShading(GraphicsPipelineTransitionBits *transition,
                                               bool enable,
                                               float value)
{
    mInputAssemblyAndRasterizationStateInfo.bits.sampleShadingEnable = enable;
    if (enable)
    {
        SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.minSampleShading,
                    static_cast<uint16_t>(value * kMinSampleShadingScale));
    }
    else
    {
        mInputAssemblyAndRasterizationStateInfo.misc.minSampleShading = kMinSampleShadingScale;
    }

    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, bits));
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, misc));
}

void GraphicsPipelineDesc::setSingleBlend(uint32_t colorIndexGL,
                                          bool enabled,
                                          VkBlendOp op,
                                          VkBlendFactor srcFactor,
                                          VkBlendFactor dstFactor)
{
    mInputAssemblyAndRasterizationStateInfo.misc.blendEnableMask |=
        static_cast<uint8_t>(1 << colorIndexGL);

    PackedColorBlendAttachmentState &blendAttachmentState =
        mColorBlendStateInfo.attachments[colorIndexGL];

    blendAttachmentState.colorBlendOp = op;
    blendAttachmentState.alphaBlendOp = op;

    blendAttachmentState.srcColorBlendFactor = srcFactor;
    blendAttachmentState.dstColorBlendFactor = dstFactor;
    blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
    blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
}

void GraphicsPipelineDesc::updateBlendEnabled(GraphicsPipelineTransitionBits *transition,
                                              gl::DrawBufferMask blendEnabledMask)
{
    SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.blendEnableMask,
                blendEnabledMask.bits());
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, misc));
}

void GraphicsPipelineDesc::updateBlendEquations(GraphicsPipelineTransitionBits *transition,
                                                const gl::BlendStateExt &blendStateExt,
                                                gl::DrawBufferMask attachmentMask)
{
    constexpr size_t kSizeBits = sizeof(PackedColorBlendAttachmentState) * 8;

    for (size_t attachmentIndex : attachmentMask)
    {
        PackedColorBlendAttachmentState &blendAttachmentState =
            mColorBlendStateInfo.attachments[attachmentIndex];
        blendAttachmentState.colorBlendOp =
            PackGLBlendOp(blendStateExt.getEquationColorIndexed(attachmentIndex));
        blendAttachmentState.alphaBlendOp =
            PackGLBlendOp(blendStateExt.getEquationAlphaIndexed(attachmentIndex));
        transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(mColorBlendStateInfo, attachments,
                                                         attachmentIndex, kSizeBits));
    }
}

void GraphicsPipelineDesc::updateBlendFuncs(GraphicsPipelineTransitionBits *transition,
                                            const gl::BlendStateExt &blendStateExt,
                                            gl::DrawBufferMask attachmentMask)
{
    constexpr size_t kSizeBits = sizeof(PackedColorBlendAttachmentState) * 8;
    for (size_t attachmentIndex : attachmentMask)
    {
        PackedColorBlendAttachmentState &blendAttachmentState =
            mColorBlendStateInfo.attachments[attachmentIndex];
        blendAttachmentState.srcColorBlendFactor =
            PackGLBlendFactor(blendStateExt.getSrcColorIndexed(attachmentIndex));
        blendAttachmentState.dstColorBlendFactor =
            PackGLBlendFactor(blendStateExt.getDstColorIndexed(attachmentIndex));
        blendAttachmentState.srcAlphaBlendFactor =
            PackGLBlendFactor(blendStateExt.getSrcAlphaIndexed(attachmentIndex));
        blendAttachmentState.dstAlphaBlendFactor =
            PackGLBlendFactor(blendStateExt.getDstAlphaIndexed(attachmentIndex));
        transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(mColorBlendStateInfo, attachments,
                                                         attachmentIndex, kSizeBits));
    }
}

void GraphicsPipelineDesc::resetBlendFuncsAndEquations(GraphicsPipelineTransitionBits *transition,
                                                       const gl::BlendStateExt &blendStateExt,
                                                       gl::DrawBufferMask previousAttachmentsMask,
                                                       gl::DrawBufferMask newAttachmentsMask)
{
    // A framebuffer with attachments in P was bound, and now one with attachments in N is bound.
    // We need to clear blend funcs and equations for attachments in P that are not in N.  That is
    // attachments in P&~N.
    const gl::DrawBufferMask attachmentsToClear = previousAttachmentsMask & ~newAttachmentsMask;
    // We also need to restore blend funcs and equations for attachments in N that are not in P.
    const gl::DrawBufferMask attachmentsToAdd = newAttachmentsMask & ~previousAttachmentsMask;
    constexpr size_t kSizeBits                = sizeof(PackedColorBlendAttachmentState) * 8;

    for (size_t attachmentIndex : attachmentsToClear)
    {
        PackedColorBlendAttachmentState &blendAttachmentState =
            mColorBlendStateInfo.attachments[attachmentIndex];

        blendAttachmentState.colorBlendOp        = VK_BLEND_OP_ADD;
        blendAttachmentState.alphaBlendOp        = VK_BLEND_OP_ADD;
        blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
        blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
        blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
        blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;

        transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(mColorBlendStateInfo, attachments,
                                                         attachmentIndex, kSizeBits));
    }

    if (attachmentsToAdd.any())
    {
        updateBlendFuncs(transition, blendStateExt, attachmentsToAdd);
        updateBlendEquations(transition, blendStateExt, attachmentsToAdd);
    }
}

void GraphicsPipelineDesc::setColorWriteMasks(gl::BlendStateExt::ColorMaskStorage::Type colorMasks,
                                              const gl::DrawBufferMask &alphaMask,
                                              const gl::DrawBufferMask &enabledDrawBuffers)
{
    PackedColorBlendStateInfo &colorBlend = mColorBlendStateInfo;

    for (uint32_t colorIndexGL = 0; colorIndexGL < gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
         colorIndexGL++)
    {
        uint8_t colorMask =
            gl::BlendStateExt::ColorMaskStorage::GetValueIndexed(colorIndexGL, colorMasks);

        uint8_t mask = 0;
        if (enabledDrawBuffers.test(colorIndexGL))
        {
            mask = alphaMask[colorIndexGL] ? (colorMask & ~VK_COLOR_COMPONENT_A_BIT) : colorMask;
        }
        Int4Array_Set(colorBlend.colorWriteMaskBits, colorIndexGL, mask);
    }
}

void GraphicsPipelineDesc::setSingleColorWriteMask(uint32_t colorIndexGL,
                                                   VkColorComponentFlags colorComponentFlags)
{
    uint8_t colorMask = static_cast<uint8_t>(colorComponentFlags);
    Int4Array_Set(mColorBlendStateInfo.colorWriteMaskBits, colorIndexGL, colorMask);
}

void GraphicsPipelineDesc::updateColorWriteMasks(
    GraphicsPipelineTransitionBits *transition,
    gl::BlendStateExt::ColorMaskStorage::Type colorMasks,
    const gl::DrawBufferMask &alphaMask,
    const gl::DrawBufferMask &enabledDrawBuffers)
{
    setColorWriteMasks(colorMasks, alphaMask, enabledDrawBuffers);

    for (size_t colorIndexGL = 0; colorIndexGL < gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
         colorIndexGL++)
    {
        transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(mColorBlendStateInfo, colorWriteMaskBits,
                                                         colorIndexGL, 4));
    }
}

void GraphicsPipelineDesc::setDepthTestEnabled(bool enabled)
{
    mDynamicState.ds1And2.depthTest = enabled;
}

void GraphicsPipelineDesc::setDepthWriteEnabled(bool enabled)
{
    mDynamicState.ds1And2.depthWrite = enabled;
}

void GraphicsPipelineDesc::setDepthFunc(VkCompareOp op)
{
    SetBitField(mDynamicState.ds1And2.depthCompareOp, op);
}

void GraphicsPipelineDesc::setDepthClampEnabled(bool enabled)
{
    mInputAssemblyAndRasterizationStateInfo.bits.depthClampEnable = enabled;
}

void GraphicsPipelineDesc::setStencilTestEnabled(bool enabled)
{
    mDynamicState.ds1And2.stencilTest = enabled;
}

void GraphicsPipelineDesc::setStencilFrontFuncs(VkCompareOp compareOp)
{
    SetBitField(mDynamicState.ds1.front.ops.compare, compareOp);
}

void GraphicsPipelineDesc::setStencilBackFuncs(VkCompareOp compareOp)
{
    SetBitField(mDynamicState.ds1.back.ops.compare, compareOp);
}

void GraphicsPipelineDesc::setStencilFrontOps(VkStencilOp failOp,
                                              VkStencilOp passOp,
                                              VkStencilOp depthFailOp)
{
    SetBitField(mDynamicState.ds1.front.ops.fail, failOp);
    SetBitField(mDynamicState.ds1.front.ops.pass, passOp);
    SetBitField(mDynamicState.ds1.front.ops.depthFail, depthFailOp);
}

void GraphicsPipelineDesc::setStencilBackOps(VkStencilOp failOp,
                                             VkStencilOp passOp,
                                             VkStencilOp depthFailOp)
{
    SetBitField(mDynamicState.ds1.back.ops.fail, failOp);
    SetBitField(mDynamicState.ds1.back.ops.pass, passOp);
    SetBitField(mDynamicState.ds1.back.ops.depthFail, depthFailOp);
}

void GraphicsPipelineDesc::updateDepthTestEnabled(GraphicsPipelineTransitionBits *transition,
                                                  const gl::DepthStencilState &depthStencilState,
                                                  const gl::Framebuffer *drawFramebuffer)
{
    // Only enable the depth test if the draw framebuffer has a depth buffer.  It's possible that
    // we're emulating a stencil-only buffer with a depth-stencil buffer
    setDepthTestEnabled(depthStencilState.depthTest && drawFramebuffer->hasDepth());
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

void GraphicsPipelineDesc::updateDepthFunc(GraphicsPipelineTransitionBits *transition,
                                           const gl::DepthStencilState &depthStencilState)
{
    setDepthFunc(gl_vk::GetCompareOp(depthStencilState.depthFunc));
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

void GraphicsPipelineDesc::updateSurfaceRotation(GraphicsPipelineTransitionBits *transition,
                                                 const SurfaceRotation surfaceRotation)
{
    SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.surfaceRotation,
                IsRotatedAspectRatio(surfaceRotation));
    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, misc));
}

void GraphicsPipelineDesc::updateDepthWriteEnabled(GraphicsPipelineTransitionBits *transition,
                                                   const gl::DepthStencilState &depthStencilState,
                                                   const gl::Framebuffer *drawFramebuffer)
{
    // Don't write to depth buffers that should not exist
    const bool depthWriteEnabled =
        drawFramebuffer->hasDepth() && depthStencilState.depthTest && depthStencilState.depthMask;
    if (static_cast<bool>(mDynamicState.ds1And2.depthWrite) != depthWriteEnabled)
    {
        setDepthWriteEnabled(depthWriteEnabled);
        transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
    }
}

void GraphicsPipelineDesc::updateStencilTestEnabled(GraphicsPipelineTransitionBits *transition,
                                                    const gl::DepthStencilState &depthStencilState,
                                                    const gl::Framebuffer *drawFramebuffer)
{
    // Only enable the stencil test if the draw framebuffer has a stencil buffer.  It's possible
    // that we're emulating a depth-only buffer with a depth-stencil buffer
    setStencilTestEnabled(depthStencilState.stencilTest && drawFramebuffer->hasStencil());
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

void GraphicsPipelineDesc::updateStencilFrontFuncs(GraphicsPipelineTransitionBits *transition,
                                                   const gl::DepthStencilState &depthStencilState)
{
    setStencilFrontFuncs(gl_vk::GetCompareOp(depthStencilState.stencilFunc));
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1));
}

void GraphicsPipelineDesc::updateStencilBackFuncs(GraphicsPipelineTransitionBits *transition,
                                                  const gl::DepthStencilState &depthStencilState)
{
    setStencilBackFuncs(gl_vk::GetCompareOp(depthStencilState.stencilBackFunc));
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1));
}

void GraphicsPipelineDesc::updateStencilFrontOps(GraphicsPipelineTransitionBits *transition,
                                                 const gl::DepthStencilState &depthStencilState)
{
    setStencilFrontOps(gl_vk::GetStencilOp(depthStencilState.stencilFail),
                       gl_vk::GetStencilOp(depthStencilState.stencilPassDepthPass),
                       gl_vk::GetStencilOp(depthStencilState.stencilPassDepthFail));
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1));
}

void GraphicsPipelineDesc::updateStencilBackOps(GraphicsPipelineTransitionBits *transition,
                                                const gl::DepthStencilState &depthStencilState)
{
    setStencilBackOps(gl_vk::GetStencilOp(depthStencilState.stencilBackFail),
                      gl_vk::GetStencilOp(depthStencilState.stencilBackPassDepthPass),
                      gl_vk::GetStencilOp(depthStencilState.stencilBackPassDepthFail));
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1));
}

void GraphicsPipelineDesc::updatePolygonOffsetFillEnabled(
    GraphicsPipelineTransitionBits *transition,
    bool enabled)
{
    mDynamicState.ds1And2.depthBiasEnable = enabled;
    transition->set(ANGLE_GET_TRANSITION_BIT(mDynamicState, ds1And2));
}

void GraphicsPipelineDesc::setRenderPassDesc(const RenderPassDesc &renderPassDesc)
{
    mRenderPassDesc = renderPassDesc;
}

void GraphicsPipelineDesc::updateSubpass(GraphicsPipelineTransitionBits *transition,
                                         uint32_t subpass)
{
    if (mInputAssemblyAndRasterizationStateInfo.bits.subpass != subpass)
    {
        SetBitField(mInputAssemblyAndRasterizationStateInfo.bits.subpass, subpass);
        transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, bits));
    }
}

void GraphicsPipelineDesc::updatePatchVertices(GraphicsPipelineTransitionBits *transition,
                                               GLuint value)
{
    SetBitField(mInputAssemblyAndRasterizationStateInfo.misc.patchVertices, value);

    transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndRasterizationStateInfo, misc));
}

void GraphicsPipelineDesc::resetSubpass(GraphicsPipelineTransitionBits *transition)
{
    updateSubpass(transition, 0);
}

void GraphicsPipelineDesc::nextSubpass(GraphicsPipelineTransitionBits *transition)
{
    updateSubpass(transition, mInputAssemblyAndRasterizationStateInfo.bits.subpass + 1);
}

void GraphicsPipelineDesc::setSubpass(uint32_t subpass)
{
    SetBitField(mInputAssemblyAndRasterizationStateInfo.bits.subpass, subpass);
}

uint32_t GraphicsPipelineDesc::getSubpass() const
{
    return mInputAssemblyAndRasterizationStateInfo.bits.subpass;
}

void GraphicsPipelineDesc::updateEmulatedDitherControl(GraphicsPipelineTransitionBits *transition,
                                                       uint16_t value)
{
    // Make sure we don't waste time resetting this to zero in the common no-dither case.
    ASSERT(value != 0 || mDither.emulatedDitherControl != 0);

    mDither.emulatedDitherControl = value;
    transition->set(ANGLE_GET_TRANSITION_BIT(mDither, emulatedDitherControl));
}

void GraphicsPipelineDesc::updateRenderPassDesc(GraphicsPipelineTransitionBits *transition,
                                                const RenderPassDesc &renderPassDesc)
{
    setRenderPassDesc(renderPassDesc);

    // The RenderPass is a special case where it spans multiple bits but has no member.
    constexpr size_t kFirstBit =
        offsetof(GraphicsPipelineDesc, mRenderPassDesc) >> kTransitionByteShift;
    constexpr size_t kBitCount = kRenderPassDescSize >> kTransitionByteShift;
    for (size_t bit = 0; bit < kBitCount; ++bit)
    {
        transition->set(kFirstBit + bit);
    }
}

void GraphicsPipelineDesc::setRenderPassSampleCount(GLint samples)
{
    mRenderPassDesc.setSamples(samples);
}

void GraphicsPipelineDesc::setRenderPassColorAttachmentFormat(size_t colorIndexGL,
                                                              angle::FormatID formatID)
{
    mRenderPassDesc.packColorAttachment(colorIndexGL, formatID);
}

// AttachmentOpsArray implementation.
AttachmentOpsArray::AttachmentOpsArray()
{
    memset(&mOps, 0, sizeof(PackedAttachmentOpsDesc) * mOps.size());
}

AttachmentOpsArray::~AttachmentOpsArray() = default;

AttachmentOpsArray::AttachmentOpsArray(const AttachmentOpsArray &other)
{
    memcpy(&mOps, &other.mOps, sizeof(PackedAttachmentOpsDesc) * mOps.size());
}

AttachmentOpsArray &AttachmentOpsArray::operator=(const AttachmentOpsArray &other)
{
    memcpy(&mOps, &other.mOps, sizeof(PackedAttachmentOpsDesc) * mOps.size());
    return *this;
}

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

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

void AttachmentOpsArray::initWithLoadStore(PackedAttachmentIndex index,
                                           ImageLayout initialLayout,
                                           ImageLayout finalLayout)
{
    setLayouts(index, initialLayout, finalLayout);
    setOps(index, RenderPassLoadOp::Load, RenderPassStoreOp::Store);
    setStencilOps(index, RenderPassLoadOp::Load, RenderPassStoreOp::Store);
}

void AttachmentOpsArray::setLayouts(PackedAttachmentIndex index,
                                    ImageLayout initialLayout,
                                    ImageLayout finalLayout)
{
    PackedAttachmentOpsDesc &ops = mOps[index.get()];
    SetBitField(ops.initialLayout, initialLayout);
    SetBitField(ops.finalLayout, finalLayout);
}

void AttachmentOpsArray::setOps(PackedAttachmentIndex index,
                                RenderPassLoadOp loadOp,
                                RenderPassStoreOp storeOp)
{
    PackedAttachmentOpsDesc &ops = mOps[index.get()];
    SetBitField(ops.loadOp, loadOp);
    SetBitField(ops.storeOp, storeOp);
    ops.isInvalidated = false;
}

void AttachmentOpsArray::setStencilOps(PackedAttachmentIndex index,
                                       RenderPassLoadOp loadOp,
                                       RenderPassStoreOp storeOp)
{
    PackedAttachmentOpsDesc &ops = mOps[index.get()];
    SetBitField(ops.stencilLoadOp, loadOp);
    SetBitField(ops.stencilStoreOp, storeOp);
    ops.isStencilInvalidated = false;
}

void AttachmentOpsArray::setClearOp(PackedAttachmentIndex index)
{
    PackedAttachmentOpsDesc &ops = mOps[index.get()];
    SetBitField(ops.loadOp, RenderPassLoadOp::Clear);
}

void AttachmentOpsArray::setClearStencilOp(PackedAttachmentIndex index)
{
    PackedAttachmentOpsDesc &ops = mOps[index.get()];
    SetBitField(ops.stencilLoadOp, RenderPassLoadOp::Clear);
}

size_t AttachmentOpsArray::hash() const
{
    return angle::ComputeGenericHash(mOps);
}

bool operator==(const AttachmentOpsArray &lhs, const AttachmentOpsArray &rhs)
{
    return (memcmp(&lhs, &rhs, sizeof(AttachmentOpsArray)) == 0);
}

// DescriptorSetLayoutDesc implementation.
DescriptorSetLayoutDesc::DescriptorSetLayoutDesc() : mPackedDescriptorSetLayout{} {}

DescriptorSetLayoutDesc::~DescriptorSetLayoutDesc() = default;

DescriptorSetLayoutDesc::DescriptorSetLayoutDesc(const DescriptorSetLayoutDesc &other) = default;

DescriptorSetLayoutDesc &DescriptorSetLayoutDesc::operator=(const DescriptorSetLayoutDesc &other) =
    default;

size_t DescriptorSetLayoutDesc::hash() const
{
    return angle::ComputeGenericHash(mPackedDescriptorSetLayout);
}

bool DescriptorSetLayoutDesc::operator==(const DescriptorSetLayoutDesc &other) const
{
    return (memcmp(&mPackedDescriptorSetLayout, &other.mPackedDescriptorSetLayout,
                   sizeof(mPackedDescriptorSetLayout)) == 0);
}

void DescriptorSetLayoutDesc::update(uint32_t bindingIndex,
                                     VkDescriptorType descriptorType,
                                     uint32_t count,
                                     VkShaderStageFlags stages,
                                     const Sampler *immutableSampler)
{
    ASSERT(static_cast<size_t>(descriptorType) < std::numeric_limits<uint16_t>::max());
    ASSERT(count < std::numeric_limits<uint16_t>::max());

    PackedDescriptorSetBinding &packedBinding = mPackedDescriptorSetLayout[bindingIndex];

    SetBitField(packedBinding.type, descriptorType);
    SetBitField(packedBinding.count, count);
    SetBitField(packedBinding.stages, stages);
    packedBinding.immutableSampler = VK_NULL_HANDLE;
    packedBinding.pad              = 0;

    if (immutableSampler)
    {
        ASSERT(count == 1);
        packedBinding.immutableSampler = immutableSampler->getHandle();
    }
}

void DescriptorSetLayoutDesc::unpackBindings(DescriptorSetLayoutBindingVector *bindings,
                                             std::vector<VkSampler> *immutableSamplers) const
{
    for (uint32_t bindingIndex = 0; bindingIndex < kMaxDescriptorSetLayoutBindings; ++bindingIndex)
    {
        const PackedDescriptorSetBinding &packedBinding = mPackedDescriptorSetLayout[bindingIndex];
        if (packedBinding.count == 0)
            continue;

        VkDescriptorSetLayoutBinding binding = {};
        binding.binding                      = bindingIndex;
        binding.descriptorCount              = packedBinding.count;
        binding.descriptorType               = static_cast<VkDescriptorType>(packedBinding.type);
        binding.stageFlags = static_cast<VkShaderStageFlags>(packedBinding.stages);
        if (packedBinding.immutableSampler != VK_NULL_HANDLE)
        {
            ASSERT(packedBinding.count == 1);
            immutableSamplers->push_back(packedBinding.immutableSampler);
            binding.pImmutableSamplers = reinterpret_cast<const VkSampler *>(angle::DirtyPointer);
        }

        bindings->push_back(binding);
    }
    if (!immutableSamplers->empty())
    {
        // Patch up pImmutableSampler addresses now that the vector is stable
        int immutableIndex = 0;
        for (VkDescriptorSetLayoutBinding &binding : *bindings)
        {
            if (binding.pImmutableSamplers)
            {
                binding.pImmutableSamplers = &(*immutableSamplers)[immutableIndex];
                immutableIndex++;
            }
        }
    }
}

// PipelineLayoutDesc implementation.
PipelineLayoutDesc::PipelineLayoutDesc()
    : mDescriptorSetLayouts{}, mPushConstantRange{}, mPadding(0)
{}

PipelineLayoutDesc::~PipelineLayoutDesc() = default;

PipelineLayoutDesc::PipelineLayoutDesc(const PipelineLayoutDesc &other) = default;

PipelineLayoutDesc &PipelineLayoutDesc::operator=(const PipelineLayoutDesc &rhs)
{
    mDescriptorSetLayouts = rhs.mDescriptorSetLayouts;
    mPushConstantRange    = rhs.mPushConstantRange;
    return *this;
}

size_t PipelineLayoutDesc::hash() const
{
    return angle::ComputeGenericHash(*this);
}

bool PipelineLayoutDesc::operator==(const PipelineLayoutDesc &other) const
{
    return memcmp(this, &other, sizeof(PipelineLayoutDesc)) == 0;
}

void PipelineLayoutDesc::updateDescriptorSetLayout(DescriptorSetIndex setIndex,
                                                   const DescriptorSetLayoutDesc &desc)
{
    mDescriptorSetLayouts[setIndex] = desc;
}

void PipelineLayoutDesc::updatePushConstantRange(VkShaderStageFlags stageMask,
                                                 uint32_t offset,
                                                 uint32_t size)
{
    SetBitField(mPushConstantRange.offset, offset);
    SetBitField(mPushConstantRange.size, size);
    SetBitField(mPushConstantRange.stageMask, stageMask);
}

// PipelineHelper implementation.
PipelineHelper::PipelineHelper() = default;

PipelineHelper::~PipelineHelper() = default;

void PipelineHelper::destroy(VkDevice device)
{
    mPipeline.destroy(device);
}

void PipelineHelper::addTransition(GraphicsPipelineTransitionBits bits,
                                   const GraphicsPipelineDesc *desc,
                                   PipelineHelper *pipeline)
{
    mTransitions.emplace_back(bits, desc, pipeline);
}

// DescriptorSetDesc implementation.
size_t DescriptorSetDesc::hash() const
{
    if (mDescriptorInfos.empty())
    {
        return 0;
    }

    return angle::ComputeGenericHash(mDescriptorInfos.data(),
                                     sizeof(mDescriptorInfos[0]) * mDescriptorInfos.size());
}

// FramebufferDesc implementation.

FramebufferDesc::FramebufferDesc()
{
    reset();
}

FramebufferDesc::~FramebufferDesc()                                       = default;
FramebufferDesc::FramebufferDesc(const FramebufferDesc &other)            = default;
FramebufferDesc &FramebufferDesc::operator=(const FramebufferDesc &other) = default;

void FramebufferDesc::update(uint32_t index, ImageOrBufferViewSubresourceSerial serial)
{
    static_assert(kMaxFramebufferAttachments + 1 < std::numeric_limits<uint8_t>::max(),
                  "mMaxIndex size is too small");
    ASSERT(index < kMaxFramebufferAttachments);
    mSerials[index] = serial;
    if (serial.viewSerial.valid())
    {
        SetBitField(mMaxIndex, std::max(mMaxIndex, static_cast<uint16_t>(index + 1)));
    }
}

void FramebufferDesc::updateColor(uint32_t index, ImageOrBufferViewSubresourceSerial serial)
{
    update(kFramebufferDescColorIndexOffset + index, serial);
}

void FramebufferDesc::updateColorResolve(uint32_t index, ImageOrBufferViewSubresourceSerial serial)
{
    update(kFramebufferDescColorResolveIndexOffset + index, serial);
}

void FramebufferDesc::updateUnresolveMask(FramebufferNonResolveAttachmentMask unresolveMask)
{
    SetBitField(mUnresolveAttachmentMask, unresolveMask.bits());
}

void FramebufferDesc::updateDepthStencil(ImageOrBufferViewSubresourceSerial serial)
{
    update(kFramebufferDescDepthStencilIndex, serial);
}

void FramebufferDesc::updateDepthStencilResolve(ImageOrBufferViewSubresourceSerial serial)
{
    update(kFramebufferDescDepthStencilResolveIndexOffset, serial);
}

size_t FramebufferDesc::hash() const
{
    return angle::ComputeGenericHash(&mSerials, sizeof(mSerials[0]) * mMaxIndex) ^
           mHasFramebufferFetch << 26 ^ mIsRenderToTexture << 25 ^ mLayerCount << 16 ^
           mUnresolveAttachmentMask;
}

void FramebufferDesc::reset()
{
    mMaxIndex                = 0;
    mHasFramebufferFetch     = false;
    mLayerCount              = 0;
    mSrgbWriteControlMode    = 0;
    mUnresolveAttachmentMask = 0;
    mIsRenderToTexture       = 0;
    memset(&mSerials, 0, sizeof(mSerials));
}

bool FramebufferDesc::operator==(const FramebufferDesc &other) const
{
    if (mMaxIndex != other.mMaxIndex || mLayerCount != other.mLayerCount ||
        mUnresolveAttachmentMask != other.mUnresolveAttachmentMask ||
        mHasFramebufferFetch != other.mHasFramebufferFetch ||
        mSrgbWriteControlMode != other.mSrgbWriteControlMode ||
        mIsRenderToTexture != other.mIsRenderToTexture)
    {
        return false;
    }

    size_t validRegionSize = sizeof(mSerials[0]) * mMaxIndex;
    return memcmp(&mSerials, &other.mSerials, validRegionSize) == 0;
}

uint32_t FramebufferDesc::attachmentCount() const
{
    uint32_t count = 0;
    for (const ImageOrBufferViewSubresourceSerial &serial : mSerials)
    {
        if (serial.viewSerial.valid())
        {
            count++;
        }
    }
    return count;
}

FramebufferNonResolveAttachmentMask FramebufferDesc::getUnresolveAttachmentMask() const
{
    return FramebufferNonResolveAttachmentMask(mUnresolveAttachmentMask);
}

void FramebufferDesc::updateLayerCount(uint32_t layerCount)
{
    SetBitField(mLayerCount, layerCount);
}

void FramebufferDesc::updateFramebufferFetchMode(bool hasFramebufferFetch)
{
    SetBitField(mHasFramebufferFetch, hasFramebufferFetch);
}

void FramebufferDesc::updateRenderToTexture(bool isRenderToTexture)
{
    SetBitField(mIsRenderToTexture, isRenderToTexture);
}

// YcbcrConversionDesc implementation
YcbcrConversionDesc::YcbcrConversionDesc()
{
    reset();
}

YcbcrConversionDesc::~YcbcrConversionDesc() = default;

YcbcrConversionDesc::YcbcrConversionDesc(const YcbcrConversionDesc &other) = default;

YcbcrConversionDesc &YcbcrConversionDesc::operator=(const YcbcrConversionDesc &rhs) = default;

size_t YcbcrConversionDesc::hash() const
{
    return angle::ComputeGenericHash(*this);
}

bool YcbcrConversionDesc::operator==(const YcbcrConversionDesc &other) const
{
    return (memcmp(this, &other, sizeof(YcbcrConversionDesc)) == 0);
}

void YcbcrConversionDesc::reset()
{
    mExternalOrVkFormat = 0;
    mIsExternalFormat   = 0;
    mConversionModel    = 0;
    mColorRange         = 0;
    mXChromaOffset      = 0;
    mYChromaOffset      = 0;
    mChromaFilter       = 0;
    mRSwizzle           = 0;
    mGSwizzle           = 0;
    mBSwizzle           = 0;
    mASwizzle           = 0;
    mPadding            = 0;
    mReserved           = 0;
}

void YcbcrConversionDesc::updateChromaFilter(VkFilter filter)
{
    SetBitField(mChromaFilter, filter);
}

void YcbcrConversionDesc::update(RendererVk *rendererVk,
                                 uint64_t externalFormat,
                                 VkSamplerYcbcrModelConversion conversionModel,
                                 VkSamplerYcbcrRange colorRange,
                                 VkChromaLocation xChromaOffset,
                                 VkChromaLocation yChromaOffset,
                                 VkFilter chromaFilter,
                                 VkComponentMapping components,
                                 angle::FormatID intendedFormatID)
{
    const vk::Format &vkFormat = rendererVk->getFormat(intendedFormatID);
    ASSERT(externalFormat != 0 || vkFormat.getIntendedFormat().isYUV);

    SetBitField(mIsExternalFormat, (externalFormat) ? 1 : 0);
    mExternalOrVkFormat = (externalFormat)
                              ? externalFormat
                              : vkFormat.getActualImageVkFormat(vk::ImageAccess::SampleOnly);
    SetBitField(mConversionModel, conversionModel);
    SetBitField(mColorRange, colorRange);
    SetBitField(mXChromaOffset, xChromaOffset);
    SetBitField(mYChromaOffset, yChromaOffset);
    SetBitField(mChromaFilter, chromaFilter);
    SetBitField(mRSwizzle, components.r);
    SetBitField(mGSwizzle, components.g);
    SetBitField(mBSwizzle, components.b);
    SetBitField(mASwizzle, components.a);
}

angle::Result YcbcrConversionDesc::init(Context *context,
                                        SamplerYcbcrConversion *conversionOut) const
{
    // Create the VkSamplerYcbcrConversion
    VkSamplerYcbcrConversionCreateInfo samplerYcbcrConversionInfo = {};
    samplerYcbcrConversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
    samplerYcbcrConversionInfo.format =
        getExternalFormat() == 0 ? static_cast<VkFormat>(mExternalOrVkFormat) : VK_FORMAT_UNDEFINED;
    samplerYcbcrConversionInfo.xChromaOffset = static_cast<VkChromaLocation>(mXChromaOffset);
    samplerYcbcrConversionInfo.yChromaOffset = static_cast<VkChromaLocation>(mYChromaOffset);
    samplerYcbcrConversionInfo.ycbcrModel =
        static_cast<VkSamplerYcbcrModelConversion>(mConversionModel);
    samplerYcbcrConversionInfo.ycbcrRange   = static_cast<VkSamplerYcbcrRange>(mColorRange);
    samplerYcbcrConversionInfo.chromaFilter = static_cast<VkFilter>(mChromaFilter);
    samplerYcbcrConversionInfo.components   = {
          static_cast<VkComponentSwizzle>(mRSwizzle), static_cast<VkComponentSwizzle>(mGSwizzle),
          static_cast<VkComponentSwizzle>(mBSwizzle), static_cast<VkComponentSwizzle>(mASwizzle)};

#ifdef VK_USE_PLATFORM_ANDROID_KHR
    VkExternalFormatANDROID externalFormat = {};
    if (getExternalFormat() != 0)
    {
        externalFormat.sType             = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
        externalFormat.externalFormat    = mExternalOrVkFormat;
        samplerYcbcrConversionInfo.pNext = &externalFormat;
    }
#else
    // We do not support external format for any platform other than Android.
    ASSERT(mIsExternalFormat == 0);
#endif  // VK_USE_PLATFORM_ANDROID_KHR

    ANGLE_VK_TRY(context, conversionOut->init(context->getDevice(), samplerYcbcrConversionInfo));
    return angle::Result::Continue;
}

// SamplerDesc implementation.
SamplerDesc::SamplerDesc()
{
    reset();
}

SamplerDesc::~SamplerDesc() = default;

SamplerDesc::SamplerDesc(const SamplerDesc &other) = default;

SamplerDesc &SamplerDesc::operator=(const SamplerDesc &rhs) = default;

SamplerDesc::SamplerDesc(ContextVk *contextVk,
                         const gl::SamplerState &samplerState,
                         bool stencilMode,
                         const YcbcrConversionDesc *ycbcrConversionDesc,
                         angle::FormatID intendedFormatID)
{
    update(contextVk, samplerState, stencilMode, ycbcrConversionDesc, intendedFormatID);
}

void SamplerDesc::reset()
{
    mMipLodBias    = 0.0f;
    mMaxAnisotropy = 0.0f;
    mMinLod        = 0.0f;
    mMaxLod        = 0.0f;
    mYcbcrConversionDesc.reset();
    mMagFilter         = 0;
    mMinFilter         = 0;
    mMipmapMode        = 0;
    mAddressModeU      = 0;
    mAddressModeV      = 0;
    mAddressModeW      = 0;
    mCompareEnabled    = 0;
    mCompareOp         = 0;
    mPadding           = 0;
    mBorderColorType   = 0;
    mBorderColor.red   = 0.0f;
    mBorderColor.green = 0.0f;
    mBorderColor.blue  = 0.0f;
    mBorderColor.alpha = 0.0f;
    mReserved          = 0;
}

void SamplerDesc::update(ContextVk *contextVk,
                         const gl::SamplerState &samplerState,
                         bool stencilMode,
                         const YcbcrConversionDesc *ycbcrConversionDesc,
                         angle::FormatID intendedFormatID)
{
    const angle::FeaturesVk &featuresVk = contextVk->getFeatures();
    mMipLodBias                         = 0.0f;
    if (featuresVk.forceTextureLodOffset1.enabled)
    {
        mMipLodBias = 1.0f;
    }
    else if (featuresVk.forceTextureLodOffset2.enabled)
    {
        mMipLodBias = 2.0f;
    }
    else if (featuresVk.forceTextureLodOffset3.enabled)
    {
        mMipLodBias = 3.0f;
    }
    else if (featuresVk.forceTextureLodOffset4.enabled)
    {
        mMipLodBias = 4.0f;
    }

    mMaxAnisotropy = samplerState.getMaxAnisotropy();
    mMinLod        = samplerState.getMinLod();
    mMaxLod        = samplerState.getMaxLod();

    if (ycbcrConversionDesc && ycbcrConversionDesc->valid())
    {
        // Update the SamplerYcbcrConversionCache key
        mYcbcrConversionDesc = *ycbcrConversionDesc;
    }

    bool compareEnable    = samplerState.getCompareMode() == GL_COMPARE_REF_TO_TEXTURE;
    VkCompareOp compareOp = gl_vk::GetCompareOp(samplerState.getCompareFunc());
    // When sampling from stencil, deqp tests expect texture compare to have no effect
    // dEQP - GLES31.functional.stencil_texturing.misc.compare_mode_effect
    // states: NOTE: Texture compare mode has no effect when reading stencil values.
    if (stencilMode)
    {
        compareEnable = VK_FALSE;
        compareOp     = VK_COMPARE_OP_ALWAYS;
    }

    GLenum magFilter = samplerState.getMagFilter();
    GLenum minFilter = samplerState.getMinFilter();
    if (featuresVk.forceNearestFiltering.enabled)
    {
        magFilter = gl::ConvertToNearestFilterMode(magFilter);
        minFilter = gl::ConvertToNearestFilterMode(minFilter);
    }
    if (featuresVk.forceNearestMipFiltering.enabled)
    {
        minFilter = gl::ConvertToNearestMipFilterMode(minFilter);
    }

    SetBitField(mMagFilter, gl_vk::GetFilter(magFilter));
    SetBitField(mMinFilter, gl_vk::GetFilter(minFilter));
    SetBitField(mMipmapMode, gl_vk::GetSamplerMipmapMode(samplerState.getMinFilter()));
    SetBitField(mAddressModeU, gl_vk::GetSamplerAddressMode(samplerState.getWrapS()));
    SetBitField(mAddressModeV, gl_vk::GetSamplerAddressMode(samplerState.getWrapT()));
    SetBitField(mAddressModeW, gl_vk::GetSamplerAddressMode(samplerState.getWrapR()));
    SetBitField(mCompareEnabled, compareEnable);
    SetBitField(mCompareOp, compareOp);

    if (!gl::IsMipmapFiltered(minFilter))
    {
        // Per the Vulkan spec, GL_NEAREST and GL_LINEAR do not map directly to Vulkan, so
        // they must be emulated (See "Mapping of OpenGL to Vulkan filter modes")
        SetBitField(mMipmapMode, VK_SAMPLER_MIPMAP_MODE_NEAREST);
        mMinLod = 0.0f;
        mMaxLod = 0.25f;
    }

    mPadding = 0;

    mBorderColorType =
        (samplerState.getBorderColor().type == angle::ColorGeneric::Type::Float) ? 0 : 1;

    // Adjust border color according to intended format
    const vk::Format &vkFormat = contextVk->getRenderer()->getFormat(intendedFormatID);
    gl::ColorGeneric adjustedBorderColor =
        AdjustBorderColor(samplerState.getBorderColor(), vkFormat.getIntendedFormat(), stencilMode);
    mBorderColor = adjustedBorderColor.colorF;

    mReserved = 0;
}

angle::Result SamplerDesc::init(ContextVk *contextVk, Sampler *sampler) const
{
    const gl::Extensions &extensions = contextVk->getExtensions();

    bool anisotropyEnable = extensions.textureFilterAnisotropicEXT && mMaxAnisotropy > 1.0f;

    VkSamplerCreateInfo createInfo     = {};
    createInfo.sType                   = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
    createInfo.flags                   = 0;
    createInfo.magFilter               = static_cast<VkFilter>(mMagFilter);
    createInfo.minFilter               = static_cast<VkFilter>(mMinFilter);
    createInfo.mipmapMode              = static_cast<VkSamplerMipmapMode>(mMipmapMode);
    createInfo.addressModeU            = static_cast<VkSamplerAddressMode>(mAddressModeU);
    createInfo.addressModeV            = static_cast<VkSamplerAddressMode>(mAddressModeV);
    createInfo.addressModeW            = static_cast<VkSamplerAddressMode>(mAddressModeW);
    createInfo.mipLodBias              = mMipLodBias;
    createInfo.anisotropyEnable        = anisotropyEnable;
    createInfo.maxAnisotropy           = mMaxAnisotropy;
    createInfo.compareEnable           = mCompareEnabled ? VK_TRUE : VK_FALSE;
    createInfo.compareOp               = static_cast<VkCompareOp>(mCompareOp);
    createInfo.minLod                  = mMinLod;
    createInfo.maxLod                  = mMaxLod;
    createInfo.borderColor             = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
    createInfo.unnormalizedCoordinates = VK_FALSE;

    // Note: because we don't detect changes to this hint (no dirty bit), if a sampler is created
    // with the hint enabled, and then the hint gets disabled, the next render will do so with the
    // hint enabled.
    VkSamplerFilteringPrecisionGOOGLE filteringInfo = {};
    GLenum hint = contextVk->getState().getTextureFilteringHint();
    if (hint == GL_NICEST)
    {
        ASSERT(extensions.textureFilteringHintCHROMIUM);
        filteringInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_FILTERING_PRECISION_GOOGLE;
        filteringInfo.samplerFilteringPrecisionMode =
            VK_SAMPLER_FILTERING_PRECISION_MODE_HIGH_GOOGLE;
        AddToPNextChain(&createInfo, &filteringInfo);
    }

    VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {};
    if (mYcbcrConversionDesc.valid())
    {
        ASSERT((contextVk->getRenderer()->getFeatures().supportsYUVSamplerConversion.enabled));
        samplerYcbcrConversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO;
        samplerYcbcrConversionInfo.pNext = nullptr;
        ANGLE_TRY(contextVk->getRenderer()->getYuvConversionCache().getSamplerYcbcrConversion(
            contextVk, mYcbcrConversionDesc, &samplerYcbcrConversionInfo.conversion));
        AddToPNextChain(&createInfo, &samplerYcbcrConversionInfo);

        // Vulkan spec requires these settings:
        createInfo.addressModeU            = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
        createInfo.addressModeV            = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
        createInfo.addressModeW            = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
        createInfo.anisotropyEnable        = VK_FALSE;
        createInfo.unnormalizedCoordinates = VK_FALSE;
    }

    VkSamplerCustomBorderColorCreateInfoEXT customBorderColorInfo = {};
    if (createInfo.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER ||
        createInfo.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER ||
        createInfo.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)
    {
        ASSERT((contextVk->getRenderer()->getFeatures().supportsCustomBorderColor.enabled));
        customBorderColorInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT;

        customBorderColorInfo.customBorderColor.float32[0] = mBorderColor.red;
        customBorderColorInfo.customBorderColor.float32[1] = mBorderColor.green;
        customBorderColorInfo.customBorderColor.float32[2] = mBorderColor.blue;
        customBorderColorInfo.customBorderColor.float32[3] = mBorderColor.alpha;

        if (mBorderColorType == static_cast<uint32_t>(angle::ColorGeneric::Type::Float))
        {
            createInfo.borderColor = VK_BORDER_COLOR_FLOAT_CUSTOM_EXT;
        }
        else
        {
            createInfo.borderColor = VK_BORDER_COLOR_INT_CUSTOM_EXT;
        }

        vk::AddToPNextChain(&createInfo, &customBorderColorInfo);
    }
    ANGLE_VK_TRY(contextVk, sampler->init(contextVk->getDevice(), createInfo));

    return angle::Result::Continue;
}

size_t SamplerDesc::hash() const
{
    return angle::ComputeGenericHash(*this);
}

bool SamplerDesc::operator==(const SamplerDesc &other) const
{
    return (memcmp(this, &other, sizeof(SamplerDesc)) == 0);
}

// SamplerHelper implementation.
SamplerHelper::SamplerHelper(ContextVk *contextVk)
    : mSamplerSerial(contextVk->getRenderer()->getResourceSerialFactory().generateSamplerSerial())
{}

SamplerHelper::~SamplerHelper() {}

SamplerHelper::SamplerHelper(SamplerHelper &&samplerHelper)
{
    *this = std::move(samplerHelper);
}

SamplerHelper &SamplerHelper::operator=(SamplerHelper &&rhs)
{
    std::swap(mSampler, rhs.mSampler);
    std::swap(mSamplerSerial, rhs.mSamplerSerial);
    return *this;
}

// RenderPassHelper implementation.
RenderPassHelper::RenderPassHelper() : mPerfCounters{} {}

RenderPassHelper::~RenderPassHelper() = default;

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

RenderPassHelper &RenderPassHelper::operator=(RenderPassHelper &&other)
{
    mRenderPass   = std::move(other.mRenderPass);
    mPerfCounters = std::move(other.mPerfCounters);
    return *this;
}

void RenderPassHelper::destroy(VkDevice device)
{
    mRenderPass.destroy(device);
}

const RenderPass &RenderPassHelper::getRenderPass() const
{
    return mRenderPass;
}

RenderPass &RenderPassHelper::getRenderPass()
{
    return mRenderPass;
}

const RenderPassPerfCounters &RenderPassHelper::getPerfCounters() const
{
    return mPerfCounters;
}

RenderPassPerfCounters &RenderPassHelper::getPerfCounters()
{
    return mPerfCounters;
}

// DescriptorSetDesc implementation.
void DescriptorSetDesc::updateWriteDesc(const WriteDescriptorDesc &writeDesc)
{
    uint32_t binding              = writeDesc.binding;
    WriteDescriptorDesc &destDesc = mWriteDescriptors[binding];

    ASSERT(writeDesc.descriptorCount > 0);

    destDesc = writeDesc;
}

void DescriptorSetDesc::updateDescriptorSet(UpdateDescriptorSetsBuilder *updateBuilder,
                                            const DescriptorDescHandles *handles,
                                            VkDescriptorSet descriptorSet) const
{
    for (uint32_t writeIndex = 0; writeIndex < static_cast<uint32_t>(mWriteDescriptors.size());
         ++writeIndex)
    {
        const WriteDescriptorDesc &writeDesc = mWriteDescriptors[writeIndex];

        if (writeDesc.descriptorCount == 0)
        {
            continue;
        }

        VkWriteDescriptorSet &writeSet = updateBuilder->allocWriteDescriptorSet();

        writeSet.descriptorCount  = writeDesc.descriptorCount;
        writeSet.descriptorType   = static_cast<VkDescriptorType>(writeDesc.descriptorType);
        writeSet.dstArrayElement  = 0;
        writeSet.dstBinding       = writeIndex;
        writeSet.dstSet           = descriptorSet;
        writeSet.pBufferInfo      = nullptr;
        writeSet.pImageInfo       = nullptr;
        writeSet.pNext            = nullptr;
        writeSet.pTexelBufferView = nullptr;
        writeSet.sType            = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;

        uint32_t infoDescIndex = writeDesc.descriptorInfoIndex;

        switch (writeSet.descriptorType)
        {
            case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
            case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
            {
                ASSERT(writeDesc.descriptorCount == 1);
                VkBufferView &bufferView  = updateBuilder->allocBufferView();
                bufferView                = handles[infoDescIndex].bufferView;
                writeSet.pTexelBufferView = &bufferView;
                break;
            }
            case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
            case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
            case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
            case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
            {
                VkDescriptorBufferInfo *writeBuffers =
                    updateBuilder->allocDescriptorBufferInfos(writeSet.descriptorCount);
                for (uint32_t arrayElement = 0; arrayElement < writeSet.descriptorCount;
                     ++arrayElement)
                {
                    const DescriptorInfoDesc &infoDesc =
                        mDescriptorInfos[infoDescIndex + arrayElement];
                    VkDescriptorBufferInfo &bufferInfo = writeBuffers[arrayElement];
                    bufferInfo.buffer = handles[infoDescIndex + arrayElement].buffer;
                    bufferInfo.offset = infoDesc.imageViewSerialOrOffset;
                    bufferInfo.range  = infoDesc.imageLayoutOrRange;
                }
                writeSet.pBufferInfo = writeBuffers;
                break;
            }
            case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
            case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
            case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
            case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
            {
                VkDescriptorImageInfo *writeImages =
                    updateBuilder->allocDescriptorImageInfos(writeSet.descriptorCount);
                for (uint32_t arrayElement = 0; arrayElement < writeSet.descriptorCount;
                     ++arrayElement)
                {
                    const DescriptorInfoDesc &infoDesc =
                        mDescriptorInfos[infoDescIndex + arrayElement];
                    VkDescriptorImageInfo &imageInfo = writeImages[arrayElement];

                    ImageLayout imageLayout = static_cast<ImageLayout>(infoDesc.imageLayoutOrRange);

                    imageInfo.imageLayout = ConvertImageLayoutToVkImageLayout(imageLayout);
                    imageInfo.imageView   = handles[infoDescIndex + arrayElement].imageView;
                    imageInfo.sampler     = handles[infoDescIndex + arrayElement].sampler;
                }
                writeSet.pImageInfo = writeImages;
                break;
            }

            default:
                UNREACHABLE();
                break;
        }
    }
}

void DescriptorSetDesc::streamOut(std::ostream &ostr) const
{
    ostr << mWriteDescriptors.size() << " write descriptor descs:\n";

    for (uint32_t index = 0; index < static_cast<uint32_t>(mWriteDescriptors.size()); ++index)
    {
        const WriteDescriptorDesc &writeDesc = mWriteDescriptors[index];
        ostr << static_cast<int>(writeDesc.binding) << ": "
             << static_cast<int>(writeDesc.descriptorCount) << " "
             << kDescriptorTypeNameMap[writeDesc.descriptorType] << " descriptors: ";

        for (uint32_t offset = 0; offset < writeDesc.descriptorCount; ++offset)
        {
            const DescriptorInfoDesc &infoDesc =
                mDescriptorInfos[writeDesc.descriptorInfoIndex + offset];
            ostr << "{" << infoDesc.imageLayoutOrRange << ", " << infoDesc.imageSubresourceRange
                 << ", " << infoDesc.imageViewSerialOrOffset << ", "
                 << infoDesc.samplerOrBufferSerial << "}";
        }

        ostr << "\n";
    }
}

// DescriptorSetDescBuilder implementation.
DescriptorSetDescBuilder::DescriptorSetDescBuilder() = default;

DescriptorSetDescBuilder::~DescriptorSetDescBuilder() = default;

DescriptorSetDescBuilder::DescriptorSetDescBuilder(const DescriptorSetDescBuilder &other)
    : mDesc(other.mDesc),
      mHandles(other.mHandles),
      mDynamicOffsets(other.mDynamicOffsets),
      mCurrentInfoIndex(other.mCurrentInfoIndex)
{}

DescriptorSetDescBuilder &DescriptorSetDescBuilder::operator=(const DescriptorSetDescBuilder &other)
{
    mDesc             = other.mDesc;
    mHandles          = other.mHandles;
    mDynamicOffsets   = other.mDynamicOffsets;
    mCurrentInfoIndex = other.mCurrentInfoIndex;
    return *this;
}

void DescriptorSetDescBuilder::reset()
{
    mDesc.reset();
    mHandles.clear();
    mDynamicOffsets.clear();
    mCurrentInfoIndex = 0;
}

void DescriptorSetDescBuilder::updateWriteDesc(uint32_t bindingIndex,
                                               VkDescriptorType descriptorType,
                                               uint32_t descriptorCount)
{
    if (mDesc.hasWriteDescAtIndex(bindingIndex))
    {
        uint32_t infoIndex          = mDesc.getInfoDescIndex(bindingIndex);
        uint32_t oldDescriptorCount = mDesc.getDescriptorSetCount(bindingIndex);
        if (descriptorCount != oldDescriptorCount)
        {
            ASSERT(infoIndex + oldDescriptorCount == mCurrentInfoIndex);
            ASSERT(descriptorCount > oldDescriptorCount);
            uint32_t additionalDescriptors = descriptorCount - oldDescriptorCount;
            mDesc.incrementDescriptorCount(bindingIndex, additionalDescriptors);
            mCurrentInfoIndex += additionalDescriptors;
        }
    }
    else
    {
        WriteDescriptorDesc writeDesc = {};
        SetBitField(writeDesc.binding, bindingIndex);
        SetBitField(writeDesc.descriptorCount, descriptorCount);
        SetBitField(writeDesc.descriptorType, descriptorType);
        SetBitField(writeDesc.descriptorInfoIndex, mCurrentInfoIndex);
        mCurrentInfoIndex += descriptorCount;

        mDesc.updateWriteDesc(writeDesc);
    }
}

void DescriptorSetDescBuilder::updateUniformWrite(uint32_t shaderStageCount)
{
    for (uint32_t shaderIndex = 0; shaderIndex < shaderStageCount; ++shaderIndex)
    {
        updateWriteDesc(shaderIndex, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1);
    }
}

void DescriptorSetDescBuilder::updateUniformBuffer(uint32_t bindingIndex,
                                                   const BufferHelper &bufferHelper,
                                                   VkDeviceSize bufferRange)
{
    DescriptorInfoDesc infoDesc = {};
    SetBitField(infoDesc.imageLayoutOrRange, bufferRange);
    infoDesc.samplerOrBufferSerial = bufferHelper.getBlockSerial().getValue();

    uint32_t infoIndex = mDesc.getInfoDescIndex(bindingIndex);

    mDesc.updateInfoDesc(infoIndex, infoDesc);
    mHandles[infoIndex].buffer = bufferHelper.getBuffer().getHandle();
}

void DescriptorSetDescBuilder::updateTransformFeedbackWrite(
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    uint32_t xfbBufferCount)
{
    updateWriteDesc(variableInfoMap.getXfbBufferBinding(0), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
                    xfbBufferCount);
}

void DescriptorSetDescBuilder::updateTransformFeedbackBuffer(
    const Context *context,
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    uint32_t xfbBufferIndex,
    const BufferHelper &bufferHelper,
    VkDeviceSize bufferOffset,
    VkDeviceSize bufferRange)
{
    uint32_t baseBinding = variableInfoMap.getXfbBufferBinding(0);

    RendererVk *renderer = context->getRenderer();
    VkDeviceSize offsetAlignment =
        renderer->getPhysicalDeviceProperties().limits.minStorageBufferOffsetAlignment;
    // Set the offset as close as possible to the requested offset while remaining aligned.
    VkDeviceSize alignedOffset = (bufferOffset / offsetAlignment) * offsetAlignment;
    VkDeviceSize adjustedRange = bufferRange + (bufferOffset - alignedOffset);

    DescriptorInfoDesc infoDesc = {};
    SetBitField(infoDesc.imageLayoutOrRange, adjustedRange);
    SetBitField(infoDesc.imageViewSerialOrOffset, alignedOffset);
    infoDesc.samplerOrBufferSerial = bufferHelper.getBlockSerial().getValue();

    uint32_t infoIndex = mDesc.getInfoDescIndex(baseBinding) + xfbBufferIndex;

    mDesc.updateInfoDesc(infoIndex, infoDesc);
    mHandles[infoIndex].buffer = bufferHelper.getBuffer().getHandle();
}

void DescriptorSetDescBuilder::updateUniformsAndXfb(Context *context,
                                                    const gl::ProgramExecutable &executable,
                                                    const ProgramExecutableVk &executableVk,
                                                    const BufferHelper *currentUniformBuffer,
                                                    const BufferHelper &emptyBuffer,
                                                    bool activeUnpaused,
                                                    TransformFeedbackVk *transformFeedbackVk)
{
    gl::ShaderBitSet linkedStages = executable.getLinkedShaderStages();

    const ShaderInterfaceVariableInfoMap &variableInfoMap = executableVk.getVariableInfoMap();

    for (const gl::ShaderType shaderType : linkedStages)
    {
        uint32_t binding = variableInfoMap.getDefaultUniformBinding(shaderType);
        updateWriteDesc(binding, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1);

        VkDeviceSize bufferRange = executableVk.getDefaultUniformAlignedSize(context, shaderType);
        if (bufferRange == 0)
        {
            updateUniformBuffer(binding, emptyBuffer, emptyBuffer.getSize());
        }
        else
        {
            ASSERT(currentUniformBuffer);
            updateUniformBuffer(binding, *currentUniformBuffer, bufferRange);
        }

        if (transformFeedbackVk && shaderType == gl::ShaderType::Vertex &&
            context->getRenderer()->getFeatures().emulateTransformFeedback.enabled)
        {
            transformFeedbackVk->updateTransformFeedbackDescriptorDesc(
                context, executable, variableInfoMap, emptyBuffer, activeUnpaused, this);
        }
    }
}

void UpdatePreCacheActiveTextures(const gl::ActiveTextureMask &activeTextures,
                                  const gl::ActiveTextureArray<TextureVk *> &textures,
                                  const gl::SamplerBindingVector &samplers,
                                  DescriptorSetDesc *desc)
{
    desc->reset();

    for (size_t textureIndex : activeTextures)
    {
        TextureVk *textureVk = textures[textureIndex];

        DescriptorInfoDesc infoDesc = {};

        if (textureVk->getState().getType() == gl::TextureType::Buffer)
        {
            ImageOrBufferViewSubresourceSerial imageViewSerial = textureVk->getBufferViewSerial();
            infoDesc.imageViewSerialOrOffset = imageViewSerial.viewSerial.getValue();
        }
        else
        {
            gl::Sampler *sampler       = samplers[textureIndex].get();
            const SamplerVk *samplerVk = sampler ? vk::GetImpl(sampler) : nullptr;

            const SamplerHelper &samplerHelper =
                samplerVk ? samplerVk->getSampler() : textureVk->getSampler();
            const gl::SamplerState &samplerState =
                sampler ? sampler->getSamplerState() : textureVk->getState().getSamplerState();

            ImageOrBufferViewSubresourceSerial imageViewSerial =
                textureVk->getImageViewSubresourceSerial(samplerState);

            // Layout is implicit.

            infoDesc.imageViewSerialOrOffset = imageViewSerial.viewSerial.getValue();
            infoDesc.samplerOrBufferSerial   = samplerHelper.getSamplerSerial().getValue();
            memcpy(&infoDesc.imageSubresourceRange, &imageViewSerial.subresource, sizeof(uint32_t));
        }

        desc->updateInfoDesc(static_cast<uint32_t>(textureIndex), infoDesc);
    }
}

angle::Result DescriptorSetDescBuilder::updateFullActiveTextures(
    Context *context,
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    const gl::ProgramExecutable &executable,
    const gl::ActiveTextureArray<TextureVk *> &textures,
    const gl::SamplerBindingVector &samplers,
    bool emulateSeamfulCubeMapSampling,
    PipelineType pipelineType)
{
    reset();
    for (gl::ShaderType shaderType : executable.getLinkedShaderStages())
    {
        ANGLE_TRY(updateExecutableActiveTexturesForShader(
            context, shaderType, variableInfoMap, executable, textures, samplers,
            emulateSeamfulCubeMapSampling, pipelineType));
    }

    return angle::Result::Continue;
}

angle::Result DescriptorSetDescBuilder::updateExecutableActiveTexturesForShader(
    Context *context,
    gl::ShaderType shaderType,
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    const gl::ProgramExecutable &executable,
    const gl::ActiveTextureArray<TextureVk *> &textures,
    const gl::SamplerBindingVector &samplers,
    bool emulateSeamfulCubeMapSampling,
    PipelineType pipelineType)
{
    const std::vector<gl::SamplerBinding> &samplerBindings = executable.getSamplerBindings();
    const std::vector<gl::LinkedUniform> &uniforms         = executable.getUniforms();
    const gl::ActiveTextureTypeArray &textureTypes         = executable.getActiveSamplerTypes();

    for (uint32_t textureIndex = 0; textureIndex < samplerBindings.size(); ++textureIndex)
    {
        const gl::SamplerBinding &samplerBinding = samplerBindings[textureIndex];
        uint32_t uniformIndex = executable.getUniformIndexFromSamplerIndex(textureIndex);
        const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];

        if (!samplerUniform.isActive(shaderType))
        {
            continue;
        }

        const ShaderInterfaceVariableInfo &info = variableInfoMap.getIndexedVariableInfo(
            shaderType, ShaderVariableType::Texture, textureIndex);
        if (info.isDuplicate)
        {
            continue;
        }

        uint32_t arraySize       = static_cast<uint32_t>(samplerBinding.boundTextureUnits.size());
        uint32_t descriptorCount = arraySize * gl::ArraySizeProduct(samplerUniform.outerArraySizes);
        VkDescriptorType descriptorType = (samplerBinding.textureType == gl::TextureType::Buffer)
                                              ? VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER
                                              : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;

        updateWriteDesc(info.binding, descriptorType, descriptorCount);

        for (uint32_t arrayElement = 0; arrayElement < arraySize; ++arrayElement)
        {
            GLuint textureUnit   = samplerBinding.boundTextureUnits[arrayElement];
            TextureVk *textureVk = textures[textureUnit];

            DescriptorInfoDesc infoDesc = {};

            uint32_t infoIndex = mDesc.getInfoDescIndex(info.binding) + arrayElement +
                                 samplerUniform.outerArrayOffset;

            if (textureTypes[textureUnit] == gl::TextureType::Buffer)
            {
                ImageOrBufferViewSubresourceSerial imageViewSerial =
                    textureVk->getBufferViewSerial();
                infoDesc.imageViewSerialOrOffset = imageViewSerial.viewSerial.getValue();

                const BufferView *view = nullptr;
                ANGLE_TRY(textureVk->getBufferViewAndRecordUse(context, nullptr, false, &view));
                mHandles[infoIndex].bufferView = view->getHandle();
            }
            else
            {
                gl::Sampler *sampler       = samplers[textureUnit].get();
                const SamplerVk *samplerVk = sampler ? vk::GetImpl(sampler) : nullptr;

                const SamplerHelper &samplerHelper =
                    samplerVk ? samplerVk->getSampler() : textureVk->getSampler();
                const gl::SamplerState &samplerState =
                    sampler ? sampler->getSamplerState() : textureVk->getState().getSamplerState();

                ImageOrBufferViewSubresourceSerial imageViewSerial =
                    textureVk->getImageViewSubresourceSerial(samplerState);

                ImageLayout imageLayout = textureVk->getImage().getCurrentImageLayout();

                SetBitField(infoDesc.imageLayoutOrRange, imageLayout);
                infoDesc.imageViewSerialOrOffset = imageViewSerial.viewSerial.getValue();
                infoDesc.samplerOrBufferSerial   = samplerHelper.getSamplerSerial().getValue();
                memcpy(&infoDesc.imageSubresourceRange, &imageViewSerial.subresource,
                       sizeof(uint32_t));

                mHandles[infoIndex].sampler = samplerHelper.get().getHandle();

                if (emulateSeamfulCubeMapSampling)
                {
                    // If emulating seamful cube mapping, use the fetch image view.  This is
                    // basically the same image view as read, except it's a 2DArray view for
                    // cube maps.
                    const ImageView &imageView = textureVk->getFetchImageView(
                        context, samplerState.getSRGBDecode(), samplerUniform.texelFetchStaticUse);
                    mHandles[infoIndex].imageView = imageView.getHandle();
                }
                else
                {
                    const ImageView &imageView = textureVk->getReadImageView(
                        context, samplerState.getSRGBDecode(), samplerUniform.texelFetchStaticUse);
                    mHandles[infoIndex].imageView = imageView.getHandle();
                }
            }

            mDesc.updateInfoDesc(infoIndex, infoDesc);
        }
    }

    return angle::Result::Continue;
}

void DescriptorSetDescBuilder::updateShaderBuffers(
    gl::ShaderType shaderType,
    ShaderVariableType variableType,
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    const gl::BufferVector &buffers,
    const std::vector<gl::InterfaceBlock> &blocks,
    VkDescriptorType descriptorType,
    VkDeviceSize maxBoundBufferRange,
    const BufferHelper &emptyBuffer)
{
    // Initialize the descriptor writes in a first pass. This ensures we can pack the structures
    // corresponding to array elements tightly.
    for (uint32_t bufferIndex = 0; bufferIndex < blocks.size(); ++bufferIndex)
    {
        const gl::InterfaceBlock &block = blocks[bufferIndex];

        if (!block.isActive(shaderType))
        {
            continue;
        }

        const ShaderInterfaceVariableInfo &info =
            variableInfoMap.getIndexedVariableInfo(shaderType, variableType, bufferIndex);
        if (info.isDuplicate)
        {
            continue;
        }

        if (block.isArray && block.arrayElement > 0)
        {
            mDesc.incrementDescriptorCount(info.binding, 1);
            mCurrentInfoIndex++;
        }
        else
        {
            updateWriteDesc(info.binding, descriptorType, 1);
        }
    }

    // Now that we have the proper array elements counts, initialize the info structures.
    for (uint32_t bufferIndex = 0; bufferIndex < blocks.size(); ++bufferIndex)
    {
        const gl::InterfaceBlock &block                           = blocks[bufferIndex];
        const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding = buffers[block.binding];

        if (!block.isActive(shaderType))
        {
            continue;
        }

        const ShaderInterfaceVariableInfo &info =
            variableInfoMap.getIndexedVariableInfo(shaderType, variableType, bufferIndex);
        if (info.isDuplicate)
        {
            continue;
        }

        uint32_t binding       = info.binding;
        uint32_t arrayElement  = block.isArray ? block.arrayElement : 0;
        uint32_t infoDescIndex = mDesc.getInfoDescIndex(binding) + arrayElement;

        if (bufferBinding.get() == nullptr)
        {
            DescriptorInfoDesc emptyDesc = {};
            SetBitField(emptyDesc.imageLayoutOrRange, emptyBuffer.getSize());
            emptyDesc.imageViewSerialOrOffset = 0;
            emptyDesc.samplerOrBufferSerial   = emptyBuffer.getBlockSerial().getValue();

            mDesc.updateInfoDesc(infoDescIndex, emptyDesc);

            mHandles[infoDescIndex].buffer = emptyBuffer.getBuffer().getHandle();

            if (IsDynamicDescriptor(descriptorType))
            {
                mDynamicOffsets[infoDescIndex] = 0;
            }
        }
        else
        {
            // Limit bound buffer size to maximum resource binding size.
            GLsizeiptr boundBufferSize = gl::GetBoundBufferAvailableSize(bufferBinding);
            VkDeviceSize size = std::min<VkDeviceSize>(boundBufferSize, maxBoundBufferRange);

            // Make sure there's no possible under/overflow with binding size.
            static_assert(sizeof(VkDeviceSize) >= sizeof(bufferBinding.getSize()),
                          "VkDeviceSize too small");
            ASSERT(bufferBinding.getSize() >= 0);

            BufferVk *bufferVk         = vk::GetImpl(bufferBinding.get());
            BufferHelper &bufferHelper = bufferVk->getBuffer();

            DescriptorInfoDesc infoDesc = {};
            SetBitField(infoDesc.imageLayoutOrRange, size);
            infoDesc.samplerOrBufferSerial = bufferHelper.getBlockSerial().getValue();

            VkDeviceSize offset = bufferBinding.getOffset() + bufferHelper.getOffset();

            if (IsDynamicDescriptor(descriptorType))
            {
                SetBitField(mDynamicOffsets[infoDescIndex], offset);
            }
            else
            {
                SetBitField(infoDesc.imageViewSerialOrOffset, offset);
            }

            mDesc.updateInfoDesc(infoDescIndex, infoDesc);

            mHandles[infoDescIndex].buffer = bufferHelper.getBuffer().getHandle();
        }
    }
}

void DescriptorSetDescBuilder::updateAtomicCounters(
    gl::ShaderType shaderType,
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    const gl::BufferVector &buffers,
    const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers,
    const VkDeviceSize requiredOffsetAlignment,
    vk::BufferHelper *emptyBuffer)
{
    if (atomicCounterBuffers.empty() || !variableInfoMap.hasAtomicCounterInfo(shaderType))
    {
        return;
    }

    static_assert(!IsDynamicDescriptor(kStorageBufferDescriptorType),
                  "This method needs an update to handle dynamic descriptors");

    uint32_t binding = variableInfoMap.getAtomicCounterBufferBinding(shaderType, 0);

    updateWriteDesc(binding, kStorageBufferDescriptorType,
                    gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS);

    uint32_t baseInfoIndex = mDesc.getInfoDescIndex(binding);

    DescriptorInfoDesc emptyDesc = {};
    SetBitField(emptyDesc.imageLayoutOrRange, emptyBuffer->getSize());
    emptyDesc.imageViewSerialOrOffset = 0;
    emptyDesc.samplerOrBufferSerial   = emptyBuffer->getBlockSerial().getValue();

    // Bind the empty buffer to every array slot that's unused.
    for (uint32_t arrayElement = 0;
         arrayElement < gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS; ++arrayElement)
    {
        uint32_t infoIndex = baseInfoIndex + arrayElement;
        mDesc.updateInfoDesc(infoIndex, emptyDesc);
        mHandles[infoIndex].buffer = emptyBuffer->getBuffer().getHandle();
    }

    for (const gl::AtomicCounterBuffer &atomicCounterBuffer : atomicCounterBuffers)
    {
        int arrayElement                                          = atomicCounterBuffer.binding;
        const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding = buffers[arrayElement];

        uint32_t infoIndex = baseInfoIndex + arrayElement;

        if (bufferBinding.get() == nullptr)
        {
            mDesc.updateInfoDesc(infoIndex, emptyDesc);
            mHandles[infoIndex].buffer = emptyBuffer->getBuffer().getHandle();
            continue;
        }

        BufferVk *bufferVk             = vk::GetImpl(bufferBinding.get());
        vk::BufferHelper &bufferHelper = bufferVk->getBuffer();

        VkDeviceSize offset = bufferBinding.getOffset() + bufferHelper.getOffset();

        VkDeviceSize alignedOffset = (offset / requiredOffsetAlignment) * requiredOffsetAlignment;
        VkDeviceSize offsetDiff    = offset - alignedOffset;

        offset = alignedOffset;

        VkDeviceSize range = gl::GetBoundBufferAvailableSize(bufferBinding) + offsetDiff;

        DescriptorInfoDesc infoDesc = {};
        SetBitField(infoDesc.imageLayoutOrRange, range);
        SetBitField(infoDesc.imageViewSerialOrOffset, offset);
        infoDesc.samplerOrBufferSerial = bufferHelper.getBlockSerial().getValue();

        mDesc.updateInfoDesc(infoIndex, infoDesc);
        mHandles[infoIndex].buffer = bufferHelper.getBuffer().getHandle();
    }
}

angle::Result DescriptorSetDescBuilder::updateImages(
    Context *context,
    gl::ShaderType shaderType,
    const gl::ProgramExecutable &executable,
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    const gl::ActiveTextureArray<TextureVk *> &activeImages,
    const std::vector<gl::ImageUnit> &imageUnits)
{
    RendererVk *renderer                               = context->getRenderer();
    const std::vector<gl::ImageBinding> &imageBindings = executable.getImageBindings();
    const std::vector<gl::LinkedUniform> &uniforms     = executable.getUniforms();

    if (imageBindings.empty())
    {
        return angle::Result::Continue;
    }

    for (uint32_t imageIndex = 0; imageIndex < imageBindings.size(); ++imageIndex)
    {
        const gl::ImageBinding &imageBinding = imageBindings[imageIndex];
        uint32_t uniformIndex                = executable.getUniformIndexFromImageIndex(imageIndex);
        const gl::LinkedUniform &imageUniform = uniforms[uniformIndex];

        if (!imageUniform.isActive(shaderType))
        {
            continue;
        }

        const ShaderInterfaceVariableInfo &info = variableInfoMap.getIndexedVariableInfo(
            shaderType, ShaderVariableType::Image, imageIndex);
        if (info.isDuplicate)
        {
            continue;
        }

        uint32_t arraySize       = static_cast<uint32_t>(imageBinding.boundImageUnits.size());
        uint32_t descriptorCount = arraySize * gl::ArraySizeProduct(imageUniform.outerArraySizes);
        VkDescriptorType descriptorType = (imageBinding.textureType == gl::TextureType::Buffer)
                                              ? VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
                                              : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;

        updateWriteDesc(info.binding, descriptorType, descriptorCount);

        // Texture buffers use buffer views, so they are especially handled.
        if (imageBinding.textureType == gl::TextureType::Buffer)
        {
            // Handle format reinterpretation by looking for a view with the format specified in
            // the shader (if any, instead of the format specified to glTexBuffer).
            const vk::Format *format = nullptr;
            if (imageUniform.imageUnitFormat != GL_NONE)
            {
                format = &renderer->getFormat(imageUniform.imageUnitFormat);
            }

            for (uint32_t arrayElement = 0; arrayElement < arraySize; ++arrayElement)
            {
                GLuint imageUnit     = imageBinding.boundImageUnits[arrayElement];
                TextureVk *textureVk = activeImages[imageUnit];

                uint32_t infoIndex = mDesc.getInfoDescIndex(info.binding) + arrayElement +
                                     imageUniform.outerArrayOffset;

                const vk::BufferView *view = nullptr;
                ANGLE_TRY(textureVk->getBufferViewAndRecordUse(context, format, true, &view));

                DescriptorInfoDesc infoDesc = {};
                infoDesc.imageViewSerialOrOffset =
                    textureVk->getBufferViewSerial().viewSerial.getValue();

                mDesc.updateInfoDesc(infoIndex, infoDesc);
                mHandles[infoIndex].bufferView = view->getHandle();
            }
        }
        else
        {
            for (uint32_t arrayElement = 0; arrayElement < arraySize; ++arrayElement)
            {
                GLuint imageUnit             = imageBinding.boundImageUnits[arrayElement];
                const gl::ImageUnit &binding = imageUnits[imageUnit];
                TextureVk *textureVk         = activeImages[imageUnit];

                vk::ImageHelper *image         = &textureVk->getImage();
                const vk::ImageView *imageView = nullptr;

                vk::ImageOrBufferViewSubresourceSerial serial =
                    textureVk->getStorageImageViewSerial(binding);

                ANGLE_TRY(textureVk->getStorageImageView(context, binding, &imageView));

                uint32_t infoIndex = mDesc.getInfoDescIndex(info.binding) + arrayElement +
                                     imageUniform.outerArrayOffset;

                // Note: binding.access is unused because it is implied by the shader.

                DescriptorInfoDesc infoDesc = {};
                SetBitField(infoDesc.imageLayoutOrRange, image->getCurrentImageLayout());
                memcpy(&infoDesc.imageSubresourceRange, &serial.subresource, sizeof(uint32_t));
                infoDesc.imageViewSerialOrOffset = serial.viewSerial.getValue();

                mDesc.updateInfoDesc(infoIndex, infoDesc);
                mHandles[infoIndex].imageView = imageView->getHandle();
            }
        }
    }

    return angle::Result::Continue;
}

angle::Result DescriptorSetDescBuilder::updateInputAttachments(
    vk::Context *context,
    gl::ShaderType shaderType,
    const gl::ProgramExecutable &executable,
    const ShaderInterfaceVariableInfoMap &variableInfoMap,
    FramebufferVk *framebufferVk)
{
    if (shaderType != gl::ShaderType::Fragment)
    {
        return angle::Result::Continue;
    }

    const std::vector<gl::LinkedUniform> &uniforms = executable.getUniforms();

    if (!executable.usesFramebufferFetch())
    {
        return angle::Result::Continue;
    }

    const uint32_t baseUniformIndex              = executable.getFragmentInoutRange().low();
    const gl::LinkedUniform &baseInputAttachment = uniforms.at(baseUniformIndex);
    std::string baseMappedName                   = baseInputAttachment.mappedName;

    const ShaderInterfaceVariableInfo &baseInfo =
        variableInfoMap.getFramebufferFetchInfo(shaderType);
    if (baseInfo.isDuplicate)
    {
        return angle::Result::Continue;
    }

    uint32_t baseBinding = baseInfo.binding - baseInputAttachment.location;

    for (size_t colorIndex : framebufferVk->getState().getColorAttachmentsMask())
    {
        uint32_t binding = baseBinding + static_cast<uint32_t>(colorIndex);
        updateWriteDesc(binding, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1);

        RenderTargetVk *renderTargetVk = framebufferVk->getColorDrawRenderTarget(colorIndex);
        const vk::ImageView *imageView = nullptr;

        ANGLE_TRY(renderTargetVk->getImageView(context, &imageView));

        uint32_t infoIndex = mDesc.getInfoDescIndex(binding);

        DescriptorInfoDesc infoDesc = {};

        // We just need any layout that represents GENERAL. The serial is also not totally precise.
        ImageOrBufferViewSubresourceSerial serial = renderTargetVk->getDrawSubresourceSerial();
        SetBitField(infoDesc.imageLayoutOrRange, ImageLayout::FragmentShaderWrite);
        infoDesc.imageViewSerialOrOffset = serial.viewSerial.getValue();
        memcpy(&infoDesc.imageSubresourceRange, &serial.subresource, sizeof(uint32_t));

        mDesc.updateInfoDesc(infoIndex, infoDesc);
        mHandles[infoIndex].imageView = imageView->getHandle();
    }

    return angle::Result::Continue;
}

void DescriptorSetDescBuilder::updateDescriptorSet(UpdateDescriptorSetsBuilder *updateBuilder,
                                                   VkDescriptorSet descriptorSet) const
{
    mDesc.updateDescriptorSet(updateBuilder, mHandles.data(), descriptorSet);
}
}  // namespace vk

// RenderPassCache implementation.
RenderPassCache::RenderPassCache() = default;

RenderPassCache::~RenderPassCache()
{
    ASSERT(mPayload.empty());
}

void RenderPassCache::destroy(RendererVk *rendererVk)
{
    rendererVk->accumulateCacheStats(VulkanCacheType::CompatibleRenderPass,
                                     mCompatibleRenderPassCacheStats);
    rendererVk->accumulateCacheStats(VulkanCacheType::RenderPassWithOps,
                                     mRenderPassWithOpsCacheStats);

    VkDevice device = rendererVk->getDevice();

    for (auto &outerIt : mPayload)
    {
        for (auto &innerIt : outerIt.second)
        {
            innerIt.second.destroy(device);
        }
    }
    mPayload.clear();
}

angle::Result RenderPassCache::addRenderPass(ContextVk *contextVk,
                                             const vk::RenderPassDesc &desc,
                                             vk::RenderPass **renderPassOut)
{
    // Insert some placeholder attachment ops.  Note that render passes with different ops are still
    // compatible. The load/store values are not important as they are aren't used for real RPs.
    //
    // It would be nice to pre-populate the cache in the Renderer so we rarely miss here.
    vk::AttachmentOpsArray ops;

    vk::PackedAttachmentIndex colorIndexVk(0);
    for (uint32_t colorIndexGL = 0; colorIndexGL < desc.colorAttachmentRange(); ++colorIndexGL)
    {
        if (!desc.isColorAttachmentEnabled(colorIndexGL))
        {
            continue;
        }

        ops.initWithLoadStore(colorIndexVk, vk::ImageLayout::ColorAttachment,
                              vk::ImageLayout::ColorAttachment);
        ++colorIndexVk;
    }

    if (desc.hasDepthStencilAttachment())
    {
        // This API is only called by getCompatibleRenderPass(). What we need here is to create a
        // compatible renderpass with the desc. Vulkan spec says image layout are not counted toward
        // render pass compatibility: "Two render passes are compatible if their corresponding
        // color, input, resolve, and depth/stencil attachment references are compatible and if they
        // are otherwise identical except for: Initial and final image layout in attachment
        // descriptions; Image layout in attachment references". We pick the most used layout here
        // since it doesn't matter.
        vk::ImageLayout imageLayout = vk::ImageLayout::DepthStencilAttachment;
        ops.initWithLoadStore(colorIndexVk, imageLayout, imageLayout);
    }

    return getRenderPassWithOpsImpl(contextVk, desc, ops, false, renderPassOut);
}

angle::Result RenderPassCache::getRenderPassWithOps(ContextVk *contextVk,
                                                    const vk::RenderPassDesc &desc,
                                                    const vk::AttachmentOpsArray &attachmentOps,
                                                    vk::RenderPass **renderPassOut)
{
    return getRenderPassWithOpsImpl(contextVk, desc, attachmentOps, true, renderPassOut);
}

angle::Result RenderPassCache::getRenderPassWithOpsImpl(ContextVk *contextVk,
                                                        const vk::RenderPassDesc &desc,
                                                        const vk::AttachmentOpsArray &attachmentOps,
                                                        bool updatePerfCounters,
                                                        vk::RenderPass **renderPassOut)
{
    auto outerIt = mPayload.find(desc);
    if (outerIt != mPayload.end())
    {
        InnerCache &innerCache = outerIt->second;

        auto innerIt = innerCache.find(attachmentOps);
        if (innerIt != innerCache.end())
        {
            // TODO(jmadill): Could possibly use an MRU cache here.
            vk::GetRenderPassAndUpdateCounters(contextVk, updatePerfCounters, &innerIt->second,
                                               renderPassOut);
            mRenderPassWithOpsCacheStats.hit();
            return angle::Result::Continue;
        }
    }
    else
    {
        auto emplaceResult = mPayload.emplace(desc, InnerCache());
        outerIt            = emplaceResult.first;
    }

    mRenderPassWithOpsCacheStats.missAndIncrementSize();
    vk::RenderPassHelper newRenderPass;
    ANGLE_TRY(vk::InitializeRenderPassFromDesc(contextVk, desc, attachmentOps, &newRenderPass));

    InnerCache &innerCache = outerIt->second;
    auto insertPos         = innerCache.emplace(attachmentOps, std::move(newRenderPass));
    vk::GetRenderPassAndUpdateCounters(contextVk, updatePerfCounters, &insertPos.first->second,
                                       renderPassOut);

    // TODO(jmadill): Trim cache, and pre-populate with the most common RPs on startup.
    return angle::Result::Continue;
}

// GraphicsPipelineCache implementation.
GraphicsPipelineCache::GraphicsPipelineCache() = default;

GraphicsPipelineCache::~GraphicsPipelineCache()
{
    ASSERT(mPayload.empty());
}

void GraphicsPipelineCache::destroy(RendererVk *rendererVk)
{
    accumulateCacheStats(rendererVk);

    VkDevice device = rendererVk->getDevice();

    for (auto &item : mPayload)
    {
        vk::PipelineHelper &pipeline = item.second;
        pipeline.destroy(device);
    }

    mPayload.clear();
}

void GraphicsPipelineCache::release(ContextVk *context)
{
    if (kDumpPipelineCacheGraph && !mPayload.empty())
    {
        vk::DumpPipelineCacheGraph(context->getPipelineCacheGraphStream(), mPayload);
    }

    for (auto &item : mPayload)
    {
        vk::PipelineHelper &pipeline = item.second;
        context->addGarbage(&pipeline.getPipeline());
    }

    mPayload.clear();
}

void GraphicsPipelineCache::reset()
{
    mPayload.clear();
}

angle::Result GraphicsPipelineCache::insertPipeline(
    ContextVk *contextVk,
    const vk::PipelineCache &pipelineCacheVk,
    const vk::RenderPass &compatibleRenderPass,
    const vk::PipelineLayout &pipelineLayout,
    const gl::AttributesMask &activeAttribLocationsMask,
    const gl::ComponentTypeMask &programAttribsTypeMask,
    const gl::DrawBufferMask &missingOutputsMask,
    const vk::ShaderAndSerialMap &shaders,
    const vk::SpecializationConstants &specConsts,
    const vk::GraphicsPipelineDesc &desc,
    const vk::GraphicsPipelineDesc **descPtrOut,
    vk::PipelineHelper **pipelineOut)
{
    vk::Pipeline newPipeline;

    // This "if" is left here for the benefit of VulkanPipelineCachePerfTest.
    if (contextVk != nullptr)
    {
        ANGLE_TRY(desc.initializePipeline(contextVk, pipelineCacheVk, compatibleRenderPass,
                                          pipelineLayout, activeAttribLocationsMask,
                                          programAttribsTypeMask, missingOutputsMask, shaders,
                                          specConsts, &newPipeline));
    }

    // The Serial will be updated outside of this query.
    auto insertedItem = mPayload.emplace(desc, std::move(newPipeline));
    *descPtrOut       = &insertedItem.first->first;
    *pipelineOut      = &insertedItem.first->second;

    return angle::Result::Continue;
}

void GraphicsPipelineCache::populate(const vk::GraphicsPipelineDesc &desc, vk::Pipeline &&pipeline)
{
    auto item = mPayload.find(desc);
    if (item != mPayload.end())
    {
        return;
    }

    mPayload.emplace(desc, std::move(pipeline));
}

// DescriptorSetLayoutCache implementation.
DescriptorSetLayoutCache::DescriptorSetLayoutCache() = default;

DescriptorSetLayoutCache::~DescriptorSetLayoutCache()
{
    ASSERT(mPayload.empty());
}

void DescriptorSetLayoutCache::destroy(RendererVk *rendererVk)
{
    rendererVk->accumulateCacheStats(VulkanCacheType::DescriptorSetLayout, mCacheStats);

    VkDevice device = rendererVk->getDevice();

    for (auto &item : mPayload)
    {
        vk::RefCountedDescriptorSetLayout &layout = item.second;
        ASSERT(!layout.isReferenced());
        layout.get().destroy(device);
    }

    mPayload.clear();
}

angle::Result DescriptorSetLayoutCache::getDescriptorSetLayout(
    vk::Context *context,
    const vk::DescriptorSetLayoutDesc &desc,
    vk::BindingPointer<vk::DescriptorSetLayout> *descriptorSetLayoutOut)
{
    auto iter = mPayload.find(desc);
    if (iter != mPayload.end())
    {
        vk::RefCountedDescriptorSetLayout &layout = iter->second;
        descriptorSetLayoutOut->set(&layout);
        mCacheStats.hit();
        return angle::Result::Continue;
    }

    mCacheStats.missAndIncrementSize();
    // We must unpack the descriptor set layout description.
    vk::DescriptorSetLayoutBindingVector bindingVector;
    std::vector<VkSampler> immutableSamplers;
    desc.unpackBindings(&bindingVector, &immutableSamplers);

    VkDescriptorSetLayoutCreateInfo createInfo = {};
    createInfo.sType        = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    createInfo.flags        = 0;
    createInfo.bindingCount = static_cast<uint32_t>(bindingVector.size());
    createInfo.pBindings    = bindingVector.data();

    vk::DescriptorSetLayout newLayout;
    ANGLE_VK_TRY(context, newLayout.init(context->getDevice(), createInfo));

    auto insertedItem =
        mPayload.emplace(desc, vk::RefCountedDescriptorSetLayout(std::move(newLayout)));
    vk::RefCountedDescriptorSetLayout &insertedLayout = insertedItem.first->second;
    descriptorSetLayoutOut->set(&insertedLayout);

    return angle::Result::Continue;
}

// PipelineLayoutCache implementation.
PipelineLayoutCache::PipelineLayoutCache() = default;

PipelineLayoutCache::~PipelineLayoutCache()
{
    ASSERT(mPayload.empty());
}

void PipelineLayoutCache::destroy(RendererVk *rendererVk)
{
    accumulateCacheStats(rendererVk);

    VkDevice device = rendererVk->getDevice();

    for (auto &item : mPayload)
    {
        vk::RefCountedPipelineLayout &layout = item.second;
        layout.get().destroy(device);
    }

    mPayload.clear();
}

angle::Result PipelineLayoutCache::getPipelineLayout(
    vk::Context *context,
    const vk::PipelineLayoutDesc &desc,
    const vk::DescriptorSetLayoutPointerArray &descriptorSetLayouts,
    vk::BindingPointer<vk::PipelineLayout> *pipelineLayoutOut)
{
    auto iter = mPayload.find(desc);
    if (iter != mPayload.end())
    {
        vk::RefCountedPipelineLayout &layout = iter->second;
        pipelineLayoutOut->set(&layout);
        mCacheStats.hit();
        return angle::Result::Continue;
    }

    mCacheStats.missAndIncrementSize();
    // Note this does not handle gaps in descriptor set layouts gracefully.
    angle::FixedVector<VkDescriptorSetLayout, vk::kMaxDescriptorSetLayouts> setLayoutHandles;
    for (const vk::BindingPointer<vk::DescriptorSetLayout> &layoutPtr : descriptorSetLayouts)
    {
        if (layoutPtr.valid())
        {
            VkDescriptorSetLayout setLayout = layoutPtr.get().getHandle();
            if (setLayout != VK_NULL_HANDLE)
            {
                setLayoutHandles.push_back(setLayout);
            }
        }
    }

    const vk::PackedPushConstantRange &descPushConstantRange = desc.getPushConstantRange();
    VkPushConstantRange pushConstantRange;
    pushConstantRange.stageFlags = descPushConstantRange.stageMask;
    pushConstantRange.offset     = descPushConstantRange.offset;
    pushConstantRange.size       = descPushConstantRange.size;

    // No pipeline layout found. We must create a new one.
    VkPipelineLayoutCreateInfo createInfo = {};
    createInfo.sType                      = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    createInfo.flags                      = 0;
    createInfo.setLayoutCount             = static_cast<uint32_t>(setLayoutHandles.size());
    createInfo.pSetLayouts                = setLayoutHandles.data();
    if (pushConstantRange.size > 0)
    {
        createInfo.pushConstantRangeCount = 1;
        createInfo.pPushConstantRanges    = &pushConstantRange;
    }

    vk::PipelineLayout newLayout;
    ANGLE_VK_TRY(context, newLayout.init(context->getDevice(), createInfo));

    auto insertedItem = mPayload.emplace(desc, vk::RefCountedPipelineLayout(std::move(newLayout)));
    vk::RefCountedPipelineLayout &insertedLayout = insertedItem.first->second;
    pipelineLayoutOut->set(&insertedLayout);

    return angle::Result::Continue;
}

// YuvConversionCache implementation
SamplerYcbcrConversionCache::SamplerYcbcrConversionCache() = default;

SamplerYcbcrConversionCache::~SamplerYcbcrConversionCache()
{
    ASSERT(mExternalFormatPayload.empty() && mVkFormatPayload.empty());
}

void SamplerYcbcrConversionCache::destroy(RendererVk *rendererVk)
{
    rendererVk->accumulateCacheStats(VulkanCacheType::SamplerYcbcrConversion, mCacheStats);

    VkDevice device = rendererVk->getDevice();

    for (auto &iter : mExternalFormatPayload)
    {
        vk::SamplerYcbcrConversion &samplerYcbcrConversion = iter.second;
        samplerYcbcrConversion.destroy(device);

        rendererVk->onDeallocateHandle(vk::HandleType::SamplerYcbcrConversion);
    }

    for (auto &iter : mVkFormatPayload)
    {
        vk::SamplerYcbcrConversion &samplerYcbcrConversion = iter.second;
        samplerYcbcrConversion.destroy(device);

        rendererVk->onDeallocateHandle(vk::HandleType::SamplerYcbcrConversion);
    }

    mExternalFormatPayload.clear();
    mVkFormatPayload.clear();
}

angle::Result SamplerYcbcrConversionCache::getSamplerYcbcrConversion(
    vk::Context *context,
    const vk::YcbcrConversionDesc &ycbcrConversionDesc,
    VkSamplerYcbcrConversion *vkSamplerYcbcrConversionOut)
{
    ASSERT(ycbcrConversionDesc.valid());
    ASSERT(vkSamplerYcbcrConversionOut);

    SamplerYcbcrConversionMap &payload =
        (ycbcrConversionDesc.getExternalFormat() != 0) ? mExternalFormatPayload : mVkFormatPayload;
    const auto iter = payload.find(ycbcrConversionDesc);
    if (iter != payload.end())
    {
        vk::SamplerYcbcrConversion &samplerYcbcrConversion = iter->second;
        mCacheStats.hit();
        *vkSamplerYcbcrConversionOut = samplerYcbcrConversion.getHandle();
        return angle::Result::Continue;
    }

    mCacheStats.missAndIncrementSize();

    // Create the VkSamplerYcbcrConversion
    vk::SamplerYcbcrConversion wrappedSamplerYcbcrConversion;
    ANGLE_TRY(ycbcrConversionDesc.init(context, &wrappedSamplerYcbcrConversion));

    auto insertedItem = payload.emplace(
        ycbcrConversionDesc, vk::SamplerYcbcrConversion(std::move(wrappedSamplerYcbcrConversion)));
    vk::SamplerYcbcrConversion &insertedSamplerYcbcrConversion = insertedItem.first->second;
    *vkSamplerYcbcrConversionOut = insertedSamplerYcbcrConversion.getHandle();

    context->getRenderer()->onAllocateHandle(vk::HandleType::SamplerYcbcrConversion);

    return angle::Result::Continue;
}

// SamplerCache implementation.
SamplerCache::SamplerCache() = default;

SamplerCache::~SamplerCache()
{
    ASSERT(mPayload.empty());
}

void SamplerCache::destroy(RendererVk *rendererVk)
{
    rendererVk->accumulateCacheStats(VulkanCacheType::Sampler, mCacheStats);

    VkDevice device = rendererVk->getDevice();

    for (auto &iter : mPayload)
    {
        vk::RefCountedSampler &sampler = iter.second;
        ASSERT(!sampler.isReferenced());
        sampler.get().get().destroy(device);

        rendererVk->onDeallocateHandle(vk::HandleType::Sampler);
    }

    mPayload.clear();
}

angle::Result SamplerCache::getSampler(ContextVk *contextVk,
                                       const vk::SamplerDesc &desc,
                                       vk::SamplerBinding *samplerOut)
{
    auto iter = mPayload.find(desc);
    if (iter != mPayload.end())
    {
        vk::RefCountedSampler &sampler = iter->second;
        samplerOut->set(&sampler);
        mCacheStats.hit();
        return angle::Result::Continue;
    }

    mCacheStats.missAndIncrementSize();
    vk::SamplerHelper samplerHelper(contextVk);
    ANGLE_TRY(desc.init(contextVk, &samplerHelper.get()));

    vk::RefCountedSampler newSampler(std::move(samplerHelper));
    auto insertedItem                      = mPayload.emplace(desc, std::move(newSampler));
    vk::RefCountedSampler &insertedSampler = insertedItem.first->second;
    samplerOut->set(&insertedSampler);

    contextVk->getRenderer()->onAllocateHandle(vk::HandleType::Sampler);

    return angle::Result::Continue;
}
}  // namespace rx
