Edit

kc3-lang/angle/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp

Branch :

  • Show log

    Commit

  • Author : Mohan Maiya
    Date : 2021-06-08 13:12:24
    Hash : 5c8bf081
    Message : Vulkan: Add support for YUV internal format extension 1. Add a function to upload YUV image data to textures 2. Modify stageSubresourceUpdate method to account for YUV images 3. Create VkSamplerYcbcrConversion when initializing ImageHelper 4. Update hasImmutableSampler to account for native YUV format support 5. Skip initializeNonZeroMemory for YUV formats Bug: angleproject:5773 Test: Texture2DTestES3.TexStorage2D*Yuv*Vulkan* Change-Id: I270f04bbf903cf2bf19f100eb95f32953d491c39 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2947767 Commit-Queue: Mohan Maiya <m.maiya@samsung.com> Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
  • //
    // 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/VertexArrayVk.h"
    #include "libANGLE/renderer/vulkan/vk_format_utils.h"
    #include "libANGLE/renderer/vulkan/vk_helpers.h"
    
    #include <type_traits>
    
    namespace rx
    {
    namespace vk
    {
    
    namespace
    {
    
    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);
            default:
                UNREACHABLE();
                return 0;
        }
    }
    
    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;
        }
    }
    
    VkStencilOp PackGLStencilOp(GLenum compareOp)
    {
        switch (compareOp)
        {
            case GL_KEEP:
                return VK_STENCIL_OP_KEEP;
            case GL_ZERO:
                return VK_STENCIL_OP_ZERO;
            case GL_REPLACE:
                return VK_STENCIL_OP_REPLACE;
            case GL_INCR:
                return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
            case GL_DECR:
                return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
            case GL_INCR_WRAP:
                return VK_STENCIL_OP_INCREMENT_AND_WRAP;
            case GL_DECR_WRAP:
                return VK_STENCIL_OP_DECREMENT_AND_WRAP;
            case GL_INVERT:
                return VK_STENCIL_OP_INVERT;
            default:
                UNREACHABLE();
                return VK_STENCIL_OP_KEEP;
        }
    }
    
    VkCompareOp PackGLCompareFunc(GLenum compareFunc)
    {
        switch (compareFunc)
        {
            case GL_NEVER:
                return VK_COMPARE_OP_NEVER;
            case GL_ALWAYS:
                return VK_COMPARE_OP_ALWAYS;
            case GL_LESS:
                return VK_COMPARE_OP_LESS;
            case GL_LEQUAL:
                return VK_COMPARE_OP_LESS_OR_EQUAL;
            case GL_EQUAL:
                return VK_COMPARE_OP_EQUAL;
            case GL_GREATER:
                return VK_COMPARE_OP_GREATER;
            case GL_GEQUAL:
                return VK_COMPARE_OP_GREATER_OR_EQUAL;
            case GL_NOTEQUAL:
                return VK_COMPARE_OP_NOT_EQUAL;
            default:
                UNREACHABLE();
                return VK_COMPARE_OP_NEVER;
        }
    }
    
    void UnpackAttachmentDesc(VkAttachmentDescription *desc,
                              const Format &format,
                              uint8_t samples,
                              const PackedAttachmentOpsDesc &ops)
    {
        desc->flags   = 0;
        desc->format  = format.actualImageVkFormat();
        desc->samples = gl_vk::GetSamples(samples);
        desc->loadOp  = static_cast<VkAttachmentLoadOp>(ops.loadOp);
        desc->storeOp =
            ConvertRenderPassStoreOpToVkStoreOp(static_cast<RenderPassStoreOp>(ops.storeOp));
        desc->stencilLoadOp = static_cast<VkAttachmentLoadOp>(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,
                                          const Format &format,
                                          bool usedAsInputAttachment,
                                          bool isInvalidated)
    {
        desc->flags  = 0;
        desc->format = format.actualImageVkFormat();
    
        // This function is for color resolve attachments.
        const angle::Format &angleFormat = format.actualImageFormat();
        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,
                                                 const Format &format,
                                                 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 = format.actualImageVkFormat();
    
        // This function is for depth/stencil resolve attachment.
        const angle::Format &angleFormat = format.intendedFormat();
        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,
                            uint8_t stencilReference,
                            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 = packedState.compareMask;
        stateOut->writeMask   = packedState.writeMask;
        stateOut->reference   = stencilReference;
    }
    
    void UnpackBlendAttachmentState(const PackedColorBlendAttachmentState &packedState,
                                    VkPipelineColorBlendAttachmentState *stateOut)
    {
        stateOut->srcColorBlendFactor = static_cast<VkBlendFactor>(packedState.srcColorBlendFactor);
        stateOut->dstColorBlendFactor = static_cast<VkBlendFactor>(packedState.dstColorBlendFactor);
        stateOut->colorBlendOp        = static_cast<VkBlendOp>(packedState.colorBlendOp);
        stateOut->srcAlphaBlendFactor = static_cast<VkBlendFactor>(packedState.srcAlphaBlendFactor);
        stateOut->dstAlphaBlendFactor = static_cast<VkBlendFactor>(packedState.dstAlphaBlendFactor);
        stateOut->alphaBlendOp        = static_cast<VkBlendOp>(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;
    }
    
    void InitializeInputAttachmentSubpassDependencies(
        std::vector<VkSubpassDependency> *subpassDependencies,
        uint32_t subpassIndex)
    {
        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;
        dependency->srcAccessMask   = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        dependency->dstAccessMask   = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
        dependency->dependencyFlags = VK_DEPENDENCY_BY_REGION_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 isRenderToTexture,
                                    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(!isRenderToTexture);
            subpassDescriptions.back().pNext = &depthStencilResolve;
        }
        else
        {
            RendererVk *renderer = context->getRenderer();
    
            ASSERT(isRenderToTexture);
            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,
                                           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->depthClears += ds.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
        countersOut->depthLoads += ds.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
        countersOut->depthStores +=
            ds.storeOp == static_cast<uint16_t>(RenderPassStoreOp::Store) ? 1 : 0;
    
        countersOut->stencilClears += ds.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
        countersOut->stencilLoads += ds.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
        countersOut->stencilStores +=
            ds.stencilStoreOp == static_cast<uint16_t>(RenderPassStoreOp::Store) ? 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->depthClears += dsResolve.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
        countersOut->depthLoads += dsResolve.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
        countersOut->depthStores +=
            dsResolve.storeOp == static_cast<uint16_t>(RenderPassStoreOp::Store) ? 1 : 0;
    
        countersOut->stencilClears += dsResolve.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? 1 : 0;
        countersOut->stencilLoads += dsResolve.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD ? 1 : 0;
        countersOut->stencilStores +=
            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: currently there are no counters for load/store ops of color
            // attachments, so there's no risk of double counting.
            UpdateRenderPassColorPerfCounters(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);
                }
            }
        }
    
        // 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)
    {
        RendererVk *renderer = contextVk->getRenderer();
    
        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 isRenderToTexture    = desc.isRenderToTexture();
    
        const uint8_t descSamples            = desc.samples();
        const uint8_t attachmentSamples      = isRenderToTexture ? 1 : descSamples;
        const uint8_t renderToTextureSamples = isRenderToTexture ? 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 formatID = desc[colorIndexGL];
            ASSERT(formatID != angle::FormatID::NONE);
            const Format &format = renderer->getFormat(formatID);
    
            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()], format, attachmentSamples,
                                 ops[attachmentCount]);
    
            angle::FormatID attachmentFormat = format.actualImageFormatID;
    
            // 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(attachmentFormat);
            if (linearFormat != angle::FormatID::NONE)
            {
                if (desc.getSRGBWriteControlMode() == gl::SrgbWriteControlMode::Linear)
                {
                    attachmentFormat = linearFormat;
                }
            }
            attachmentDescs[attachmentCount.get()].format =
                contextVk->getRenderer()->getFormat(attachmentFormat).actualImageVkFormat();
            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 formatID = desc[depthStencilIndexGL];
            ASSERT(formatID != angle::FormatID::NONE);
            const Format &format = renderer->getFormat(formatID);
    
            depthStencilAttachmentRef.attachment = attachmentCount.get();
            depthStencilAttachmentRef.layout     = ConvertImageLayoutToVkImageLayout(
                static_cast<ImageLayout>(ops[attachmentCount].initialLayout));
    
            UnpackAttachmentDesc(&attachmentDescs[attachmentCount.get()], format, 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));
    
            const Format &format = renderer->getFormat(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);
            }
    
            UnpackColorResolveAttachmentDesc(&attachmentDescs[attachmentCount.get()], format,
                                             desc.hasColorUnresolveAttachment(colorIndexGL),
                                             isColorInvalidated.test(colorIndexGL));
    
            ++attachmentCount;
        }
    
        // Pack depth/stencil resolve attachment, if any
        if (desc.hasDepthStencilResolveAttachment())
        {
            ASSERT(desc.hasDepthStencilAttachment());
    
            uint32_t depthStencilIndexGL = static_cast<uint32_t>(desc.depthStencilAttachmentIndex());
    
            const Format &format             = renderer->getFormat(desc[depthStencilIndexGL]);
            const angle::Format &angleFormat = format.intendedFormat();
    
            // 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()], format, 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);
        }
    
        if (needInputAttachments)
        {
            uint32_t drawSubpassIndex = static_cast<uint32_t>(subpassDesc.size()) - 1;
            InitializeInputAttachmentSubpassDependencies(&subpassDependencies, drawSubpassIndex);
        }
    
        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 (TODO: verify that extension has no hints), 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 || desc.isRenderToTexture())
        {
            ANGLE_TRY(CreateRenderPass2(contextVk, createInfo, depthStencilResolve, multiviewInfo,
                                        desc.hasDepthUnresolveAttachment(),
                                        desc.hasStencilUnresolveAttachment(), desc.isRenderToTexture(),
                                        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)
        {
            PerfCounters &counters                   = contextVk->getPerfCounters();
            const RenderPassPerfCounters &rpCounters = renderPassHelper->getPerfCounters();
    
            counters.depthClears += rpCounters.depthClears;
            counters.depthLoads += rpCounters.depthLoads;
            counters.depthStores += rpCounters.depthStores;
            counters.stencilClears += rpCounters.stencilClears;
            counters.stencilLoads += rpCounters.stencilLoads;
            counters.stencilStores += rpCounters.stencilStores;
            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::DrawableWidth:
                    (*specializationEntriesOut)[id].offset =
                        offsetof(vk::SpecializationConstants, drawableWidth);
                    (*specializationEntriesOut)[id].size = sizeof(specConsts.drawableWidth);
                    break;
                case sh::vk::SpecializationConstantId::DrawableHeight:
                    (*specializationEntriesOut)[id].offset =
                        offsetof(vk::SpecializationConstants, drawableHeight);
                    (*specializationEntriesOut)[id].size = sizeof(specConsts.drawableHeight);
                    break;
                default:
                    UNREACHABLE();
                    break;
            }
        }
    
        specializationInfoOut->mapEntryCount = static_cast<uint32_t>(specializationEntriesOut->size());
        specializationInfoOut->pMapEntries   = specializationEntriesOut->data();
        specializationInfoOut->dataSize      = sizeof(specConsts);
        specializationInfoOut->pData         = &specConsts;
    }
    
    // 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},
    }};
    }  // 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
    {
        return angle::ComputeGenericHash(*this);
    }
    
    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.stride, 0);
            SetBitField(packedAttrib.divisor, 0);
            SetBitField(packedAttrib.format, defaultFormat);
            SetBitField(packedAttrib.compressed, 0);
            SetBitField(packedAttrib.offset, 0);
        }
    
        mRasterizationAndMultisampleStateInfo.bits.subpass = 0;
        mRasterizationAndMultisampleStateInfo.bits.depthClampEnable =
            contextVk->getFeatures().depthClamping.enabled ? VK_TRUE : VK_FALSE;
        mRasterizationAndMultisampleStateInfo.bits.rasterizationDiscardEnable = 0;
        SetBitField(mRasterizationAndMultisampleStateInfo.bits.polygonMode, VK_POLYGON_MODE_FILL);
        SetBitField(mRasterizationAndMultisampleStateInfo.bits.cullMode, VK_CULL_MODE_BACK_BIT);
        SetBitField(mRasterizationAndMultisampleStateInfo.bits.frontFace,
                    VK_FRONT_FACE_COUNTER_CLOCKWISE);
        mRasterizationAndMultisampleStateInfo.bits.depthBiasEnable    = 0;
        mRasterizationAndMultisampleStateInfo.depthBiasConstantFactor = 0.0f;
        mRasterizationAndMultisampleStateInfo.depthBiasClamp          = 0.0f;
        mRasterizationAndMultisampleStateInfo.depthBiasSlopeFactor    = 0.0f;
        mRasterizationAndMultisampleStateInfo.lineWidth               = 1.0f;
    
        mRasterizationAndMultisampleStateInfo.bits.rasterizationSamples = 1;
        mRasterizationAndMultisampleStateInfo.bits.sampleShadingEnable  = 0;
        mRasterizationAndMultisampleStateInfo.minSampleShading          = 1.0f;
        for (uint32_t &sampleMask : mRasterizationAndMultisampleStateInfo.sampleMask)
        {
            sampleMask = 0xFFFFFFFF;
        }
        mRasterizationAndMultisampleStateInfo.bits.alphaToCoverageEnable = 0;
        mRasterizationAndMultisampleStateInfo.bits.alphaToOneEnable      = 0;
    
        mDepthStencilStateInfo.enable.depthTest  = 0;
        mDepthStencilStateInfo.enable.depthWrite = 1;
        SetBitField(mDepthStencilStateInfo.depthCompareOpAndSurfaceRotation.depthCompareOp,
                    VK_COMPARE_OP_LESS);
        mDepthStencilStateInfo.enable.depthBoundsTest = 0;
        mDepthStencilStateInfo.enable.stencilTest     = 0;
        mDepthStencilStateInfo.minDepthBounds         = 0.0f;
        mDepthStencilStateInfo.maxDepthBounds         = 0.0f;
        SetBitField(mDepthStencilStateInfo.front.ops.fail, VK_STENCIL_OP_KEEP);
        SetBitField(mDepthStencilStateInfo.front.ops.pass, VK_STENCIL_OP_KEEP);
        SetBitField(mDepthStencilStateInfo.front.ops.depthFail, VK_STENCIL_OP_KEEP);
        SetBitField(mDepthStencilStateInfo.front.ops.compare, VK_COMPARE_OP_ALWAYS);
        SetBitField(mDepthStencilStateInfo.front.compareMask, 0xFF);
        SetBitField(mDepthStencilStateInfo.front.writeMask, 0xFF);
        mDepthStencilStateInfo.frontStencilReference = 0;
        SetBitField(mDepthStencilStateInfo.back.ops.fail, VK_STENCIL_OP_KEEP);
        SetBitField(mDepthStencilStateInfo.back.ops.pass, VK_STENCIL_OP_KEEP);
        SetBitField(mDepthStencilStateInfo.back.ops.depthFail, VK_STENCIL_OP_KEEP);
        SetBitField(mDepthStencilStateInfo.back.ops.compare, VK_COMPARE_OP_ALWAYS);
        SetBitField(mDepthStencilStateInfo.back.compareMask, 0xFF);
        SetBitField(mDepthStencilStateInfo.back.writeMask, 0xFF);
        mDepthStencilStateInfo.backStencilReference = 0;
    
        mDepthStencilStateInfo.depthCompareOpAndSurfaceRotation.surfaceRotation =
            static_cast<uint8_t>(SurfaceRotation::Identity);
    
        PackedInputAssemblyAndColorBlendStateInfo &inputAndBlend = mInputAssemblyAndColorBlendStateInfo;
        inputAndBlend.logic.opEnable                             = 0;
        inputAndBlend.logic.op          = static_cast<uint32_t>(VK_LOGIC_OP_CLEAR);
        inputAndBlend.blendEnableMask   = 0;
        inputAndBlend.blendConstants[0] = 0.0f;
        inputAndBlend.blendConstants[1] = 0.0f;
        inputAndBlend.blendConstants[2] = 0.0f;
        inputAndBlend.blendConstants[3] = 0.0f;
    
        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(inputAndBlend.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(&inputAndBlend.attachments[0],
                  &inputAndBlend.attachments[gl::IMPLEMENTATION_MAX_DRAW_BUFFERS],
                  blendAttachmentState);
    
        SetBitField(inputAndBlend.primitive.topology, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
        SetBitField(inputAndBlend.primitive.patchVertices, 3);
        inputAndBlend.primitive.restartEnable = 0;
    
        mDrawableSize.width  = 1;
        mDrawableSize.height = 1;
    }
    
    angle::Result GraphicsPipelineDesc::initializePipeline(
        ContextVk *contextVk,
        const PipelineCache &pipelineCacheVk,
        const RenderPass &compatibleRenderPass,
        const PipelineLayout &pipelineLayout,
        const gl::AttributesMask &activeAttribLocationsMask,
        const gl::ComponentTypeMask &programAttribsTypeMask,
        const ShaderModule *vertexModule,
        const ShaderModule *fragmentModule,
        const ShaderModule *geometryModule,
        const ShaderModule *tessControlModule,
        const ShaderModule *tessEvaluationModule,
        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.
        ASSERT(vertexModule != nullptr);
        VkPipelineShaderStageCreateInfo vertexStage = {};
        SetPipelineShaderStageInfo(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                                   VK_SHADER_STAGE_VERTEX_BIT, vertexModule->getHandle(),
                                   specializationInfo, &vertexStage);
        shaderStages.push_back(vertexStage);
    
        if (tessControlModule)
        {
            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);
        }
    
        if (tessEvaluationModule)
        {
            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);
        }
    
        if (geometryModule)
        {
            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 rasterizationDiscardEnable = true
        if (fragmentModule && !mRasterizationAndMultisampleStateInfo.bits.rasterizationDiscardEnable)
        {
            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>(packedAttrib.stride);
            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 &angleFormat = format.intendedFormat();
            VkFormat vkFormat                = format.actualBufferVkFormat(packedAttrib.compressed);
    
            gl::ComponentType attribType =
                GetVertexAttributeComponentType(angleFormat.isPureInt(), angleFormat.vertexAttribType);
            gl::ComponentType programAttribType =
                gl::GetComponentTypeMask(programAttribsTypeMask, attribIndex);
    
            // This forces stride to 0 when glVertexAttribute specifies a different type from the
            // program's attribute type except when the type mismatch is a mismatched integer sign.
            if (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(angleFormat);
                    const Format &convertedFormat =
                        contextVk->getRenderer()->getFormat(convertedFormatID);
                    ASSERT(angleFormat.channelCount == convertedFormat.intendedFormat().channelCount);
                    ASSERT(angleFormat.redBits == convertedFormat.intendedFormat().redBits);
                    ASSERT(angleFormat.greenBits == convertedFormat.intendedFormat().greenBits);
                    ASSERT(angleFormat.blueBits == convertedFormat.intendedFormat().blueBits);
                    ASSERT(angleFormat.alphaBits == convertedFormat.intendedFormat().alphaBits);
    
                    vkFormat = convertedFormat.actualBufferVkFormat(packedAttrib.compressed);
                }
    
                GLenum programAttributeType =
                    contextVk->getState().getProgramExecutable()->getProgramInputs()[attribIndex].type;
                GLuint attribSize = gl::GetVertexFormatFromID(formatID).components;
                GLuint shaderVarSize =
                    static_cast<GLuint>(gl::VariableColumnCount(programAttributeType));
    
                ASSERT(contextVk->getNativeExtensions().relaxedVertexAttributeTypeANGLE);
                if (programAttribType == gl::ComponentType::Float ||
                    attribType == gl::ComponentType::Float || attribSize != shaderVarSize)
                {
                    bindingDesc.stride = 0;  // Prevent out-of-bounds accesses.
                }
            }
    
            // The binding index could become more dynamic in ES 3.1.
            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;
    
        // 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>(mInputAssemblyAndColorBlendStateInfo.primitive.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>(mInputAssemblyAndColorBlendStateInfo.primitive.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;
    
        const PackedRasterizationAndMultisampleStateInfo &rasterAndMS =
            mRasterizationAndMultisampleStateInfo;
    
        // Rasterizer state.
        rasterState.sType            = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
        rasterState.flags            = 0;
        rasterState.depthClampEnable = static_cast<VkBool32>(rasterAndMS.bits.depthClampEnable);
        rasterState.rasterizerDiscardEnable =
            static_cast<VkBool32>(rasterAndMS.bits.rasterizationDiscardEnable);
        rasterState.polygonMode             = static_cast<VkPolygonMode>(rasterAndMS.bits.polygonMode);
        rasterState.cullMode                = static_cast<VkCullModeFlags>(rasterAndMS.bits.cullMode);
        rasterState.frontFace               = static_cast<VkFrontFace>(rasterAndMS.bits.frontFace);
        rasterState.depthBiasEnable         = static_cast<VkBool32>(rasterAndMS.bits.depthBiasEnable);
        rasterState.depthBiasConstantFactor = rasterAndMS.depthBiasConstantFactor;
        rasterState.depthBiasClamp          = rasterAndMS.depthBiasClamp;
        rasterState.depthBiasSlopeFactor    = rasterAndMS.depthBiasSlopeFactor;
        rasterState.lineWidth               = rasterAndMS.lineWidth;
        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 (rasterAndMS.bits.rasterizationSamples <= 1 &&
            !rasterAndMS.bits.rasterizationDiscardEnable && !rasterAndMS.bits.alphaToCoverageEnable &&
            !rasterAndMS.bits.alphaToOneEnable && !rasterAndMS.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().supportsTransformFeedbackExtension.enabled)
        {
            rasterStreamState.rasterizationStream = 0;
            *pNextPtr                             = &rasterStreamState;
            pNextPtr                              = &rasterStreamState.pNext;
        }
    
        // Multisample state.
        multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
        multisampleState.flags = 0;
        multisampleState.rasterizationSamples =
            gl_vk::GetSamples(rasterAndMS.bits.rasterizationSamples);
        multisampleState.sampleShadingEnable =
            static_cast<VkBool32>(rasterAndMS.bits.sampleShadingEnable);
        multisampleState.minSampleShading = rasterAndMS.minSampleShading;
        multisampleState.pSampleMask      = rasterAndMS.sampleMask;
        multisampleState.alphaToCoverageEnable =
            static_cast<VkBool32>(rasterAndMS.bits.alphaToCoverageEnable);
        multisampleState.alphaToOneEnable = static_cast<VkBool32>(rasterAndMS.bits.alphaToOneEnable);
    
        // Depth/stencil state.
        depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
        depthStencilState.flags = 0;
        depthStencilState.depthTestEnable =
            static_cast<VkBool32>(mDepthStencilStateInfo.enable.depthTest);
        depthStencilState.depthWriteEnable =
            static_cast<VkBool32>(mDepthStencilStateInfo.enable.depthWrite);
        depthStencilState.depthCompareOp = static_cast<VkCompareOp>(
            mDepthStencilStateInfo.depthCompareOpAndSurfaceRotation.depthCompareOp);
        depthStencilState.depthBoundsTestEnable =
            static_cast<VkBool32>(mDepthStencilStateInfo.enable.depthBoundsTest);
        depthStencilState.stencilTestEnable =
            static_cast<VkBool32>(mDepthStencilStateInfo.enable.stencilTest);
        UnpackStencilState(mDepthStencilStateInfo.front, mDepthStencilStateInfo.frontStencilReference,
                           &depthStencilState.front);
        UnpackStencilState(mDepthStencilStateInfo.back, mDepthStencilStateInfo.backStencilReference,
                           &depthStencilState.back);
        depthStencilState.minDepthBounds = mDepthStencilStateInfo.minDepthBounds;
        depthStencilState.maxDepthBounds = mDepthStencilStateInfo.maxDepthBounds;
    
        const PackedInputAssemblyAndColorBlendStateInfo &inputAndBlend =
            mInputAssemblyAndColorBlendStateInfo;
    
        blendState.sType           = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
        blendState.flags           = 0;
        blendState.logicOpEnable   = static_cast<VkBool32>(inputAndBlend.logic.opEnable);
        blendState.logicOp         = static_cast<VkLogicOp>(inputAndBlend.logic.op);
        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()) &&
            mRasterizationAndMultisampleStateInfo.bits.subpass == 0)
        {
            blendState.attachmentCount =
                static_cast<uint32_t>(mRenderPassDesc.getColorUnresolveAttachmentMask().count());
        }
    
        for (int i = 0; i < 4; i++)
        {
            blendState.blendConstants[i] = inputAndBlend.blendConstants[i];
        }
    
        const gl::DrawBufferMask blendEnableMask(inputAndBlend.blendEnableMask);
    
        // Zero-init all states.
        blendAttachmentState = {};
    
        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])
                                .actualImageFormat()
                                .isInt());
                    state.blendEnable = VK_TRUE;
                    UnpackBlendAttachmentState(inputAndBlend.attachments[colorIndexGL], &state);
                }
            }
            state.colorWriteMask =
                Int4Array_Get<VkColorComponentFlags>(inputAndBlend.colorWriteMaskBits, colorIndexGL);
        }
    
        // Dynamic state
        angle::FixedVector<VkDynamicState, 2> dynamicStateList;
        dynamicStateList.push_back(VK_DYNAMIC_STATE_VIEWPORT);
        dynamicStateList.push_back(VK_DYNAMIC_STATE_SCISSOR);
    
        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 (tessControlModule && tessEvaluationModule)
        {
            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>(inputAndBlend.primitive.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             = mRasterizationAndMultisampleStateInfo.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(GraphicsPipelineTransitionBits *transition,
                                                 uint32_t attribIndex,
                                                 GLuint stride,
                                                 GLuint divisor,
                                                 angle::FormatID format,
                                                 bool compressed,
                                                 GLuint relativeOffset)
    {
        PackedAttribDesc &packedAttrib = mVertexInputAttribs.attribs[attribIndex];
    
        SetBitField(packedAttrib.stride, stride);
        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);
    
        // Cover the next dirty bit conservatively. Because each attribute is 6 bytes.
        transition->set(kBit);
        transition->set(kBit + 1);
    }
    
    void GraphicsPipelineDesc::updateTopology(GraphicsPipelineTransitionBits *transition,
                                              gl::PrimitiveMode drawMode)
    {
        VkPrimitiveTopology vkTopology = gl_vk::GetPrimitiveTopology(drawMode);
        SetBitField(mInputAssemblyAndColorBlendStateInfo.primitive.topology, vkTopology);
    
        transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndColorBlendStateInfo, primitive));
    }
    
    void GraphicsPipelineDesc::updatePrimitiveRestartEnabled(GraphicsPipelineTransitionBits *transition,
                                                             bool primitiveRestartEnabled)
    {
        mInputAssemblyAndColorBlendStateInfo.primitive.restartEnable =
            static_cast<uint16_t>(primitiveRestartEnabled);
        transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndColorBlendStateInfo, primitive));
    }
    
    void GraphicsPipelineDesc::setCullMode(VkCullModeFlagBits cullMode)
    {
        SetBitField(mRasterizationAndMultisampleStateInfo.bits.cullMode, cullMode);
    }
    
    void GraphicsPipelineDesc::updateCullMode(GraphicsPipelineTransitionBits *transition,
                                              const gl::RasterizerState &rasterState)
    {
        setCullMode(gl_vk::GetCullMode(rasterState));
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
    }
    
    void GraphicsPipelineDesc::updateFrontFace(GraphicsPipelineTransitionBits *transition,
                                               const gl::RasterizerState &rasterState,
                                               bool invertFrontFace)
    {
        mRasterizationAndMultisampleStateInfo.bits.frontFace =
            static_cast<uint16_t>(gl_vk::GetFrontFace(rasterState.frontFace, invertFrontFace));
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
    }
    
    void GraphicsPipelineDesc::updateLineWidth(GraphicsPipelineTransitionBits *transition,
                                               float lineWidth)
    {
        mRasterizationAndMultisampleStateInfo.lineWidth = lineWidth;
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, lineWidth));
    }
    
    void GraphicsPipelineDesc::updateRasterizerDiscardEnabled(
        GraphicsPipelineTransitionBits *transition,
        bool rasterizerDiscardEnabled)
    {
        mRasterizationAndMultisampleStateInfo.bits.rasterizationDiscardEnable =
            static_cast<uint32_t>(rasterizerDiscardEnabled);
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
    }
    
    uint32_t GraphicsPipelineDesc::getRasterizationSamples() const
    {
        return mRasterizationAndMultisampleStateInfo.bits.rasterizationSamples;
    }
    
    void GraphicsPipelineDesc::setRasterizationSamples(uint32_t rasterizationSamples)
    {
        mRasterizationAndMultisampleStateInfo.bits.rasterizationSamples = rasterizationSamples;
    }
    
    void GraphicsPipelineDesc::updateRasterizationSamples(GraphicsPipelineTransitionBits *transition,
                                                          uint32_t rasterizationSamples)
    {
        setRasterizationSamples(rasterizationSamples);
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
    }
    
    void GraphicsPipelineDesc::updateAlphaToCoverageEnable(GraphicsPipelineTransitionBits *transition,
                                                           bool enable)
    {
        mRasterizationAndMultisampleStateInfo.bits.alphaToCoverageEnable = enable;
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
    }
    
    void GraphicsPipelineDesc::updateAlphaToOneEnable(GraphicsPipelineTransitionBits *transition,
                                                      bool enable)
    {
        mRasterizationAndMultisampleStateInfo.bits.alphaToOneEnable = enable;
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
    }
    
    void GraphicsPipelineDesc::updateSampleMask(GraphicsPipelineTransitionBits *transition,
                                                uint32_t maskNumber,
                                                uint32_t mask)
    {
        ASSERT(maskNumber < gl::MAX_SAMPLE_MASK_WORDS);
        mRasterizationAndMultisampleStateInfo.sampleMask[maskNumber] = mask;
    
        constexpr size_t kMaskBits =
            sizeof(mRasterizationAndMultisampleStateInfo.sampleMask[0]) * kBitsPerByte;
        transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo,
                                                         sampleMask, maskNumber, kMaskBits));
    }
    
    void GraphicsPipelineDesc::updateSampleShading(GraphicsPipelineTransitionBits *transition,
                                                   bool enable,
                                                   float value)
    {
        mRasterizationAndMultisampleStateInfo.bits.sampleShadingEnable = enable;
        mRasterizationAndMultisampleStateInfo.minSampleShading         = (enable ? value : 1.0f);
    
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
        transition->set(
            ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, minSampleShading));
    }
    
    void GraphicsPipelineDesc::updateBlendColor(GraphicsPipelineTransitionBits *transition,
                                                const gl::ColorF &color)
    {
        mInputAssemblyAndColorBlendStateInfo.blendConstants[0] = color.red;
        mInputAssemblyAndColorBlendStateInfo.blendConstants[1] = color.green;
        mInputAssemblyAndColorBlendStateInfo.blendConstants[2] = color.blue;
        mInputAssemblyAndColorBlendStateInfo.blendConstants[3] = color.alpha;
        constexpr size_t kSize = sizeof(mInputAssemblyAndColorBlendStateInfo.blendConstants[0]) * 8;
    
        for (int index = 0; index < 4; ++index)
        {
            const size_t kBit = ANGLE_GET_INDEXED_TRANSITION_BIT(mInputAssemblyAndColorBlendStateInfo,
                                                                 blendConstants, index, kSize);
            transition->set(kBit);
        }
    }
    
    void GraphicsPipelineDesc::updateBlendEnabled(GraphicsPipelineTransitionBits *transition,
                                                  gl::DrawBufferMask blendEnabledMask)
    {
        mInputAssemblyAndColorBlendStateInfo.blendEnableMask =
            static_cast<uint8_t>(blendEnabledMask.bits());
        transition->set(
            ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndColorBlendStateInfo, blendEnableMask));
    }
    
    void GraphicsPipelineDesc::updateBlendEquations(GraphicsPipelineTransitionBits *transition,
                                                    const gl::BlendStateExt &blendStateExt)
    {
        constexpr size_t kSize = sizeof(PackedColorBlendAttachmentState) * 8;
    
        for (size_t attachmentIndex = 0; attachmentIndex < blendStateExt.mMaxDrawBuffers;
             ++attachmentIndex)
        {
            PackedColorBlendAttachmentState &blendAttachmentState =
                mInputAssemblyAndColorBlendStateInfo.attachments[attachmentIndex];
            blendAttachmentState.colorBlendOp =
                PackGLBlendOp(blendStateExt.getEquationColorIndexed(attachmentIndex));
            blendAttachmentState.alphaBlendOp =
                PackGLBlendOp(blendStateExt.getEquationAlphaIndexed(attachmentIndex));
            transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(mInputAssemblyAndColorBlendStateInfo,
                                                             attachments, attachmentIndex, kSize));
        }
    }
    
    void GraphicsPipelineDesc::updateBlendFuncs(GraphicsPipelineTransitionBits *transition,
                                                const gl::BlendStateExt &blendStateExt)
    {
        constexpr size_t kSize = sizeof(PackedColorBlendAttachmentState) * 8;
        for (size_t attachmentIndex = 0; attachmentIndex < blendStateExt.mMaxDrawBuffers;
             ++attachmentIndex)
        {
            PackedColorBlendAttachmentState &blendAttachmentState =
                mInputAssemblyAndColorBlendStateInfo.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(mInputAssemblyAndColorBlendStateInfo,
                                                             attachments, attachmentIndex, kSize));
        }
    }
    
    void GraphicsPipelineDesc::setColorWriteMasks(gl::BlendStateExt::ColorMaskStorage::Type colorMasks,
                                                  const gl::DrawBufferMask &alphaMask,
                                                  const gl::DrawBufferMask &enabledDrawBuffers)
    {
        PackedInputAssemblyAndColorBlendStateInfo &inputAndBlend = mInputAssemblyAndColorBlendStateInfo;
    
        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(inputAndBlend.colorWriteMaskBits, colorIndexGL, mask);
        }
    }
    
    void GraphicsPipelineDesc::setSingleColorWriteMask(uint32_t colorIndexGL,
                                                       VkColorComponentFlags colorComponentFlags)
    {
        PackedInputAssemblyAndColorBlendStateInfo &inputAndBlend = mInputAssemblyAndColorBlendStateInfo;
        uint8_t colorMask = static_cast<uint8_t>(colorComponentFlags);
        Int4Array_Set(inputAndBlend.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(mInputAssemblyAndColorBlendStateInfo,
                                                             colorWriteMaskBits, colorIndexGL, 4));
        }
    }
    
    void GraphicsPipelineDesc::setDepthTestEnabled(bool enabled)
    {
        mDepthStencilStateInfo.enable.depthTest = enabled;
    }
    
    void GraphicsPipelineDesc::setDepthWriteEnabled(bool enabled)
    {
        mDepthStencilStateInfo.enable.depthWrite = enabled;
    }
    
    void GraphicsPipelineDesc::setDepthFunc(VkCompareOp op)
    {
        SetBitField(mDepthStencilStateInfo.depthCompareOpAndSurfaceRotation.depthCompareOp, op);
    }
    
    void GraphicsPipelineDesc::setDepthClampEnabled(bool enabled)
    {
        mRasterizationAndMultisampleStateInfo.bits.depthClampEnable = enabled;
    }
    
    void GraphicsPipelineDesc::setStencilTestEnabled(bool enabled)
    {
        mDepthStencilStateInfo.enable.stencilTest = enabled;
    }
    
    void GraphicsPipelineDesc::setStencilFrontFuncs(uint8_t reference,
                                                    VkCompareOp compareOp,
                                                    uint8_t compareMask)
    {
        mDepthStencilStateInfo.frontStencilReference = reference;
        mDepthStencilStateInfo.front.compareMask     = compareMask;
        SetBitField(mDepthStencilStateInfo.front.ops.compare, compareOp);
    }
    
    void GraphicsPipelineDesc::setStencilBackFuncs(uint8_t reference,
                                                   VkCompareOp compareOp,
                                                   uint8_t compareMask)
    {
        mDepthStencilStateInfo.backStencilReference = reference;
        mDepthStencilStateInfo.back.compareMask     = compareMask;
        SetBitField(mDepthStencilStateInfo.back.ops.compare, compareOp);
    }
    
    void GraphicsPipelineDesc::setStencilFrontOps(VkStencilOp failOp,
                                                  VkStencilOp passOp,
                                                  VkStencilOp depthFailOp)
    {
        SetBitField(mDepthStencilStateInfo.front.ops.fail, failOp);
        SetBitField(mDepthStencilStateInfo.front.ops.pass, passOp);
        SetBitField(mDepthStencilStateInfo.front.ops.depthFail, depthFailOp);
    }
    
    void GraphicsPipelineDesc::setStencilBackOps(VkStencilOp failOp,
                                                 VkStencilOp passOp,
                                                 VkStencilOp depthFailOp)
    {
        SetBitField(mDepthStencilStateInfo.back.ops.fail, failOp);
        SetBitField(mDepthStencilStateInfo.back.ops.pass, passOp);
        SetBitField(mDepthStencilStateInfo.back.ops.depthFail, depthFailOp);
    }
    
    void GraphicsPipelineDesc::setStencilFrontWriteMask(uint8_t mask)
    {
        mDepthStencilStateInfo.front.writeMask = mask;
    }
    
    void GraphicsPipelineDesc::setStencilBackWriteMask(uint8_t mask)
    {
        mDepthStencilStateInfo.back.writeMask = mask;
    }
    
    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(mDepthStencilStateInfo, enable));
    }
    
    void GraphicsPipelineDesc::updateDepthFunc(GraphicsPipelineTransitionBits *transition,
                                               const gl::DepthStencilState &depthStencilState)
    {
        setDepthFunc(PackGLCompareFunc(depthStencilState.depthFunc));
        transition->set(
            ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, depthCompareOpAndSurfaceRotation));
    }
    
    void GraphicsPipelineDesc::updateSurfaceRotation(GraphicsPipelineTransitionBits *transition,
                                                     const SurfaceRotation surfaceRotation)
    {
        SetBitField(mDepthStencilStateInfo.depthCompareOpAndSurfaceRotation.surfaceRotation,
                    surfaceRotation);
        transition->set(
            ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, depthCompareOpAndSurfaceRotation));
    }
    
    void GraphicsPipelineDesc::updateDepthWriteEnabled(GraphicsPipelineTransitionBits *transition,
                                                       const gl::DepthStencilState &depthStencilState,
                                                       const gl::Framebuffer *drawFramebuffer)
    {
        // Don't write to depth buffers that should not exist
        setDepthWriteEnabled(drawFramebuffer->hasDepth() ? depthStencilState.depthMask : false);
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, enable));
    }
    
    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(mDepthStencilStateInfo, enable));
    }
    
    void GraphicsPipelineDesc::updateStencilFrontFuncs(GraphicsPipelineTransitionBits *transition,
                                                       GLint ref,
                                                       const gl::DepthStencilState &depthStencilState)
    {
        setStencilFrontFuncs(static_cast<uint8_t>(ref),
                             PackGLCompareFunc(depthStencilState.stencilFunc),
                             static_cast<uint8_t>(depthStencilState.stencilMask));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, front));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, frontStencilReference));
    }
    
    void GraphicsPipelineDesc::updateStencilBackFuncs(GraphicsPipelineTransitionBits *transition,
                                                      GLint ref,
                                                      const gl::DepthStencilState &depthStencilState)
    {
        setStencilBackFuncs(static_cast<uint8_t>(ref),
                            PackGLCompareFunc(depthStencilState.stencilBackFunc),
                            static_cast<uint8_t>(depthStencilState.stencilBackMask));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, back));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, backStencilReference));
    }
    
    void GraphicsPipelineDesc::updateStencilFrontOps(GraphicsPipelineTransitionBits *transition,
                                                     const gl::DepthStencilState &depthStencilState)
    {
        setStencilFrontOps(PackGLStencilOp(depthStencilState.stencilFail),
                           PackGLStencilOp(depthStencilState.stencilPassDepthPass),
                           PackGLStencilOp(depthStencilState.stencilPassDepthFail));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, front));
    }
    
    void GraphicsPipelineDesc::updateStencilBackOps(GraphicsPipelineTransitionBits *transition,
                                                    const gl::DepthStencilState &depthStencilState)
    {
        setStencilBackOps(PackGLStencilOp(depthStencilState.stencilBackFail),
                          PackGLStencilOp(depthStencilState.stencilBackPassDepthPass),
                          PackGLStencilOp(depthStencilState.stencilBackPassDepthFail));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, back));
    }
    
    void GraphicsPipelineDesc::updateStencilFrontWriteMask(
        GraphicsPipelineTransitionBits *transition,
        const gl::DepthStencilState &depthStencilState,
        const gl::Framebuffer *drawFramebuffer)
    {
        // Don't write to stencil buffers that should not exist
        setStencilFrontWriteMask(static_cast<uint8_t>(
            drawFramebuffer->hasStencil() ? depthStencilState.stencilWritemask : 0));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, front));
    }
    
    void GraphicsPipelineDesc::updateStencilBackWriteMask(
        GraphicsPipelineTransitionBits *transition,
        const gl::DepthStencilState &depthStencilState,
        const gl::Framebuffer *drawFramebuffer)
    {
        // Don't write to stencil buffers that should not exist
        setStencilBackWriteMask(static_cast<uint8_t>(
            drawFramebuffer->hasStencil() ? depthStencilState.stencilBackWritemask : 0));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, back));
    }
    
    void GraphicsPipelineDesc::updatePolygonOffsetFillEnabled(
        GraphicsPipelineTransitionBits *transition,
        bool enabled)
    {
        mRasterizationAndMultisampleStateInfo.bits.depthBiasEnable = enabled;
        transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
    }
    
    void GraphicsPipelineDesc::updatePolygonOffset(GraphicsPipelineTransitionBits *transition,
                                                   const gl::RasterizerState &rasterState)
    {
        mRasterizationAndMultisampleStateInfo.depthBiasSlopeFactor    = rasterState.polygonOffsetFactor;
        mRasterizationAndMultisampleStateInfo.depthBiasConstantFactor = rasterState.polygonOffsetUnits;
        transition->set(
            ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, depthBiasSlopeFactor));
        transition->set(
            ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, depthBiasConstantFactor));
    }
    
    void GraphicsPipelineDesc::setRenderPassDesc(const RenderPassDesc &renderPassDesc)
    {
        mRenderPassDesc = renderPassDesc;
    }
    
    void GraphicsPipelineDesc::updateDrawableSize(GraphicsPipelineTransitionBits *transition,
                                                  uint32_t width,
                                                  uint32_t height)
    {
        SetBitField(mDrawableSize.width, width);
        SetBitField(mDrawableSize.height, height);
        transition->set(ANGLE_GET_TRANSITION_BIT(mDrawableSize, width));
        transition->set(ANGLE_GET_TRANSITION_BIT(mDrawableSize, height));
    }
    
    void GraphicsPipelineDesc::updateSubpass(GraphicsPipelineTransitionBits *transition,
                                             uint32_t subpass)
    {
        if (mRasterizationAndMultisampleStateInfo.bits.subpass != subpass)
        {
            SetBitField(mRasterizationAndMultisampleStateInfo.bits.subpass, subpass);
            transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
        }
    }
    
    void GraphicsPipelineDesc::updatePatchVertices(GraphicsPipelineTransitionBits *transition,
                                                   GLuint value)
    {
        SetBitField(mInputAssemblyAndColorBlendStateInfo.primitive.patchVertices, value);
    
        transition->set(ANGLE_GET_TRANSITION_BIT(mInputAssemblyAndColorBlendStateInfo, primitive));
    }
    
    void GraphicsPipelineDesc::resetSubpass(GraphicsPipelineTransitionBits *transition)
    {
        updateSubpass(transition, 0);
    }
    
    void GraphicsPipelineDesc::nextSubpass(GraphicsPipelineTransitionBits *transition)
    {
        updateSubpass(transition, mRasterizationAndMultisampleStateInfo.bits.subpass + 1);
    }
    
    void GraphicsPipelineDesc::setSubpass(uint32_t subpass)
    {
        SetBitField(mRasterizationAndMultisampleStateInfo.bits.subpass, subpass);
    }
    
    uint32_t GraphicsPipelineDesc::getSubpass() const
    {
        return mRasterizationAndMultisampleStateInfo.bits.subpass;
    }
    
    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);
        }
    }
    
    // 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, VK_ATTACHMENT_LOAD_OP_LOAD, RenderPassStoreOp::Store);
        setStencilOps(index, VK_ATTACHMENT_LOAD_OP_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,
                                    VkAttachmentLoadOp loadOp,
                                    RenderPassStoreOp storeOp)
    {
        PackedAttachmentOpsDesc &ops = mOps[index.get()];
        SetBitField(ops.loadOp, loadOp);
        SetBitField(ops.storeOp, storeOp);
        ops.isInvalidated = false;
    }
    
    void AttachmentOpsArray::setStencilOps(PackedAttachmentIndex index,
                                           VkAttachmentLoadOp 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, VK_ATTACHMENT_LOAD_OP_CLEAR);
    }
    
    void AttachmentOpsArray::setClearStencilOp(PackedAttachmentIndex index)
    {
        PackedAttachmentOpsDesc &ops = mOps[index.get()];
        SetBitField(ops.stencilLoadOp, VK_ATTACHMENT_LOAD_OP_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 type,
                                         uint32_t count,
                                         VkShaderStageFlags stages,
                                         const Sampler *immutableSampler)
    {
        ASSERT(static_cast<size_t>(type) < std::numeric_limits<uint16_t>::max());
        ASSERT(count < std::numeric_limits<uint16_t>::max());
    
        PackedDescriptorSetBinding &packedBinding = mPackedDescriptorSetLayout[bindingIndex];
    
        SetBitField(packedBinding.type, type);
        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{}, mPushConstantRanges{} {}
    
    PipelineLayoutDesc::~PipelineLayoutDesc() = default;
    
    PipelineLayoutDesc::PipelineLayoutDesc(const PipelineLayoutDesc &other) = default;
    
    PipelineLayoutDesc &PipelineLayoutDesc::operator=(const PipelineLayoutDesc &rhs)
    {
        mDescriptorSetLayouts = rhs.mDescriptorSetLayouts;
        mPushConstantRanges   = rhs.mPushConstantRanges;
        return *this;
    }
    
    size_t PipelineLayoutDesc::hash() const
    {
        return angle::ComputeGenericHash(*this);
    }
    
    bool PipelineLayoutDesc::operator==(const PipelineLayoutDesc &other) const
    {
        return memcmp(this, &other, sizeof(PipelineLayoutDesc)) == 0;
    }
    
    void PipelineLayoutDesc::updateDescriptorSetLayout(DescriptorSetIndex setIndex,
                                                       const DescriptorSetLayoutDesc &desc)
    {
        mDescriptorSetLayouts[setIndex] = desc;
    }
    
    void PipelineLayoutDesc::updatePushConstantRange(gl::ShaderType shaderType,
                                                     uint32_t offset,
                                                     uint32_t size)
    {
        ASSERT(shaderType == gl::ShaderType::Vertex || shaderType == gl::ShaderType::Fragment ||
               shaderType == gl::ShaderType::Geometry || shaderType == gl::ShaderType::Compute);
        PackedPushConstantRange &packed = mPushConstantRanges[shaderType];
        packed.offset                   = offset;
        packed.size                     = size;
    }
    
    const PushConstantRangeArray<PackedPushConstantRange> &PipelineLayoutDesc::getPushConstantRanges()
        const
    {
        return mPushConstantRanges;
    }
    
    // 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);
    }
    
    TextureDescriptorDesc::TextureDescriptorDesc() : mMaxIndex(0)
    {
        mSerials.fill({kInvalidImageOrBufferViewSubresourceSerial, kInvalidSamplerSerial});
    }
    
    TextureDescriptorDesc::~TextureDescriptorDesc()                                  = default;
    TextureDescriptorDesc::TextureDescriptorDesc(const TextureDescriptorDesc &other) = default;
    TextureDescriptorDesc &TextureDescriptorDesc::operator=(const TextureDescriptorDesc &other) =
        default;
    
    void TextureDescriptorDesc::update(size_t index,
                                       ImageOrBufferViewSubresourceSerial viewSerial,
                                       SamplerSerial samplerSerial)
    {
        if (index >= mMaxIndex)
        {
            mMaxIndex = static_cast<uint32_t>(index + 1);
        }
    
        mSerials[index].view    = viewSerial;
        mSerials[index].sampler = samplerSerial;
    }
    
    size_t TextureDescriptorDesc::hash() const
    {
        return angle::ComputeGenericHash(&mSerials, sizeof(TexUnitSerials) * mMaxIndex);
    }
    
    void TextureDescriptorDesc::reset()
    {
        memset(mSerials.data(), 0, sizeof(mSerials[0]) * mMaxIndex);
        mMaxIndex = 0;
    }
    
    bool TextureDescriptorDesc::operator==(const TextureDescriptorDesc &other) const
    {
        if (mMaxIndex != other.mMaxIndex)
            return false;
    
        if (mMaxIndex == 0)
            return true;
    
        return memcmp(mSerials.data(), other.mSerials.data(), sizeof(TexUnitSerials) * mMaxIndex) == 0;
    }
    
    // UniformsAndXfbDescriptorDesc implementation.
    UniformsAndXfbDescriptorDesc::UniformsAndXfbDescriptorDesc()
    {
        reset();
    }
    
    UniformsAndXfbDescriptorDesc::~UniformsAndXfbDescriptorDesc() = default;
    UniformsAndXfbDescriptorDesc::UniformsAndXfbDescriptorDesc(
        const UniformsAndXfbDescriptorDesc &other)                      = default;
    UniformsAndXfbDescriptorDesc &UniformsAndXfbDescriptorDesc::operator=(
        const UniformsAndXfbDescriptorDesc &other) = default;
    
    size_t UniformsAndXfbDescriptorDesc::hash() const
    {
        ASSERT(mBufferCount > 0);
    
        return angle::ComputeGenericHash(&mBufferSerials, sizeof(mBufferSerials[0]) * mBufferCount) ^
               angle::ComputeGenericHash(
                   &mXfbBufferOffsets,
                   sizeof(mXfbBufferOffsets[0]) * (mBufferCount - kDefaultUniformBufferCount));
    }
    
    void UniformsAndXfbDescriptorDesc::reset()
    {
        mBufferCount = 0;
        memset(&mBufferSerials, 0, sizeof(mBufferSerials));
        memset(&mXfbBufferOffsets, 0, sizeof(mXfbBufferOffsets));
    }
    
    bool UniformsAndXfbDescriptorDesc::operator==(const UniformsAndXfbDescriptorDesc &other) const
    {
        if (mBufferCount != other.mBufferCount)
        {
            return false;
        }
    
        ASSERT(mBufferCount > 0);
    
        return memcmp(&mBufferSerials, &other.mBufferSerials,
                      sizeof(mBufferSerials[0]) * mBufferCount) == 0 &&
               memcmp(&mXfbBufferOffsets, &other.mXfbBufferOffsets,
                      sizeof(mXfbBufferOffsets[0]) * (mBufferCount - kDefaultUniformBufferCount)) == 0;
    }
    
    // ShaderBuffersDescriptorDesc implementation.
    ShaderBuffersDescriptorDesc::ShaderBuffersDescriptorDesc()
    {
        reset();
    }
    
    ShaderBuffersDescriptorDesc::~ShaderBuffersDescriptorDesc() = default;
    
    ShaderBuffersDescriptorDesc::ShaderBuffersDescriptorDesc(const ShaderBuffersDescriptorDesc &other) =
        default;
    
    ShaderBuffersDescriptorDesc &ShaderBuffersDescriptorDesc::operator=(
        const ShaderBuffersDescriptorDesc &other) = default;
    
    size_t ShaderBuffersDescriptorDesc::hash() const
    {
        return angle::ComputeGenericHash(mPayload.data(), sizeof(mPayload[0]) * mPayload.size());
    }
    
    void ShaderBuffersDescriptorDesc::reset()
    {
        mPayload.clear();
    }
    
    bool ShaderBuffersDescriptorDesc::operator==(const ShaderBuffersDescriptorDesc &other) const
    {
        return mPayload == other.mPayload;
    }
    
    // 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);
    }
    
    // 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,
                             uint64_t externalFormat,
                             angle::FormatID formatID)
    {
        update(contextVk, samplerState, stencilMode, externalFormat, formatID);
    }
    
    void SamplerDesc::reset()
    {
        mMipLodBias         = 0.0f;
        mMaxAnisotropy      = 0.0f;
        mMinLod             = 0.0f;
        mMaxLod             = 0.0f;
        mExternalOrVkFormat = 0;
        mMagFilter          = 0;
        mMinFilter          = 0;
        mMipmapMode         = 0;
        mAddressModeU       = 0;
        mAddressModeV       = 0;
        mAddressModeW       = 0;
        mCompareEnabled     = 0;
        mCompareOp          = 0;
        mIsExternalFormat   = 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,
                             uint64_t externalFormat,
                             angle::FormatID formatID)
    {
        const angle::FeaturesVk &featuresVk = contextVk->getFeatures();
        mMipLodBias                         = 0.0f;
        for (size_t lodOffsetFeatureIdx = 0;
             lodOffsetFeatureIdx < featuresVk.forceTextureLODOffset.size(); lodOffsetFeatureIdx++)
        {
            if (featuresVk.forceTextureLODOffset[lodOffsetFeatureIdx].enabled)
            {
                // Make sure only one forceTextureLODOffset feature is set.
                ASSERT(mMipLodBias == 0.0f);
                mMipLodBias = static_cast<float>(lodOffsetFeatureIdx + 1);
            }
        }
    
        mMaxAnisotropy = samplerState.getMaxAnisotropy();
        mMinLod        = samplerState.getMinLod();
        mMaxLod        = samplerState.getMaxLod();
    
        // GL has no notion of external format, this must be provided from metadata from the image
        const vk::Format &vkFormat = contextVk->getRenderer()->getFormat(formatID);
        mIsExternalFormat          = (externalFormat != 0) ? 1 : 0;
        mExternalOrVkFormat        = (externalFormat != 0)
                                  ? externalFormat
                                  : (vkFormat.intendedFormat().isYUV)
                                        ? static_cast<uint64_t>(vkFormat.actualImageVkFormat())
                                        : 0;
    
        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;
    
        mBorderColor = samplerState.getBorderColor().colorF;
        if (vkFormat.intendedFormatID != angle::FormatID::NONE)
        {
            LoadTextureBorderFunctionInfo loadFunction = vkFormat.textureBorderLoadFunctions();
            loadFunction.loadFunction(mBorderColor);
        }
    
        mReserved = 0;
    }
    
    angle::Result SamplerDesc::init(ContextVk *contextVk, Sampler *sampler) const
    {
        const gl::Extensions &extensions = contextVk->getExtensions();
    
        bool anisotropyEnable = extensions.textureFilterAnisotropic && 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.textureFilteringCHROMIUM);
            filteringInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_FILTERING_PRECISION_GOOGLE;
            filteringInfo.samplerFilteringPrecisionMode =
                VK_SAMPLER_FILTERING_PRECISION_MODE_HIGH_GOOGLE;
            AddToPNextChain(&createInfo, &filteringInfo);
        }
    
        VkSamplerYcbcrConversionInfo yuvConversionInfo = {};
        if (mExternalOrVkFormat)
        {
            ASSERT((contextVk->getRenderer()->getFeatures().supportsYUVSamplerConversion.enabled));
            yuvConversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO;
            yuvConversionInfo.pNext = nullptr;
            yuvConversionInfo.conversion =
                contextVk->getRenderer()->getYuvConversionCache().getSamplerYcbcrConversion(
                    mExternalOrVkFormat, (mIsExternalFormat == 1));
            AddToPNextChain(&createInfo, &yuvConversionInfo);
    
            // 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;
            // VUID-VkSamplerCreateInfo-minFilter VkCreateSampler:
            // VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT
            // specifies that the format can have different chroma, min, and mag filters. However,
            // VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT is
            // not supported for VkSamplerYcbcrConversionCreateInfo.format = VK_FORMAT_UNDEFINED so
            // minFilter/magFilter needs to be equal to chromaFilter.
            // HardwareBufferImageSiblingVkAndroid() forces VK_FILTER_NEAREST, so force
            // VK_FILTER_NEAREST here too.
            createInfo.magFilter = VK_FILTER_NEAREST;
            createInfo.minFilter = VK_FILTER_NEAREST;
        }
    
        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().supportsCustomBorderColorEXT.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;
    }
    }  // 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.miss();
        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)
    {
        for (auto &item : mPayload)
        {
            vk::PipelineHelper &pipeline = item.second;
            context->addGarbage(&pipeline.getPipeline());
        }
    
        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 vk::ShaderModule *vertexModule,
        const vk::ShaderModule *fragmentModule,
        const vk::ShaderModule *geometryModule,
        const vk::ShaderModule *tessControlModule,
        const vk::ShaderModule *tessEvaluationModule,
        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)
        {
            contextVk->getRenderer()->onNewGraphicsPipeline();
            ANGLE_TRY(desc.initializePipeline(
                contextVk, pipelineCacheVk, compatibleRenderPass, pipelineLayout,
                activeAttribLocationsMask, programAttribsTypeMask, vertexModule, fragmentModule,
                geometryModule, tessControlModule, tessEvaluationModule, 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.miss();
        // 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.miss();
        // 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::PushConstantRangeArray<vk::PackedPushConstantRange> &descPushConstantRanges =
            desc.getPushConstantRanges();
    
        gl::ShaderVector<VkPushConstantRange> pushConstantRanges;
    
        for (const gl::ShaderType shaderType : gl::AllShaderTypes())
        {
            const vk::PackedPushConstantRange &pushConstantDesc = descPushConstantRanges[shaderType];
            if (pushConstantDesc.size > 0)
            {
                VkPushConstantRange range;
                range.stageFlags = gl_vk::kShaderStageMap[shaderType];
                range.offset     = pushConstantDesc.offset;
                range.size       = pushConstantDesc.size;
    
                pushConstantRanges.push_back(range);
            }
        }
    
        // 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();
        createInfo.pushConstantRangeCount     = static_cast<uint32_t>(pushConstantRanges.size());
        createInfo.pPushConstantRanges        = pushConstantRanges.data();
    
        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::RefCountedSamplerYcbcrConversion &yuvSampler = iter.second;
            ASSERT(!yuvSampler.isReferenced());
            yuvSampler.get().destroy(device);
    
            rendererVk->getActiveHandleCounts().onDeallocate(vk::HandleType::SamplerYcbcrConversion);
        }
    
        for (auto &iter : mVkFormatPayload)
        {
            vk::RefCountedSamplerYcbcrConversion &yuvSampler = iter.second;
            ASSERT(!yuvSampler.isReferenced());
            yuvSampler.get().destroy(device);
    
            rendererVk->getActiveHandleCounts().onDeallocate(vk::HandleType::SamplerYcbcrConversion);
        }
    
        mExternalFormatPayload.clear();
        mVkFormatPayload.clear();
    }
    
    template <typename T>
    angle::Result SamplerYcbcrConversionCache::getYuvConversionImpl(
        vk::Context *context,
        T format,
        SamplerYcbcrConversionMap<T> *payload,
        const VkSamplerYcbcrConversionCreateInfo &yuvConversionCreateInfo,
        vk::BindingPointer<vk::SamplerYcbcrConversion> *yuvConversionOut)
    {
        const auto iter = payload->find(format);
        if (iter != payload->end())
        {
            vk::RefCountedSamplerYcbcrConversion &yuvConversion = iter->second;
            yuvConversionOut->set(&yuvConversion);
            mCacheStats.hit();
            return angle::Result::Continue;
        }
    
        mCacheStats.miss();
        vk::SamplerYcbcrConversion wrappedYuvConversion;
        ANGLE_VK_TRY(context, wrappedYuvConversion.init(context->getDevice(), yuvConversionCreateInfo));
    
        auto insertedItem = payload->emplace(
            format, vk::RefCountedSamplerYcbcrConversion(std::move(wrappedYuvConversion)));
        vk::RefCountedSamplerYcbcrConversion &insertedYuvConversion = insertedItem.first->second;
        yuvConversionOut->set(&insertedYuvConversion);
    
        context->getRenderer()->getActiveHandleCounts().onAllocate(
            vk::HandleType::SamplerYcbcrConversion);
    
        return angle::Result::Continue;
    }
    
    angle::Result SamplerYcbcrConversionCache::getYuvConversion(
        vk::Context *context,
        uint64_t externalOrVkFormat,
        bool isExternalFormat,
        const VkSamplerYcbcrConversionCreateInfo &yuvConversionCreateInfo,
        vk::BindingPointer<vk::SamplerYcbcrConversion> *yuvConversionOut)
    {
        if (isExternalFormat)
        {
            return getYuvConversionImpl(context, externalOrVkFormat, &mExternalFormatPayload,
                                        yuvConversionCreateInfo, yuvConversionOut);
        }
        else
        {
            return getYuvConversionImpl(context, static_cast<VkFormat>(externalOrVkFormat),
                                        &mVkFormatPayload, yuvConversionCreateInfo, yuvConversionOut);
        }
    }
    
    template <typename T>
    VkSamplerYcbcrConversion SamplerYcbcrConversionCache::getSamplerYcbcrConversionImpl(
        T format,
        const SamplerYcbcrConversionMap<T> &payload) const
    {
        const auto iter = payload.find(format);
        if (iter != payload.end())
        {
            const vk::RefCountedSamplerYcbcrConversion &yuvConversion = iter->second;
            return yuvConversion.get().getHandle();
        }
    
        // Should never get here if we have a valid format.
        UNREACHABLE();
        return VK_NULL_HANDLE;
    }
    
    VkSamplerYcbcrConversion SamplerYcbcrConversionCache::getSamplerYcbcrConversion(
        uint64_t externalOrVkFormat,
        bool isExternalFormat) const
    {
        if (isExternalFormat)
        {
            return getSamplerYcbcrConversionImpl(externalOrVkFormat, mExternalFormatPayload);
        }
        else
        {
            return getSamplerYcbcrConversionImpl(static_cast<VkFormat>(externalOrVkFormat),
                                                 mVkFormatPayload);
        }
    }
    
    // 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->getActiveHandleCounts().onDeallocate(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.miss();
        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()->getActiveHandleCounts().onAllocate(vk::HandleType::Sampler);
    
        return angle::Result::Continue;
    }
    
    // DriverUniformsDescriptorSetCache implementation.
    void DriverUniformsDescriptorSetCache::destroy(RendererVk *rendererVk)
    {
        accumulateCacheStats(rendererVk);
        mPayload.clear();
    }
    
    // DescriptorSetCache implementation.
    template <typename Key, VulkanCacheType CacheType>
    void DescriptorSetCache<Key, CacheType>::destroy(RendererVk *rendererVk)
    {
        this->accumulateCacheStats(rendererVk);
        mPayload.clear();
    }
    
    // RendererVk's methods are not accessible in vk_cache_utils.h
    // Below declarations are needed to avoid linker errors.
    // Unclear why Clang warns about weak vtables in this case.
    ANGLE_DISABLE_WEAK_TEMPLATE_VTABLES_WARNING
    template class DescriptorSetCache<vk::TextureDescriptorDesc, VulkanCacheType::TextureDescriptors>;
    
    template class DescriptorSetCache<vk::UniformsAndXfbDescriptorDesc,
                                      VulkanCacheType::UniformsAndXfbDescriptors>;
    
    template class DescriptorSetCache<vk::ShaderBuffersDescriptorDesc,
                                      VulkanCacheType::ShaderBuffersDescriptors>;
    ANGLE_REENABLE_WEAK_TEMPLATE_VTABLES_WARNING
    }  // namespace rx