//
// Copyright 2016 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_renderer.cpp:
//    Implements the class methods for Renderer.
//

#ifdef UNSAFE_BUFFERS_BUILD
#    pragma allow_unsafe_buffers
#endif

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

// Placing this first seems to solve an intellisense bug.
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/vulkan/vk_utils.h"

#include <EGL/eglext.h>
#include <fstream>

#include "common/debug.h"
#include "common/platform.h"
#include "common/system_utils.h"
#include "common/vulkan/libvulkan_loader.h"
#include "common/vulkan/vulkan_icd.h"
#include "gpu_info_util/SystemInfo_vulkan.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/renderer/driver_utils.h"
#include "libANGLE/renderer/vulkan/CompilerVk.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/DisplayVk.h"
#include "libANGLE/renderer/vulkan/FramebufferVk.h"
#include "libANGLE/renderer/vulkan/ProgramVk.h"
#include "libANGLE/renderer/vulkan/SyncVk.h"
#include "libANGLE/renderer/vulkan/VertexArrayVk.h"
#include "libANGLE/renderer/vulkan/vk_caps_utils.h"
#include "libANGLE/renderer/vulkan/vk_format_utils.h"
#include "libANGLE/renderer/vulkan/vk_resource.h"
#include "libANGLE/trace.h"
#include "platform/PlatformMethods.h"
#include "vulkan/vulkan_core.h"

// Consts
namespace
{
#if defined(ANGLE_PLATFORM_ANDROID)
constexpr const char *kDefaultPipelineCacheGraphDumpPath = "/data/local/tmp/angle_dumps/";
#else
constexpr const char *kDefaultPipelineCacheGraphDumpPath = "";
#endif  // ANGLE_PLATFORM_ANDROID

constexpr VkFormatFeatureFlags kInvalidFormatFeatureFlags = static_cast<VkFormatFeatureFlags>(-1);

#if defined(ANGLE_EXPOSE_NON_CONFORMANT_EXTENSIONS_AND_VERSIONS)
constexpr bool kExposeNonConformantExtensionsAndVersions = true;
#else
constexpr bool kExposeNonConformantExtensionsAndVersions = false;
#endif

#if defined(ANGLE_ENABLE_CRC_FOR_PIPELINE_CACHE)
constexpr bool kEnableCRCForPipelineCache = true;
#else
constexpr bool kEnableCRCForPipelineCache = false;
#endif

#if defined(ANGLE_ENABLE_VULKAN_API_DUMP_LAYER)
constexpr bool kEnableVulkanAPIDumpLayer = true;
#else
constexpr bool kEnableVulkanAPIDumpLayer = false;
#endif
}  // anonymous namespace

namespace rx
{
namespace
{
constexpr angle::PackedEnumMap<QueueSubmitReason, const char *> kQueueSubmitReason = {{
    {QueueSubmitReason::EGLBindTexImage, "Queue submission imminent due to eglBindTexImage()"},
    {QueueSubmitReason::EGLSwapBuffers, "Queue submission imminent due to eglSwapBuffers()"},
    {QueueSubmitReason::EGLWaitClient, "Queue submission imminent due to eglWaitClient()"},
    {QueueSubmitReason::DeferredFlush, "Queue submission imminent due to deferred flushing"},
    {QueueSubmitReason::GLFinish, "Queue submission imminent due to glFinish()"},
    {QueueSubmitReason::GLFlush, "Queue submission imminent due to glFlush()"},
    {QueueSubmitReason::GLReadPixels, "Queue submission imminent due to glReadPixels()"},
    {QueueSubmitReason::AcquireNextImage, "Queue submission imminent due to acquiring next image"},
    {QueueSubmitReason::ContextChange, "Queue submission imminent due to context change"},
    {QueueSubmitReason::ContextDestruction, "Queue submission imminent due to context destruction"},
    {QueueSubmitReason::ContextPriorityChange,
     "Queue submission imminent due to context priority change"},
    {QueueSubmitReason::SurfaceUnMakeCurrent,
     "Queue submission imminent due to unmaking current on surface"},
    {QueueSubmitReason::CopyBufferToImageOneOff,
     "Queue submission imminent after one-off copying buffer to image"},
    {QueueSubmitReason::CopyBufferToSurfaceImage,
     "Queue submission imminent after copying buffer to surface image"},
    {QueueSubmitReason::CopySurfaceImageToBuffer,
     "Queue submission imminent after copying surface image to buffer"},
    {QueueSubmitReason::ForeignImageRelease,
     "Queue submission imminent due to releasing foreign image"},
    {QueueSubmitReason::ImageUseThenReleaseToExternal,
     "Queue submission imminent due to image used in render pass being released"},
    {QueueSubmitReason::TextureReformatToRenderable,
     "Queue submission imminent due to reformatting texture to a renderable fallback"},
    {QueueSubmitReason::CopyTextureOnCPU,
     "Queue submission imminent due to fallback to CPU when copying texture"},
    {QueueSubmitReason::GenerateMipmapOnCPU,
     "Queue submission imminent due to fallback to CPU when generating mipmaps"},
    {QueueSubmitReason::ExternalSemaphoreSignal,
     "Queue submission imminent due to external semaphore signal"},
    {QueueSubmitReason::GetQueryResult, "Queue submission imminent after getting query result"},
    {QueueSubmitReason::GetTimestamp, "Queue submission imminent after getting the timestamp"},
    {QueueSubmitReason::SyncCPUGPUTime,
     "Queue submission imminent due to synchronizing CPU and GPU time"},
    {QueueSubmitReason::SyncObjectInit, "Queue submission imminent after initializing sync object"},
    {QueueSubmitReason::SyncObjectClientWait,
     "Queue submission imminent due to sync object client wait"},
    {QueueSubmitReason::SyncObjectWithFdInit,
     "Queue submission imminent due to inserting sync object with file descriptor"},
    {QueueSubmitReason::DeviceLocalBufferMap,
     "Queue submission imminent due to mapping device local buffer"},
    {QueueSubmitReason::BufferWriteThenMap,
     "Queue submission imminent due to mapping buffer being written to by render pass"},
    {QueueSubmitReason::BufferInUseWhenSynchronizedMap,
     "Queue submission imminent due to mapping buffer in use by GPU without "
     "GL_MAP_UNSYNCHRONIZED_BIT"},
    {QueueSubmitReason::WaitSemaphore, "Queue submission imminent after waiting for semaphore"},
    {QueueSubmitReason::ExcessivePendingGarbage,
     "Queue submission imminent due to exceeding pending garbage memory size threshold"},
    {QueueSubmitReason::OutOfMemory,
     "Queue submission imminent due to context finish to mitigate running out of device memory"},
    {QueueSubmitReason::RenderPassCountLimitReached,
     "Queue submission imminent due to exceeding the threshold for render pass command count"},
    {QueueSubmitReason::RenderPassCommandLimitReached,
     "Queue submission imminent due to exceeding write command limit at draw time"},
    {QueueSubmitReason::BufferToImageUpdateLimitReached,
     "Queue submission imminent due to exceeding buffer-to-image update size limit"},
    {QueueSubmitReason::ForceSubmitStagedTexture,
     "Queue submission imminent due to staged texture updates"},
    {QueueSubmitReason::InitializeMemory, "Queue submission imminent due to initializing memory"},
}};
}  // namespace

std::ostream &operator<<(std::ostream &os, const QueueSubmitReason reason)
{
    os << kQueueSubmitReason[reason];
    return os;
}

namespace vk
{
namespace
{
constexpr uint32_t kMinDefaultUniformBufferSize = 16 * 1024u;
// This size is picked based on experience. Majority of devices support 64K
// maxUniformBufferSize. Since this is per context buffer, a bigger buffer size reduces the
// number of descriptor set allocations, so we picked the maxUniformBufferSize that most
// devices supports. It may need further tuning based on specific device needs and balance
// between performance and memory usage.
constexpr uint32_t kPreferredDefaultUniformBufferSize = 64 * 1024u;

// The limit below is used to avoid allocating too many device memory handles. If the device has an
// allocation count limit less than or equal to this threshold, the initial size for the dynamic
// buffers will be larger.
constexpr size_t kAllocationCountThresholdForDynamicVertexDataSize = 4096;

// Maximum size to use VMA image suballocation. Any allocation greater than or equal to this
// value will use a dedicated VkDeviceMemory.
constexpr size_t kImageSizeThresholdForDedicatedMemoryAllocation = 8 * 1024 * 1024;

// Maximum size for an allocated memory for a single object.
constexpr VkDeviceSize kMemoryAllocationSizeLimit = 1 * 1024 * 1024 * 1024;

// Pipeline cache header version. It should be incremented any time there is an update to the cache
// header or data structure.
constexpr uint32_t kPipelineCacheVersion = 3;

// Update the pipeline cache every this many swaps.
constexpr uint32_t kPipelineCacheVkUpdatePeriod = 60;

// Per the Vulkan specification, ANGLE must indicate the highest version of Vulkan functionality
// that it uses.  The Vulkan validation layers will issue messages for any core functionality that
// requires a higher version.
//
// ANGLE specifically limits its core version to Vulkan 1.1 and relies on availability of
// extensions.  While implementations are not required to expose an extension that is promoted to
// later versions, they always do so in practice.  Avoiding later core versions helps keep the
// initialization logic simpler.
constexpr uint32_t kPreferredVulkanAPIVersion = VK_API_VERSION_1_1;

bool IsVulkan11(uint32_t apiVersion)
{
    return apiVersion >= VK_API_VERSION_1_1;
}

bool IsRADV(uint32_t vendorId, uint32_t driverId, const char *deviceName)
{
    // Check against RADV driver id first.
    if (driverId == VK_DRIVER_ID_MESA_RADV)
    {
        return true;
    }

    // Otherwise, look for RADV in the device name.  This works for both RADV
    // and Venus-over-RADV.
    return IsAMD(vendorId) && strstr(deviceName, "RADV") != nullptr;
}

bool IsMesaPanVK(uint32_t driverId)
{
    // rely on VkDriverId alone to identify MESA PanVK driver
    return driverId == VK_DRIVER_ID_MESA_PANVK;
}

bool IsQualcommOpenSource(uint32_t vendorId, uint32_t driverId, const char *deviceName)
{
    if (!IsQualcomm(vendorId))
    {
        return false;
    }

    // Where driver id is available, distinguish by driver id:
    if (driverId != 0)
    {
        return driverId != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
    }

    // Otherwise, look for Venus or Turnip in the device name.
    return strstr(deviceName, "Venus") != nullptr || strstr(deviceName, "Turnip") != nullptr;
}

bool IsXclipse()
{
    if (!IsAndroid())
    {
        return false;
    }

    std::string modelName;
    if (!angle::android::GetSystemProperty(angle::android::kModelSystemPropertyName, &modelName))
    {
        return 0;
    }

    // Improve this when more Xclipse devices are available
    return strstr(modelName.c_str(), "SM-S901B") != nullptr ||
           strstr(modelName.c_str(), "SM-S926B") != nullptr;
}

bool StrLess(const char *a, const char *b)
{
    return strcmp(a, b) < 0;
}

bool ExtensionFound(const char *needle, const vk::ExtensionNameList &haystack)
{
    // NOTE: The list must be sorted.
    return std::binary_search(haystack.begin(), haystack.end(), needle, StrLess);
}

VkResult VerifyExtensionsPresent(const vk::ExtensionNameList &haystack,
                                 const vk::ExtensionNameList &needles)
{
    // NOTE: The lists must be sorted.
    if (std::includes(haystack.begin(), haystack.end(), needles.begin(), needles.end(), StrLess))
    {
        return VK_SUCCESS;
    }
    for (const char *needle : needles)
    {
        if (!ExtensionFound(needle, haystack))
        {
            ERR() << "Extension not supported: " << needle;
        }
    }
    return VK_ERROR_EXTENSION_NOT_PRESENT;
}

// Array of Validation error/warning messages that will be ignored, should include bugID
constexpr const char *kSkippedMessages[] = {
    // http://anglebug.com/42266825
    "Undefined-Value-ShaderOutputNotConsumed",
    "Undefined-Value-ShaderInputNotProduced",
    // ANGLE sets gl_Layer when the framebuffer is not layered, but VVL does not see that.  When
    // layered, if gl_Layer is out of bounds, the results are undefined in both GL and Vulkan.
    // http://anglebug.com/372390039
    "Undefined-Layer-Written",
    // http://anglebug.com/42263850
    "VUID-vkCmdDraw-magFilter-04553",
    "VUID-vkCmdDrawIndexed-magFilter-04553",
    // http://anglebug.com/42265014
    "vkEnumeratePhysicalDevices: One or more layers modified physical devices",
    // http://anglebug.com/42265797
    "VUID-vkCmdBindVertexBuffers2-pStrides-06209",
    // http://anglebug.com/42266199
    "VUID-vkDestroySemaphore-semaphore-01137",
    "VUID-vkDestroySemaphore-semaphore-05149",
    // http://anglebug.com/42266819
    "VUID-vkCmdDraw-None-09000",
    "VUID-vkCmdDrawIndexed-None-09002",
    // http://anglebug.com/40644894
    "VUID-VkDescriptorImageInfo-imageView-06711",
    "VUID-VkDescriptorImageInfo-descriptorType-06713",
    // http://crbug.com/1412096
    "VUID-VkImageCreateInfo-pNext-00990",
    // http://anglebug.com/42266565
    "VUID-VkGraphicsPipelineCreateInfo-Input-07904",
    "VUID-VkGraphicsPipelineCreateInfo-Input-07905",
    "VUID-vkCmdDrawIndexed-None-07835",
    "VUID-VkGraphicsPipelineCreateInfo-Input-08733",
    "VUID-vkCmdDraw-Input-08734",
    // https://anglebug.com/42266639
    "VUID-VkVertexInputBindingDivisorDescriptionKHR-divisor-01870",
    "VUID-VkVertexInputBindingDivisorDescription-divisor-01870",
    // https://anglebug.com/42265766
    "VUID-vkCmdBlitImage-srcImage-00240",
    // https://anglebug.com/42266678
    // VVL bug: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7858
    "VUID-vkCmdDraw-None-08608",
    "VUID-vkCmdDrawIndexed-None-08608",
    "VUID-vkCmdDraw-None-07843",
    "VUID-vkCmdDrawIndexed-None-07843",
    "VUID-vkCmdDraw-None-07844",
    "VUID-vkCmdDrawIndexed-None-07844",
    "VUID-vkCmdDraw-None-07847",
    "VUID-vkCmdDrawIndexed-None-07847",
    // Invalid feedback loop caused by the application
    "VUID-vkCmdDraw-None-09000",
    "VUID-vkCmdDrawIndexed-None-09000",
    "VUID-vkCmdDraw-None-09002",
    "VUID-vkCmdDrawIndexed-None-09002",
    "VUID-vkCmdDraw-None-09003",
    "VUID-vkCmdDrawIndexed-None-09003",
    // https://anglebug.com/42266764
    "VUID-VkDescriptorImageInfo-imageView-07796",
    // https://issuetracker.google.com/303441816
    "VUID-VkRenderPassBeginInfo-renderPass-00904",
    // http://anglebug.com/42266888
    "VUID-VkMemoryAllocateInfo-allocationSize-01742",
    "VUID-VkMemoryDedicatedAllocateInfo-image-01878",
    // http://anglebug.com/42266890
    "VUID-vkCmdDraw-pNext-09461",
    // http://anglebug.com/42266893
    "VUID-VkImportMemoryFdInfoKHR-handleType-00667",
    // http://anglebug.com/42266904
    "VUID-VkImportMemoryWin32HandleInfoKHR-handleType-00658",
    // https://anglebug.com/42266947
    "VUID-VkPipelineVertexInputStateCreateInfo-pNext-pNext",
    // https://issuetracker.google.com/319228278
    "VUID-vkCmdDrawIndexed-format-07753",
    "VUID-vkCmdDraw-format-07753",
    "Undefined-Value-ShaderFragmentOutputMismatch",
    // https://anglebug.com/336652255
    "VUID-vkCmdDraw-None-09600",
    // https://issuetracker.google.com/336847261
    "VUID-VkImageCreateInfo-pNext-02397",
    "VUID-vkCmdDraw-None-06550",
    // https://anglebug.com/345304850
    "WARNING-Shader-OutputNotConsumed",
    // https://anglebug.com/383311444
    "VUID-vkCmdDraw-None-09462",
    // https://anglebug.com/394598758
    "VUID-vkBindBufferMemory-size-01037",
    // https://anglebug.com/443133082
    "VUID-vkCmdDraw-None-09549",
    // https://anglebug.com/470128354
    "VUID-vkCmdEndQuery-None-07007",
    // https://anglebug.com/475549551
    "VUID-VkGraphicsPipelineCreateInfo-renderPass-09652",
    // https://anglebug.com/512394647
    "VUID-VkImageCreateInfo-imageCreateMaxMipLevels-02251",
};

// Validation messages that should be ignored only when VK_EXT_primitive_topology_list_restart is
// not present.
constexpr const char *kNoListRestartSkippedMessages[] = {
    // http://anglebug.com/42262476
    "VUID-VkPipelineInputAssemblyStateCreateInfo-topology-06252",
};

// Validation messages that should be ignored only when VK_KHR_maintenance5 is not present.
constexpr const char *kNoMaintenance5SkippedMessages[] = {
    // https://anglebug.com/42266575#comment4
    "VUID-VkBufferViewCreateInfo-format-08779",
    // https://anglebug.com/42266675
    "VUID-VkGraphicsPipelineCreateInfo-topology-08773",
    "VUID-vkCmdDraw-primitiveTopology-10748",
    "VUID-vkCmdDrawIndexed-primitiveTopology-10748",
    "VUID-vkCmdDrawIndirect-primitiveTopology-10748",
    "VUID-vkCmdDrawIndexedIndirect-primitiveTopology-10748",
};

// Validation messages that should be ignored only when VK_KHR_maintenance9 is not present.
constexpr const char *kNoMaintenance9SkippedMessages[] = {
    // http://issuetracker.google.com/429339330
    "WARNING-VkImageSubresourceRange-layerCount-compatibility",
};

// Validation messages that should be ignored only when VK_KHR_swapchain_maintenance1 is not
// present.
constexpr const char *kNoSwapchainMaintenance1SkippedMessages[] = {
    // Without VK_KHR_swapchain_maintenance1, there is technically no correct way to know when a
    // VkSwapchainKHR can be destroyed.  If vkDeviceWaitIdle() is called, that's blessed as "good
    // enough", but ANGLE does not call that function.  With VK_KHR_swapchain_maintenance1, the last
    // present's fence is appropriately waited on.  This technicality with older drivers is ignored.
    // See https://gitlab.khronos.org/vulkan/vulkan/-/issues/4761
    "VUID-vkDestroySwapchainKHR-swapchain-01282",
};

// Validation messages that should be ignored only when preferBGR565ToRGB565 is enabled.
constexpr const char *kPreferBGR565SkippedMessages[] = {
    // http://anglebug.com/42264464
    "VUID-VkSamplerCustomBorderColorCreateInfoEXT-format-04015",
};

// Validation messages that should be ignored only when exposeNonConformantExtensionsAndVersions is
// enabled on certain test platforms.
constexpr const char *kExposeNonConformantSkippedMessages[] = {
    // http://issuetracker.google.com/376899587
    "VUID-VkSwapchainCreateInfoKHR-presentMode-01427",
};

// When using Vulkan secondary command buffers, the command buffer is begun with the current
// framebuffer specified in pInheritanceInfo::framebuffer.  If the framebuffer is multisampled
// and is resolved, an optimization would change the framebuffer to add the resolve target and
// use a subpass resolve operation instead.  The following error complains that the framebuffer
// used to start the render pass and the one specified in pInheritanceInfo::framebuffer must be
// equal, which is not true in that case.  In practice, this is benign, as the part of the
// framebuffer that's accessed by the command buffer is identically laid out.
// http://anglebug.com/42265307
constexpr const char *kSkippedMessagesWithRenderPassObjectsAndVulkanSCB[] = {
    "VUID-vkCmdExecuteCommands-pCommandBuffers-00099",
};

// When MSRTSS-rendering to a cubemap, ANGLE queries support without using the CUBE_COMPATIBLE
// create flag because otherwise Vulkan requires that the driver returns VK_SAMPLE_COUNT_1_BIT.  VVL
// doesn't do this however, so it complains that multisampling is not supported.
constexpr const char *kSkippedMessagesWithMSRTSSWithoutDynamicRendering[] = {
    "VUID-VkFramebufferCreateInfo-samples-07009",
    "VUID-VkRenderPassAttachmentBeginInfo-pAttachments-07010",
};

// VVL bugs with dynamic rendering
constexpr const char *kSkippedMessagesWithDynamicRendering[] = {
    // https://anglebug.com/42266678
    // VVL bugs with rasterizer discard:
    // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7858
    "VUID-vkCmdDraw-dynamicRenderingUnusedAttachments-08914",
    "VUID-vkCmdDraw-dynamicRenderingUnusedAttachments-08917",
    "VUID-vkCmdDrawIndexed-dynamicRenderingUnusedAttachments-08914",
    "VUID-vkCmdDrawIndexed-dynamicRenderingUnusedAttachments-08917",
    "VUID-vkCmdDraw-pDepthAttachment-08964",
    "VUID-vkCmdDraw-pStencilAttachment-08965",
    "VUID-vkCmdDrawIndexed-pDepthAttachment-08964",
    "VUID-vkCmdDrawIndexed-pStencilAttachment-08965",
    "VUID-vkCmdDraw-None-07843",
    "VUID-vkCmdDraw-None-07844",
    "VUID-vkCmdDraw-None-07847",
    "VUID-vkCmdDrawIndexed-None-07843",
    "VUID-vkCmdDrawIndexed-None-07844",
    "VUID-vkCmdDrawIndexed-None-07847",
    "VUID-vkCmdDraw-multisampledRenderToSingleSampled-07285",
    "VUID-vkCmdDraw-multisampledRenderToSingleSampled-07286",
    "VUID-vkCmdDraw-multisampledRenderToSingleSampled-07287",
    "VUID-vkCmdDrawIndexed-multisampledRenderToSingleSampled-07285",
    "VUID-vkCmdDrawIndexed-multisampledRenderToSingleSampled-07286",
    "VUID-vkCmdDrawIndexed-multisampledRenderToSingleSampled-07287",
};

// Some syncval errors are resolved in the presence of the NONE load or store render pass ops.  For
// those, ANGLE makes no further attempt to resolve them and expects vendor support for the
// extensions instead.  The list of skipped messages is split based on this support.
constexpr vk::SkippedSyncvalMessage kSkippedSyncvalMessages[] = {
    // https://issuetracker.google.com/316337308
    // DifferentStencilMasksTest.DrawWithSameEffectiveMask/ES2_Vulkan_SwiftShader
    // VulkanPerformanceCounterTest.NewTextureDoesNotBreakRenderPass for both depth and stencil
    // Hit in the asphalt_9
    // http://anglebug.com/42265363
    // dead_by_daylight
    // From: TraceTest.diablo_immortal http://anglebug.com/42266309 (Linux AMD)
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = BeginRenderingError",
         "access = "
         "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "command = vkCmdBeginRenderingKHR",
         "prior_command = vkCmdPipelineBarrier",
         "load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE",
     }},
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = RenderPassLoadOpError",
         "access = "
         "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "command = vkCmdBeginRenderPass",
         "prior_command = vkCmdPipelineBarrier",
         "load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE",
     }},
    // http://anglebug.com/42265427
    // From: TraceTest.blade_and_soul_revolution
    // FramebufferFetchES31.ReopenRenderPass/ES3_1_Vulkan
    {"SYNC-HAZARD-READ-AFTER-WRITE",
     true,
     {
         "message_type = RenderPassLoadOpError",
         "access = "
         "VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT(VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)",
         "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "command = vkCmdBeginRenderPass",
         "prior_command = vkCmdEndRenderPass",
         "load_op = VK_ATTACHMENT_LOAD_OP_LOAD",
     }},
    // http://anglebug.com/42265363
    // http://anglebug.com/415382790
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     true,
     {
         "message_type = RenderPassLayoutTransitionError",
         "access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT(VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT)",
         "command = vkCmdBeginRenderPass",
         "prior_command = vkCmdEndRenderPass",
         "old_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL",
         "new_layout = VK_IMAGE_LAYOUT_GENERAL",
     }},
    // http://anglebug.com/415383266
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     true,
     {
         "message_type = RenderPassLayoutTransitionError",
         "access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT(VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT)",
         "command = vkCmdBeginRenderPass",
         "prior_command = vkCmdDrawIndexed",
         "old_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL",
         "new_layout = VK_IMAGE_LAYOUT_GENERAL",
     }},
    // http://anglebug.com/399191283
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {"message_type = BeginRenderingError",
      "access = "
      "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
      "BIT)",
      "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION", "prior_command = vkCmdPipelineBarrier",
      "command = vkCmdBeginRenderingKHR", "load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE"}},
    // https://anglebug.com/400789178, prior_command could be vkCmdEndRenderPass or
    // vkCmdEndRenderingKHR
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {"message_type = ImageBarrierError", "hazard_type = WRITE_AFTER_WRITE",
      "access = SYNC_IMAGE_LAYOUT_TRANSITION",
      "prior_access = "
      "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT)",
      "command = vkCmdPipelineBarrier", "prior_command = vkCmdEndRender"}},
    // https://anglebug.com/400789178
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {"message_type = RenderPassAttachmentError", "hazard_type = WRITE_AFTER_WRITE",
      "access = "
      "VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT(VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT)",
      "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION", "command = vkCmdDrawIndexed",
      "prior_command = vkCmdEndRenderPass"}},
    // False positive: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10667
    {"SYNC-HAZARD-WRITE-AFTER-READ",
     false,
     {"message_type = DynamicRenderingAttachmentError", "hazard_type = WRITE_AFTER_READ",
      "access = "
      "VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT(VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT)",
      "prior_access = VK_PIPELINE_STAGE_2_BLIT_BIT(VK_ACCESS_2_TRANSFER_READ_BIT)",
      "command = vkCmdDraw", "prior_command = vkCmdBlitImage"}},
    // https://anglebug.com/456785955
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {"hazard_type = WRITE_AFTER_WRITE",
      "access = "
      "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
      "BIT)",
      "prior_access = "
      "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT)",
      "command = vkCmdBeginRender", "prior_command = vkCmdEndRender",
      "load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE"}},
    {"SYNC-HAZARD-READ-AFTER-WRITE",
     false,
     {"message_type = RenderPassLoadOpError", "hazard_type = READ_AFTER_WRITE",
      "access = "
      "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT)",
      "prior_access = "
      "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT)",
      "command = vkCmdBeginRenderPass", "prior_command = vkCmdEndRenderPass",
      "load_op = VK_ATTACHMENT_LOAD_OP_LOAD"}}};

// Messages that should not be generated if the feature to force-enable providing the size pointer
// to vkCmdBindVertexBuffers2() is disabled.
constexpr vk::SkippedSyncvalMessage kSkippedSyncvalMessagesWithoutForcedSizePointer[] = {
    // From: TraceTest.special_forces_group_2 http://anglebug.com/42264123
    // http://anglebug.com/397775556
    // From: TraceTest.life_is_strange http://anglebug.com/42266180 (Linux AMD)
    // From: TraceTest.diablo_immortal http://anglebug.com/42266309 (Linux AMD)
    {"SYNC-HAZARD-READ-AFTER-WRITE",
     false,
     {
         "message_type = BufferError",
         "access = "
         "VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT(VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT)",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_COPY_BIT(VK_ACCESS_2_TRANSFER_WRITE_BIT)",
         "command = vkCmdDrawIndexed",
         "prior_command = vkCmdCopyBuffer",
     }},
    // http://anglebug.com/394598470
    {"SYNC-HAZARD-WRITE-AFTER-READ",
     false,
     {
         "message_type = BufferCopyError",
         "access = VK_PIPELINE_STAGE_2_COPY_BIT(VK_ACCESS_2_TRANSFER_WRITE_BIT)",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT(VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT)",
         "command = vkCmdCopyBuffer",
         "prior_command = vkCmdDrawIndexed",
     }},
    // https://anglebug.com/443095908
    {"SYNC-HAZARD-WRITE-AFTER-READ",
     false,
     {"message_type = BufferCopyError", "hazard_type = WRITE_AFTER_READ",
      "access = "
      "VK_PIPELINE_STAGE_2_COPY_BIT(VK_ACCESS_2_TRANSFER_WRITE_BIT)",
      "prior_access = "
      "VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT(VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT)",
      "command = vkCmdCopyImageToBuffer", "prior_command = vkCmdDraw"}},
    {"SYNC-HAZARD-WRITE-AFTER-READ",
     false,
     {"message_type = BufferCopyError", "hazard_type = WRITE_AFTER_READ",
      "access = "
      "VK_PIPELINE_STAGE_2_COPY_BIT(VK_ACCESS_2_TRANSFER_WRITE_BIT)",
      "prior_access = "
      "VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT(VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT)",
      "command = vkCmdCopyImageToBuffer", "prior_command = vkCmdDrawIndexed"}},
    {"SYNC-HAZARD-WRITE-AFTER-READ",
     false,
     {"message_type = SubmitTimeError", "hazard_type = WRITE_AFTER_READ",
      "prior_access = "
      "VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT(VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT)",
      "read_barriers = 0", "command = vkCmdCopyImageToBuffer", "prior_command = vkCmdDrawIndexed"}},
};

// Messages that shouldn't be generated if both loadOp=NONE and storeOp=NONE are supported,
// otherwise they are expected.
constexpr vk::SkippedSyncvalMessage kSkippedSyncvalMessagesWithoutLoadStoreOpNone[] = {
    // This error is generated for multiple reasons:
    //
    // - http://anglebug.com/42264926
    //
    // Observed on AMD when running the following test:
    //
    //     dEQP-GLES2.functional.shaders.builtin_variable.pointcoord
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = BeginRenderingError",
         "hazard_type = WRITE_AFTER_WRITE",
         "access = "
         "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "write_barriers = "
         "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT|VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT("
         "VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT)",
         "command = vkCmdBeginRenderingKHR",
         "prior_command = vkCmdPipelineBarrier",
         "load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE",
     }},
    // Observed on SwiftShader when running the following tests:
    //
    //     dEQP-GLES3.functional.fbo.blit.default_framebuffer.rgb8
    //     TraceTest.life_is_strange
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = ImageBarrierError",
         "hazard_type = WRITE_AFTER_WRITE",
         "access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "write_barriers = 0",
         "command = vkCmdPipelineBarrier",
         "prior_command = vkCmdEndRenderingKHR",
     }},
    // Observed on SwiftShader when running the following tests:
    //
    //     ReadOnlyFeedbackLoopTest.ReadOnlyDepthFeedbackLoopDrawThenDepthStencilClear/ES3_Vulkan_SwiftShader
    //     VulkanPerformanceCounterTest.ClearColorBufferAndReadOnlyDepthStencilUsesSingleRenderPass*
    //     VulkanPerformanceCounterTest.ReadOnlyDepthStencilFeedbackLoopUsesSingleRenderPass/ES3_Vulkan_SwiftShader_PreferMonolithicPipelinesOverLibraries_NoMergeProgramPipelineCachesToGlobalCache
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = ImageBarrierError",
         "hazard_type = WRITE_AFTER_WRITE",
         "access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "write_barriers = 0",
         "command = vkCmdWaitEvents",
         "prior_command = vkCmdEndRenderingKHR",
     }},
    // Observed on old drivers without dynamic rendering and no support for the S8 format when
    // running the following test:
    //
    //     ClearTextureEXTTest.StencilTexture/ES3_Vulkan
    //
    // In this case ANGLE uses LOAD_OP_DONT_CARE and STORE_OP_DONT_CARE for the emulated depth
    // channel.
    //
    // Similarly, in the following test:
    //
    //     TraceTest.scary_teacher_3d
    //
    // LOAD_OP_LOAD and STORE_OP_STORE are used to preserve the depth channel without the NONE ops.
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = RenderPassStoreOpError",
         "hazard_type = WRITE_AFTER_WRITE",
         "access = "
         "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "write_barriers = "
         "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT|VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT("
         "VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT)",
         "command = vkCmdEndRenderPass",
         "prior_command = vkCmdPipelineBarrier",
         "store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE",
     }},
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = RenderPassStoreOpError",
         "hazard_type = WRITE_AFTER_WRITE",
         "access = "
         "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "prior_access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "write_barriers = "
         "VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT|VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT|VK_"
         "PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT)",
         "command = vkCmdEndRenderPass",
         "prior_command = vkCmdPipelineBarrier",
         "store_op = VK_ATTACHMENT_STORE_OP_STORE",
     }},
};

// Messages that are only generated with MSRTT emulation.  Some of these are syncval bugs (discussed
// in https://gitlab.khronos.org/vulkan/vulkan/-/issues/3840)
constexpr vk::SkippedSyncvalMessage kSkippedSyncvalMessagesWithMSRTTEmulation[] = {
    // Unknown whether ANGLE or syncval bug.
    // To repro: see http://anglebug.com/40644740#comment69
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     // TODO: it seems if this filter is removed then the error will be
     // intersepted by a different filter. Investigate the nature of the
     // error if necessary how to improve its detection.
     {
         "message_type = RenderPassLayoutTransitionError",
         "access = SYNC_IMAGE_LAYOUT_TRANSITION",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT(VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT)",
         "command = vkCmdBeginRenderPass",
         "prior_command = vkCmdEndRenderPass",
         "old_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL",
         "new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL",
     }},
    // Unknown whether ANGLE or syncval bug.  See http://anglebug.com/495832130
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = ImageSubresourceRangeError",
         "access = VK_PIPELINE_STAGE_2_CLEAR_BIT(VK_ACCESS_2_TRANSFER_WRITE_BIT)",
         "prior_access = "
         "VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "command = vkCmdClearDepthStencilImage",
         "prior_command = vkCmdNextSubpass",
     }},
    // When one aspect of depth/stencil is LOAD and the other is DONT_CARE and the image is
    // previously initialized, the transfer->depth_stencil barrier's dst access does not include
    // DEPTH_STENCIL_WRITE correctly.  See
    // MSRTTES3Test.RenderToTextureDepthOnly/ES3_1_Vulkan_SwiftShader_EnableMultisampledRenderToTexture_RobustResourceInit
    // for example.
    {"SYNC-HAZARD-WRITE-AFTER-WRITE",
     false,
     {
         "message_type = RenderPassLoadOpError",
         "access = "
         "VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT(VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_"
         "BIT)",
         "prior_access = VK_PIPELINE_STAGE_2_CLEAR_BIT(VK_ACCESS_2_TRANSFER_WRITE_BIT)",
         "command = vkCmdBeginRenderPass",
         "prior_command = vkCmdClearDepthStencilImage",
     }},
};

enum class DebugMessageReport
{
    Ignore,
    Print,
};

bool IsMessageInSkipList(const char *messageId,
                         const char *message,
                         const char *const skippedList[],
                         size_t skippedListSize)
{
    for (size_t index = 0; index < skippedListSize; ++index)
    {
        if (strstr(messageId, skippedList[index]) != nullptr)
        {
            return true;
        }
        if (strstr(message, skippedList[index]) != nullptr)
        {
            return true;
        }
    }

    return false;
}

bool SyncvalMessageMatchesSkip(const char *messageId,
                               const char *message,
                               const vk::SkippedSyncvalMessage &skip)
{
    // TODO(http://anglebug.com/391284743): Ongoing transition: textual matches -> extraProperties.
    // The skip should include at least one extraProperty
    ASSERT(skip.extraProperties[0]);

    if (strstr(messageId, skip.messageId) == nullptr)
    {
        return false;
    }
    // Check that all extraProperties entries are present in the message
    bool mismatch = false;
    for (uint32_t i = 0; i < kMaxSyncValExtraProperties; i++)
    {
        if (skip.extraProperties[i] == nullptr)
        {
            break;
        }
        if (strstr(message, skip.extraProperties[i]) == nullptr)
        {
            mismatch = true;
            break;
        }
    }
    return !mismatch;
}

// Suppress validation errors that are known.  Returns DebugMessageReport::Ignore in that case.
DebugMessageReport ShouldReportDebugMessage(Renderer *renderer,
                                            const char *messageId,
                                            const char *message)
{
    if (message == nullptr || messageId == nullptr)
    {
        return DebugMessageReport::Print;
    }

    // Check with non-syncval messages:
    const std::vector<const char *> &skippedMessages = renderer->getSkippedValidationMessages();
    if (IsMessageInSkipList(messageId, message, skippedMessages.data(), skippedMessages.size()))
    {
        return DebugMessageReport::Ignore;
    }

    // Then check with syncval messages:
    const bool isColorFramebufferFetchUsed = renderer->isColorFramebufferFetchUsed();

    for (const vk::SkippedSyncvalMessage &skip : renderer->getSkippedSyncvalMessages())
    {
        if (!SyncvalMessageMatchesSkip(messageId, message, skip))
        {
            continue;
        }

        if (skip.isDueToNonConformantCoherentColorFramebufferFetch)
        {
            // If the error is due to exposing coherent framebuffer fetch (without
            // VK_EXT_rasterization_order_attachment_access), but framebuffer fetch has not been
            // used by the application, report it.
            //
            // Note that currently syncval doesn't support the
            // VK_EXT_rasterization_order_attachment_access extension, so the syncval messages would
            // continue to be produced despite the extension.
            constexpr bool kSyncValSupportsRasterizationOrderExtension = false;
            const bool hasRasterizationOrderExtension =
                renderer->getFeatures().supportsRasterizationOrderAttachmentAccess.enabled &&
                kSyncValSupportsRasterizationOrderExtension;
            if (!isColorFramebufferFetchUsed || hasRasterizationOrderExtension)
            {
                return DebugMessageReport::Print;
            }
        }

        // Ignore the message as it matched one the the skips
        return DebugMessageReport::Ignore;
    }

    // Message didn't match any skips, report
    return DebugMessageReport::Print;
}

const char *GetVkObjectTypeName(VkObjectType type)
{
    switch (type)
    {
        case VK_OBJECT_TYPE_UNKNOWN:
            return "Unknown";
        case VK_OBJECT_TYPE_INSTANCE:
            return "Instance";
        case VK_OBJECT_TYPE_PHYSICAL_DEVICE:
            return "Physical Device";
        case VK_OBJECT_TYPE_DEVICE:
            return "Device";
        case VK_OBJECT_TYPE_QUEUE:
            return "Queue";
        case VK_OBJECT_TYPE_SEMAPHORE:
            return "Semaphore";
        case VK_OBJECT_TYPE_COMMAND_BUFFER:
            return "Command Buffer";
        case VK_OBJECT_TYPE_FENCE:
            return "Fence";
        case VK_OBJECT_TYPE_DEVICE_MEMORY:
            return "Device Memory";
        case VK_OBJECT_TYPE_BUFFER:
            return "Buffer";
        case VK_OBJECT_TYPE_IMAGE:
            return "Image";
        case VK_OBJECT_TYPE_EVENT:
            return "Event";
        case VK_OBJECT_TYPE_QUERY_POOL:
            return "Query Pool";
        case VK_OBJECT_TYPE_BUFFER_VIEW:
            return "Buffer View";
        case VK_OBJECT_TYPE_IMAGE_VIEW:
            return "Image View";
        case VK_OBJECT_TYPE_SHADER_MODULE:
            return "Shader Module";
        case VK_OBJECT_TYPE_PIPELINE_CACHE:
            return "Pipeline Cache";
        case VK_OBJECT_TYPE_PIPELINE_LAYOUT:
            return "Pipeline Layout";
        case VK_OBJECT_TYPE_RENDER_PASS:
            return "Render Pass";
        case VK_OBJECT_TYPE_PIPELINE:
            return "Pipeline";
        case VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT:
            return "Descriptor Set Layout";
        case VK_OBJECT_TYPE_SAMPLER:
            return "Sampler";
        case VK_OBJECT_TYPE_DESCRIPTOR_POOL:
            return "Descriptor Pool";
        case VK_OBJECT_TYPE_DESCRIPTOR_SET:
            return "Descriptor Set";
        case VK_OBJECT_TYPE_FRAMEBUFFER:
            return "Framebuffer";
        case VK_OBJECT_TYPE_COMMAND_POOL:
            return "Command Pool";
        case VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION:
            return "Sampler YCbCr Conversion";
        case VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE:
            return "Descriptor Update Template";
        case VK_OBJECT_TYPE_SURFACE_KHR:
            return "Surface";
        case VK_OBJECT_TYPE_SWAPCHAIN_KHR:
            return "Swapchain";
        case VK_OBJECT_TYPE_DISPLAY_KHR:
            return "Display";
        case VK_OBJECT_TYPE_DISPLAY_MODE_KHR:
            return "Display Mode";
        case VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NV:
            return "Indirect Commands Layout";
        case VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT:
            return "Debug Utils Messenger";
        case VK_OBJECT_TYPE_VALIDATION_CACHE_EXT:
            return "Validation Cache";
        case VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV:
            return "Acceleration Structure";
        default:
            return "<Unrecognized>";
    }
}

VKAPI_ATTR VkBool32 VKAPI_CALL
DebugUtilsMessenger(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
                    VkDebugUtilsMessageTypeFlagsEXT messageTypes,
                    const VkDebugUtilsMessengerCallbackDataEXT *callbackData,
                    void *userData)
{
    Renderer *renderer = static_cast<Renderer *>(userData);

    // VUID-VkDebugUtilsMessengerCallbackDataEXT-pMessage-parameter
    // pMessage must be a null-terminated UTF-8 string
    ASSERT(callbackData->pMessage != nullptr);

    // See if it's an issue we are aware of and don't want to be spammed about.
    // Always report the debug message if message ID is missing
    if (callbackData->pMessageIdName != nullptr &&
        ShouldReportDebugMessage(renderer, callbackData->pMessageIdName, callbackData->pMessage) ==
            DebugMessageReport::Ignore)
    {
        return VK_FALSE;
    }

    std::ostringstream log;
    if (callbackData->pMessageIdName != nullptr)
    {
        log << "[ " << callbackData->pMessageIdName << " ] ";
    }
    log << callbackData->pMessage << std::endl;

    // Aesthetic value based on length of the function name, line number, etc.
    constexpr size_t kStartIndent = 28;

    // Output the debug marker hierarchy under which this error has occurred.
    size_t indent = kStartIndent;
    if (callbackData->queueLabelCount > 0)
    {
        log << std::string(indent++, ' ') << "<Queue Label Hierarchy:>" << std::endl;
        for (uint32_t i = 0; i < callbackData->queueLabelCount; ++i)
        {
            log << std::string(indent++, ' ') << callbackData->pQueueLabels[i].pLabelName
                << std::endl;
        }
    }
    if (callbackData->cmdBufLabelCount > 0)
    {
        log << std::string(indent++, ' ') << "<Command Buffer Label Hierarchy:>" << std::endl;
        for (uint32_t i = 0; i < callbackData->cmdBufLabelCount; ++i)
        {
            log << std::string(indent++, ' ') << callbackData->pCmdBufLabels[i].pLabelName
                << std::endl;
        }
    }
    // Output the objects involved in this error message.
    if (callbackData->objectCount > 0)
    {
        for (uint32_t i = 0; i < callbackData->objectCount; ++i)
        {
            const char *objectName = callbackData->pObjects[i].pObjectName;
            const char *objectType = GetVkObjectTypeName(callbackData->pObjects[i].objectType);
            uint64_t objectHandle  = callbackData->pObjects[i].objectHandle;
            log << std::string(indent, ' ') << "Object: ";
            if (objectHandle == 0)
            {
                log << "VK_NULL_HANDLE";
            }
            else
            {
                log << "0x" << std::hex << objectHandle << std::dec;
            }
            log << " (type = " << objectType << "(" << callbackData->pObjects[i].objectType << "))";
            if (objectName)
            {
                log << " [" << objectName << "]";
            }
            log << std::endl;
        }
    }

    bool isError    = (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0;
    std::string msg = log.str();

    if (isError)
    {
        ERR() << msg;
    }
    else
    {
        WARN() << msg;
    }

    return VK_FALSE;
}

VKAPI_ATTR void VKAPI_CALL
MemoryReportCallback(const VkDeviceMemoryReportCallbackDataEXT *callbackData, void *userData)
{
    Renderer *renderer = static_cast<Renderer *>(userData);
    renderer->processMemoryReportCallback(*callbackData);
}

gl::Version LimitVersionTo(const gl::Version &current, const gl::Version &lower)
{
    return std::min(current, lower);
}

[[maybe_unused]] bool FencePropertiesCompatibleWithAndroid(
    const VkExternalFenceProperties &externalFenceProperties)
{
    // handleType here is the external fence type -
    // we want type compatible with creating and export/dup() Android FD

    // Imported handleType that can be exported - need for vkGetFenceFdKHR()
    if ((externalFenceProperties.exportFromImportedHandleTypes &
         VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR) == 0)
    {
        return false;
    }

    // HandleTypes which can be specified at creating a fence
    if ((externalFenceProperties.compatibleHandleTypes &
         VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR) == 0)
    {
        return false;
    }

    constexpr VkExternalFenceFeatureFlags kFeatureFlags =
        (VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT_KHR |
         VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT_KHR);
    if ((externalFenceProperties.externalFenceFeatures & kFeatureFlags) != kFeatureFlags)
    {
        return false;
    }

    return true;
}

[[maybe_unused]] bool SemaphorePropertiesCompatibleWithAndroid(
    const VkExternalSemaphoreProperties &externalSemaphoreProperties)
{
    // handleType here is the external semaphore type -
    // we want type compatible with importing an Android FD

    constexpr VkExternalSemaphoreFeatureFlags kFeatureFlags =
        (VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT_KHR);
    if ((externalSemaphoreProperties.externalSemaphoreFeatures & kFeatureFlags) != kFeatureFlags)
    {
        return false;
    }

    return true;
}

// Exclude memory type indices that include the host-visible bit from VMA image suballocation.
uint32_t GetMemoryTypeBitsExcludingHostVisible(Renderer *renderer,
                                               VkMemoryPropertyFlags propertyFlags,
                                               uint32_t availableMemoryTypeBits)
{
    const vk::MemoryProperties &memoryProperties = renderer->getMemoryProperties();
    ASSERT(memoryProperties.getMemoryTypeCount() <= 32);
    uint32_t memoryTypeBitsOut = availableMemoryTypeBits;

    // For best allocation results, the memory type indices that include the host-visible flag bit
    // are removed.
    for (size_t memoryIndex : angle::BitSet<32>(availableMemoryTypeBits))
    {
        VkMemoryPropertyFlags memoryFlags =
            memoryProperties.getMemoryType(static_cast<uint32_t>(memoryIndex)).propertyFlags;
        if ((memoryFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0)
        {
            memoryTypeBitsOut &= ~(angle::Bit<uint32_t>(memoryIndex));
            continue;
        }

        // If the protected bit is not required, all memory type indices with this bit should be
        // ignored.
        if ((memoryFlags & ~propertyFlags & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0)
        {
            memoryTypeBitsOut &= ~(angle::Bit<uint32_t>(memoryIndex));
        }
    }

    return memoryTypeBitsOut;
}

// Header data type used for the pipeline cache.
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS

class CacheDataHeader
{
  public:
    void setData(uint32_t compressedDataCRC,
                 uint32_t cacheDataSize,
                 size_t numChunks,
                 size_t chunkIndex,
                 uint32_t chunkCRC)
    {
        mVersion           = kPipelineCacheVersion;
        mCompressedDataCRC = compressedDataCRC;
        mCacheDataSize     = cacheDataSize;
        SetBitField(mNumChunks, numChunks);
        SetBitField(mChunkIndex, chunkIndex);
        mChunkCRC = chunkCRC;
    }

    void getData(uint32_t *versionOut,
                 uint32_t *compressedDataCRCOut,
                 uint32_t *cacheDataSizeOut,
                 size_t *numChunksOut,
                 size_t *chunkIndexOut,
                 uint32_t *chunkCRCOut) const
    {
        *versionOut           = mVersion;
        *compressedDataCRCOut = mCompressedDataCRC;
        *cacheDataSizeOut     = mCacheDataSize;
        *numChunksOut         = static_cast<size_t>(mNumChunks);
        *chunkIndexOut        = static_cast<size_t>(mChunkIndex);
        *chunkCRCOut          = mChunkCRC;
    }

  private:
    // For pipeline cache, the values stored in key data has the following order:
    // {headerVersion, compressedDataCRC, originalCacheSize, numChunks, chunkIndex, chunkCRC;
    // chunkCompressedData}. The header values are used to validate the data. For example, if the
    // original and compressed sizes are 70000 bytes (68k) and 68841 bytes (67k), the compressed
    // data will be divided into two chunks: {ver,crc0,70000,2,0;34421 bytes} and
    // {ver,crc1,70000,2,1;34420 bytes}.
    // The version is used to keep track of the cache format. Please note that kPipelineCacheVersion
    // must be incremented by 1 in case of any updates to the cache header or data structure. While
    // it is possible to modify the fields in the header, it is recommended to keep the version on
    // top and the same size unless absolutely necessary.

    uint32_t mVersion;
    uint32_t mCompressedDataCRC;
    uint32_t mCacheDataSize;
    uint16_t mNumChunks;
    uint16_t mChunkIndex;
    uint32_t mChunkCRC;
};

ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

// Pack header data for the pipeline cache key data.
void PackHeaderDataForPipelineCache(uint32_t compressedDataCRC,
                                    uint32_t cacheDataSize,
                                    size_t numChunks,
                                    size_t chunkIndex,
                                    uint32_t chunkCRC,
                                    CacheDataHeader *dataOut)
{
    dataOut->setData(compressedDataCRC, cacheDataSize, numChunks, chunkIndex, chunkCRC);
}

// Unpack header data from the pipeline cache key data.
void UnpackHeaderDataForPipelineCache(CacheDataHeader *data,
                                      uint32_t *versionOut,
                                      uint32_t *compressedDataCRCOut,
                                      uint32_t *cacheDataSizeOut,
                                      size_t *numChunksOut,
                                      size_t *chunkIndexOut,
                                      uint32_t *chunkCRCOut)
{
    data->getData(versionOut, compressedDataCRCOut, cacheDataSizeOut, numChunksOut, chunkIndexOut,
                  chunkCRCOut);
}

void ComputePipelineCacheVkChunkKey(const VkPhysicalDeviceProperties &physicalDeviceProperties,
                                    const size_t slotIndex,
                                    const size_t chunkIndex,
                                    angle::BlobCacheKey *hashOut)
{
    angle::BlobCacheHasher hasher;
    hasher.Init();

    // Start with the name
    const char *pipelineCacheName = "ANGLE Pipeline Cache: ";
    hasher.Update(pipelineCacheName, strlen(pipelineCacheName));

    // Add the pipeline cache UUID to make sure the blob cache always gives a compatible pipeline
    // cache. pipelineCacheUUID is a uint8_t[VK_UUID_SIZE]
    hasher.Update(&physicalDeviceProperties.pipelineCacheUUID, VK_UUID_SIZE);

    // Add the vendor and device id too for good measure.
    angle::UpdateHashWithValue(hasher, physicalDeviceProperties.vendorID);
    angle::UpdateHashWithValue(hasher, physicalDeviceProperties.deviceID);

    // Add slotIndex and chunkIndex to generate unique keys.
    angle::UpdateHashWithValue(hasher, slotIndex);
    angle::UpdateHashWithValue(hasher, chunkIndex);

    ASSERT(hashOut);
    hasher.Final();
    memcpy(hashOut->data(), hasher.Digest(), angle::kBlobCacheKeyLength);
}

struct PipelineCacheVkChunkInfo
{
    const uint8_t *data;
    size_t dataSize;
    uint32_t crc;
    angle::BlobCacheKey cacheHash;
};

// Enough to store 32M data using 64K chunks.
constexpr size_t kFastPipelineCacheVkChunkInfosSize = 512;
using PipelineCacheVkChunkInfos =
    angle::FastVector<PipelineCacheVkChunkInfo, kFastPipelineCacheVkChunkInfosSize>;

PipelineCacheVkChunkInfos GetPipelineCacheVkChunkInfos(Renderer *renderer,
                                                       const angle::MemoryBuffer &compressedData,
                                                       const size_t numChunks,
                                                       const size_t chunkSize,
                                                       const size_t slotIndex);

// Returns the number of stored chunks.  "lastNumStoredChunks" is the number of chunks,
// stored in the last call.  If it is positive, function will only restore missing chunks.
size_t StorePipelineCacheVkChunks(vk::GlobalOps *globalOps,
                                  Renderer *renderer,
                                  const size_t lastNumStoredChunks,
                                  const PipelineCacheVkChunkInfos &chunkInfos,
                                  const size_t cacheDataSize,
                                  angle::MemoryBuffer *scratchBuffer);

// Erasing is done by writing 1/0-sized chunks starting from the startChunk.
void ErasePipelineCacheVkChunks(vk::GlobalOps *globalOps,
                                Renderer *renderer,
                                const size_t startChunk,
                                const size_t numChunks,
                                const size_t slotIndex,
                                angle::MemoryBuffer *scratchBuffer);

void CompressAndStorePipelineCacheVk(vk::GlobalOps *globalOps,
                                     Renderer *renderer,
                                     const std::vector<uint8_t> &cacheData,
                                     const size_t maxTotalSize)
{
    // Though the pipeline cache will be compressed and divided into several chunks to store in blob
    // cache, the largest total size of blob cache is only 2M in android now, so there is no use to
    // handle big pipeline cache when android will reject it finally.
    if (cacheData.size() >= maxTotalSize)
    {
        static bool warned = false;
        if (!warned)
        {
            // TODO: handle the big pipeline cache. http://anglebug.com/42263322
            WARN() << "Skip syncing pipeline cache data when it's larger than maxTotalSize. "
                      "(this message will no longer repeat)";
            warned = true;
        }
        return;
    }

    // To make it possible to store more pipeline cache data, compress the whole pipelineCache.
    angle::MemoryBuffer compressedData;

    if (!angle::CompressBlob(cacheData.size(), cacheData.data(), &compressedData))
    {
        WARN() << "Skip syncing pipeline cache data as it failed compression.";
        return;
    }

    // If the size of compressedData is larger than (kMaxBlobCacheSize - sizeof(numChunks)),
    // the pipelineCache still can't be stored in blob cache. Divide the large compressed
    // pipelineCache into several parts to store separately. There is no function to
    // query the limit size in android.
    constexpr size_t kMaxBlobCacheSize = 64 * 1024;

    const size_t numChunks = UnsignedCeilDivide(static_cast<unsigned int>(compressedData.size()),
                                                kMaxBlobCacheSize - sizeof(CacheDataHeader));
    ASSERT(numChunks <= UINT16_MAX);
    const size_t chunkSize = UnsignedCeilDivide(static_cast<unsigned int>(compressedData.size()),
                                                static_cast<unsigned int>(numChunks));

    angle::MemoryBuffer scratchBuffer;
    if (!scratchBuffer.resize(sizeof(CacheDataHeader) + chunkSize))
    {
        WARN() << "Skip syncing pipeline cache data due to out of memory.";
        return;
    }

    size_t previousSlotIndex = 0;
    const size_t slotIndex   = renderer->getNextPipelineCacheBlobCacheSlotIndex(&previousSlotIndex);
    const size_t previousNumChunks = renderer->updatePipelineCacheChunkCount(numChunks);
    const bool isSlotChanged       = (slotIndex != previousSlotIndex);

    PipelineCacheVkChunkInfos chunkInfos =
        GetPipelineCacheVkChunkInfos(renderer, compressedData, numChunks, chunkSize, slotIndex);

    // Store all chunks without checking if they already exist (because they can't).
    size_t numStoredChunks = StorePipelineCacheVkChunks(globalOps, renderer, 0, chunkInfos,
                                                        cacheData.size(), &scratchBuffer);
    ASSERT(numStoredChunks == numChunks);

    // Erase all chunks from the previous slot or any trailing chunks from the current slot.
    ASSERT(renderer->getFeatures().useDualPipelineBlobCacheSlots.enabled == isSlotChanged);
    if (isSlotChanged || previousNumChunks > numChunks)
    {
        const size_t startChunk = isSlotChanged ? 0 : numChunks;
        ErasePipelineCacheVkChunks(globalOps, renderer, startChunk, previousNumChunks,
                                   previousSlotIndex, &scratchBuffer);
    }

    if (!renderer->getFeatures().verifyPipelineCacheInBlobCache.enabled)
    {
        // No need to verify and restore possibly evicted chunks.
        return;
    }

    // Verify and restore possibly evicted chunks.
    do
    {
        const size_t lastNumStoredChunks = numStoredChunks;
        numStoredChunks = StorePipelineCacheVkChunks(globalOps, renderer, lastNumStoredChunks,
                                                     chunkInfos, cacheData.size(), &scratchBuffer);
        // Number of stored chunks must decrease so the loop can eventually exit.
        ASSERT(numStoredChunks < lastNumStoredChunks);

        // If blob cache evicts old items first, any possibly evicted chunks in the first call,
        // should have been restored in the above call without triggering another eviction, so no
        // need to continue the loop.
    } while (!renderer->getFeatures().hasBlobCacheThatEvictsOldItemsFirst.enabled &&
             numStoredChunks > 0);
}

PipelineCacheVkChunkInfos GetPipelineCacheVkChunkInfos(Renderer *renderer,
                                                       const angle::MemoryBuffer &compressedData,
                                                       const size_t numChunks,
                                                       const size_t chunkSize,
                                                       const size_t slotIndex)
{
    const VkPhysicalDeviceProperties &physicalDeviceProperties =
        renderer->getPhysicalDeviceProperties();

    PipelineCacheVkChunkInfos chunkInfos(numChunks);
    uint32_t chunkCrc = kEnableCRCForPipelineCache ? angle::InitCRC32() : 0;

    for (size_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex)
    {
        const size_t compressedOffset = chunkIndex * chunkSize;
        const uint8_t *data           = compressedData.data() + compressedOffset;
        const size_t dataSize = std::min(chunkSize, compressedData.size() - compressedOffset);

        // Create unique hash key.
        angle::BlobCacheKey cacheHash;
        ComputePipelineCacheVkChunkKey(physicalDeviceProperties, slotIndex, chunkIndex, &cacheHash);

        if (kEnableCRCForPipelineCache)
        {
            // Generate running CRC. Last chunk will have CRC of the entire data.
            chunkCrc = angle::UpdateCRC32(chunkCrc, data, dataSize);
        }

        chunkInfos[chunkIndex] = PipelineCacheVkChunkInfo{data, dataSize, chunkCrc, cacheHash};
    }

    return chunkInfos;
}

size_t StorePipelineCacheVkChunks(vk::GlobalOps *globalOps,
                                  Renderer *renderer,
                                  const size_t lastNumStoredChunks,
                                  const PipelineCacheVkChunkInfos &chunkInfos,
                                  const size_t cacheDataSize,
                                  angle::MemoryBuffer *scratchBuffer)
{
    // Store chunks in revers order, so when 0 chunk is available - all chunks are available.

    angle::FastVector<bool, kFastPipelineCacheVkChunkInfosSize> isMissing;
    size_t numChunksToStore = chunkInfos.size();

    // Need to check existing chunks if this is not the first time this function is called.
    if (lastNumStoredChunks > 0)
    {
        isMissing.resize(chunkInfos.size());
        numChunksToStore = 0;

        // Defer storing chunks until all missing chunks are found to avoid unnecessary stores.
        size_t chunkIndex = chunkInfos.size();
        while (chunkIndex > 0)
        {
            --chunkIndex;
            const PipelineCacheVkChunkInfo &chunkInfo = chunkInfos[chunkIndex];

            angle::BlobCacheValue value;
            if (globalOps->getBlob(chunkInfo.cacheHash, &value) &&
                value.size() == sizeof(CacheDataHeader) + chunkInfo.dataSize)
            {
                if (renderer->getFeatures().hasBlobCacheThatEvictsOldItemsFirst.enabled)
                {
                    // No need to check next chunks, since they are newer than the current and
                    // should also be present.
                    break;
                }
                continue;
            }

            isMissing[chunkIndex] = true;
            ++numChunksToStore;

            if (numChunksToStore == lastNumStoredChunks)
            {
                // No need to restore missing chunks, since new number is already same as was stored
                // last time.
                static bool warned = false;
                if (!warned)
                {
                    WARN() << "Skip syncing pipeline cache data due to not able to store "
                           << numChunksToStore << " chunks (out of " << chunkInfos.size()
                           << ") into the blob cache. (this message will no longer repeat)";
                    warned = true;
                }
                return 0;
            }
        }

        if (numChunksToStore == 0)
        {
            return 0;
        }
    }

    // Now store/restore chunks.

    // Last chunk have CRC of the entire data.
    const uint32_t compressedDataCRC = chunkInfos.back().crc;

    ASSERT(scratchBuffer != nullptr);
    angle::MemoryBuffer &keyData = *scratchBuffer;

    size_t chunkIndex = chunkInfos.size();
    while (chunkIndex > 0)
    {
        --chunkIndex;
        if (lastNumStoredChunks > 0 && !isMissing[chunkIndex])
        {
            // Skip restoring chunk if it is not missing.
            continue;
        }
        const PipelineCacheVkChunkInfo &chunkInfo = chunkInfos[chunkIndex];

        // Add the header data, followed by the compressed data.
        ASSERT(cacheDataSize <= UINT32_MAX);
        CacheDataHeader headerData = {};
        PackHeaderDataForPipelineCache(compressedDataCRC, static_cast<uint32_t>(cacheDataSize),
                                       chunkInfos.size(), chunkIndex, chunkInfo.crc, &headerData);
        keyData.setSize(sizeof(CacheDataHeader) + chunkInfo.dataSize);
        memcpy(keyData.data(), &headerData, sizeof(CacheDataHeader));
        memcpy(keyData.data() + sizeof(CacheDataHeader), chunkInfo.data, chunkInfo.dataSize);

        globalOps->putBlob(chunkInfo.cacheHash, keyData);
    }

    return numChunksToStore;
}

void ErasePipelineCacheVkChunks(vk::GlobalOps *globalOps,
                                Renderer *renderer,
                                const size_t startChunk,
                                const size_t numChunks,
                                const size_t slotIndex,
                                angle::MemoryBuffer *scratchBuffer)
{
    const VkPhysicalDeviceProperties &physicalDeviceProperties =
        renderer->getPhysicalDeviceProperties();

    ASSERT(scratchBuffer != nullptr);
    angle::MemoryBuffer &keyData = *scratchBuffer;

    keyData.setSize(
        renderer->getFeatures().useEmptyBlobsToEraseOldPipelineCacheFromBlobCache.enabled ? 0 : 1);

    // Fill data (if any) with zeroes for security.
    memset(keyData.data(), 0, keyData.size());

    for (size_t chunkIndex = startChunk; chunkIndex < numChunks; ++chunkIndex)
    {
        egl::BlobCache::Key chunkCacheHash;
        ComputePipelineCacheVkChunkKey(physicalDeviceProperties, slotIndex, chunkIndex,
                                       &chunkCacheHash);
        globalOps->putBlob(chunkCacheHash, keyData);
    }
}

class CompressAndStorePipelineCacheTask : public angle::Closure
{
  public:
    CompressAndStorePipelineCacheTask(vk::GlobalOps *globalOps,
                                      Renderer *renderer,
                                      std::vector<uint8_t> &&cacheData,
                                      size_t kMaxTotalSize)
        : mGlobalOps(globalOps),
          mRenderer(renderer),
          mCacheData(std::move(cacheData)),
          mMaxTotalSize(kMaxTotalSize)
    {}

    void operator()() override
    {
        ANGLE_TRACE_EVENT0("gpu.angle", "CompressAndStorePipelineCacheVk");
        CompressAndStorePipelineCacheVk(mGlobalOps, mRenderer, mCacheData, mMaxTotalSize);
    }

  private:
    vk::GlobalOps *mGlobalOps;
    Renderer *mRenderer;
    std::vector<uint8_t> mCacheData;
    size_t mMaxTotalSize;
};

angle::Result GetAndDecompressPipelineCacheVk(vk::ErrorContext *context,
                                              vk::GlobalOps *globalOps,
                                              angle::MemoryBuffer *uncompressedData,
                                              bool *success)
{
    // Make sure that the bool output is initialized to false.
    *success = false;

    Renderer *renderer = context->getRenderer();

    const VkPhysicalDeviceProperties &physicalDeviceProperties =
        renderer->getPhysicalDeviceProperties();

    const size_t firstSlotIndex = renderer->getNextPipelineCacheBlobCacheSlotIndex(nullptr);
    size_t slotIndex            = firstSlotIndex;

    angle::BlobCacheKey chunkCacheHash;
    angle::BlobCacheValue keyData;

    // Iterate over available slots until data is found (only expected single slot with data).
    while (true)
    {
        // Compute the hash key of chunkIndex 0 and find the first cache data in blob cache.
        ComputePipelineCacheVkChunkKey(physicalDeviceProperties, slotIndex, 0, &chunkCacheHash);

        if (globalOps->getBlob(chunkCacheHash, &keyData) &&
            keyData.size() >= sizeof(CacheDataHeader))
        {
            // Found slot with data.
            break;
        }
        // Nothing in the cache for current slotIndex.

        slotIndex = renderer->getNextPipelineCacheBlobCacheSlotIndex(nullptr);
        if (slotIndex == firstSlotIndex)
        {
            // Nothing in all slots.
            return angle::Result::Continue;
        }
        // Try next slot.
    }

    // Get the number of chunks and other values from the header for data validation.
    uint32_t cacheVersion;
    uint32_t compressedDataCRC;
    uint32_t uncompressedCacheDataSize;
    size_t numChunks;
    size_t chunkIndex0;
    uint32_t chunkCRC;

    CacheDataHeader headerData = {};
    memcpy(&headerData, keyData.data(), sizeof(CacheDataHeader));
    UnpackHeaderDataForPipelineCache(&headerData, &cacheVersion, &compressedDataCRC,
                                     &uncompressedCacheDataSize, &numChunks, &chunkIndex0,
                                     &chunkCRC);
    if (cacheVersion == kPipelineCacheVersion)
    {
        // The data must not contain corruption.
        if (chunkIndex0 != 0 || numChunks == 0 || uncompressedCacheDataSize == 0)
        {
            FATAL() << "Unexpected values while unpacking chunk index 0: " << "cacheVersion = "
                    << cacheVersion << ", chunkIndex = " << chunkIndex0
                    << ", numChunks = " << numChunks
                    << ", uncompressedCacheDataSize = " << uncompressedCacheDataSize;
        }
    }
    else
    {
        WARN() << "Change in cache header version detected: " << "newVersion = "
               << kPipelineCacheVersion << ", existingVersion = " << cacheVersion;

        return angle::Result::Continue;
    }

    renderer->updatePipelineCacheChunkCount(numChunks);

    size_t chunkSize      = keyData.size() - sizeof(CacheDataHeader);
    size_t compressedSize = 0;

    uint32_t computedChunkCRC = kEnableCRCForPipelineCache ? angle::InitCRC32() : 0;

    // Allocate enough memory.
    angle::MemoryBuffer compressedData;
    ANGLE_VK_CHECK(context, compressedData.resize(chunkSize * numChunks),
                   VK_ERROR_INITIALIZATION_FAILED);

    // To combine the parts of the pipelineCache data.
    for (size_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex)
    {
        // Avoid processing 0 chunk again.
        if (chunkIndex > 0)
        {
            // Get the unique key by chunkIndex.
            ComputePipelineCacheVkChunkKey(physicalDeviceProperties, slotIndex, chunkIndex,
                                           &chunkCacheHash);

            if (!globalOps->getBlob(chunkCacheHash, &keyData) ||
                keyData.size() < sizeof(CacheDataHeader))
            {
                // Can't find every part of the cache data.
                WARN() << "Failed to get pipeline cache chunk " << chunkIndex << " of "
                       << numChunks;
                return angle::Result::Continue;
            }

            // Validate the header values and ensure there is enough space to store.
            uint32_t checkCacheVersion;
            uint32_t checkCompressedDataCRC;
            uint32_t checkUncompressedCacheDataSize;
            size_t checkNumChunks;
            size_t checkChunkIndex;

            memcpy(&headerData, keyData.data(), sizeof(CacheDataHeader));
            UnpackHeaderDataForPipelineCache(
                &headerData, &checkCacheVersion, &checkCompressedDataCRC,
                &checkUncompressedCacheDataSize, &checkNumChunks, &checkChunkIndex, &chunkCRC);

            chunkSize = keyData.size() - sizeof(CacheDataHeader);
            bool isHeaderDataCorrupted =
                (checkCacheVersion != cacheVersion) || (checkNumChunks != numChunks) ||
                (checkUncompressedCacheDataSize != uncompressedCacheDataSize) ||
                (checkCompressedDataCRC != compressedDataCRC) || (checkChunkIndex != chunkIndex) ||
                (compressedData.size() < compressedSize + chunkSize);
            if (isHeaderDataCorrupted)
            {
                WARN() << "Pipeline cache chunk header corrupted or old chunk: "
                       << "checkCacheVersion = " << checkCacheVersion
                       << ", cacheVersion = " << cacheVersion
                       << ", checkNumChunks = " << checkNumChunks << ", numChunks = " << numChunks
                       << ", checkUncompressedCacheDataSize = " << checkUncompressedCacheDataSize
                       << ", uncompressedCacheDataSize = " << uncompressedCacheDataSize
                       << ", checkCompressedDataCRC = " << checkCompressedDataCRC
                       << ", compressedDataCRC = " << compressedDataCRC
                       << ", checkChunkIndex = " << checkChunkIndex
                       << ", chunkIndex = " << chunkIndex
                       << ", compressedData.size() = " << compressedData.size()
                       << ", (compressedSize + chunkSize) = " << (compressedSize + chunkSize);
                return angle::Result::Continue;
            }
        }

        // CRC of the chunk should match the values in the header.
        if (kEnableCRCForPipelineCache)
        {
            computedChunkCRC = angle::UpdateCRC32(
                computedChunkCRC, keyData.data() + sizeof(CacheDataHeader), chunkSize);
            if (computedChunkCRC != chunkCRC)
            {
                if (chunkCRC == 0)
                {
                    // This could be due to the cache being populated before
                    // kEnableCRCForPipelineCache was enabled.
                    WARN() << "Expected chunk CRC = " << chunkCRC
                           << ", Actual chunk CRC = " << computedChunkCRC;
                    return angle::Result::Continue;
                }

                // If the expected CRC is non-zero and does not match the actual CRC from the data,
                // there has been an unexpected data corruption.
                ERR() << "Expected chunk CRC = " << chunkCRC
                      << ", Actual chunk CRC = " << computedChunkCRC;

                ERR() << "Data extracted from the cache headers: " << std::hex
                      << ", compressedDataCRC = 0x" << compressedDataCRC << "numChunks = 0x"
                      << numChunks << ", uncompressedCacheDataSize = 0x"
                      << uncompressedCacheDataSize;

                FATAL() << "CRC check failed; possible pipeline cache data corruption.";
                return angle::Result::Stop;
            }
        }

        memcpy(compressedData.data() + compressedSize, keyData.data() + sizeof(CacheDataHeader),
               chunkSize);
        compressedSize += chunkSize;
    }

    // CRC for compressed data and size for decompressed data should match the values in the header.
    if (kEnableCRCForPipelineCache)
    {
        // Last chunk have CRC of the entire data.
        uint32_t computedCompressedDataCRC = computedChunkCRC;
        // Per chunk CRC check must handle any data corruption.  Assert is possible only if header
        // was incorrectly written in the first place (bug in the code), or all chunks headers were
        // corrupted in the exact same way, which is almost impossible.
        ASSERT(computedCompressedDataCRC == compressedDataCRC);
    }

    ANGLE_VK_CHECK(context,
                   angle::DecompressBlob(compressedData.data(), compressedSize,
                                         uncompressedCacheDataSize, uncompressedData),
                   VK_ERROR_INITIALIZATION_FAILED);

    if (uncompressedData->size() != uncompressedCacheDataSize)
    {
        WARN() << "Expected uncompressed size = " << uncompressedCacheDataSize
               << ", Actual uncompressed size = " << uncompressedData->size();
        return angle::Result::Continue;
    }

    *success = true;
    return angle::Result::Continue;
}

// Environment variable (and associated Android property) to enable Vulkan debug-utils markers
constexpr char kEnableDebugMarkersVarName[]      = "ANGLE_ENABLE_DEBUG_MARKERS";
constexpr char kEnableDebugMarkersPropertyName[] = "debug.angle.markers";

ANGLE_INLINE gl::ShadingRate GetShadingRateEXTFromVkExtent(const VkExtent2D &extent)
{
    if (extent.width == 1)
    {
        if (extent.height == 1)
        {
            return gl::ShadingRate::_1x1;
        }
        else if (extent.height == 2)
        {
            return gl::ShadingRate::_1x2;
        }
        else if (extent.height == 4)
        {
            return gl::ShadingRate::_1x4;
        }
    }
    else if (extent.width == 2)
    {
        if (extent.height == 1)
        {
            return gl::ShadingRate::_2x1;
        }
        else if (extent.height == 2)
        {
            return gl::ShadingRate::_2x2;
        }
        else if (extent.height == 4)
        {
            return gl::ShadingRate::_2x4;
        }
    }
    else if (extent.width == 4)
    {
        if (extent.height == 1)
        {
            return gl::ShadingRate::_4x1;
        }
        else if (extent.height == 2)
        {
            return gl::ShadingRate::_4x2;
        }
        else if (extent.height == 4)
        {
            return gl::ShadingRate::_4x4;
        }
    }

    return gl::ShadingRate::Undefined;
}

void DumpPipelineCacheGraph(Renderer *renderer, const std::ostringstream &graph)
{
    std::string dumpPath = renderer->getPipelineCacheGraphDumpPath();
    if (dumpPath.size() == 0)
    {
        WARN() << "No path supplied for pipeline cache graph dump!";
        return;
    }

    static std::atomic<uint32_t> sContextIndex(0);
    std::string filename = dumpPath;
    filename += angle::GetExecutableName();
    filename += std::to_string(sContextIndex.fetch_add(1));
    filename += ".dump";

    INFO() << "Dumping pipeline cache transition graph to: \"" << filename << "\"";

    std::ofstream out = std::ofstream(filename, std::ofstream::binary);
    if (!out.is_open())
    {
        ERR() << "Failed to open \"" << filename << "\"";
    }

    out << "digraph {\n" << " node [shape=box";
    if (renderer->getFeatures().supportsPipelineCreationFeedback.enabled)
    {
        out << ",color=green";
    }
    out << "]\n";
    out << graph.str();
    out << "}\n";
    out.close();
}

bool CanSupportMSRTSSForRGBA8(Renderer *renderer)
{
    // The support is checked for a basic 2D texture.
    constexpr VkImageUsageFlags kImageUsageFlags = vk::kImageUsageTransferBits |
                                                   VK_IMAGE_USAGE_SAMPLED_BIT |
                                                   VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    VkImageCreateFlags imageCreateFlags =
        GetMinimalImageCreateFlags(renderer, gl::TextureType::_2D, kImageUsageFlags) |
        VK_IMAGE_CREATE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_BIT_EXT;

    bool supportsMSRTTUsageRGBA8 = vk::ImageHelper::FormatSupportsUsage(
        renderer, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
        kImageUsageFlags, imageCreateFlags, nullptr, nullptr,
        vk::ImageHelper::FormatSupportCheck::RequireMultisampling);
    bool supportsMSRTTUsageRGBA8SRGB = vk::ImageHelper::FormatSupportsUsage(
        renderer, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
        kImageUsageFlags, imageCreateFlags, nullptr, nullptr,
        vk::ImageHelper::FormatSupportCheck::RequireMultisampling);

    return supportsMSRTTUsageRGBA8 && supportsMSRTTUsageRGBA8SRGB;
}

VkResult RetrieveDeviceLostInfoFromDevice(VkDevice device,
                                          VkPhysicalDeviceFaultFeaturesEXT faultFeatures)
{
    // For VkDeviceFaultAddressTypeEXT in VK_EXT_device_fault
    constexpr const char *kDeviceFaultAddressTypeMessage[] = {
        "None",
        "InvalidRead",
        "InvalidWrite",
        "InvalidExecute",
        "InstructionPointerUnknown",
        "InstructionPointerInvalid",
        "InstructionPointerFault",
    };

    // At first, the data regarding the number of faults is collected, so the proper allocations can
    // be made to store the incoming data.
    VkDeviceFaultCountsEXT faultCounts = {};
    faultCounts.sType                  = VK_STRUCTURE_TYPE_DEVICE_FAULT_COUNTS_EXT;

    VkResult result = vkGetDeviceFaultInfoEXT(device, &faultCounts, nullptr);
    if (result != VK_SUCCESS)
    {
        return result;
    }

    VkDeviceFaultInfoEXT faultInfos = {};
    faultInfos.sType                = VK_STRUCTURE_TYPE_DEVICE_FAULT_INFO_EXT;

    std::vector<VkDeviceFaultAddressInfoEXT> addressInfos(faultCounts.addressInfoCount);
    faultInfos.pAddressInfos = addressInfos.data();

    std::vector<VkDeviceFaultVendorInfoEXT> vendorInfos(faultCounts.vendorInfoCount);
    faultInfos.pVendorInfos = vendorInfos.data();

    // The vendor binary data will be logged in chunks of 4 bytes.
    uint32_t vendorBinaryDataChunkCount =
        (static_cast<uint32_t>(faultCounts.vendorBinarySize) + 3) / 4;
    std::vector<uint32_t> vendorBinaryDataChunks(vendorBinaryDataChunkCount, 0);
    faultInfos.pVendorBinaryData = vendorBinaryDataChunks.data();

    result = vkGetDeviceFaultInfoEXT(device, &faultCounts, &faultInfos);
    if (result != VK_SUCCESS)
    {
        return result;
    }

    // Collect the fault information from the device.
    std::stringstream faultString;
    faultString << "Fault description: <" << faultInfos.description << ">" << std::endl;

    for (auto &addressFault : addressInfos)
    {
        // Based on the spec, The address precision is a power of two, and shows the lower and upper
        // address ranges where the error could be:
        // - lowerAddress = (reportedAddress & ~(addressPrecision - 1))
        // - upperAddress = (reportedAddress |  (addressPrecision - 1))
        // For example, if the reported address is 0x12345 and the precision is 16, it shows that
        // the address could be between 0x12340 and 0x1234F.
        faultString << "--> Address fault reported at 0x" << std::hex
                    << addressFault.reportedAddress << " | Precision range: 0x"
                    << addressFault.addressPrecision << " (" << std::dec
                    << std::log2(addressFault.addressPrecision) << " bits) | Operation: "
                    << kDeviceFaultAddressTypeMessage[addressFault.addressType] << std::endl;
    }

    for (auto &vendorFault : vendorInfos)
    {
        faultString << "--> Vendor-specific fault reported (Code " << std::dec
                    << vendorFault.vendorFaultCode << "): <" << vendorFault.description
                    << "> | Fault Data: 0x" << std::hex << vendorFault.vendorFaultData << std::endl;
    }

    if (faultFeatures.deviceFaultVendorBinary)
    {
        // The binary data must start with the header in the format of the following type:
        // - VkDeviceFaultVendorBinaryHeaderVersionOneEXT (56 bytes)
        faultString << "--> Vendor-specific binary crash dump (" << faultCounts.vendorBinarySize
                    << " bytes, in hex):" << std::endl;

        constexpr uint32_t kVendorBinaryDataChunksPerLine = 8;
        for (uint32_t i = 0; i < vendorBinaryDataChunkCount; i++)
        {
            faultString << "0x" << std::hex << std::setw(8) << std::setfill('0')
                        << vendorBinaryDataChunks[i]
                        << ((i + 1) % kVendorBinaryDataChunksPerLine != 0 ? " " : "\n");
        }
        faultString << std::endl;
    }
    else
    {
        faultString << "--> Vendor-specific binary crash dump not available." << std::endl;
    }

    // Output the log stream.
    WARN() << faultString.str();
    return VK_SUCCESS;
}
}  // namespace

// OneOffCommandPool implementation.
OneOffCommandPool::OneOffCommandPool() : mProtectionType(vk::ProtectionType::InvalidEnum) {}

void OneOffCommandPool::init(vk::ProtectionType protectionType)
{
    ASSERT(!mCommandPool.valid());
    mProtectionType = protectionType;
}

void OneOffCommandPool::destroy(VkDevice device)
{
    std::unique_lock<angle::SimpleMutex> lock(mMutex);
    for (PendingOneOffCommands &pending : mPendingCommands)
    {
        pending.commandBuffer.releaseHandle();
    }
    mCommandPool.destroy(device);
    mProtectionType = vk::ProtectionType::InvalidEnum;
}

angle::Result OneOffCommandPool::getCommandBuffer(vk::ErrorContext *context,
                                                  vk::ScopedPrimaryCommandBuffer *commandBufferOut)
{
    std::unique_lock<angle::SimpleMutex> lock(mMutex);

    if (!mPendingCommands.empty() &&
        context->getRenderer()->hasResourceUseFinished(mPendingCommands.front().use))
    {
        commandBufferOut->assign(std::move(lock),
                                 std::move(mPendingCommands.front().commandBuffer));
        mPendingCommands.pop_front();
        // No need to explicitly call reset() on |commandBufferOut|, since the begin() call below
        // will do it implicitly.
    }
    else
    {
        if (!mCommandPool.valid())
        {
            VkCommandPoolCreateInfo createInfo = {};
            createInfo.sType                   = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
            createInfo.flags                   = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT |
                                                 VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
            ASSERT(mProtectionType == vk::ProtectionType::Unprotected ||
                   mProtectionType == vk::ProtectionType::Protected);
            if (mProtectionType == vk::ProtectionType::Protected)
            {
                createInfo.flags |= VK_COMMAND_POOL_CREATE_PROTECTED_BIT;
            }
            createInfo.queueFamilyIndex = context->getRenderer()->getQueueFamilyIndex();
            ANGLE_VK_TRY(context, mCommandPool.init(context->getDevice(), createInfo));
        }

        VkCommandBufferAllocateInfo allocInfo = {};
        allocInfo.sType                       = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        allocInfo.level                       = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        allocInfo.commandBufferCount          = 1;
        allocInfo.commandPool                 = mCommandPool.getHandle();

        PrimaryCommandBuffer newCommandBuffer;
        ANGLE_VK_TRY(context, newCommandBuffer.init(context->getDevice(), allocInfo));
        commandBufferOut->assign(std::move(lock), std::move(newCommandBuffer));
    }

    VkCommandBufferBeginInfo beginInfo = {};
    beginInfo.sType                    = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags                    = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    beginInfo.pInheritanceInfo         = nullptr;
    ANGLE_VK_TRY(context, commandBufferOut->get().begin(beginInfo));

    return angle::Result::Continue;
}

void OneOffCommandPool::releaseCommandBuffer(const QueueSerial &submitQueueSerial,
                                             vk::PrimaryCommandBuffer &&primary)
{
    std::unique_lock<angle::SimpleMutex> lock(mMutex);
    mPendingCommands.push_back({vk::ResourceUse(submitQueueSerial), std::move(primary)});
}

// Renderer implementation.
Renderer::Renderer()
    : mGlobalOps(nullptr),
      mLibVulkanLibrary(nullptr),
      mCapsInitialized(false),
      mInstanceVersion(0),
      mDeviceVersion(0),
      mInstance(VK_NULL_HANDLE),
      mEnableValidationLayers(false),
      mEnableDebugUtils(false),
      mAngleDebuggerMode(false),
      mEnabledICD(angle::vk::ICD::Default),
      mDebugUtilsMessenger(VK_NULL_HANDLE),
      mPhysicalDevice(VK_NULL_HANDLE),
      mPhysicalDeviceProperties(mPhysicalDeviceProperties2.properties),
      mCurrentQueueFamilyIndex(std::numeric_limits<uint32_t>::max()),
      mMaxVertexAttribDivisor(1),
      mMaxVertexAttribStride(0),
      mMaxColorInputAttachmentCount(0),
      mDefaultUniformBufferSize(kPreferredDefaultUniformBufferSize),
      mDevice(VK_NULL_HANDLE),
      mDeviceLost(false),
      mStagingBufferAlignment(1),
      mHostVisibleVertexConversionBufferMemoryTypeIndex(kInvalidMemoryTypeIndex),
      mDeviceLocalVertexConversionBufferMemoryTypeIndex(kInvalidMemoryTypeIndex),
      mVertexConversionBufferAlignment(1),
      mCurrentPipelineCacheBlobCacheSlotIndex(0),
      mPipelineCacheChunkCount(0),
      mPipelineCacheVkUpdateTimeout(kPipelineCacheVkUpdatePeriod),
      mPipelineCacheSizeAtLastSync(0),
      mPipelineCacheInitialized(false),
      mIsColorFramebufferFetchCoherent(false),
      mIsColorFramebufferFetchUsed(false),
      mCleanUpThread(this, &mCommandQueue),
      mSupportedBufferWritePipelineStageMask(0),
      mSupportedVulkanShaderStageMask(0),
      mMemoryAllocationTracker(MemoryAllocationTracker(this)),
      mMaxMemoryAllocationSize(0),
      mMaxBufferMemorySizeLimit(0),
      mNativeVectorWidthDouble(0),
      mNativeVectorWidthHalf(0),
      mPreferredVectorWidthDouble(0),
      mPreferredVectorWidthHalf(0),
      mMinRPWriteCommandCountToEarlySubmit(UINT32_MAX)
{
    VkFormatProperties invalid = {0, 0, kInvalidFormatFeatureFlags};
    mFormatProperties.fill(invalid);
    mStagingBufferMemoryTypeIndex.fill(kInvalidMemoryTypeIndex);

    // We currently don't have any big-endian devices in the list of supported platforms.  There are
    // a number of places in the Vulkan backend that make this assumption.  This assertion is made
    // early to fail immediately on big-endian platforms.
    ASSERT(IsLittleEndian());

    mDumpPipelineCacheGraph =
        (angle::GetEnvironmentVarOrAndroidProperty("ANGLE_DUMP_PIPELINE_CACHE_GRAPH",
                                                   "angle.dump_pipeline_cache_graph") == "1");

    mPipelineCacheGraphDumpPath = angle::GetEnvironmentVarOrAndroidProperty(
        "ANGLE_PIPELINE_CACHE_GRAPH_DUMP_PATH", "angle.pipeline_cache_graph_dump_path");
    if (mPipelineCacheGraphDumpPath.size() == 0)
    {
        mPipelineCacheGraphDumpPath = kDefaultPipelineCacheGraphDumpPath;
    }
}

Renderer::~Renderer() {}

bool Renderer::hasSharedGarbage()
{
    return !mSharedGarbageList.empty() || !mSuballocationGarbageList.empty();
}

void Renderer::onDestroy(vk::ErrorContext *context)
{
    if (isDeviceLost())
    {
        handleDeviceLost();
    }

    (void)(finishResourceUse(context, mSubmittedResourceUse));

    if (mPlaceHolderDescriptorSetLayout)
    {
        ASSERT(mPlaceHolderDescriptorSetLayout.unique());
        mPlaceHolderDescriptorSetLayout.reset();
    }

    mCleanUpThread.destroy(context);
    mCommandQueue.destroy(context);

    // mCommandQueue.destroy should already set "last completed" serials to infinite.
    cleanupGarbage(nullptr);
    ASSERT(!hasSharedGarbage());
    ASSERT(mOrphanedBufferBlockList.empty());
    mSamplerCache.destroy(this);
    mYuvConversionCache.destroy(this);

    mRefCountedEventRecycler.destroy(mDevice);

    for (OneOffCommandPool &oneOffCommandPool : mOneOffCommandPoolMap)
    {
        oneOffCommandPool.destroy(mDevice);
    }

    mPipelineCacheInitialized = false;
    mPipelineCache.destroy(mDevice);

    mVkFormatDescriptorCountMap.clear();

    mOutsideRenderPassCommandBufferRecycler.onDestroy();
    mRenderPassCommandBufferRecycler.onDestroy();

    mImageMemorySuballocator.destroy(this);
    mAllocator.destroy();

    // When the renderer is being destroyed, it is possible to check if all the allocated memory
    // throughout the execution has been freed.
    mMemoryAllocationTracker.onDestroy();

    if (mDevice)
    {
        vkDestroyDevice(mDevice, nullptr);
        mDevice = VK_NULL_HANDLE;
    }

    if (mDebugUtilsMessenger)
    {
        vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugUtilsMessenger, nullptr);
    }

    logCacheStats();

    if (mInstance)
    {
        vkDestroyInstance(mInstance, nullptr);
        mInstance = VK_NULL_HANDLE;
    }

    if (mCompressEvent)
    {
        mCompressEvent->wait();
        mCompressEvent.reset();
    }

    mMemoryProperties.destroy();
    mPhysicalDevice = VK_NULL_HANDLE;

    mEnabledInstanceExtensions.clear();
    mEnabledDeviceExtensions.clear();

    ASSERT(!hasSharedGarbage());

    if (mLibVulkanLibrary)
    {
        angle::CloseSystemLibrary(mLibVulkanLibrary);
        mLibVulkanLibrary = nullptr;
    }

    if (!mPipelineCacheGraph.str().empty())
    {
        DumpPipelineCacheGraph(this, mPipelineCacheGraph);
    }
}

VkResult Renderer::retrieveDeviceLostDetails() const
{
    if (!getFeatures().supportsDeviceFault.enabled)
    {
        return VK_SUCCESS;
    }
    return RetrieveDeviceLostInfoFromDevice(mDevice, mFaultFeatures);
}

void Renderer::notifyDeviceLost()
{
    mDeviceLost = true;
    mGlobalOps->notifyDeviceLost();
}

bool Renderer::isDeviceLost() const
{
    return mDeviceLost;
}

angle::Result Renderer::enableInstanceExtensions(vk::ErrorContext *context,
                                                 const VulkanLayerVector &enabledInstanceLayerNames,
                                                 const char *wsiExtension,
                                                 UseVulkanSwapchain useVulkanSwapchain,
                                                 bool canLoadDebugUtils)
{
    // Enumerate instance extensions that are provided by the vulkan implementation and implicit
    // layers.
    uint32_t instanceExtensionCount = 0;
    {
        ANGLE_SCOPED_DISABLE_LSAN();
        ANGLE_SCOPED_DISABLE_MSAN();
        ANGLE_VK_TRY(context, vkEnumerateInstanceExtensionProperties(
                                  nullptr, &instanceExtensionCount, nullptr));
    }

    std::vector<VkExtensionProperties> instanceExtensionProps(instanceExtensionCount);
    if (instanceExtensionCount > 0)
    {
        ANGLE_SCOPED_DISABLE_LSAN();
        ANGLE_SCOPED_DISABLE_MSAN();
        ANGLE_VK_TRY(context, vkEnumerateInstanceExtensionProperties(
                                  nullptr, &instanceExtensionCount, instanceExtensionProps.data()));
        // In case fewer items were returned than requested, resize instanceExtensionProps to the
        // number of extensions returned (i.e. instanceExtensionCount).
        instanceExtensionProps.resize(instanceExtensionCount);
    }

    // Enumerate instance extensions that are provided by explicit layers.
    for (const char *layerName : enabledInstanceLayerNames)
    {
        uint32_t previousExtensionCount      = static_cast<uint32_t>(instanceExtensionProps.size());
        uint32_t instanceLayerExtensionCount = 0;
        {
            ANGLE_SCOPED_DISABLE_LSAN();
            ANGLE_SCOPED_DISABLE_MSAN();
            ANGLE_VK_TRY(context, vkEnumerateInstanceExtensionProperties(
                                      layerName, &instanceLayerExtensionCount, nullptr));
        }
        instanceExtensionProps.resize(previousExtensionCount + instanceLayerExtensionCount);
        {
            ANGLE_SCOPED_DISABLE_LSAN();
            ANGLE_SCOPED_DISABLE_MSAN();
            ANGLE_VK_TRY(context, vkEnumerateInstanceExtensionProperties(
                                      layerName, &instanceLayerExtensionCount,
                                      instanceExtensionProps.data() + previousExtensionCount));
        }
        // In case fewer items were returned than requested, resize instanceExtensionProps to the
        // number of extensions returned (i.e. instanceLayerExtensionCount).
        instanceExtensionProps.resize(previousExtensionCount + instanceLayerExtensionCount);
    }

    // Get the list of instance extensions that are available.
    vk::ExtensionNameList instanceExtensionNames;
    if (!instanceExtensionProps.empty())
    {
        for (const VkExtensionProperties &i : instanceExtensionProps)
        {
            instanceExtensionNames.push_back(i.extensionName);
        }
        std::sort(instanceExtensionNames.begin(), instanceExtensionNames.end(), StrLess);
    }

    // Set ANGLE features that depend on instance extensions
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSurfaceCapabilities2Extension,
        ExtensionFound(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, instanceExtensionNames) &&
            useVulkanSwapchain == UseVulkanSwapchain::Yes);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsSurfaceProtectedCapabilitiesExtension,
                            ExtensionFound(VK_KHR_SURFACE_PROTECTED_CAPABILITIES_EXTENSION_NAME,
                                           instanceExtensionNames) &&
                                useVulkanSwapchain == UseVulkanSwapchain::Yes);

    // TODO: Validation layer has a bug when vkGetPhysicalDeviceSurfaceFormats2KHR is called
    // on Mock ICD with surface handle set as VK_NULL_HANDLE. http://anglebug.com/42266098
    // b/267953710: VK_GOOGLE_surfaceless_query isn't working on some Samsung Xclipse builds
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSurfacelessQueryExtension,
        ExtensionFound(VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME, instanceExtensionNames) &&
            useVulkanSwapchain == UseVulkanSwapchain::Yes && !isMockICDEnabled() && !IsXclipse());

    // VK_KHR_external_fence_capabilities and VK_KHR_extenral_semaphore_capabilities are promoted to
    // core in Vulkan 1.1
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsExternalFenceCapabilities, true);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsExternalSemaphoreCapabilities, true);

    // On macOS, there is no native Vulkan driver, so we need to enable the
    // portability enumeration extension to allow use of MoltenVK.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsPortabilityEnumeration,
        ExtensionFound(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, instanceExtensionNames));

    ANGLE_FEATURE_CONDITION(&mFeatures, enablePortabilityEnumeration,
                            mFeatures.supportsPortabilityEnumeration.enabled && IsApple());

    // Enable extensions that could be used
    if (useVulkanSwapchain == UseVulkanSwapchain::Yes)
    {
        mEnabledInstanceExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
        if (ExtensionFound(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, instanceExtensionNames))
        {
            mEnabledInstanceExtensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
        }

        const bool hasSurfaceMaintenance1EXT =
            ExtensionFound(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, instanceExtensionNames);
        const bool hasSurfaceMaintenance1KHR =
            ExtensionFound(VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME, instanceExtensionNames);

        ANGLE_FEATURE_CONDITION(
            &mFeatures, supportsSurfaceMaintenance1,
            !isMockICDEnabled() && (hasSurfaceMaintenance1KHR || hasSurfaceMaintenance1EXT));

        if (mFeatures.supportsSurfaceMaintenance1.enabled)
        {
            if (hasSurfaceMaintenance1KHR)
            {
                mEnabledInstanceExtensions.push_back(VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME);
            }
            if (hasSurfaceMaintenance1EXT)
            {
                mEnabledInstanceExtensions.push_back(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME);
            }
        }
    }

    if (wsiExtension)
    {
        mEnabledInstanceExtensions.push_back(wsiExtension);
    }

    mEnableDebugUtils = canLoadDebugUtils && mEnableValidationLayers &&
                        ExtensionFound(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, instanceExtensionNames);

    if (mEnableDebugUtils)
    {
        mEnabledInstanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
    }

    if (mFeatures.supportsSurfaceCapabilities2Extension.enabled)
    {
        mEnabledInstanceExtensions.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
    }

    if (mFeatures.supportsSurfaceProtectedCapabilitiesExtension.enabled)
    {
        mEnabledInstanceExtensions.push_back(VK_KHR_SURFACE_PROTECTED_CAPABILITIES_EXTENSION_NAME);
    }

    if (mFeatures.supportsSurfacelessQueryExtension.enabled)
    {
        mEnabledInstanceExtensions.push_back(VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME);
    }

    if (mFeatures.enablePortabilityEnumeration.enabled)
    {
        mEnabledInstanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
    }

    // Verify the required extensions are in the extension names set. Fail if not.
    std::sort(mEnabledInstanceExtensions.begin(), mEnabledInstanceExtensions.end(), StrLess);
    ANGLE_VK_TRY(context,
                 VerifyExtensionsPresent(instanceExtensionNames, mEnabledInstanceExtensions));

    return angle::Result::Continue;
}

angle::Result Renderer::initialize(vk::ErrorContext *context,
                                   vk::GlobalOps *globalOps,
                                   angle::vk::ICD desiredICD,
                                   uint32_t preferredVendorId,
                                   uint32_t preferredDeviceId,
                                   const uint8_t *preferredDeviceUuid,
                                   const uint8_t *preferredDriverUuid,
                                   VkDriverId preferredDriverId,
                                   UseDebugLayers useDebugLayers,
                                   const char *wsiExtension,
                                   const char *wsiLayer,
                                   angle::NativeWindowSystem nativeWindowSystem,
                                   const angle::FeatureOverrides &featureOverrides)
{
    bool canLoadDebugUtils = true;
#if defined(ANGLE_SHARED_LIBVULKAN)
    {
        ANGLE_SCOPED_DISABLE_MSAN();
        mLibVulkanLibrary = angle::vk::OpenLibVulkan();
        ANGLE_VK_CHECK(context, mLibVulkanLibrary, VK_ERROR_INITIALIZATION_FAILED);

        PFN_vkGetInstanceProcAddr vulkanLoaderGetInstanceProcAddr =
            reinterpret_cast<PFN_vkGetInstanceProcAddr>(
                angle::GetLibrarySymbol(mLibVulkanLibrary, "vkGetInstanceProcAddr"));

        // Set all vk* function ptrs
        volkInitializeCustom(vulkanLoaderGetInstanceProcAddr);

        uint32_t ver = volkGetInstanceVersion();
        if (!IsAndroid() && ver < VK_MAKE_API_VERSION(0, 1, 1, 91))
        {
            // http://crbug.com/1205999 - non-Android Vulkan Loader versions before 1.1.91 have a
            // bug which prevents loading VK_EXT_debug_utils function pointers.
            canLoadDebugUtils = false;
        }
    }
#endif  // defined(ANGLE_SHARED_LIBVULKAN)

    mGlobalOps = globalOps;

    // While the validation layer is loaded by default whenever present, apidump layer
    // activation is controlled by an environment variable/android property allowing
    // the two layers to be controlled independently.
    bool enableApiDumpLayer =
        kEnableVulkanAPIDumpLayer && angle::GetEnvironmentVarOrAndroidProperty(
                                         "ANGLE_ENABLE_VULKAN_API_DUMP_LAYER",
                                         "debug.angle.enable_vulkan_api_dump_layer") == "1";

    bool loadLayers = (useDebugLayers != UseDebugLayers::No) || enableApiDumpLayer;
    angle::vk::ScopedVkLoaderEnvironment scopedEnvironment(loadLayers, desiredICD);
    bool debugLayersLoaded  = scopedEnvironment.canEnableDebugLayers();
    mEnableValidationLayers = debugLayersLoaded;
    enableApiDumpLayer      = enableApiDumpLayer && debugLayersLoaded;
    mEnabledICD             = scopedEnvironment.getEnabledICD();

    // Gather global layer properties.
    uint32_t instanceLayerCount = 0;
    {
        ANGLE_SCOPED_DISABLE_LSAN();
        ANGLE_SCOPED_DISABLE_MSAN();
        ANGLE_VK_TRY(context, vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr));
    }

    std::vector<VkLayerProperties> instanceLayerProps(instanceLayerCount);
    if (instanceLayerCount > 0)
    {
        ANGLE_SCOPED_DISABLE_LSAN();
        ANGLE_SCOPED_DISABLE_MSAN();
        ANGLE_VK_TRY(context, vkEnumerateInstanceLayerProperties(&instanceLayerCount,
                                                                 instanceLayerProps.data()));
    }

    mEnabledInstanceLayerNames.clear();

    if (enableApiDumpLayer)
    {
        mEnabledInstanceLayerNames.push_back("VK_LAYER_LUNARG_api_dump");
    }

    if (mEnableValidationLayers)
    {
        const bool layersRequested = useDebugLayers == UseDebugLayers::Yes;
        mEnableValidationLayers = GetAvailableValidationLayers(instanceLayerProps, layersRequested,
                                                               &mEnabledInstanceLayerNames);
    }

    if (wsiLayer != nullptr)
    {
        mEnabledInstanceLayerNames.push_back(wsiLayer);
    }

    auto enumerateInstanceVersion = reinterpret_cast<PFN_vkEnumerateInstanceVersion>(
        vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion"));

    uint32_t highestApiVersion = mInstanceVersion = VK_API_VERSION_1_0;
    if (enumerateInstanceVersion)
    {
        {
            ANGLE_SCOPED_DISABLE_LSAN();
            ANGLE_SCOPED_DISABLE_MSAN();
            ANGLE_VK_TRY(context, enumerateInstanceVersion(&mInstanceVersion));
        }

        if (IsVulkan11(mInstanceVersion))
        {
            // This is the highest version of core Vulkan functionality that ANGLE uses.  Per the
            // Vulkan spec, the application is allowed to specify a higher version than supported by
            // the instance.  ANGLE still respects the *device's* version.
            highestApiVersion = kPreferredVulkanAPIVersion;
        }
    }

    if (mInstanceVersion < angle::vk::kMinimumVulkanAPIVersion)
    {
        WARN() << "ANGLE Requires a minimum Vulkan instance version of 1.1";
        ANGLE_VK_TRY(context, VK_ERROR_INCOMPATIBLE_DRIVER);
    }

    const UseVulkanSwapchain useVulkanSwapchain = wsiExtension != nullptr || wsiLayer != nullptr
                                                      ? UseVulkanSwapchain::Yes
                                                      : UseVulkanSwapchain::No;
    ANGLE_TRY(enableInstanceExtensions(context, mEnabledInstanceLayerNames, wsiExtension,
                                       useVulkanSwapchain, canLoadDebugUtils));

    const std::string appName = angle::GetExecutableName();

    mApplicationInfo                    = {};
    mApplicationInfo.sType              = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    mApplicationInfo.pApplicationName   = appName.c_str();
    mApplicationInfo.applicationVersion = 1;
    mApplicationInfo.pEngineName        = "ANGLE";
    mApplicationInfo.engineVersion      = 1;
    mApplicationInfo.apiVersion         = highestApiVersion;

    VkInstanceCreateInfo instanceInfo = {};
    instanceInfo.sType                = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    instanceInfo.flags                = 0;
    instanceInfo.pApplicationInfo     = &mApplicationInfo;

    // Enable requested layers and extensions.
    instanceInfo.enabledExtensionCount = static_cast<uint32_t>(mEnabledInstanceExtensions.size());
    instanceInfo.ppEnabledExtensionNames =
        mEnabledInstanceExtensions.empty() ? nullptr : mEnabledInstanceExtensions.data();

    instanceInfo.enabledLayerCount   = static_cast<uint32_t>(mEnabledInstanceLayerNames.size());
    instanceInfo.ppEnabledLayerNames = mEnabledInstanceLayerNames.data();

    // On macOS, there is no native Vulkan driver, so we need to enable the
    // portability enumeration extension to allow use of MoltenVK.
    if (mFeatures.enablePortabilityEnumeration.enabled)
    {
        instanceInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
    }

    // Fine grain control of validation layer features
    const char *name                     = "VK_LAYER_KHRONOS_validation";
    const VkBool32 setting_validate_core = VK_TRUE;
    // SyncVal is very slow (https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7285)
    // for VkEvent which causes a few tests fail on the bots. Disable syncVal if VkEvent is enabled
    // for now.
    const VkBool32 setting_validate_sync = IsAndroid() ? VK_FALSE : VK_TRUE;
    const VkBool32 setting_thread_safety = VK_TRUE;
    // http://anglebug.com/42265520 - Shader validation caching is broken on Android
    const VkBool32 setting_check_shaders = IsAndroid() ? VK_FALSE : VK_TRUE;
    // http://b/316013423 Disable QueueSubmit Synchronization Validation. Lots of failures and some
    // test timeout due to https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7285
    const VkBool32 setting_syncval_submit_time_validation   = VK_FALSE;
    const VkBool32 setting_syncval_message_extra_properties = VK_TRUE;
    const VkLayerSettingEXT layerSettings[]                 = {
        {name, "validate_core", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_core},
        {name, "validate_sync", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_sync},
        {name, "thread_safety", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_thread_safety},
        {name, "check_shaders", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_check_shaders},
        {name, "syncval_submit_time_validation", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1,
         &setting_syncval_submit_time_validation},
        {name, "syncval_message_extra_properties", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1,
         &setting_syncval_message_extra_properties},
    };
    VkLayerSettingsCreateInfoEXT layerSettingsCreateInfo = {
        VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT, nullptr,
        static_cast<uint32_t>(std::size(layerSettings)), layerSettings};
    if (mEnableValidationLayers)
    {
        vk::AddToPNextChain(&instanceInfo, &layerSettingsCreateInfo);
    }

    {
        ANGLE_SCOPED_DISABLE_MSAN();
        ANGLE_VK_TRY(context, vkCreateInstance(&instanceInfo, nullptr, &mInstance));
#if defined(ANGLE_SHARED_LIBVULKAN)
        // Load volk if we are linking dynamically
        volkLoadInstance(mInstance);
#endif  // defined(ANGLE_SHARED_LIBVULKAN)

        // For promoted extensions, initialize their entry points from the core version.
        initializeInstanceExtensionEntryPointsFromCore();
    }

    if (mEnableDebugUtils)
    {
        // Use the newer EXT_debug_utils if it exists.
#if !defined(ANGLE_SHARED_LIBVULKAN)
        InitDebugUtilsEXTFunctions(mInstance);
#endif  // !defined(ANGLE_SHARED_LIBVULKAN)

        // Create the messenger callback.
        VkDebugUtilsMessengerCreateInfoEXT messengerInfo = {};

        constexpr VkDebugUtilsMessageSeverityFlagsEXT kSeveritiesToLog =
            VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
            VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;

        constexpr VkDebugUtilsMessageTypeFlagsEXT kMessagesToLog =
            VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
            VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
            VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;

        messengerInfo.sType           = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
        messengerInfo.messageSeverity = kSeveritiesToLog;
        messengerInfo.messageType     = kMessagesToLog;
        messengerInfo.pfnUserCallback = &DebugUtilsMessenger;
        messengerInfo.pUserData       = this;

        ANGLE_VK_TRY(context, vkCreateDebugUtilsMessengerEXT(mInstance, &messengerInfo, nullptr,
                                                             &mDebugUtilsMessenger));
    }

    uint32_t physicalDeviceCount = 0;
    ANGLE_VK_TRY(context, vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr));
    ANGLE_VK_CHECK(context, physicalDeviceCount > 0, VK_ERROR_INITIALIZATION_FAILED);

    std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
    ANGLE_VK_TRY(context, vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount,
                                                     physicalDevices.data()));
    ChoosePhysicalDevice(vkGetPhysicalDeviceProperties2, physicalDevices, mEnabledICD,
                         preferredVendorId, preferredDeviceId, preferredDeviceUuid,
                         preferredDriverUuid, preferredDriverId, &mPhysicalDevice,
                         &mPhysicalDeviceProperties2, &mPhysicalDeviceIDProperties,
                         &mDriverProperties);

    // The device version that is assumed by ANGLE is the minimum of the actual device version and
    // the highest it's allowed to use.
    mDeviceVersion = std::min(mPhysicalDeviceProperties.apiVersion, highestApiVersion);

    if (mDeviceVersion < angle::vk::kMinimumVulkanAPIVersion)
    {
        WARN() << "ANGLE Requires a minimum Vulkan device version of 1.1";
        ANGLE_VK_TRY(context, VK_ERROR_INCOMPATIBLE_DRIVER);
    }

    mGarbageCollectionFlushThreshold =
        static_cast<uint32_t>(mPhysicalDeviceProperties.limits.maxMemoryAllocationCount *
                              kPercentMaxMemoryAllocationCount);
    vkGetPhysicalDeviceFeatures(mPhysicalDevice, &mPhysicalDeviceFeatures);

    // Ensure we can find a graphics queue family.
    uint32_t queueFamilyCount = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyCount, nullptr);

    ANGLE_VK_CHECK(context, queueFamilyCount > 0, VK_ERROR_INITIALIZATION_FAILED);

    mQueueFamilyProperties2.resize(queueFamilyCount);
    for (uint32_t i = 0; i < queueFamilyCount; i++)
    {
        mQueueFamilyProperties2[i]       = {};
        mQueueFamilyProperties2[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
    }

    // Query all supported global priorities if supported.
    std::vector<VkQueueFamilyGlobalPriorityProperties> globalPriorityProperties;
    if (mFeatures.supportsGlobalPriorityQuery.enabled)
    {
        globalPriorityProperties.resize(queueFamilyCount);
        for (uint32_t i = 0; i < queueFamilyCount; i++)
        {
            globalPriorityProperties[i] = {};
            globalPriorityProperties[i].sType =
                VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
            vk::AddToPNextChain(&mQueueFamilyProperties2[i], &globalPriorityProperties[i]);
        }
    }

    vkGetPhysicalDeviceQueueFamilyProperties2(mPhysicalDevice, &queueFamilyCount,
                                              mQueueFamilyProperties2.data());

    uint32_t queueFamilyMatchCount = 0;

    VkQueueFlags queueFamilyBits = VK_QUEUE_FLAG_BITS_MAX_ENUM;
    uint32_t firstQueueFamily    = QueueFamily::kInvalidIndex;
    if (nativeWindowSystem == angle::NativeWindowSystem::NullCompute)
    {
        queueFamilyBits = VK_QUEUE_COMPUTE_BIT;
        firstQueueFamily =
            QueueFamily::FindIndex(mQueueFamilyProperties2, queueFamilyBits, VK_QUEUE_PROTECTED_BIT,
                                   VK_QUEUE_GRAPHICS_BIT, &queueFamilyMatchCount);
    }
    if (queueFamilyMatchCount == 0)
    {
        queueFamilyBits = VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT;
        firstQueueFamily =
            QueueFamily::FindIndex(mQueueFamilyProperties2, queueFamilyBits, VK_QUEUE_PROTECTED_BIT,
                                   0, &queueFamilyMatchCount);
    }

    ANGLE_VK_CHECK(context,
                   queueFamilyMatchCount > 0 && firstQueueFamily != QueueFamily::kInvalidIndex,
                   VK_ERROR_INITIALIZATION_FAILED);

    VkQueueGlobalPriority globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM;
    if (mFeatures.supportsGlobalPriorityQuery.enabled &&
        HasRequiredGlobalPriority(globalPriorityProperties[firstQueueFamily],
                                  VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT))
    {
        // Realtime global priority is supported, so we can use it in
        // queueGlobalPriorityCreateInfo
        globalPriority = VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT;
    }

    // Store the physical device memory properties so we can find the right memory pools.
    mMemoryProperties.init(mPhysicalDevice);
    ANGLE_VK_CHECK(context, mMemoryProperties.getMemoryTypeCount() > 0,
                   VK_ERROR_INITIALIZATION_FAILED);

    // The counters for the memory allocation tracker should be initialized.
    // Each memory allocation could be made in one of the available memory heaps. We initialize
    // the per-heap memory allocation trackers for MemoryAllocationType objects here, after
    // mMemoryProperties has been set up.
    mMemoryAllocationTracker.initMemoryTrackers();

    // Determine the threshold for pending garbage sizes.
    calculatePendingGarbageSizeLimit();

    ANGLE_TRY(setupDevice(context, featureOverrides, useVulkanSwapchain, nativeWindowSystem));

    // If only one queue family, that's the only choice and the device is initialize with that.
    // If there is more than one queue, we still create the device with the first queue family
    // and hope for the best.  We cannot wait for a window surface to know which supports
    // present because of EGL_KHR_surfaceless_context or simply pbuffers.  So far, only MoltenVk
    // seems to expose multiple queue families, and using the first queue family is fine with
    // it.
    ANGLE_TRY(createDeviceAndQueue(context, firstQueueFamily, globalPriority));

    // Initialize the format table.
    mFormatTable.initialize(this, &mNativeTextureCaps);

    // Null terminate the extension list returned for EGL_VULKAN_INSTANCE_EXTENSIONS_ANGLE.
    mEnabledInstanceExtensions.push_back(nullptr);

    for (vk::ProtectionType protectionType : angle::AllEnums<vk::ProtectionType>())
    {
        mOneOffCommandPoolMap[protectionType].init(protectionType);
    }

    // Initialize place holder descriptor set layout for empty DescriptorSetLayoutDesc
    ASSERT(!mPlaceHolderDescriptorSetLayout);
    VkDescriptorSetLayoutCreateInfo createInfo = {};
    createInfo.sType        = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    createInfo.flags        = 0;
    createInfo.bindingCount = 0;
    createInfo.pBindings    = nullptr;

    mPlaceHolderDescriptorSetLayout = vk::DescriptorSetLayoutPtr::MakeShared(context->getDevice());
    ANGLE_VK_TRY(context, mPlaceHolderDescriptorSetLayout->init(context->getDevice(), createInfo));
    ASSERT(mPlaceHolderDescriptorSetLayout->valid());

    return angle::Result::Continue;
}

angle::Result Renderer::initializeMemoryAllocator(vk::ErrorContext *context)
{
    mTileMemoyTypeIndex = kInvalidMemoryTypeIndex;
    if (mFeatures.supportsTileMemoryHeap.enabled)
    {
        mTileMemoyTypeIndex = mMemoryProperties.findTileMemoryTypeIndex();
        ASSERT(mTileMemoyTypeIndex != kInvalidMemoryTypeIndex);
    }

    // The preferred heap block size was picked by looking at memory usage of
    // Android apps. The allocator will start making blocks at 1/8 the max size
    // and builds up block size as needed before capping at the max set here.
    mPreferredLargeHeapBlockSize = 8 * 1024 * 1024;

    // The first allocated buffer block from an empty buffer pool has a smaller size in order to
    // reduce the memory footprint.
    mPreferredInitialBufferBlockSize = 1 * 1024 * 1024;
    ASSERT(mPreferredInitialBufferBlockSize <= mPreferredLargeHeapBlockSize);

    // Create VMA allocator
    ANGLE_VK_TRY(context,
                 mAllocator.init(mPhysicalDevice, mDevice, mInstance, mApplicationInfo.apiVersion,
                                 mPreferredLargeHeapBlockSize));

    // Figure out the alignment for default buffer allocations
    VkBufferCreateInfo createInfo    = {};
    createInfo.sType                 = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    createInfo.flags                 = 0;
    createInfo.size                  = 4096;
    createInfo.usage                 = GetDefaultBufferUsageFlags(this);
    createInfo.sharingMode           = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.queueFamilyIndexCount = 0;
    createInfo.pQueueFamilyIndices   = nullptr;

    vk::DeviceScoped<vk::Buffer> tempBuffer(mDevice);
    tempBuffer.get().init(mDevice, createInfo);

    VkMemoryRequirements defaultBufferMemoryRequirements;
    tempBuffer.get().getMemoryRequirements(mDevice, &defaultBufferMemoryRequirements);
    ASSERT(gl::isPow2(defaultBufferMemoryRequirements.alignment));

    const VkPhysicalDeviceLimits &limitsVk = getPhysicalDeviceProperties().limits;
    ASSERT(gl::isPow2(limitsVk.minUniformBufferOffsetAlignment));
    ASSERT(gl::isPow2(limitsVk.minStorageBufferOffsetAlignment));
    ASSERT(gl::isPow2(limitsVk.minTexelBufferOffsetAlignment));
    ASSERT(gl::isPow2(limitsVk.minMemoryMapAlignment));

    mDefaultBufferAlignment =
        std::max({static_cast<size_t>(limitsVk.minUniformBufferOffsetAlignment),
                  static_cast<size_t>(limitsVk.minStorageBufferOffsetAlignment),
                  static_cast<size_t>(limitsVk.minTexelBufferOffsetAlignment),
                  static_cast<size_t>(limitsVk.minMemoryMapAlignment),
                  static_cast<size_t>(defaultBufferMemoryRequirements.alignment)});

    // Initialize staging buffer memory type index and alignment.
    // These buffers will only be used as transfer sources or transfer targets.
    createInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
    VkMemoryPropertyFlags requiredFlags, preferredFlags;
    bool persistentlyMapped = mFeatures.persistentlyMappedBuffers.enabled;

    // Uncached coherent staging buffer.
    requiredFlags  = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
    preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
    ANGLE_VK_TRY(context,
                 mAllocator.findMemoryTypeIndexForBufferInfo(
                     createInfo, requiredFlags, preferredFlags, persistentlyMapped,
                     &mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::UnCachedCoherent]));
    ASSERT(mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::UnCachedCoherent] !=
           kInvalidMemoryTypeIndex);

    // Cached coherent staging buffer.  Note coherent is preferred but not required, which means we
    // may get non-coherent memory type.
    requiredFlags   = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
    preferredFlags  = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
    VkResult result = mAllocator.findMemoryTypeIndexForBufferInfo(
        createInfo, requiredFlags, preferredFlags, persistentlyMapped,
        &mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::CachedPreferCoherent]);
    if (result == VK_SUCCESS)
    {
        ASSERT(mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::CachedPreferCoherent] !=
               kInvalidMemoryTypeIndex);
    }
    else
    {
        // Android studio may not expose host cached memory pool. Fall back to host uncached.
        mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::CachedPreferCoherent] =
            mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::UnCachedCoherent];
    }

    // Cached Non-coherent staging buffer
    requiredFlags  = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
    preferredFlags = 0;
    result         = mAllocator.findMemoryTypeIndexForBufferInfo(
        createInfo, requiredFlags, preferredFlags, persistentlyMapped,
        &mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::CachedNonCoherent]);
    if (result == VK_SUCCESS)
    {
        ASSERT(mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::CachedNonCoherent] !=
               kInvalidMemoryTypeIndex);
    }
    else
    {
        // Android studio may not expose host cached memory pool. Fall back to host uncached.
        mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::CachedNonCoherent] =
            mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::UnCachedCoherent];
    }

    // Alignment
    mStagingBufferAlignment =
        static_cast<size_t>(mPhysicalDeviceProperties.limits.minMemoryMapAlignment);
    ASSERT(gl::isPow2(mPhysicalDeviceProperties.limits.nonCoherentAtomSize));
    ASSERT(gl::isPow2(mPhysicalDeviceProperties.limits.optimalBufferCopyOffsetAlignment));
    // Usually minTexelBufferOffsetAlignment is much smaller than  nonCoherentAtomSize
    ASSERT(gl::isPow2(mPhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment));
    mStagingBufferAlignment = std::max(
        {mStagingBufferAlignment,
         static_cast<size_t>(mPhysicalDeviceProperties.limits.optimalBufferCopyOffsetAlignment),
         static_cast<size_t>(mPhysicalDeviceProperties.limits.nonCoherentAtomSize),
         static_cast<size_t>(mPhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment)});
    ASSERT(gl::isPow2(mStagingBufferAlignment));

    // Device local vertex conversion buffer
    createInfo.usage = vk::kVertexBufferUsageFlags;
    requiredFlags    = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
    preferredFlags   = 0;
    ANGLE_VK_TRY(context, mAllocator.findMemoryTypeIndexForBufferInfo(
                              createInfo, requiredFlags, preferredFlags, persistentlyMapped,
                              &mDeviceLocalVertexConversionBufferMemoryTypeIndex));
    ASSERT(mDeviceLocalVertexConversionBufferMemoryTypeIndex != kInvalidMemoryTypeIndex);

    // Host visible and non-coherent vertex conversion buffer, which is the same as non-coherent
    // staging buffer
    mHostVisibleVertexConversionBufferMemoryTypeIndex =
        mStagingBufferMemoryTypeIndex[vk::MemoryCoherency::CachedNonCoherent];

    // We may use compute shader to do conversion, so we must meet
    // minStorageBufferOffsetAlignment requirement as well. Also take into account non-coherent
    // alignment requirements.
    mVertexConversionBufferAlignment = std::max(
        {vk::kVertexBufferAlignment,
         static_cast<size_t>(mPhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment),
         static_cast<size_t>(mPhysicalDeviceProperties.limits.nonCoherentAtomSize),
         static_cast<size_t>(defaultBufferMemoryRequirements.alignment)});
    ASSERT(gl::isPow2(mVertexConversionBufferAlignment));

    return angle::Result::Continue;
}

// The following features and properties are not promoted to any core Vulkan versions (up to Vulkan
// 1.3):
//
// - VK_EXT_line_rasterization:                        bresenhamLines (feature)
// - VK_EXT_provoking_vertex:                          provokingVertexLast (feature)
// - VK_EXT_vertex_attribute_divisor:                  vertexAttributeInstanceRateDivisor (feature),
//                                                     maxVertexAttribDivisor (property)
// - VK_EXT_transform_feedback:                        transformFeedback (feature),
//                                                     geometryStreams (feature)
// - VK_EXT_index_type_uint8:                          indexTypeUint8 (feature)
// - VK_EXT_device_memory_report:                      deviceMemoryReport (feature)
// - VK_EXT_multisampled_render_to_single_sampled:     multisampledRenderToSingleSampled (feature)
// - VK_EXT_image_2d_view_of_3d:                       image2DViewOf3D (feature)
//                                                     sampler2DViewOf3D (feature)
// - VK_EXT_custom_border_color:                       customBorderColors (feature)
//                                                     customBorderColorWithoutFormat (feature)
// - VK_EXT_depth_clamp_zero_one:                      depthClampZeroOne (feature)
// - VK_EXT_depth_clip_control:                        depthClipControl (feature)
// - VK_EXT_primitives_generated_query:                primitivesGeneratedQuery (feature),
//                                                     primitivesGeneratedQueryWithRasterizerDiscard
//                                                                                        (property)
// - VK_EXT_primitive_topology_list_restart:           primitiveTopologyListRestart (feature)
// - VK_EXT_graphics_pipeline_library:                 graphicsPipelineLibrary (feature),
//                                                     graphicsPipelineLibraryFastLinking (property)
// - VK_KHR_fragment_shading_rate:                     pipelineFragmentShadingRate (feature)
// - VK_EXT_fragment_shader_interlock:                 fragmentShaderPixelInterlock (feature)
// - VK_EXT_pipeline_robustness:                       pipelineRobustness (feature)
// - VK_EXT_pipeline_protected_access:                 pipelineProtectedAccess (feature)
// - VK_EXT_rasterization_order_attachment_access or
//   VK_ARM_rasterization_order_attachment_access:     rasterizationOrderColorAttachmentAccess
//                                                                                   (feature)
//                                                     rasterizationOrderDepthAttachmentAccess
//                                                                                   (feature)
//                                                     rasterizationOrderStencilAttachmentAccess
//                                                                                   (feature)
// - VK_KHR_swapchain_maintenance1 or
//   VK_EXT_swapchain_maintenance1:                    swapchainMaintenance1 (feature)
// - VK_EXT_legacy_dithering:                          supportsLegacyDithering (feature)
// - VK_EXT_physical_device_drm:                       hasPrimary (property),
//                                                     hasRender (property)
// - VK_EXT_host_image_copy:                           hostImageCopy (feature),
//                                                     pCopySrcLayouts (property),
//                                                     pCopyDstLayouts (property),
//                                                     identicalMemoryTypeRequirements (property)
// - VK_ANDROID_external_format_resolve:               externalFormatResolve (feature)
// - VK_EXT_vertex_input_dynamic_state:                vertexInputDynamicState (feature)
// - VK_KHR_dynamic_rendering_local_read:              dynamicRenderingLocalRead (feature)
// - VK_EXT_shader_atomic_float                        shaderImageFloat32Atomics (feature)
// - VK_EXT_image_compression_control                  imageCompressionControl (feature)
// - VK_EXT_image_compression_control_swapchain        imageCompressionControlSwapchain (feature)
// - VK_EXT_device_fault                               deviceFault (feature),
//                                                     deviceFaultVendorBinary (feature)
// - VK_EXT_astc_decode_mode                           decodeModeSharedExponent (feature)
// - VK_KHR_unified_image_layouts                      unifiedImageLayouts (feature)
// - VK_EXT_global_priority_query                      globalPriorityQuery (feature)
// - VK_EXT_external_memory_host                       minImportedHostPointerAlignment (property)
// - VK_QCOM_tile_memory_heap                          tileMemoryHeapFeatures (feature)
//                                                     tileMemoryHeapProperties (property)
// - VK_EXT_texture_compression_astc_3d                textureCompressionASTC_3D (feature)
// - VK_AMD_shader_core_properties
//

void Renderer::appendDeviceExtensionFeaturesNotPromoted(
    const vk::ExtensionNameList &deviceExtensionNames,
    VkPhysicalDeviceFeatures2KHR *deviceFeatures,
    VkPhysicalDeviceProperties2 *deviceProperties)
{
    if (ExtensionFound(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mLineRasterizationFeatures);
    }

    if (ExtensionFound(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mProvokingVertexFeatures);
    }

    if (ExtensionFound(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mVertexAttributeDivisorFeatures);
        vk::AddToPNextChain(deviceProperties, &mVertexAttributeDivisorProperties);
    }

    if (ExtensionFound(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mTransformFeedbackFeatures);
    }

    if (ExtensionFound(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mIndexTypeUint8Features);
    }

    if (ExtensionFound(VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mMemoryReportFeatures);
    }

    if (ExtensionFound(VK_EXT_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_EXTENSION_NAME,
                       deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mMultisampledRenderToSingleSampledFeatures);
    }

    if (ExtensionFound(VK_EXT_IMAGE_2D_VIEW_OF_3D_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mImage2dViewOf3dFeatures);
    }

    if (ExtensionFound(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mCustomBorderColorFeatures);
    }

    if (ExtensionFound(VK_EXT_DEPTH_CLAMP_ZERO_ONE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mDepthClampZeroOneFeatures);
    }

    if (ExtensionFound(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mDepthClipControlFeatures);
    }

    if (ExtensionFound(VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mPrimitivesGeneratedQueryFeatures);
    }

    if (ExtensionFound(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mPrimitiveTopologyListRestartFeatures);
    }

    if (ExtensionFound(VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mGraphicsPipelineLibraryFeatures);
        vk::AddToPNextChain(deviceProperties, &mGraphicsPipelineLibraryProperties);
    }

    if (ExtensionFound(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mFragmentShadingRateFeatures);
        vk::AddToPNextChain(deviceProperties, &mFragmentShadingRateProperties);
    }

    if (ExtensionFound(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mFragmentShaderInterlockFeatures);
    }

    if (ExtensionFound(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mPipelineRobustnessFeatures);
    }

    if (ExtensionFound(VK_EXT_PIPELINE_PROTECTED_ACCESS_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mPipelineProtectedAccessFeatures);
    }

    // The EXT and ARM versions are interchangeable. The structs and enums alias each other.
    if (ExtensionFound(VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME,
                       deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mRasterizationOrderAttachmentAccessFeatures);
    }
    else if (ExtensionFound(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME,
                            deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mRasterizationOrderAttachmentAccessFeatures);
    }

    if (ExtensionFound(VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mShaderAtomicFloatFeatures);
    }

    if (ExtensionFound(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, deviceExtensionNames) ||
        ExtensionFound(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mSwapchainMaintenance1Features);
    }

    if (ExtensionFound(VK_EXT_LEGACY_DITHERING_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mDitheringFeatures);
    }

    if (ExtensionFound(VK_EXT_PHYSICAL_DEVICE_DRM_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceProperties, &mDrmProperties);
    }

    if (ExtensionFound(VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME, deviceExtensionNames))
    {
        // VkPhysicalDeviceHostImageCopyPropertiesEXT has a count + array query.  Typically, that
        // requires getting the properties once with a nullptr array, to get the count, and then
        // again with an array of that size.  For simplicity, ANGLE just uses an array that's big
        // enough.  If that array goes terribly large in the future, ANGLE may lose knowledge of
        // some likely esoteric layouts, which doesn't really matter.
        constexpr uint32_t kMaxLayoutCount = 50;
        mHostImageCopySrcLayoutsStorage.resize(kMaxLayoutCount, VK_IMAGE_LAYOUT_UNDEFINED);
        mHostImageCopyDstLayoutsStorage.resize(kMaxLayoutCount, VK_IMAGE_LAYOUT_UNDEFINED);
        mHostImageCopyProperties.copySrcLayoutCount = kMaxLayoutCount;
        mHostImageCopyProperties.copyDstLayoutCount = kMaxLayoutCount;
        mHostImageCopyProperties.pCopySrcLayouts    = mHostImageCopySrcLayoutsStorage.data();
        mHostImageCopyProperties.pCopyDstLayouts    = mHostImageCopyDstLayoutsStorage.data();

        vk::AddToPNextChain(deviceFeatures, &mHostImageCopyFeatures);
        vk::AddToPNextChain(deviceProperties, &mHostImageCopyProperties);
    }

    if (ExtensionFound(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mVertexInputDynamicStateFeatures);
    }

#if defined(ANGLE_PLATFORM_ANDROID)
    if (ExtensionFound(VK_ANDROID_EXTERNAL_FORMAT_RESOLVE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mExternalFormatResolveFeatures);
        vk::AddToPNextChain(deviceProperties, &mExternalFormatResolveProperties);
    }
#endif

    if (ExtensionFound(VK_KHR_DYNAMIC_RENDERING_LOCAL_READ_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mDynamicRenderingLocalReadFeatures);
    }

    if (ExtensionFound(VK_KHR_UNIFIED_IMAGE_LAYOUTS_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mUnifiedImageLayoutsFeatures);
    }

    if (ExtensionFound(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mBlendOperationAdvancedFeatures);
    }

    if (ExtensionFound(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mImageCompressionControlFeatures);
    }
    if (ExtensionFound(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME,
                       deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mImageCompressionControlSwapchainFeatures);
    }
    if (ExtensionFound(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mFaultFeatures);
    }
    if (ExtensionFound(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mPhysicalDeviceAstcDecodeFeatures);
    }
    if (ExtensionFound(VK_EXT_GLOBAL_PRIORITY_QUERY_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mPhysicalDeviceGlobalPriorityQueryFeatures);
    }
    if (ExtensionFound(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceProperties, &mExternalMemoryHostProperties);
    }
    if (ExtensionFound(VK_EXT_TEXTURE_COMPRESSION_ASTC_3D_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mTextureCompressionASTC3DFeatures);
    }

    if (ExtensionFound(VK_QCOM_TILE_MEMORY_HEAP_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mTileMemoryHeapFeatures);
        vk::AddToPNextChain(deviceProperties, &mTileMemoryHeapProperties);
    }

    if (ExtensionFound(VK_AMD_SHADER_CORE_PROPERTIES_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceProperties, &mShaderCorePropertiesAMD);
    }
}

// The following features and properties used by ANGLE have been promoted to Vulkan 1.1:
//
// - (unpublished VK_KHR_subgroup):         supportedStages (property),
//                                          supportedOperations (property)
// - (unpublished VK_KHR_protected_memory): protectedMemory (feature)
// - VK_KHR_sampler_ycbcr_conversion:       samplerYcbcrConversion (feature)
// - VK_KHR_multiview:                      multiview (feature),
//                                          maxMultiviewViewCount (property)
// - VK_KHR_16bit_storage:                  storageBuffer16BitAccess (feature)
//                                          uniformAndStorageBuffer16BitAccess (feature)
//                                          storagePushConstant16 (feature)
//                                          storageInputOutput16 (feature)
// - VK_KHR_variable_pointers:              variablePointers (feature)
//                                          variablePointersStorageBuffer (feature)
//
//
// Note that subgroup and protected memory features and properties came from unpublished extensions
// and are core in Vulkan 1.1.
//
void Renderer::appendDeviceExtensionFeaturesPromotedTo11(
    const vk::ExtensionNameList &deviceExtensionNames,
    VkPhysicalDeviceFeatures2KHR *deviceFeatures,
    VkPhysicalDeviceProperties2 *deviceProperties)
{
    vk::AddToPNextChain(deviceProperties, &mSubgroupProperties);
    vk::AddToPNextChain(deviceFeatures, &mProtectedMemoryFeatures);
    vk::AddToPNextChain(deviceFeatures, &mSamplerYcbcrConversionFeatures);
    vk::AddToPNextChain(deviceFeatures, &mMultiviewFeatures);
    vk::AddToPNextChain(deviceProperties, &mMultiviewProperties);
    vk::AddToPNextChain(deviceFeatures, &m16BitStorageFeatures);
    vk::AddToPNextChain(deviceFeatures, &mVariablePointersFeatures);
    vk::AddToPNextChain(deviceProperties, &mMaintenance3Properties);
}

// The following features and properties used by ANGLE have been promoted to Vulkan 1.2:
//
// - VK_KHR_shader_float16_int8:            shaderFloat16 (feature),
//                                          shaderInt8 (feature)
// - VK_KHR_depth_stencil_resolve:          supportedDepthResolveModes (property),
//                                          independentResolveNone (property)
// - VK_KHR_driver_properties:              driverName (property),
//                                          driverID (property)
// - VK_KHR_shader_subgroup_extended_types: shaderSubgroupExtendedTypes (feature)
// - VK_EXT_host_query_reset:               hostQueryReset (feature)
// - VK_KHR_imageless_framebuffer:          imagelessFramebuffer (feature)
// - VK_KHR_timeline_semaphore:             timelineSemaphore (feature)
// - VK_KHR_8bit_storage                    storageBuffer8BitAccess (feature)
//                                          uniformAndStorageBuffer8BitAccess (feature)
//                                          storagePushConstant8 (feature)
// - VK_KHR_shader_float_controls           shaderRoundingModeRTEFloat16 (property)
//                                          shaderRoundingModeRTEFloat32 (property)
//                                          shaderRoundingModeRTEFloat64 (property)
//                                          shaderRoundingModeRTZFloat16 (property)
//                                          shaderRoundingModeRTZFloat32 (property)
//                                          shaderRoundingModeRTZFloat64 (property)
//                                          shaderDenormPreserveFloat16 (property)
//                                          shaderDenormPreserveFloat16 (property)
//                                          shaderDenormPreserveFloat16 (property)
//                                          shaderDenormFlushToZeroFloat16 (property)
//                                          shaderDenormFlushToZeroFloat32 (property)
//                                          shaderDenormFlushToZeroFloat64 (property)
//                                          shaderSignedZeroInfNanPreserveFloat16 (property)
//                                          shaderSignedZeroInfNanPreserveFloat32 (property)
//                                          shaderSignedZeroInfNanPreserveFloat64 (property)
// - VK_KHR_uniform_buffer_standard_layout: uniformBufferStandardLayout (feature)
// - VK_KHR_buffer_device_address:          bufferDeviceAddress (feature)
// - VK_KHR_shader_atomic_int64:            shaderBufferInt64Atomics (feature)
//                                          shaderSharedInt64Atomics (feature)
//
// Note that supportedDepthResolveModes is used just to check if the property struct is populated.
// ANGLE always uses VK_RESOLVE_MODE_SAMPLE_ZERO_BIT for both depth and stencil, and support for
// this bit is mandatory as long as the extension (or Vulkan 1.2) exists.
//
void Renderer::appendDeviceExtensionFeaturesPromotedTo12(
    const vk::ExtensionNameList &deviceExtensionNames,
    VkPhysicalDeviceFeatures2KHR *deviceFeatures,
    VkPhysicalDeviceProperties2 *deviceProperties)
{
    if (ExtensionFound(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceProperties, &mFloatControlProperties);
    }

    if (ExtensionFound(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mShaderFloat16Int8Features);
    }

    if (ExtensionFound(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceProperties, &mDepthStencilResolveProperties);
    }

    if (ExtensionFound(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceProperties, &mDriverProperties);
    }

    if (ExtensionFound(VK_KHR_SHADER_SUBGROUP_EXTENDED_TYPES_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mSubgroupExtendedTypesFeatures);
    }

    if (ExtensionFound(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mHostQueryResetFeatures);
    }

    if (ExtensionFound(VK_KHR_IMAGELESS_FRAMEBUFFER_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mImagelessFramebufferFeatures);
    }

    if (ExtensionFound(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mTimelineSemaphoreFeatures);
    }

    if (ExtensionFound(VK_KHR_8BIT_STORAGE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &m8BitStorageFeatures);
    }

    if (ExtensionFound(VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mUniformBufferStandardLayoutFeatures);
    }

    if (ExtensionFound(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mBufferDeviceAddressFeatures);
    }

    if (ExtensionFound(VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mShaderAtomicInt64Features);
    }
}

// The following features and properties used by ANGLE have been promoted to Vulkan 1.3:
//
// - VK_EXT_extended_dynamic_state:             extendedDynamicState (feature)
// - VK_EXT_extended_dynamic_state2:            extendedDynamicState2 (feature),
//                                              extendedDynamicState2LogicOp (feature)
// - VK_KHR_synchronization2:                   synchronization2 (feature)
// - VK_KHR_dynamic_rendering:                  dynamicRendering (feature)
// - VK_KHR_maintenance5:                       maintenance5 (feature)
// - VK_EXT_texture_compression_astc_hdr:       textureCompressionASTC_HDR(feature)
// - VK_KHR_shader_integer_dot_product:         shaderIntegerDotProduct (feature)
// - VK_EXT_shader_demote_to_helper_invocation: shaderDemoteToHelperInvocation (feature)
//
// Note that VK_EXT_extended_dynamic_state2 is partially promoted to Vulkan 1.3.  If ANGLE creates a
// Vulkan 1.3 device, it would still need to enable this extension separately for
// extendedDynamicState2LogicOp.
//
void Renderer::appendDeviceExtensionFeaturesPromotedTo13(
    const vk::ExtensionNameList &deviceExtensionNames,
    VkPhysicalDeviceFeatures2KHR *deviceFeatures,
    VkPhysicalDeviceProperties2 *deviceProperties)
{
    if (ExtensionFound(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mExtendedDynamicStateFeatures);
    }

    if (ExtensionFound(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mExtendedDynamicState2Features);
    }

    if (ExtensionFound(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mSynchronization2Features);
    }

    if (ExtensionFound(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mDynamicRenderingFeatures);
    }

    if (ExtensionFound(VK_KHR_MAINTENANCE_5_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mMaintenance5Features);
    }

    if (ExtensionFound(VK_EXT_TEXTURE_COMPRESSION_ASTC_HDR_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mTextureCompressionASTCHDRFeatures);
    }

    if (ExtensionFound(VK_KHR_SHADER_INTEGER_DOT_PRODUCT_EXTENSION_NAME, deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mShaderIntegerDotProductFeatures);
        vk::AddToPNextChain(deviceProperties, &mShaderIntegerDotProductProperties);
    }

    if (ExtensionFound(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME,
                       deviceExtensionNames))
    {
        vk::AddToPNextChain(deviceFeatures, &mShaderDemoteToHelperInvocationFeatures);
    }
}

void Renderer::queryDeviceExtensionFeatures(const vk::ExtensionNameList &deviceExtensionNames)
{
    // Default initialize all extension features to false.
    mLineRasterizationFeatures = {};
    mLineRasterizationFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT;

    mProvokingVertexFeatures = {};
    mProvokingVertexFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT;

    mVertexAttributeDivisorFeatures = {};
    mVertexAttributeDivisorFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT;

    mVertexAttributeDivisorProperties = {};
    mVertexAttributeDivisorProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT;

    mTransformFeedbackFeatures = {};
    mTransformFeedbackFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT;

    mIndexTypeUint8Features       = {};
    mIndexTypeUint8Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT;

    mSubgroupProperties       = {};
    mSubgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;

    mSubgroupExtendedTypesFeatures = {};
    mSubgroupExtendedTypesFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES;

    mMemoryReportFeatures = {};
    mMemoryReportFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_MEMORY_REPORT_FEATURES_EXT;

    mShaderFloat16Int8Features = {};
    mShaderFloat16Int8Features.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES;

    mDepthStencilResolveProperties = {};
    mDepthStencilResolveProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES;

    mCustomBorderColorFeatures = {};
    mCustomBorderColorFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT;

    mMultisampledRenderToSingleSampledFeatures = {};
    mMultisampledRenderToSingleSampledFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_FEATURES_EXT;

    mImage2dViewOf3dFeatures = {};
    mImage2dViewOf3dFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_2D_VIEW_OF_3D_FEATURES_EXT;

    mMultiviewFeatures       = {};
    mMultiviewFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;

    mMultiviewProperties       = {};
    mMultiviewProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;

    mMaintenance3Properties       = {};
    mMaintenance3Properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES;

    mFaultFeatures       = {};
    mFaultFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;

    mDriverProperties       = {};
    mDriverProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;

    mSamplerYcbcrConversionFeatures = {};
    mSamplerYcbcrConversionFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;

    mProtectedMemoryFeatures       = {};
    mProtectedMemoryFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;

    mHostQueryResetFeatures       = {};
    mHostQueryResetFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT;

    mDepthClampZeroOneFeatures = {};
    mDepthClampZeroOneFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLAMP_ZERO_ONE_FEATURES_EXT;

    mDepthClipControlFeatures = {};
    mDepthClipControlFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT;

    mPrimitivesGeneratedQueryFeatures = {};
    mPrimitivesGeneratedQueryFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT;

    mPrimitiveTopologyListRestartFeatures = {};
    mPrimitiveTopologyListRestartFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT;

    mExtendedDynamicStateFeatures = {};
    mExtendedDynamicStateFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;

    mExtendedDynamicState2Features = {};
    mExtendedDynamicState2Features.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT;

    mGraphicsPipelineLibraryFeatures = {};
    mGraphicsPipelineLibraryFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT;

    mGraphicsPipelineLibraryProperties = {};
    mGraphicsPipelineLibraryProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_PROPERTIES_EXT;

    mVertexInputDynamicStateFeatures = {};
    mVertexInputDynamicStateFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT;

    mDynamicRenderingFeatures = {};
    mDynamicRenderingFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR;

    mDynamicRenderingLocalReadFeatures = {};
    mDynamicRenderingLocalReadFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_LOCAL_READ_FEATURES_KHR;

    mFragmentShadingRateFeatures = {};
    mFragmentShadingRateFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR;

    mFragmentShadingRateProperties = {};
    mFragmentShadingRateProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;

    mFragmentShaderInterlockFeatures = {};
    mFragmentShaderInterlockFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT;

    mImagelessFramebufferFeatures = {};
    mImagelessFramebufferFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES_KHR;

    mPipelineRobustnessFeatures = {};
    mPipelineRobustnessFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT;

    mPipelineProtectedAccessFeatures = {};
    mPipelineProtectedAccessFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROTECTED_ACCESS_FEATURES_EXT;

    mRasterizationOrderAttachmentAccessFeatures = {};
    mRasterizationOrderAttachmentAccessFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT;

    mMaintenance5Features       = {};
    mMaintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR;

    mShaderAtomicFloatFeatures = {};
    mShaderAtomicFloatFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT;

    mSwapchainMaintenance1Features = {};
    mSwapchainMaintenance1Features.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR;

    mDitheringFeatures       = {};
    mDitheringFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LEGACY_DITHERING_FEATURES_EXT;

    mDrmProperties       = {};
    mDrmProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT;

    mTimelineSemaphoreFeatures = {};
    mTimelineSemaphoreFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR;

    mHostImageCopyFeatures       = {};
    mHostImageCopyFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT;

    mHostImageCopyProperties = {};
    mHostImageCopyProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_PROPERTIES_EXT;

    m8BitStorageFeatures       = {};
    m8BitStorageFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR;

    m16BitStorageFeatures       = {};
    m16BitStorageFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR;

    mSynchronization2Features       = {};
    mSynchronization2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES;

    mBlendOperationAdvancedFeatures = {};
    mBlendOperationAdvancedFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT;

    mVariablePointersFeatures = {};
    mVariablePointersFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES_KHR;

    // Rounding and denormal caps from VK_KHR_float_controls_properties
    mFloatControlProperties       = {};
    mFloatControlProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES;

    mImageCompressionControlFeatures = {};
    mImageCompressionControlFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT;

    mImageCompressionControlSwapchainFeatures = {};
    mImageCompressionControlSwapchainFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT;

    mTextureCompressionASTCHDRFeatures = {};
    mTextureCompressionASTCHDRFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES;

    mUniformBufferStandardLayoutFeatures = {};
    mUniformBufferStandardLayoutFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES;

    mPhysicalDeviceAstcDecodeFeatures = {};
    mPhysicalDeviceAstcDecodeFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ASTC_DECODE_FEATURES_EXT;

    mUnifiedImageLayoutsFeatures = {};
    mUnifiedImageLayoutsFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFIED_IMAGE_LAYOUTS_FEATURES_KHR;

    mShaderIntegerDotProductFeatures = {};
    mShaderIntegerDotProductFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_FEATURES;

    mShaderIntegerDotProductProperties = {};
    mShaderIntegerDotProductProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_PROPERTIES;

    mShaderDemoteToHelperInvocationFeatures = {};
    mShaderDemoteToHelperInvocationFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES;

    mPhysicalDeviceGlobalPriorityQueryFeatures = {};
    mPhysicalDeviceGlobalPriorityQueryFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_EXT;

    mExternalMemoryHostProperties = {};
    mExternalMemoryHostProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT;

    mBufferDeviceAddressFeatures = {};
    mBufferDeviceAddressFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;

    mShaderAtomicInt64Features = {};
    mShaderAtomicInt64Features.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES;

    mTextureCompressionASTC3DFeatures = {};
    mTextureCompressionASTC3DFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_3D_FEATURES_EXT;

#if defined(ANGLE_PLATFORM_ANDROID)
    mExternalFormatResolveFeatures = {};
    mExternalFormatResolveFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FORMAT_RESOLVE_FEATURES_ANDROID;

    mExternalFormatResolveProperties = {};
    mExternalFormatResolveProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FORMAT_RESOLVE_PROPERTIES_ANDROID;
#endif

    mTileMemoryHeapFeatures = {};
    mTileMemoryHeapFeatures.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TILE_MEMORY_HEAP_FEATURES_QCOM;
    mTileMemoryHeapProperties = {};
    mTileMemoryHeapProperties.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TILE_MEMORY_HEAP_PROPERTIES_QCOM;

    mShaderCorePropertiesAMD       = {};
    mShaderCorePropertiesAMD.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CORE_PROPERTIES_AMD;

    // Query features and properties.
    VkPhysicalDeviceFeatures2KHR deviceFeatures = {};
    deviceFeatures.sType                        = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;

    VkPhysicalDeviceProperties2 deviceProperties = {};
    deviceProperties.sType                       = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;

    appendDeviceExtensionFeaturesNotPromoted(deviceExtensionNames, &deviceFeatures,
                                             &deviceProperties);
    appendDeviceExtensionFeaturesPromotedTo11(deviceExtensionNames, &deviceFeatures,
                                              &deviceProperties);
    appendDeviceExtensionFeaturesPromotedTo12(deviceExtensionNames, &deviceFeatures,
                                              &deviceProperties);
    appendDeviceExtensionFeaturesPromotedTo13(deviceExtensionNames, &deviceFeatures,
                                              &deviceProperties);

    vkGetPhysicalDeviceFeatures2(mPhysicalDevice, &deviceFeatures);
    vkGetPhysicalDeviceProperties2(mPhysicalDevice, &deviceProperties);

    // Clean up pNext chains
    mLineRasterizationFeatures.pNext                  = nullptr;
    mMemoryReportFeatures.pNext                       = nullptr;
    mProvokingVertexFeatures.pNext                    = nullptr;
    mVertexAttributeDivisorFeatures.pNext             = nullptr;
    mVertexAttributeDivisorProperties.pNext           = nullptr;
    mTransformFeedbackFeatures.pNext                  = nullptr;
    mIndexTypeUint8Features.pNext                     = nullptr;
    mSubgroupProperties.pNext                         = nullptr;
    mSubgroupExtendedTypesFeatures.pNext              = nullptr;
    mCustomBorderColorFeatures.pNext                  = nullptr;
    mShaderFloat16Int8Features.pNext                  = nullptr;
    mDepthStencilResolveProperties.pNext              = nullptr;
    mMultisampledRenderToSingleSampledFeatures.pNext  = nullptr;
    mImage2dViewOf3dFeatures.pNext                    = nullptr;
    mMultiviewFeatures.pNext                          = nullptr;
    mMultiviewProperties.pNext                        = nullptr;
    mDriverProperties.pNext                           = nullptr;
    mSamplerYcbcrConversionFeatures.pNext             = nullptr;
    mProtectedMemoryFeatures.pNext                    = nullptr;
    mHostQueryResetFeatures.pNext                     = nullptr;
    mDepthClampZeroOneFeatures.pNext                  = nullptr;
    mDepthClipControlFeatures.pNext                   = nullptr;
    mPrimitivesGeneratedQueryFeatures.pNext           = nullptr;
    mPrimitiveTopologyListRestartFeatures.pNext       = nullptr;
    mExtendedDynamicStateFeatures.pNext               = nullptr;
    mExtendedDynamicState2Features.pNext              = nullptr;
    mGraphicsPipelineLibraryFeatures.pNext            = nullptr;
    mGraphicsPipelineLibraryProperties.pNext          = nullptr;
    mVertexInputDynamicStateFeatures.pNext            = nullptr;
    mDynamicRenderingFeatures.pNext                   = nullptr;
    mDynamicRenderingLocalReadFeatures.pNext          = nullptr;
    mFragmentShadingRateFeatures.pNext                = nullptr;
    mFragmentShaderInterlockFeatures.pNext            = nullptr;
    mImagelessFramebufferFeatures.pNext               = nullptr;
    mPipelineRobustnessFeatures.pNext                 = nullptr;
    mPipelineProtectedAccessFeatures.pNext            = nullptr;
    mRasterizationOrderAttachmentAccessFeatures.pNext = nullptr;
    mShaderAtomicFloatFeatures.pNext                  = nullptr;
    mMaintenance5Features.pNext                       = nullptr;
    mSwapchainMaintenance1Features.pNext              = nullptr;
    mDitheringFeatures.pNext                          = nullptr;
    mDrmProperties.pNext                              = nullptr;
    mTimelineSemaphoreFeatures.pNext                  = nullptr;
    mHostImageCopyFeatures.pNext                      = nullptr;
    mHostImageCopyProperties.pNext                    = nullptr;
    m8BitStorageFeatures.pNext                        = nullptr;
    m16BitStorageFeatures.pNext                       = nullptr;
    mSynchronization2Features.pNext                   = nullptr;
    mBlendOperationAdvancedFeatures.pNext             = nullptr;
    mVariablePointersFeatures.pNext                   = nullptr;
    mFloatControlProperties.pNext                     = nullptr;
    mImageCompressionControlFeatures.pNext            = nullptr;
    mImageCompressionControlSwapchainFeatures.pNext   = nullptr;
    mTextureCompressionASTCHDRFeatures.pNext          = nullptr;
    mUniformBufferStandardLayoutFeatures.pNext        = nullptr;
    mMaintenance3Properties.pNext                     = nullptr;
    mFaultFeatures.pNext                              = nullptr;
    mPhysicalDeviceAstcDecodeFeatures.pNext           = nullptr;
    mUnifiedImageLayoutsFeatures.pNext                = nullptr;
    mShaderIntegerDotProductFeatures.pNext            = nullptr;
    mShaderIntegerDotProductProperties.pNext          = nullptr;
    mShaderDemoteToHelperInvocationFeatures.pNext     = nullptr;
    mPhysicalDeviceGlobalPriorityQueryFeatures.pNext  = nullptr;
    mExternalMemoryHostProperties.pNext               = nullptr;
    mBufferDeviceAddressFeatures.pNext                = nullptr;
    mShaderAtomicInt64Features.pNext                  = nullptr;
    mTextureCompressionASTC3DFeatures.pNext           = nullptr;
#if defined(ANGLE_PLATFORM_ANDROID)
    mExternalFormatResolveFeatures.pNext   = nullptr;
    mExternalFormatResolveProperties.pNext = nullptr;
#endif
    mTileMemoryHeapFeatures.pNext   = nullptr;
    mTileMemoryHeapProperties.pNext = nullptr;
    mShaderCorePropertiesAMD.pNext  = nullptr;
}

// See comment above appendDeviceExtensionFeaturesNotPromoted.  Additional extensions are enabled
// here which don't have feature structs:
//
// - VK_KHR_shared_presentable_image
// - VK_EXT_device_memory_report
// - VK_EXT_memory_budget
// - VK_KHR_incremental_present
// - VK_EXT_queue_family_foreign
// - VK_ANDROID_external_memory_android_hardware_buffer
// - VK_KHR_external_memory_fd
// - VK_KHR_external_memory_fuchsia
// - VK_KHR_external_semaphore_fd
// - VK_KHR_external_fence_fd
// - VK_FUCHSIA_external_semaphore
// - VK_EXT_shader_stencil_export
// - VK_EXT_load_store_op_none
// - VK_QCOM_render_pass_store_ops
// - VK_GOOGLE_display_timing
// - VK_EXT_external_memory_dma_buf
// - VK_EXT_image_drm_format_modifier
// - VK_EXT_blend_operation_advanced
// - VK_EXT_full_screen_exclusive
// - VK_EXT_image_compression_control
// - VK_EXT_image_compression_control_swapchain
// - VK_AMD_shader_core_properties
//
void Renderer::enableDeviceExtensionsNotPromoted(const vk::ExtensionNameList &deviceExtensionNames)
{
    if (mFeatures.supportsSharedPresentableImageExtension.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SHARED_PRESENTABLE_IMAGE_EXTENSION_NAME);
    }

    if (mFeatures.supportsDepthClampZeroOne.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_DEPTH_CLAMP_ZERO_ONE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mDepthClampZeroOneFeatures);
    }

    if (mFeatures.supportsMemoryBudget.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
    }

    if (mFeatures.supportsIncrementalPresent.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME);
    }

#if defined(ANGLE_PLATFORM_ANDROID)
    if (mFeatures.supportsAndroidHardwareBuffer.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME);
        mEnabledDeviceExtensions.push_back(
            VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
    }
#else
    ASSERT(!mFeatures.supportsAndroidHardwareBuffer.enabled);
#endif

    if (mFeatures.supportsExternalMemoryFd.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
    }

    if (mFeatures.supportsExternalMemoryFuchsia.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME);
    }

    if (mFeatures.supportsExternalSemaphoreFd.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
    }

    if (mFeatures.supportsExternalFenceFd.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME);
    }

    if (mFeatures.supportsExternalSemaphoreFuchsia.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
    }

    if (mFeatures.supportsShaderStencilExport.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
    }

    if (mFeatures.supportsRenderPassLoadStoreOpNone.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME);
    }
    else if (mFeatures.supportsRenderPassStoreOpNone.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_QCOM_RENDER_PASS_STORE_OPS_EXTENSION_NAME);
    }

    if (mFeatures.supportsTimestampSurfaceAttribute.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME);
    }

    if (mFeatures.bresenhamLineRasterization.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mLineRasterizationFeatures);
    }

    if (mFeatures.provokingVertex.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mProvokingVertexFeatures);
    }

    if (mVertexAttributeDivisorFeatures.vertexAttributeInstanceRateDivisor)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mVertexAttributeDivisorFeatures);

        // We only store 8 bit divisor in GraphicsPipelineDesc so capping value & we emulate if
        // exceeded
        mMaxVertexAttribDivisor =
            std::min(mVertexAttributeDivisorProperties.maxVertexAttribDivisor,
                     static_cast<uint32_t>(std::numeric_limits<uint8_t>::max()));
    }

    if (mFeatures.supportsTransformFeedbackExtension.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mTransformFeedbackFeatures);
    }

    if (mFeatures.supportsCustomBorderColor.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mCustomBorderColorFeatures);
    }

    if (mFeatures.supportsIndexTypeUint8.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mIndexTypeUint8Features);
    }

    if (mFeatures.supportsMultisampledRenderToSingleSampled.enabled)
    {
        mEnabledDeviceExtensions.push_back(
            VK_EXT_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mMultisampledRenderToSingleSampledFeatures);
    }

    if (mFeatures.logMemoryReportCallbacks.enabled || mFeatures.logMemoryReportStats.enabled)
    {
        ASSERT(mMemoryReportFeatures.deviceMemoryReport);
        mEnabledDeviceExtensions.push_back(VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mMemoryReportFeatures);
    }

    if (mFeatures.supportsExternalMemoryDmaBuf.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME);
    }

    if (mFeatures.supportsImageDrmFormatModifier.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME);
    }

    if (mFeatures.supportsExternalMemoryHost.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
    }

    if (mFeatures.supportsDepthClipControl.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mDepthClipControlFeatures);
    }

    if (mFeatures.supportsPrimitivesGeneratedQuery.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mPrimitivesGeneratedQueryFeatures);
    }

    if (mFeatures.supportsPrimitiveTopologyListRestart.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mPrimitiveTopologyListRestartFeatures);
    }

    if (mFeatures.supportsBlendOperationAdvanced.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mBlendOperationAdvancedFeatures);
    }

    if (mFeatures.supportsGraphicsPipelineLibrary.enabled)
    {
        // VK_EXT_graphics_pipeline_library requires VK_KHR_pipeline_library
        ASSERT(ExtensionFound(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME, deviceExtensionNames));
        mEnabledDeviceExtensions.push_back(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME);

        mEnabledDeviceExtensions.push_back(VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mGraphicsPipelineLibraryFeatures);
    }

    if (mFeatures.supportsFragmentShadingRate.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mFragmentShadingRateFeatures);
    }

    if (mFeatures.supportsFragmentShaderPixelInterlock.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mFragmentShaderInterlockFeatures);
    }

    if (mFeatures.supportsPipelineRobustness.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mPipelineRobustnessFeatures);
    }

    if (mFeatures.supportsPipelineProtectedAccess.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_PIPELINE_PROTECTED_ACCESS_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mPipelineProtectedAccessFeatures);
    }

    if (mFeatures.supportsRasterizationOrderAttachmentAccess.enabled)
    {
        if (ExtensionFound(VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME,
                           deviceExtensionNames))
        {
            mEnabledDeviceExtensions.push_back(
                VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME);
        }
        else
        {
            ASSERT(ExtensionFound(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME,
                                  deviceExtensionNames));
            mEnabledDeviceExtensions.push_back(
                VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME);
        }
        vk::AddToPNextChain(&mEnabledFeatures, &mRasterizationOrderAttachmentAccessFeatures);
    }

    if (!mFeatures.emulateR32fImageAtomicExchange.enabled)
    {
        ASSERT(ExtensionFound(VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME, deviceExtensionNames));
        mEnabledDeviceExtensions.push_back(VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mShaderAtomicFloatFeatures);
    }

    if (mFeatures.supportsImage2dViewOf3d.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_IMAGE_2D_VIEW_OF_3D_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mImage2dViewOf3dFeatures);
    }

    if (mFeatures.supportsSwapchainMaintenance1.enabled)
    {
        const bool hasSwapchainMaintenance1KHR =
            ExtensionFound(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, deviceExtensionNames);
        mEnabledDeviceExtensions.push_back(hasSwapchainMaintenance1KHR
                                               ? VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME
                                               : VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mSwapchainMaintenance1Features);
    }

    if (mFeatures.supportsLegacyDithering.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_LEGACY_DITHERING_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mDitheringFeatures);
    }

    if (mFeatures.supportsFormatFeatureFlags2.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME);
    }

    if (mFeatures.supportsHostImageCopy.enabled)
    {
        // VK_EXT_host_image_copy requires VK_KHR_copy_commands2 and VK_KHR_format_feature_flags2.
        // VK_KHR_format_feature_flags2 is enabled separately.
        ASSERT(ExtensionFound(VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME, deviceExtensionNames));
        ASSERT(ExtensionFound(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME, deviceExtensionNames));
        mEnabledDeviceExtensions.push_back(VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME);

        mEnabledDeviceExtensions.push_back(VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mHostImageCopyFeatures);
    }

    if (getFeatures().supportsVertexInputDynamicState.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mVertexInputDynamicStateFeatures);
    }

    if (getFeatures().supportsDynamicRenderingLocalRead.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_LOCAL_READ_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mDynamicRenderingLocalReadFeatures);
    }

    if (getFeatures().supportsUnifiedImageLayouts.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_UNIFIED_IMAGE_LAYOUTS_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mUnifiedImageLayoutsFeatures);
    }

    if (getFeatures().supportsImageCompressionControl.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mImageCompressionControlFeatures);
    }

    if (getFeatures().supportsImageCompressionControlSwapchain.enabled)
    {
        mEnabledDeviceExtensions.push_back(
            VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mImageCompressionControlSwapchainFeatures);
    }

    if (mFeatures.supportsSwapchainMutableFormat.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME);
    }

    if (mFeatures.supportsDeviceFault.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mFaultFeatures);
    }

    if (mFeatures.supportsAstcDecodeMode.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mPhysicalDeviceAstcDecodeFeatures);
    }

    if (mFeatures.supportsAstc3d.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_TEXTURE_COMPRESSION_ASTC_3D_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mTextureCompressionASTC3DFeatures);
    }

#if defined(ANGLE_PLATFORM_WINDOWS)
    // We only need the VK_EXT_full_screen_exclusive extension if we are opting
    // out of it via VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT (i.e. working
    // around driver bugs).
    if (getFeatures().supportsFullScreenExclusive.enabled &&
        getFeatures().forceDisableFullScreenExclusive.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME);
    }
#endif

#if defined(ANGLE_PLATFORM_ANDROID)
    if (mFeatures.supportsExternalFormatResolve.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_ANDROID_EXTERNAL_FORMAT_RESOLVE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mExternalFormatResolveFeatures);
    }
#endif

    if (mFeatures.supportsGlobalPriority.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME);
    }

    if (mFeatures.supportsGlobalPriorityQuery.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_GLOBAL_PRIORITY_QUERY_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mPhysicalDeviceGlobalPriorityQueryFeatures);
    }

    if (getFeatures().supportsTileMemoryHeap.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_QCOM_TILE_MEMORY_HEAP_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mTileMemoryHeapFeatures);
    }

    if (getFeatures().supportsAmdShaderCoreProperties.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_AMD_SHADER_CORE_PROPERTIES_EXTENSION_NAME);
    }
}

// See comment above appendDeviceExtensionFeaturesPromotedTo11.
void Renderer::enableDeviceExtensionsPromotedTo11(const vk::ExtensionNameList &deviceExtensionNames)
{
    // OVR_multiview disallows multiview with geometry and tessellation, so don't request these
    // features.
    mMultiviewFeatures.multiviewGeometryShader     = VK_FALSE;
    mMultiviewFeatures.multiviewTessellationShader = VK_FALSE;

    if (mFeatures.supportsMultiview.enabled)
    {
        vk::AddToPNextChain(&mEnabledFeatures, &mMultiviewFeatures);
    }

    if (mFeatures.supportsYUVSamplerConversion.enabled)
    {
        vk::AddToPNextChain(&mEnabledFeatures, &mSamplerYcbcrConversionFeatures);
    }

    if (mFeatures.supportsProtectedMemory.enabled)
    {
        vk::AddToPNextChain(&mEnabledFeatures, &mProtectedMemoryFeatures);
    }

    if (mFeatures.supports16BitStorageBuffer.enabled ||
        mFeatures.supports16BitUniformAndStorageBuffer.enabled ||
        mFeatures.supports16BitPushConstant.enabled || mFeatures.supports16BitInputOutput.enabled)
    {
        vk::AddToPNextChain(&mEnabledFeatures, &m16BitStorageFeatures);
    }

    vk::AddToPNextChain(&mEnabledFeatures, &mVariablePointersFeatures);
}

// See comment above appendDeviceExtensionFeaturesPromotedTo12.  Additional extensions are enabled
// here which don't have feature structs:
//
// - VK_KHR_create_renderpass2
// - VK_KHR_image_format_list
// - VK_KHR_shader_float_controls
// - VK_KHR_spirv_1_4
// - VK_KHR_sampler_mirror_clamp_to_edge
// - VK_KHR_depth_stencil_resolve
// - VK_KHR_buffer_device_address
//
void Renderer::enableDeviceExtensionsPromotedTo12(const vk::ExtensionNameList &deviceExtensionNames)
{
    if (mFeatures.supportsRenderpass2.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
    }

    if (mFeatures.supportsImageFormatList.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);
    }

    // There are several FP related modes defined as properties from
    // VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION, and there could be a scenario where the extension is
    // supported but none of the modes are supported. Here we enable the extension if it is found.
    if (ExtensionFound(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, deviceExtensionNames))
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
    }

    if (mFeatures.supportsSPIRV14.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
    }

    if (mFeatures.supportsSamplerMirrorClampToEdge.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME);
    }

    if (mFeatures.supportsDepthStencilResolve.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
    }

    if (mFeatures.allowGenerateMipmapWithCompute.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SHADER_SUBGROUP_EXTENDED_TYPES_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mSubgroupExtendedTypesFeatures);
    }

    if (mFeatures.supportsShaderFloat16.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mShaderFloat16Int8Features);
    }

    if (mFeatures.supportsHostQueryReset.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mHostQueryResetFeatures);
    }

    if (mFeatures.supportsImagelessFramebuffer.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_IMAGELESS_FRAMEBUFFER_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mImagelessFramebufferFeatures);
    }

    if (mFeatures.supportsTimelineSemaphore.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mTimelineSemaphoreFeatures);
    }

    if (mFeatures.supports8BitStorageBuffer.enabled ||
        mFeatures.supports8BitUniformAndStorageBuffer.enabled ||
        mFeatures.supports8BitPushConstant.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_8BIT_STORAGE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &m8BitStorageFeatures);
    }
    if (mFeatures.supportsUniformBufferStandardLayout.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mUniformBufferStandardLayoutFeatures);
    }
    if (mFeatures.supportsBufferDeviceAddress.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mBufferDeviceAddressFeatures);
    }
    if (mFeatures.supportsShaderAtomicInt64.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mShaderAtomicInt64Features);
    }
}

// See comment above appendDeviceExtensionFeaturesPromotedTo13.
void Renderer::enableDeviceExtensionsPromotedTo13(const vk::ExtensionNameList &deviceExtensionNames)
{
    if (mFeatures.supportsPipelineCreationFeedback.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
    }

    if (mFeatures.supportsExtendedDynamicState.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mExtendedDynamicStateFeatures);
    }

    if (mFeatures.supportsExtendedDynamicState2.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mExtendedDynamicState2Features);
    }

    if (mFeatures.supportsSynchronization2.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mSynchronization2Features);
    }

    if (getFeatures().supportsDynamicRendering.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mDynamicRenderingFeatures);
    }

    if (getFeatures().supportsMaintenance5.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mMaintenance5Features);
    }

    if (getFeatures().supportsTextureCompressionAstcHdr.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_EXT_TEXTURE_COMPRESSION_ASTC_HDR_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mTextureCompressionASTCHDRFeatures);
    }

    if (mFeatures.supportsShaderIntegerDotProduct.enabled)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SHADER_INTEGER_DOT_PRODUCT_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mShaderIntegerDotProductFeatures);
    }

    if (mFeatures.supportsShaderDemoteToHelperInvocation.enabled)
    {
        mEnabledDeviceExtensions.push_back(
            VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME);
        vk::AddToPNextChain(&mEnabledFeatures, &mShaderDemoteToHelperInvocationFeatures);
    }
}

angle::Result Renderer::enableDeviceExtensions(vk::ErrorContext *context,
                                               const angle::FeatureOverrides &featureOverrides,
                                               UseVulkanSwapchain useVulkanSwapchain,
                                               angle::NativeWindowSystem nativeWindowSystem)
{
    // Enumerate device extensions that are provided by the vulkan
    // implementation and implicit layers.
    uint32_t deviceExtensionCount = 0;
    ANGLE_VK_TRY(context, vkEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr,
                                                               &deviceExtensionCount, nullptr));

    // Work-around a race condition in the Android platform during Android start-up, that can cause
    // the second call to vkEnumerateDeviceExtensionProperties to have an additional extension.  In
    // that case, the second call will return VK_INCOMPLETE.  To work-around that, add 1 to
    // deviceExtensionCount and ask for one more extension property than the first call said there
    // were.  See: http://anglebug.com/42265209 and internal-to-Google bug: b/206733351.
    deviceExtensionCount++;
    std::vector<VkExtensionProperties> deviceExtensionProps(deviceExtensionCount);
    ANGLE_VK_TRY(context,
                 vkEnumerateDeviceExtensionProperties(
                     mPhysicalDevice, nullptr, &deviceExtensionCount, deviceExtensionProps.data()));
    // In case fewer items were returned than requested, resize deviceExtensionProps to the number
    // of extensions returned (i.e. deviceExtensionCount).  See: b/208937840
    deviceExtensionProps.resize(deviceExtensionCount);

    // Enumerate device extensions that are provided by explicit layers.
    for (const char *layerName : mEnabledInstanceLayerNames)
    {
        uint32_t previousExtensionCount    = static_cast<uint32_t>(deviceExtensionProps.size());
        uint32_t deviceLayerExtensionCount = 0;
        ANGLE_VK_TRY(context, vkEnumerateDeviceExtensionProperties(
                                  mPhysicalDevice, layerName, &deviceLayerExtensionCount, nullptr));
        deviceExtensionProps.resize(previousExtensionCount + deviceLayerExtensionCount);
        ANGLE_VK_TRY(context, vkEnumerateDeviceExtensionProperties(
                                  mPhysicalDevice, layerName, &deviceLayerExtensionCount,
                                  deviceExtensionProps.data() + previousExtensionCount));
        // In case fewer items were returned than requested, resize deviceExtensionProps to the
        // number of extensions returned (i.e. deviceLayerExtensionCount).
        deviceExtensionProps.resize(previousExtensionCount + deviceLayerExtensionCount);
    }

    // Get the list of device extensions that are available.
    vk::ExtensionNameList deviceExtensionNames;
    if (!deviceExtensionProps.empty())
    {
        ASSERT(deviceExtensionNames.size() <= deviceExtensionProps.size());
        for (const VkExtensionProperties &prop : deviceExtensionProps)
        {
            deviceExtensionNames.push_back(prop.extensionName);

            if (strcmp(prop.extensionName, VK_EXT_LEGACY_DITHERING_EXTENSION_NAME) == 0)
            {
                mLegacyDitheringVersion = prop.specVersion;
            }
        }
        std::sort(deviceExtensionNames.begin(), deviceExtensionNames.end(), StrLess);
    }

    if (useVulkanSwapchain == UseVulkanSwapchain::Yes)
    {
        mEnabledDeviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
    }

    // Query extensions and their features.
    queryDeviceExtensionFeatures(deviceExtensionNames);

    // Initialize features and workarounds.
    initFeatures(deviceExtensionNames, featureOverrides, useVulkanSwapchain, nativeWindowSystem);
    if (mGlobalOps->getFrontendApi() == GlobalOps::Api::OpenCL)
    {
        initOpenCLFeatures(deviceExtensionNames, featureOverrides);
    }

    // Enable extensions that could be used
    enableDeviceExtensionsNotPromoted(deviceExtensionNames);
    enableDeviceExtensionsPromotedTo11(deviceExtensionNames);
    enableDeviceExtensionsPromotedTo12(deviceExtensionNames);
    enableDeviceExtensionsPromotedTo13(deviceExtensionNames);

    std::sort(mEnabledDeviceExtensions.begin(), mEnabledDeviceExtensions.end(), StrLess);
    ANGLE_VK_TRY(context, VerifyExtensionsPresent(deviceExtensionNames, mEnabledDeviceExtensions));

    return angle::Result::Continue;
}

void Renderer::initDeviceExtensionEntryPoints()
{
#if !defined(ANGLE_SHARED_LIBVULKAN)
    // Device entry points
    if (mFeatures.supportsTransformFeedbackExtension.enabled)
    {
        InitTransformFeedbackEXTFunctions(mDevice);
    }
    if (getFeatures().supportsLogicOpDynamicState.enabled)
    {
        // VK_EXT_extended_dynamic_state2 is only partially core in Vulkan 1.3.  If the logicOp
        // dynamic state (only from the extension) is used, need to load the entry points from the
        // extension
        InitExtendedDynamicState2EXTFunctions(mDevice);
    }
    if (mFeatures.supportsFragmentShadingRate.enabled)
    {
        InitFragmentShadingRateKHRDeviceFunction(mDevice);
    }
    if (mFeatures.supportsTimestampSurfaceAttribute.enabled)
    {
        InitGetPastPresentationTimingGoogleFunction(mDevice);
    }
    if (mFeatures.supportsHostImageCopy.enabled)
    {
        InitHostImageCopyFunctions(mDevice);
    }
    if (mFeatures.supportsImageCompressionControl.enabled)
    {
        InitImageCompressionControlFunctions(mDevice);
    }
    if (mFeatures.supportsVertexInputDynamicState.enabled)
    {
        InitVertexInputDynamicStateEXTFunctions(mDevice);
    }
    if (mFeatures.supportsDynamicRenderingLocalRead.enabled)
    {
        InitDynamicRenderingLocalReadFunctions(mDevice);
    }
    if (mFeatures.supportsExternalSemaphoreFd.enabled ||
        mFeatures.supportsExternalSemaphoreFuchsia.enabled)
    {
        InitExternalSemaphoreFdFunctions(mDevice);
    }

    if (mFeatures.supportsExternalFenceFd.enabled)
    {
        InitExternalFenceFdFunctions(mDevice);
    }

#    if defined(ANGLE_PLATFORM_ANDROID)
    if (mFeatures.supportsAndroidHardwareBuffer.enabled)
    {
        InitExternalMemoryHardwareBufferANDROIDFunctions(mDevice);
    }
#    endif
    if (mFeatures.supportsSynchronization2.enabled)
    {
        InitSynchronization2Functions(mDevice);
    }
    if (mFeatures.supportsDeviceFault.enabled)
    {
        InitDeviceFaultFunctions(mDevice);
    }
    if (mFeatures.supportsMaintenance5.enabled)
    {
        InitMaintenance5Functions(mDevice);
    }
    if (mFeatures.supportsTileMemoryHeap.enabled)
    {
        InitTileMemoryHeapFunctions(mDevice);
    }
    // Extensions promoted to Vulkan 1.2
    {
        if (mFeatures.supportsHostQueryReset.enabled)
        {
            InitHostQueryResetFunctions(mDevice);
        }
        if (mFeatures.supportsRenderpass2.enabled)
        {
            InitRenderPass2KHRFunctions(mDevice);
        }
        if (mFeatures.supportsBufferDeviceAddress.enabled)
        {
            InitBufferDeviceAddressFunctions(mDevice);
        }
    }
    // Extensions promoted to Vulkan 1.3
    {
        if (mFeatures.supportsExtendedDynamicState.enabled)
        {
            InitExtendedDynamicStateEXTFunctions(mDevice);
        }
        if (mFeatures.supportsExtendedDynamicState2.enabled)
        {
            InitExtendedDynamicState2EXTFunctions(mDevice);
        }
        if (mFeatures.supportsDynamicRendering.enabled)
        {
            InitDynamicRenderingFunctions(mDevice);
        }
    }
#endif  // !defined(ANGLE_SHARED_LIBVULKAN)

    // For promoted extensions, initialize their entry points from the core version.
    initializeDeviceExtensionEntryPointsFromCore();
}

angle::Result Renderer::setupDevice(vk::ErrorContext *context,
                                    const angle::FeatureOverrides &featureOverrides,
                                    UseVulkanSwapchain useVulkanSwapchain,
                                    angle::NativeWindowSystem nativeWindowSystem)
{
    uint32_t deviceLayerCount = 0;
    ANGLE_VK_TRY(context,
                 vkEnumerateDeviceLayerProperties(mPhysicalDevice, &deviceLayerCount, nullptr));

    std::vector<VkLayerProperties> deviceLayerProps(deviceLayerCount);
    ANGLE_VK_TRY(context, vkEnumerateDeviceLayerProperties(mPhysicalDevice, &deviceLayerCount,
                                                           deviceLayerProps.data()));

    mEnabledFeatures       = {};
    mEnabledFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;

    ANGLE_TRY(
        enableDeviceExtensions(context, featureOverrides, useVulkanSwapchain, nativeWindowSystem));

    // Used to support cubemap array:
    mEnabledFeatures.features.imageCubeArray = mFeatures.supportsImageCubeArray.enabled;
    // Used to support framebuffers with multiple attachments:
    mEnabledFeatures.features.independentBlend = mPhysicalDeviceFeatures.independentBlend;
    // Used to support multi_draw_indirect
    mEnabledFeatures.features.multiDrawIndirect = mPhysicalDeviceFeatures.multiDrawIndirect;
    mEnabledFeatures.features.drawIndirectFirstInstance =
        mPhysicalDeviceFeatures.drawIndirectFirstInstance;
    // Used to support robust buffer access, if VK_EXT_pipeline_robustness is not supported.
    if (!mFeatures.supportsPipelineRobustness.enabled)
    {
        mEnabledFeatures.features.robustBufferAccess = mPhysicalDeviceFeatures.robustBufferAccess;
    }
    // Used to support Anisotropic filtering:
    mEnabledFeatures.features.samplerAnisotropy = mPhysicalDeviceFeatures.samplerAnisotropy;
    // Used to support wide lines:
    mEnabledFeatures.features.wideLines = mPhysicalDeviceFeatures.wideLines;
    // Used to emulate transform feedback:
    mEnabledFeatures.features.vertexPipelineStoresAndAtomics =
        mPhysicalDeviceFeatures.vertexPipelineStoresAndAtomics;
    // Used to implement storage buffers and images in the fragment shader:
    mEnabledFeatures.features.fragmentStoresAndAtomics =
        mPhysicalDeviceFeatures.fragmentStoresAndAtomics;
    // Used to emulate the primitives generated query:
    mEnabledFeatures.features.pipelineStatisticsQuery =
        !mFeatures.supportsPrimitivesGeneratedQuery.enabled &&
        mFeatures.supportsPipelineStatisticsQuery.enabled;
    // Used to support geometry shaders:
    mEnabledFeatures.features.geometryShader = mPhysicalDeviceFeatures.geometryShader;
    // Used to support EXT/OES_gpu_shader5:
    mEnabledFeatures.features.shaderImageGatherExtended =
        mPhysicalDeviceFeatures.shaderImageGatherExtended;
    // Used to support EXT/OES_gpu_shader5:
    mEnabledFeatures.features.shaderUniformBufferArrayDynamicIndexing =
        mPhysicalDeviceFeatures.shaderUniformBufferArrayDynamicIndexing;
    mEnabledFeatures.features.shaderSampledImageArrayDynamicIndexing =
        mPhysicalDeviceFeatures.shaderSampledImageArrayDynamicIndexing;
    // Used to support APPLE_clip_distance
    mEnabledFeatures.features.shaderClipDistance = mPhysicalDeviceFeatures.shaderClipDistance;
    // Used to support OES_sample_shading
    mEnabledFeatures.features.sampleRateShading = mPhysicalDeviceFeatures.sampleRateShading;
    // Used to support EXT_depth_clamp and depth clears through draw calls
    mEnabledFeatures.features.depthClamp = mPhysicalDeviceFeatures.depthClamp;
    // Used to support EXT_polygon_offset_clamp
    mEnabledFeatures.features.depthBiasClamp = mPhysicalDeviceFeatures.depthBiasClamp;
    // Used to support NV_polygon_mode / ANGLE_polygon_mode
    mEnabledFeatures.features.fillModeNonSolid = mPhysicalDeviceFeatures.fillModeNonSolid;
    // Used to support EXT_clip_cull_distance
    mEnabledFeatures.features.shaderCullDistance = mPhysicalDeviceFeatures.shaderCullDistance;
    // Used to support tessellation Shader:
    mEnabledFeatures.features.tessellationShader = mPhysicalDeviceFeatures.tessellationShader;
    // Used to support EXT_blend_func_extended
    mEnabledFeatures.features.dualSrcBlend = mPhysicalDeviceFeatures.dualSrcBlend;
    // Used to support ANGLE_logic_op and GLES1
    mEnabledFeatures.features.logicOp = mPhysicalDeviceFeatures.logicOp;
    // Used to support EXT_multisample_compatibility
    mEnabledFeatures.features.alphaToOne = mPhysicalDeviceFeatures.alphaToOne;
    // Used to support 16bit-integers in shader code
    mEnabledFeatures.features.shaderInt16 = mPhysicalDeviceFeatures.shaderInt16;
    // Used to support 64bit-integers in shader code
    mEnabledFeatures.features.shaderInt64 = mPhysicalDeviceFeatures.shaderInt64;
    // Used to support 64bit-floats in shader code
    mEnabledFeatures.features.shaderFloat64 =
        mFeatures.supportsShaderFloat64.enabled && mPhysicalDeviceFeatures.shaderFloat64 == VK_TRUE;

    if (!vk::OutsideRenderPassCommandBuffer::ExecutesInline() ||
        !vk::RenderPassCommandBuffer::ExecutesInline())
    {
        mEnabledFeatures.features.inheritedQueries = mPhysicalDeviceFeatures.inheritedQueries;
    }

    return angle::Result::Continue;
}

angle::Result Renderer::createDeviceAndQueue(vk::ErrorContext *context,
                                             uint32_t queueFamilyIndex,
                                             VkQueueGlobalPriority globalPriority)
{
    mCurrentQueueFamilyIndex = queueFamilyIndex;

    vk::QueueFamily queueFamily;
    queueFamily.initialize(mQueueFamilyProperties2[queueFamilyIndex].queueFamilyProperties,
                           queueFamilyIndex);
    ANGLE_VK_CHECK(context, queueFamily.getDeviceQueueCount() > 0, VK_ERROR_INITIALIZATION_FAILED);

    // We enable protected context only if both supportsProtectedMemory and device also supports
    // protected. There are cases we have to disable supportsProtectedMemory feature due to driver
    // bugs.
    bool enableProtectedContent =
        queueFamily.supportsProtected() && mFeatures.supportsProtectedMemory.enabled;

    uint32_t queueCount = std::min(queueFamily.getDeviceQueueCount(),
                                   static_cast<uint32_t>(egl::ContextPriority::EnumCount));

    // We use VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT only if queueCount >=3
    if (globalPriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT && queueCount < 3)
    {
        globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM;
    }

    uint32_t queueCreateInfoCount                                          = 1;
    VkDeviceQueueCreateInfo queueCreateInfo[3]                             = {};
    VkDeviceQueueGlobalPriorityCreateInfo queueGlobalPriorityCreateInfo[3] = {};

    // If global priority is supported, we split queueCreateInfo into three groups so that the
    // middle group uses VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT.
    if (globalPriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT)
    {
        ASSERT(mFeatures.supportsGlobalPriorityQuery.enabled);
        ASSERT(queueCount >= 3);

        // queueCreateInfo[0] is for Medium and High
        queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queueCreateInfo[0].flags =
            enableProtectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0;
        queueCreateInfo[0].queueFamilyIndex = queueFamilyIndex;
        queueCreateInfo[0].queueCount       = 2;
        queueCreateInfo[0].pQueuePriorities = &vk::QueueFamily::kQueuePriorities[0];
        queueGlobalPriorityCreateInfo[0].sType =
            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO;
        queueGlobalPriorityCreateInfo[0].globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM;
        vk::AddToPNextChain(&queueCreateInfo[0], &queueGlobalPriorityCreateInfo[0]);

        ASSERT(vk::QueueFamily::kQueuePriorities[2] == QueueFamily::kQueuePriorityRealtime);
        // queueCreateInfo[1] is for Realtime
        queueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queueCreateInfo[1].flags =
            enableProtectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0;
        queueCreateInfo[1].queueFamilyIndex = queueFamilyIndex;
        queueCreateInfo[1].queueCount       = 1;
        queueCreateInfo[1].pQueuePriorities = &vk::QueueFamily::kQueuePriorities[2];
        queueGlobalPriorityCreateInfo[1].sType =
            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO;
        queueGlobalPriorityCreateInfo[1].globalPriority = VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT;
        vk::AddToPNextChain(&queueCreateInfo[1], &queueGlobalPriorityCreateInfo[1]);
        queueCreateInfoCount++;

        if (queueCount == 4)
        {
            // queueCreateInfo[2] is for Low
            queueCreateInfo[2].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
            queueCreateInfo[2].flags =
                enableProtectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0;
            queueCreateInfo[2].queueFamilyIndex = queueFamilyIndex;
            queueCreateInfo[2].queueCount       = 1;
            queueCreateInfo[2].pQueuePriorities = &vk::QueueFamily::kQueuePriorities[3];
            queueGlobalPriorityCreateInfo[2].sType =
                VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO;
            queueGlobalPriorityCreateInfo[2].globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM;
            vk::AddToPNextChain(&queueCreateInfo[2], &queueGlobalPriorityCreateInfo[2]);
            queueCreateInfoCount++;
        }
    }
    else
    {
        queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queueCreateInfo[0].flags =
            enableProtectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0;
        queueCreateInfo[0].queueFamilyIndex = queueFamilyIndex;
        queueCreateInfo[0].queueCount       = queueCount;
        queueCreateInfo[0].pQueuePriorities = vk::QueueFamily::kQueuePriorities.data();
    }

    // Setup device initialization struct
    VkDeviceCreateInfo createInfo    = {};
    createInfo.sType                 = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    createInfo.flags                 = 0;
    createInfo.queueCreateInfoCount  = queueCreateInfoCount;
    createInfo.pQueueCreateInfos     = queueCreateInfo;
    createInfo.enabledExtensionCount = static_cast<uint32_t>(mEnabledDeviceExtensions.size());
    createInfo.ppEnabledExtensionNames =
        mEnabledDeviceExtensions.empty() ? nullptr : mEnabledDeviceExtensions.data();
    mEnabledDeviceExtensions.push_back(nullptr);

    // Enable core features without assuming VkPhysicalDeviceFeatures2KHR is accepted in the
    // pNext chain of VkDeviceCreateInfo.
    createInfo.pEnabledFeatures = &mEnabledFeatures.features;

    // Append the feature structs chain to the end of createInfo structs chain.
    if (mEnabledFeatures.pNext)
    {
        vk::AppendToPNextChain(&createInfo, mEnabledFeatures.pNext);
    }

    if (mFeatures.logMemoryReportCallbacks.enabled || mFeatures.logMemoryReportStats.enabled)
    {
        ASSERT(mMemoryReportFeatures.deviceMemoryReport);

        mMemoryReportCallback       = {};
        mMemoryReportCallback.sType = VK_STRUCTURE_TYPE_DEVICE_DEVICE_MEMORY_REPORT_CREATE_INFO_EXT;
        mMemoryReportCallback.pfnUserCallback = &MemoryReportCallback;
        mMemoryReportCallback.pUserData       = this;
        vk::AddToPNextChain(&createInfo, &mMemoryReportCallback);
    }

    // Create the list of expected VVL messages to suppress.  Done before creating the device, as it
    // may also generate messages.
    initializeValidationMessageSuppressions();

    ANGLE_VK_TRY(context, vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mDevice));
#if defined(ANGLE_SHARED_LIBVULKAN)
    // Load volk if we are loading dynamically
    volkLoadDevice(mDevice);
#endif  // defined(ANGLE_SHARED_LIBVULKAN)

    initDeviceExtensionEntryPoints();

    ANGLE_TRY(mCommandQueue.init(context, queueFamily, enableProtectedContent, queueCount));
    ANGLE_TRY(mCleanUpThread.init());

    if (mFeatures.forceMaxUniformBufferSize16KB.enabled)
    {
        mDefaultUniformBufferSize = kMinDefaultUniformBufferSize;
    }
    // Cap it with the driver limit
    mDefaultUniformBufferSize = std::min(
        mDefaultUniformBufferSize, getPhysicalDeviceProperties().limits.maxUniformBufferRange);

    // Vulkan pipeline cache will be initialized lazily in ensurePipelineCacheInitialized() method.
    ASSERT(!mPipelineCacheInitialized);
    ASSERT(!mPipelineCache.valid());

    // Track the set of supported pipeline stages.  This is used when issuing image layout
    // transitions that cover many stages (such as AllGraphicsReadOnly) to mask out unsupported
    // stages, which avoids enumerating every possible combination of stages in the layouts.
    VkPipelineStageFlags unsupportedStages = 0;
    mSupportedVulkanShaderStageMask =
        VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
    mSupportedBufferWritePipelineStageMask =
        VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;

    if (!mPhysicalDeviceFeatures.tessellationShader)
    {
        unsupportedStages |= VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT |
                             VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
    }
    else
    {
        mSupportedVulkanShaderStageMask |=
            VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
        mSupportedBufferWritePipelineStageMask |=
            VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT |
            VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
    }
    if (!mPhysicalDeviceFeatures.geometryShader)
    {
        unsupportedStages |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
    }
    else
    {
        mSupportedVulkanShaderStageMask |= VK_SHADER_STAGE_GEOMETRY_BIT;
        mSupportedBufferWritePipelineStageMask |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
    }

    if (getFeatures().supportsTransformFeedbackExtension.enabled)
    {
        mSupportedBufferWritePipelineStageMask |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT;
    }

    // Initialize the barrierData tables by removing unsupported pipeline stage bits
    InitializeEventStageToVkPipelineStageFlagsMap(&mEventStageToPipelineStageFlagsMap,
                                                  ~unsupportedStages);
    InitializeImageLayoutAndMemoryBarrierDataMap(
        getFeatures(), &mImageLayoutAndMemoryBarrierDataMap, ~unsupportedStages);

    ANGLE_TRY(initializeMemoryAllocator(context));

    // Log the memory heap stats when the device has been initialized (when debugging).
    mMemoryAllocationTracker.onDeviceInit();

    // Synchronize the Vulkan pipeline cache to the blob cache every frame
    if (mFeatures.syncPipelineCacheToBlobCacheEveryFrame.enabled)
    {
        mPipelineCacheVkUpdateTimeout = 1;
    }

    return angle::Result::Continue;
}

void Renderer::calculatePendingGarbageSizeLimit()
{
    // To find the threshold, we want the memory heap that has the largest size among other heaps.
    VkPhysicalDeviceMemoryProperties memoryProperties;
    vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memoryProperties);
    ASSERT(memoryProperties.memoryHeapCount > 0);

    VkDeviceSize maxHeapSize = memoryProperties.memoryHeaps[0].size;
    for (size_t i = 0; i < memoryProperties.memoryHeapCount; i++)
    {
        VkDeviceSize heapSize = memoryProperties.memoryHeaps[i].size;
        if (maxHeapSize < heapSize)
        {
            maxHeapSize = heapSize;
        }
    }

    // We set the limit to a portion of the heap size we found.
    constexpr float kGarbageSizeLimitCoefficient = 0.2f;
    mPendingGarbageSizeLimit =
        static_cast<VkDeviceSize>(maxHeapSize * kGarbageSizeLimitCoefficient);
}

void Renderer::initializeValidationMessageSuppressions()
{
    // Build the list of validation errors that are currently expected and should be skipped.
    mSkippedValidationMessages.insert(mSkippedValidationMessages.end(), kSkippedMessages,
                                      kSkippedMessages + ArraySize(kSkippedMessages));
    if (!getFeatures().supportsPrimitiveTopologyListRestart.enabled)
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kNoListRestartSkippedMessages,
            kNoListRestartSkippedMessages + ArraySize(kNoListRestartSkippedMessages));
    }

    if (getFeatures().exposeES32ForTesting.enabled)
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kExposeNonConformantSkippedMessages,
            kExposeNonConformantSkippedMessages + ArraySize(kExposeNonConformantSkippedMessages));
    }

    if (!getFeatures().supportsMaintenance5.enabled)
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kNoMaintenance5SkippedMessages,
            kNoMaintenance5SkippedMessages + ArraySize(kNoMaintenance5SkippedMessages));
    }

    if (!getFeatures().supportsMaintenance9.enabled)
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kNoMaintenance9SkippedMessages,
            kNoMaintenance9SkippedMessages + ArraySize(kNoMaintenance9SkippedMessages));
    }

    if (!getFeatures().supportsSwapchainMaintenance1.enabled)
    {
        mSkippedValidationMessages.insert(mSkippedValidationMessages.end(),
                                          kNoSwapchainMaintenance1SkippedMessages,
                                          kNoSwapchainMaintenance1SkippedMessages +
                                              ArraySize(kNoSwapchainMaintenance1SkippedMessages));
    }

    if (getFeatures().preferBGR565ToRGB565.enabled)
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kPreferBGR565SkippedMessages,
            kPreferBGR565SkippedMessages + ArraySize(kPreferBGR565SkippedMessages));
    }

    if (!getFeatures().preferDynamicRendering.enabled &&
        !vk::RenderPassCommandBuffer::ExecutesInline())
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kSkippedMessagesWithRenderPassObjectsAndVulkanSCB,
            kSkippedMessagesWithRenderPassObjectsAndVulkanSCB +
                ArraySize(kSkippedMessagesWithRenderPassObjectsAndVulkanSCB));
    }

    if (!getFeatures().preferDynamicRendering.enabled &&
        getFeatures().supportsMultisampledRenderToSingleSampled.enabled)
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kSkippedMessagesWithMSRTSSWithoutDynamicRendering,
            kSkippedMessagesWithMSRTSSWithoutDynamicRendering +
                ArraySize(kSkippedMessagesWithMSRTSSWithoutDynamicRendering));
    }

    if (getFeatures().preferDynamicRendering.enabled)
    {
        mSkippedValidationMessages.insert(
            mSkippedValidationMessages.end(), kSkippedMessagesWithDynamicRendering,
            kSkippedMessagesWithDynamicRendering + ArraySize(kSkippedMessagesWithDynamicRendering));
    }

    // Build the list of syncval errors that are currently expected and should be skipped.
    mSkippedSyncvalMessages.insert(mSkippedSyncvalMessages.end(), kSkippedSyncvalMessages,
                                   kSkippedSyncvalMessages + ArraySize(kSkippedSyncvalMessages));
    if (!getFeatures().forceSizePointerForBoundVertexBuffers.enabled)
    {
        mSkippedSyncvalMessages.insert(
            mSkippedSyncvalMessages.end(), kSkippedSyncvalMessagesWithoutForcedSizePointer,
            kSkippedSyncvalMessagesWithoutForcedSizePointer +
                ArraySize(kSkippedSyncvalMessagesWithoutForcedSizePointer));
    }
    if (!getFeatures().supportsRenderPassLoadStoreOpNone.enabled)
    {
        mSkippedSyncvalMessages.insert(
            mSkippedSyncvalMessages.end(), kSkippedSyncvalMessagesWithoutLoadStoreOpNone,
            kSkippedSyncvalMessagesWithoutLoadStoreOpNone +
                ArraySize(kSkippedSyncvalMessagesWithoutLoadStoreOpNone));
    }
    if (getFeatures().enableMultisampledRenderToTexture.enabled &&
        !getFeatures().supportsMultisampledRenderToSingleSampled.enabled)
    {
        mSkippedSyncvalMessages.insert(mSkippedSyncvalMessages.end(),
                                       kSkippedSyncvalMessagesWithMSRTTEmulation,
                                       kSkippedSyncvalMessagesWithMSRTTEmulation +
                                           ArraySize(kSkippedSyncvalMessagesWithMSRTTEmulation));
    }
}

angle::Result Renderer::checkQueueForSurfacePresent(vk::ErrorContext *context,
                                                    VkSurfaceKHR surface,
                                                    bool *supportedOut)
{
    // We've already initialized a device, and can't re-create it unless it's never been used.
    // If recreation is ever necessary, it should be able to deal with contexts currently running in
    // other threads using the existing queue.  For example, multiple contexts (not in a share
    // group) may be currently recording commands and rendering to pbuffers or using
    // EGL_KHR_surfaceless_context.
    ASSERT(mDevice != VK_NULL_HANDLE);
    ASSERT(mCurrentQueueFamilyIndex != std::numeric_limits<uint32_t>::max());

    // Check if the current device supports present on this surface.
    VkBool32 supportsPresent = VK_FALSE;
    ANGLE_VK_TRY(context,
                 vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, mCurrentQueueFamilyIndex,
                                                      surface, &supportsPresent));

    *supportedOut = supportsPresent == VK_TRUE;
    return angle::Result::Continue;
}

std::string Renderer::getVendorString() const
{
    return GetVendorString(mPhysicalDeviceProperties.vendorID);
}

std::string Renderer::getRendererDescription() const
{
    std::stringstream strstr;

    uint32_t apiVersion = mPhysicalDeviceProperties.apiVersion;

    strstr << "Vulkan ";
    strstr << VK_API_VERSION_MAJOR(apiVersion) << ".";
    strstr << VK_API_VERSION_MINOR(apiVersion) << ".";
    strstr << VK_API_VERSION_PATCH(apiVersion);

    strstr << " (";

    // In the case of NVIDIA, deviceName does not necessarily contain "NVIDIA". Add "NVIDIA" so that
    // Vulkan end2end tests can be selectively disabled on NVIDIA. TODO(jmadill): should not be
    // needed after http://anglebug.com/40096421 is fixed and end2end_tests use more sophisticated
    // driver detection.
    if (mPhysicalDeviceProperties.vendorID == VENDOR_ID_NVIDIA)
    {
        strstr << GetVendorString(mPhysicalDeviceProperties.vendorID) << " ";
    }

    strstr << mPhysicalDeviceProperties.deviceName;
    strstr << " (" << gl::FmtHex(mPhysicalDeviceProperties.deviceID) << ")";

    strstr << ")";

    return strstr.str();
}

std::string Renderer::getVersionString(bool includeFullVersion) const
{
    std::stringstream strstr;

    uint32_t driverVersion = mPhysicalDeviceProperties.driverVersion;
    std::string driverName = std::string(mDriverProperties.driverName);

    if (!driverName.empty())
    {
        strstr << driverName;
    }
    else
    {
        strstr << GetVendorString(mPhysicalDeviceProperties.vendorID);
    }

    if (includeFullVersion)
    {
        strstr << "-";

        if (mPhysicalDeviceProperties.vendorID == VENDOR_ID_NVIDIA)
        {
            strstr << ANGLE_VK_VERSION_MAJOR_NVIDIA(driverVersion) << ".";
            strstr << ANGLE_VK_VERSION_MINOR_NVIDIA(driverVersion) << ".";
            strstr << ANGLE_VK_VERSION_SUB_MINOR_NVIDIA(driverVersion) << ".";
            strstr << ANGLE_VK_VERSION_PATCH_NVIDIA(driverVersion);
        }
        else if (mPhysicalDeviceProperties.vendorID == VENDOR_ID_INTEL && IsWindows())
        {
            strstr << ANGLE_VK_VERSION_MAJOR_WIN_INTEL(driverVersion) << ".";
            strstr << ANGLE_VK_VERSION_MINOR_WIN_INTEL(driverVersion);
        }
        // The major version for the new QCOM drivers seems to be 512, which results in a major
        // version of 0 and a non-zero variant field when using the VK_API_VERSION_x macros.
        // Therefore, the version string is updated to show the correct major version.
        else if (mPhysicalDeviceProperties.vendorID == VENDOR_ID_QUALCOMM &&
                 !IsQualcommOpenSource(mPhysicalDeviceProperties.vendorID,
                                       mDriverProperties.driverID,
                                       mPhysicalDeviceProperties.deviceName))
        {
            strstr << (512 | VK_API_VERSION_MAJOR(driverVersion)) << ".";
            strstr << VK_API_VERSION_MINOR(driverVersion) << ".";
            strstr << VK_API_VERSION_PATCH(driverVersion);
        }
        // All other drivers use the Vulkan standard
        else
        {
            strstr << VK_API_VERSION_MAJOR(driverVersion) << ".";
            strstr << VK_API_VERSION_MINOR(driverVersion) << ".";
            strstr << VK_API_VERSION_PATCH(driverVersion);
        }
    }

    return strstr.str();
}

gl::Version Renderer::getMaxSupportedESVersion() const
{
    // Current highest supported version
    gl::Version maxVersion = gl::Version(3, 2);

    // Early out without downgrading ES version if mock ICD enabled.
    // Mock ICD doesn't expose sufficient capabilities yet.
    // https://github.com/KhronosGroup/Vulkan-Tools/issues/84
    if (isMockICDEnabled())
    {
        return maxVersion;
    }

    ensureCapsInitialized();

    // Limit to ES3.1 if there are any blockers for 3.2.
    if (mFeatures.exposeES32ForTesting.enabled)
    {
        return maxVersion;
    }
    if (!CanSupportGLES32(mNativeExtensions))
    {
        maxVersion = LimitVersionTo(maxVersion, {3, 1});
    }

    // Limit to ES3.0 if there are any blockers for 3.1.

    // ES3.1 requires at least one atomic counter buffer and four storage buffers in compute.
    // Atomic counter buffers are emulated with storage buffers.  For simplicity, we always support
    // either none or IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS atomic counter buffers.  So if
    // Vulkan doesn't support at least that many storage buffers in compute, we don't support 3.1.
    const uint32_t kMinimumStorageBuffersForES31 =
        gl::limits::kMinimumComputeStorageBuffers +
        gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS;
    if (mPhysicalDeviceProperties.limits.maxPerStageDescriptorStorageBuffers <
        kMinimumStorageBuffersForES31)
    {
        maxVersion = LimitVersionTo(maxVersion, {3, 0});
    }

    // ES3.1 requires at least a maximum offset of at least 2047.
    // If the Vulkan implementation can't support that, we cannot support 3.1.
    if (mPhysicalDeviceProperties.limits.maxVertexInputAttributeOffset < 2047)
    {
        maxVersion = LimitVersionTo(maxVersion, {3, 0});
    }

    // SSO is in ES3.1 core, so we have to cap to ES3.0 for SSO disablement.
    if (mFeatures.disableSeparateShaderObjects.enabled)
    {
        maxVersion = LimitVersionTo(maxVersion, {3, 0});
    }

    // Limit to ES2.0 if there are any blockers for 3.0.

    // VK_EXT_provoking_vertex is required for flat shading.
    if (!mFeatures.provokingVertex.enabled)
    {
        maxVersion = LimitVersionTo(maxVersion, {2, 0});
    }

    // Multisample textures (ES3.1) and multisample renderbuffers (ES3.0) require the Vulkan driver
    // to support the standard sample locations (in order to pass dEQP tests that check these
    // locations).  If the Vulkan implementation can't support that, we cannot support 3.0/3.1.
    if (mPhysicalDeviceProperties.limits.standardSampleLocations != VK_TRUE)
    {
        maxVersion = LimitVersionTo(maxVersion, {2, 0});
    }

    // If independentBlend is not supported, we can't have a mix of has-alpha and emulated-alpha
    // render targets in a framebuffer.  We also cannot perform masked clears of multiple render
    // targets.
    if (!mPhysicalDeviceFeatures.independentBlend)
    {
        maxVersion = LimitVersionTo(maxVersion, {2, 0});
    }

    // If the Vulkan transform feedback extension is not present, we use an emulation path that
    // requires the vertexPipelineStoresAndAtomics feature. Without the extension or this feature,
    // we can't currently support transform feedback.
    if (!vk::CanSupportTransformFeedbackExtension(mTransformFeedbackFeatures) &&
        !vk::CanSupportTransformFeedbackEmulation(mPhysicalDeviceFeatures))
    {
        maxVersion = LimitVersionTo(maxVersion, {2, 0});
    }

    // Limit to GLES 2.0 if maxPerStageDescriptorUniformBuffers is too low.
    // Table 6.31 MAX_VERTEX_UNIFORM_BLOCKS minimum value = 12
    // Table 6.32 MAX_FRAGMENT_UNIFORM_BLOCKS minimum value = 12
    // NOTE: We reserve some uniform buffers for emulation, so use the NativeCaps which takes this
    // into account, rather than the physical device maxPerStageDescriptorUniformBuffers limits.
    for (gl::ShaderType shaderType : gl::AllShaderTypes())
    {
        if (static_cast<GLuint>(getNativeCaps().maxShaderUniformBlocks[shaderType]) <
            gl::limits::kMinimumShaderUniformBlocks)
        {
            maxVersion = LimitVersionTo(maxVersion, {2, 0});
        }
    }

    // Limit to GLES 2.0 if maxVertexOutputComponents is too low.
    // Table 6.31 MAX VERTEX OUTPUT COMPONENTS minimum value = 64
    // NOTE: We reserve some vertex output components for emulation, so use the NativeCaps which
    // takes this into account, rather than the physical device maxVertexOutputComponents limits.
    if (static_cast<GLuint>(getNativeCaps().maxVertexOutputComponents) <
        gl::limits::kMinimumVertexOutputComponents)
    {
        maxVersion = LimitVersionTo(maxVersion, {2, 0});
    }

    return maxVersion;
}

gl::Version Renderer::getMaxConformantESVersion() const
{
    return getMaxSupportedESVersion();
}

uint32_t Renderer::getDeviceVersion() const
{
    return mDeviceVersion == 0 ? mInstanceVersion : mDeviceVersion;
}

void Renderer::queryAndCacheFragmentShadingRates()
{
    // Init required functions
#if !defined(ANGLE_SHARED_LIBVULKAN)
    InitFragmentShadingRateKHRInstanceFunction(mInstance);
#endif  // !defined(ANGLE_SHARED_LIBVULKAN)
    ASSERT(vkGetPhysicalDeviceFragmentShadingRatesKHR);

    // Query number of supported shading rates first
    uint32_t shadingRatesCount = 0;
    VkResult result =
        vkGetPhysicalDeviceFragmentShadingRatesKHR(mPhysicalDevice, &shadingRatesCount, nullptr);
    ASSERT(result == VK_SUCCESS);
    ASSERT(shadingRatesCount > 0);

    std::vector<VkPhysicalDeviceFragmentShadingRateKHR> shadingRates(
        shadingRatesCount,
        {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR, nullptr, 0, {0, 0}});

    // Query supported shading rates
    result = vkGetPhysicalDeviceFragmentShadingRatesKHR(mPhysicalDevice, &shadingRatesCount,
                                                        shadingRates.data());
    ASSERT(result == VK_SUCCESS);

    // Cache supported fragment shading rates
    mSupportedFragmentShadingRatesEXT.reset();
    mSupportedFragmentShadingRateEXTSampleCounts.fill(0u);
    for (const VkPhysicalDeviceFragmentShadingRateKHR &shadingRate : shadingRates)
    {
        if (shadingRate.sampleCounts == 0)
        {
            continue;
        }
        const gl::ShadingRate rate = GetShadingRateEXTFromVkExtent(shadingRate.fragmentSize);
        mSupportedFragmentShadingRatesEXT.set(rate);
        mSupportedFragmentShadingRateEXTSampleCounts[rate] =
            static_cast<uint16_t>(shadingRate.sampleCounts);
    }
}

bool Renderer::canSupportFragmentShadingRate() const
{
    // VK_KHR_create_renderpass2 is required for VK_KHR_fragment_shading_rate
    if (!mFeatures.supportsRenderpass2.enabled)
    {
        return false;
    }

    // Device needs to support VK_KHR_fragment_shading_rate and specifically
    // pipeline fragment shading rate.
    if (mFragmentShadingRateFeatures.pipelineFragmentShadingRate != VK_TRUE)
    {
        return false;
    }

    ASSERT(mSupportedFragmentShadingRatesEXT.any());

    // To implement GL_EXT_fragment_shading_rate and GL_QCOM_shading_rate extension
    // the Vulkan ICD needs to support at least the following shading rates
    // VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT    {1, 1}
    // VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT    {1, 2}
    // VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT    {2, 1}
    // VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT    {2, 2}
    constexpr VkSampleCountFlags krequiredSampleCounts =
        VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;

    return (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_1x1] &
            krequiredSampleCounts) == krequiredSampleCounts &&
           (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_1x2] &
            krequiredSampleCounts) == krequiredSampleCounts &&
           (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_2x1] &
            krequiredSampleCounts) == krequiredSampleCounts &&
           (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_2x2] &
            krequiredSampleCounts) == krequiredSampleCounts;
}

bool Renderer::canSupportFoveatedRendering() const
{
    // Device needs to support attachment fragment shading rate.
    if (mFragmentShadingRateFeatures.attachmentFragmentShadingRate != VK_TRUE)
    {
        return false;
    }

    ASSERT(mSupportedFragmentShadingRatesEXT.any());
    ASSERT(!mSupportedFragmentShadingRateEXTSampleCounts.empty());

    // To implement QCOM foveated rendering extensions the Vulkan ICD needs to support all sample
    // count bits listed in VkPhysicalDeviceLimits::framebufferColorSampleCounts for these shading
    // rates -
    //     {1, 1}
    //     {1, 2}
    //     {2, 1}
    //     {2, 2}
    VkSampleCountFlags framebufferSampleCounts =
        getPhysicalDeviceProperties().limits.framebufferColorSampleCounts &
        vk_gl::kSupportedSampleCounts;

    return (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_1x1] &
            framebufferSampleCounts) == framebufferSampleCounts &&
           (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_1x2] &
            framebufferSampleCounts) == framebufferSampleCounts &&
           (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_2x1] &
            framebufferSampleCounts) == framebufferSampleCounts &&
           (mSupportedFragmentShadingRateEXTSampleCounts[gl::ShadingRate::_2x2] &
            framebufferSampleCounts) == framebufferSampleCounts;
}

bool CanSupportAstcHdr3D(const Renderer *renderer)
{
    // New formats added in VK_EXT_texture_compression_astc_hdr
    std::vector<VkFormat> astcHdrFormats = {
        VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT, VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT,
        VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT,  VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT,
        VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT, VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT,
        VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT,   VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT,
        VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT,   VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT,
        VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT,   VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT,
        VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT,   VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT};

    for (VkFormat format : astcHdrFormats)
    {
        // If any ASTC HDR format does not support 3D, return false
        if (false == vk::ImageHelper::FormatSupportsUsage(
                         renderer, format, VK_IMAGE_TYPE_3D, VK_IMAGE_TILING_OPTIMAL,
                         VK_IMAGE_USAGE_SAMPLED_BIT, 0, nullptr, nullptr,
                         vk::ImageHelper::FormatSupportCheck::OnlyQuerySuccess))
        {
            return false;
        }
    }

    return true;
}

bool Renderer::supportsAstcHdr() const
{
    // When determining whether we support this, have to check for both the Vulkan
    // feaure and explicity check for 3D texture types.  The latter could be emulated
    // in the future. (http://anglebug.com/416095435)
    return getFeatures().supportsTextureCompressionAstcHdr.enabled &&
           getFeatures().supportsAstcHdr3dTextures.enabled;
}

bool Renderer::canPreferDeviceLocalMemoryHostVisible(VkPhysicalDeviceType deviceType)
{
    if (deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU)
    {
        const vk::MemoryProperties &memoryProperties = getMemoryProperties();
        static constexpr VkMemoryPropertyFlags kHostVisiableDeviceLocalFlags =
            VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
        VkDeviceSize minHostVisiableDeviceLocalHeapSize = std::numeric_limits<VkDeviceSize>::max();
        VkDeviceSize maxDeviceLocalHeapSize             = 0;
        for (uint32_t i = 0; i < memoryProperties.getMemoryTypeCount(); ++i)
        {
            if ((memoryProperties.getMemoryType(i).propertyFlags &
                 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0)
            {
                maxDeviceLocalHeapSize =
                    std::max(maxDeviceLocalHeapSize, memoryProperties.getHeapSizeForMemoryType(i));
            }
            if ((memoryProperties.getMemoryType(i).propertyFlags & kHostVisiableDeviceLocalFlags) ==
                kHostVisiableDeviceLocalFlags)
            {
                minHostVisiableDeviceLocalHeapSize =
                    std::min(minHostVisiableDeviceLocalHeapSize,
                             memoryProperties.getHeapSizeForMemoryType(i));
            }
        }
        return minHostVisiableDeviceLocalHeapSize != std::numeric_limits<VkDeviceSize>::max() &&
               minHostVisiableDeviceLocalHeapSize >=
                   static_cast<VkDeviceSize>(maxDeviceLocalHeapSize * 0.8);
    }
    return deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
}

void Renderer::initFeatures(const vk::ExtensionNameList &deviceExtensionNames,
                            const angle::FeatureOverrides &featureOverrides,
                            UseVulkanSwapchain useVulkanSwapchain,
                            angle::NativeWindowSystem nativeWindowSystem)
{
    // App based feature overrides.
    appBasedFeatureOverrides(deviceExtensionNames);

    ApplyFeatureOverrides(&mFeatures, featureOverrides);

    if (featureOverrides.allDisabled)
    {
        return;
    }

    const bool isAMD      = IsAMD(mPhysicalDeviceProperties.vendorID);
    const bool isApple    = IsAppleGPU(mPhysicalDeviceProperties.vendorID);
    const bool isARM      = IsARM(mPhysicalDeviceProperties.vendorID);
    const bool isIntel    = IsIntel(mPhysicalDeviceProperties.vendorID);
    const bool isNvidia   = IsNvidia(mPhysicalDeviceProperties.vendorID);
    const bool isPowerVR  = IsPowerVR(mPhysicalDeviceProperties.vendorID);
    const bool isQualcomm = IsQualcomm(mPhysicalDeviceProperties.vendorID);
    const bool isBroadcom = IsBroadcom(mPhysicalDeviceProperties.vendorID);
    const bool isSamsung  = IsSamsung(mPhysicalDeviceProperties.vendorID);
    const bool isSwiftShader =
        IsSwiftshader(mPhysicalDeviceProperties.vendorID, mPhysicalDeviceProperties.deviceID);
    const bool isLavapipe =
        IsLavapipe(mPhysicalDeviceProperties.vendorID, mPhysicalDeviceProperties.deviceID);
    const bool isSoftwareRenderer = isSwiftShader || isLavapipe;

    const bool isGalaxyS23 =
        IsGalaxyS23(mPhysicalDeviceProperties.vendorID, mPhysicalDeviceProperties.deviceID);

    // Distinguish between the open source and proprietary Qualcomm drivers
    const bool isQualcommOpenSource =
        IsQualcommOpenSource(mPhysicalDeviceProperties.vendorID, mDriverProperties.driverID,
                             mPhysicalDeviceProperties.deviceName);
    const bool isQualcommProprietary = isQualcomm && !isQualcommOpenSource;

    // Distinguish between the ARM proprietary driver and the Mesa open source driver
    const bool isMesaPanVK      = IsMesaPanVK(mDriverProperties.driverID);
    const bool isARMProprietary = isARM && !isMesaPanVK;

    // Lacking other explicit ways to tell if mali GPU is job manager based or command stream front
    // end based, we use maxDrawIndirectCount as equivalent since all JM based has
    // maxDrawIndirectCount==1 and all CSF based has maxDrawIndirectCount>1.
    bool isMaliJobManagerBasedGPU =
        isARM && getPhysicalDeviceProperties().limits.maxDrawIndirectCount <= 1;

    // Distinguish between the mesa and proprietary drivers
    const bool isRADV = IsRADV(mPhysicalDeviceProperties.vendorID, mDriverProperties.driverID,
                               mPhysicalDeviceProperties.deviceName);

    angle::VersionInfo driverVersion = {};
    if (isARMProprietary)
    {
        driverVersion = angle::ParseArmVulkanDriverVersion(mPhysicalDeviceProperties.driverVersion);
    }
    else if (isQualcommProprietary)
    {
        driverVersion =
            angle::ParseQualcommVulkanDriverVersion(mPhysicalDeviceProperties.driverVersion);
    }
    else if (isNvidia)
    {
        driverVersion =
            angle::ParseNvidiaVulkanDriverVersion(mPhysicalDeviceProperties.driverVersion);
    }
    else if (IsLinux() && (isIntel || isRADV))
    {
        driverVersion =
            angle::ParseMesaVulkanDriverVersion(mPhysicalDeviceProperties.driverVersion);
    }
    else if (IsWindows() && isIntel)
    {
        driverVersion =
            angle::ParseIntelWindowsVulkanDriverVersion(mPhysicalDeviceProperties.driverVersion);
    }
    else if (isAMD && !isRADV)
    {
        driverVersion = angle::ParseAMDVulkanDriverVersion(mPhysicalDeviceProperties.driverVersion);
    }
    else if (isSamsung)
    {
        driverVersion =
            angle::ParseSamsungVulkanDriverVersion(mPhysicalDeviceProperties.driverVersion);
    }

    // Classify devices based on general architecture:
    //
    // - IMR (Immediate-Mode Rendering) devices generally progress through draw calls once and use
    //   the main GPU memory (accessed through caches) to store intermediate rendering results.
    // - TBR (Tile-Based Rendering) devices issue a pre-rendering geometry pass, then run through
    //   draw calls once per tile and store intermediate rendering results on the tile cache.
    //
    // Due to these key architectural differences, some operations improve performance on one while
    // deteriorating performance on the other.  ANGLE will accordingly make some decisions based on
    // the device architecture for optimal performance on both.
    const bool isImmediateModeRenderer =
        isNvidia || isAMD || isIntel || isSamsung || isSoftwareRenderer;
    const bool isTileBasedRenderer = isARM || isPowerVR || isQualcomm || isBroadcom || isApple;

    // Make sure all known architectures are accounted for.
    if (!isImmediateModeRenderer && !isTileBasedRenderer && !isMockICDEnabled())
    {
        WARN() << "Unknown GPU architecture";
    }

    ANGLE_FEATURE_CONDITION(&mFeatures, appendAliasedMemoryDecorations, true);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSharedPresentableImageExtension,
        ExtensionFound(VK_KHR_SHARED_PRESENTABLE_IMAGE_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsGetMemoryRequirements2, true);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsBindMemory2, true);

    ANGLE_FEATURE_CONDITION(&mFeatures, bresenhamLineRasterization,
                            mLineRasterizationFeatures.bresenhamLines == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, provokingVertex,
                            mProvokingVertexFeatures.provokingVertexLast == VK_TRUE);

    // http://b/208458772. ARM proprietary driver supports this protected memory extension but we
    // are seeing excessive load/store unit activity when this extension is enabled, even if not
    // been used. Disable this extension on older ARM platforms that don't support
    // VK_EXT_pipeline_protected_access.
    // http://anglebug.com/42266183
    //
    // http://b/381285096. On Intel platforms, we want to prevent protected queues being used as
    // we cannot handle the teardown scenario if PXP termination occurs. However, enable this for
    // Android, since the issue is rare and the fallout is isolated to the specific app (rather than
    // crashing the whole system like in ChromeOS).
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsProtectedMemory,
        mProtectedMemoryFeatures.protectedMemory == VK_TRUE &&
            (!isARMProprietary ||
             mPipelineProtectedAccessFeatures.pipelineProtectedAccess == VK_TRUE) &&
            (!isIntel || IsAndroid()));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsHostQueryReset,
                            mHostQueryResetFeatures.hostQueryReset == VK_TRUE);
    // Avoid any inefficiency that may be caused by host image copy by default.  To be experimented
    // with to see on which hardware VkHostImageCopyDevicePerformanceQueryEXT::optimalDeviceAccess
    // is really performing as well as
    // VkHostImageCopyDevicePerformanceQueryEXT::identicalMemoryLayout.
    ANGLE_FEATURE_CONDITION(&mFeatures, allowHostImageCopyDespiteNonIdenticalLayout, false);

    // Force host image copy for textures with luminance/alpha formats.  This disables framebuffer
    // compression (but these formats are not renderable), and the benefits of host image copy
    // outweigh framebuffer compression on sampled textures on the following GPUs:
    //
    // - ARM
    ANGLE_FEATURE_CONDITION(&mFeatures, forceHostImageCopyForLuma, isARM);

    // VK_EXT_pipeline_creation_feedback is promoted to core in Vulkan 1.3.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsPipelineCreationFeedback,
        ExtensionFound(VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME, deviceExtensionNames));

    // Note: Protected Swapchains is not determined until we have a VkSurface to query.
    // So here vendors should indicate support so that protected_content extension
    // is enabled.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsSurfaceProtectedSwapchains, IsAndroid());

    // Work around incorrect NVIDIA point size range clamping.
    // http://anglebug.com/40644663#comment11
    // Clamp if driver version is:
    //   < 430 on Windows
    //   < 421 otherwise
    ANGLE_FEATURE_CONDITION(
        &mFeatures, clampPointSize,
        isNvidia && driverVersion < angle::VersionTriple(IsWindows() ? 430 : 421, 0, 0));

    // Affecting Nvidia drivers 535 through 551.
    ANGLE_FEATURE_CONDITION(&mFeatures, avoidOpSelectWithMismatchingRelaxedPrecision,
                            isNvidia && (driverVersion >= angle::VersionTriple(535, 0, 0) &&
                                         driverVersion < angle::VersionTriple(552, 0, 0)));

    // Affecting Linux/Intel (unknown range).
    ANGLE_FEATURE_CONDITION(&mFeatures, wrapSwitchInIfTrue, isIntel && IsLinux());

    // Vulkan implementations are not required to clamp gl_FragDepth to [0, 1] by default.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDepthClampZeroOne,
                            mDepthClampZeroOneFeatures.depthClampZeroOne == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, clampFragDepth,
                            isNvidia && !mFeatures.supportsDepthClampZeroOne.enabled);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsRenderpass2,
        ExtensionFound(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsIncrementalPresent,
        ExtensionFound(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, deviceExtensionNames));

#if defined(ANGLE_PLATFORM_ANDROID)
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsAndroidHardwareBuffer,
        IsAndroid() &&
            ExtensionFound(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
                           deviceExtensionNames) &&
            ExtensionFound(VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, deviceExtensionNames));
#endif

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsExternalMemoryFd,
        ExtensionFound(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, deviceExtensionNames));

#if defined(ANGLE_PLATFORM_WINDOWS)
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsFullScreenExclusive,
        ExtensionFound(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, deviceExtensionNames));

    // On Windows+AMD, drivers before version 0x800106 (2.0.262) would
    // implicitly enable VK_EXT_full_screen_exclusive and start returning
    // extension-specific error codes in swapchain functions. Since the
    // extension was not enabled by ANGLE, it was impossible to handle these
    // error codes correctly. On these earlier drivers, we want to explicitly
    // enable the extension and opt out of it to avoid seeing those error codes
    // entirely.
    ANGLE_FEATURE_CONDITION(&mFeatures, forceDisableFullScreenExclusive,
                            isAMD && driverVersion < angle::VersionTriple(2, 0, 262));
#endif

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsExternalMemoryFuchsia,
        ExtensionFound(VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsExternalSemaphoreFd,
        ExtensionFound(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsExternalSemaphoreFuchsia,
        ExtensionFound(VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsExternalFenceFd,
        ExtensionFound(VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME, deviceExtensionNames));

#if defined(ANGLE_PLATFORM_ANDROID) || defined(ANGLE_PLATFORM_LINUX)
    if (mFeatures.supportsExternalFenceCapabilities.enabled &&
        mFeatures.supportsExternalSemaphoreCapabilities.enabled)
    {
        VkExternalFenceProperties externalFenceProperties = {};
        externalFenceProperties.sType = VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES;

        VkPhysicalDeviceExternalFenceInfo externalFenceInfo = {};
        externalFenceInfo.sType      = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO;
        externalFenceInfo.handleType = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR;

        vkGetPhysicalDeviceExternalFenceProperties(mPhysicalDevice, &externalFenceInfo,
                                                   &externalFenceProperties);

        VkExternalSemaphoreProperties externalSemaphoreProperties = {};
        externalSemaphoreProperties.sType = VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES;

        VkPhysicalDeviceExternalSemaphoreInfo externalSemaphoreInfo = {};
        externalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO;
        externalSemaphoreInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT_KHR;

        vkGetPhysicalDeviceExternalSemaphoreProperties(mPhysicalDevice, &externalSemaphoreInfo,
                                                       &externalSemaphoreProperties);

        // There's a spec gap in eglDupNativeFenceFDANDROID and Vulkan SYNC_FD
        // fence/semaphore export, where the former treats -1 as an error while
        // the latter considers -1 as a valid return for signaled payload. ANGLE
        // relies on the implementation defined behavior that most hw Vulkan
        // drivers would return a valid sync file there, which isn't possible
        // for Lavapipe sw implementation especially since sw sync has been an
        // obsolete option both on Android and upstream Linux. So workaround to
        // disable EGL_ANDROID_native_fence_sync for Lavapipe.
        ANGLE_FEATURE_CONDITION(
            &mFeatures, supportsAndroidNativeFenceSync,
            (mFeatures.supportsExternalFenceFd.enabled &&
             FencePropertiesCompatibleWithAndroid(externalFenceProperties) &&
             mFeatures.supportsExternalSemaphoreFd.enabled &&
             SemaphorePropertiesCompatibleWithAndroid(externalSemaphoreProperties) && !isLavapipe));
    }
    else
    {
        ANGLE_FEATURE_CONDITION(&mFeatures, supportsAndroidNativeFenceSync,
                                (mFeatures.supportsExternalFenceFd.enabled &&
                                 mFeatures.supportsExternalSemaphoreFd.enabled));
    }
#endif  // defined(ANGLE_PLATFORM_ANDROID) || defined(ANGLE_PLATFORM_LINUX)

    // Disabled on SwiftShader due to http://crbug.com/40942995
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsShaderStencilExport,
        ExtensionFound(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME, deviceExtensionNames) &&
            !isSwiftShader);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsRenderPassLoadStoreOpNone,
        ExtensionFound(VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(&mFeatures, disallowMixedDepthStencilLoadOpNoneAndLoad,
                            isARMProprietary && driverVersion < angle::VersionTriple(38, 1, 0));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsRenderPassStoreOpNone,
        !mFeatures.supportsRenderPassLoadStoreOpNone.enabled &&
            ExtensionFound(VK_QCOM_RENDER_PASS_STORE_OPS_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDepthClipControl,
                            mDepthClipControlFeatures.depthClipControl == VK_TRUE);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsPrimitiveTopologyListRestart,
        mPrimitiveTopologyListRestartFeatures.primitiveTopologyListRestart == VK_TRUE);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsBlendOperationAdvanced,
        ExtensionFound(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsFormatFeatureFlags2,
        ExtensionFound(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsTransformFeedbackExtension,
                            vk::CanSupportTransformFeedbackExtension(mTransformFeedbackFeatures));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsGeometryStreamsCapability,
                            mFeatures.supportsTransformFeedbackExtension.enabled &&
                                mTransformFeedbackFeatures.geometryStreams == VK_TRUE);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsPrimitivesGeneratedQuery,
        mFeatures.supportsTransformFeedbackExtension.enabled &&
            mPrimitivesGeneratedQueryFeatures.primitivesGeneratedQuery == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, emulateTransformFeedback,
                            !mFeatures.supportsTransformFeedbackExtension.enabled &&
                                vk::CanSupportTransformFeedbackEmulation(mPhysicalDeviceFeatures));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsIndexTypeUint8,
                            mIndexTypeUint8Features.indexTypeUint8 == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDepthStencilResolve,
                            mFeatures.supportsRenderpass2.enabled &&
                                mDepthStencilResolveProperties.supportedDepthResolveModes != 0);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDepthStencilIndependentResolveNone,
                            mFeatures.supportsDepthStencilResolve.enabled &&
                                mDepthStencilResolveProperties.independentResolveNone);
    // Disable optimizing depth/stencil resolve through glBlitFramebuffer for buggy drivers:
    //
    // - Nvidia: http://anglebug.com/42267095
    // - Pixel4: http://anglebug.com/42267096
    //
    ANGLE_FEATURE_CONDITION(&mFeatures, disableDepthStencilResolveThroughAttachment,
                            isNvidia || isQualcommProprietary);

    // MSRTSS is disabled if the driver does not support it for RGBA8 and RGBA8_SRGB.
    // This is used to filter out known drivers where support for sRGB formats are missing.
    //
    // Qualcomm driver 512.821 is known to have rendering bugs with this extension.
    // http://crbug.com/413427770
    //
    // Lavapipe currently crashes in SysUI on Android 16 when ANGLE uses MSRTSS.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsMultisampledRenderToSingleSampled,
        mMultisampledRenderToSingleSampledFeatures.multisampledRenderToSingleSampled == VK_TRUE &&
            mFeatures.supportsRenderpass2.enabled &&
            mFeatures.supportsDepthStencilResolve.enabled && CanSupportMSRTSSForRGBA8(this) &&
            !(isQualcommProprietary && driverVersion < angle::VersionTriple(512, 822, 0)) &&
            !isLavapipe);

    // Preferring the MSRTSS flag is for texture initialization. If the MSRTSS is not used at first,
    // it will be used (if available) when recreating the image if it is bound to an MSRTT
    // framebuffer.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, preferMSRTSSFlagByDefault,
        mFeatures.supportsMultisampledRenderToSingleSampled.enabled && !isMesaPanVK &&
            (isARMProprietary ||
             isQualcommProprietary && driverVersion >= angle::VersionTriple(512, 777, 0)));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsImage2dViewOf3d,
                            mImage2dViewOf3dFeatures.image2DViewOf3D == VK_TRUE);

    // Note: sampler2DViewOf3D is only useful for supporting EGL_KHR_gl_texture_3D_image.  If the
    // VK_IMAGE_CREATE_2D_VIEW_COMPATIBLE_BIT_EXT added to 3D images measurable hurts sampling
    // performance, it might be better to remove support for EGL_KHR_gl_texture_3D_image in favor of
    // faster 3D images.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsSampler2dViewOf3d,
                            mFeatures.supportsImage2dViewOf3d.enabled &&
                                mImage2dViewOf3dFeatures.sampler2DViewOf3D == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsMultiview, mMultiviewFeatures.multiview == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsMultiviewMultisampleRenderToTexture,
                            mFeatures.supportsMultisampledRenderToSingleSampled.enabled &&
                                mFeatures.supportsMultiview.enabled);

    // VK_EXT_device_fault can provide more information when the device is lost.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsDeviceFault,
        ExtensionFound(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, deviceExtensionNames) &&
            mFaultFeatures.deviceFault == VK_TRUE);

    // TODO: http://anglebug.com/42264464 - drop dependency on customBorderColorWithoutFormat.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsCustomBorderColor,
        mCustomBorderColorFeatures.customBorderColors == VK_TRUE &&
            mCustomBorderColorFeatures.customBorderColorWithoutFormat == VK_TRUE);

    // If format is undefined, the borderColor is VK_BORDER_COLOR_INT_CUSTOM_EXT, and the sampler is
    // used with an image with a stencil format, then the implementation must source the custom
    // border color from either the first or second components of the borderColor, although it is
    // recommended to source it from the first component.
    ANGLE_FEATURE_CONDITION(&mFeatures, usesSecondComponentForStencilBorderColor,
                            mFeatures.supportsCustomBorderColor.enabled && isQualcommProprietary);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsMultiDrawIndirect,
                            mPhysicalDeviceFeatures.multiDrawIndirect == VK_TRUE);

    // The |WindowSurfaceVk::getWindowVisibility| method must be implemented.
    // When enabled, surface will be resized only if window is visible.
    // Notes:
    // - Enable for NVIDIA on Linux X11 because of the possible driver bug, when acquire next image
    //   continuously returns OUT_OF_DATE if recreate the swapchain while window is not visible
    //   (http://anglebug.com/397848903).
    ANGLE_FEATURE_CONDITION(&mFeatures, avoidInvisibleWindowSwapchainRecreate,
                            (isNvidia && nativeWindowSystem == angle::NativeWindowSystem::X11));

    ANGLE_FEATURE_CONDITION(&mFeatures, padBuffersToMaxVertexAttribStride, isAMD || isSamsung);
    mMaxVertexAttribStride =
        mFeatures.padBuffersToMaxVertexAttribStride.enabled
            ? std::min(static_cast<uint32_t>(gl::limits::kMaxVertexAttribStride),
                       mPhysicalDeviceProperties.limits.maxVertexInputBindingStride)
            : 0;

    // Although the maximum memory allocation size for a single object can be derived from the
    // Vulkan driver, it could still be too large for common use (e.g., ~4GB on some platforms) and
    // increase the risk of overflow if the object dimensions used for size calculations are 32-bit.
    // The limit can be restricted to a specific fixed value to reduce this risk.
    mMaxMemoryAllocationSize =
        std::min(mMaintenance3Properties.maxMemoryAllocationSize, kMemoryAllocationSizeLimit);

    // The limits related to buffer size should also take the max memory allocation size and padding
    // (if applicable) into account.
    mMaxBufferMemorySizeLimit = mMaxMemoryAllocationSize - mMaxVertexAttribStride;

    ANGLE_FEATURE_CONDITION(&mFeatures, forceD16TexFilter, IsAndroid() && isQualcommProprietary);

    // Allocation sanitization disabled by default because of a heavyweight implementation
    // that can cause OOM and timeouts.
    ANGLE_FEATURE_CONDITION(&mFeatures, allocateNonZeroMemory, false);

    // ARM proprietary driver does buffer copy on geometry pipeline, which may create a GPU pipeline
    // bubble that prevents vertex shader to overlap with fragment shader on job manager based
    // architecture. For now we always choose CPU to do copy on ARM job manager based GPU.
    // http://issuetracker.google.com/200067929 - ARM
    // http://issuetracker.google.com/477725737 - Qualcomm
    // http://issuetracker.google.com/485970552 - PowerVR
    ANGLE_FEATURE_CONDITION(&mFeatures, preferCPUForBufferSubData,
                            isARMProprietary || isQualcommProprietary || isPowerVR);

    // Prefer GPU for glCopyBufferSubData dirty region buffer update on ARM devices.
    ANGLE_FEATURE_CONDITION(&mFeatures, preferGPUForCopyBufferSubData, isARMProprietary);

    // On android, we usually are GPU limited, we try to use CPU to do data copy when other
    // conditions are the same. Set to zero will use GPU to do copy. This is subject to further
    // tuning for each platform https://issuetracker.google.com/201826021
    mMaxCopyBytesUsingCPUWhenPreservingBufferData =
        IsAndroid() ? std::numeric_limits<uint32_t>::max() : 0;

    ANGLE_FEATURE_CONDITION(&mFeatures, persistentlyMappedBuffers, true);

    ANGLE_FEATURE_CONDITION(&mFeatures, logMemoryReportCallbacks, false);
    ANGLE_FEATURE_CONDITION(&mFeatures, logMemoryReportStats, false);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsExternalMemoryDmaBuf,
        ExtensionFound(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsImageDrmFormatModifier,
        ExtensionFound(VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(&mFeatures, enablePrecisionQualifiers, true);

    // http://anglebug.com/42265957
    ANGLE_FEATURE_CONDITION(&mFeatures, varyingsRequireMatchingPrecisionInSpirv,
                            isPowerVR || isSamsung);

    // IMR devices are less sensitive to the src/dst stage masks in barriers, and behave more
    // efficiently when all barriers are aggregated, rather than individually and precisely
    // specified.
    ANGLE_FEATURE_CONDITION(&mFeatures, preferAggregateBarrierCalls, isImmediateModeRenderer);

    // For IMR devices, it's more efficient to ignore invalidate of framebuffer attachments with
    // emulated formats that have extra channels.  For TBR devices, the invalidate will be followed
    // by a clear to retain valid values in said extra channels.
    ANGLE_FEATURE_CONDITION(&mFeatures, preferSkippingInvalidateForEmulatedFormats,
                            isImmediateModeRenderer);

    ANGLE_FEATURE_CONDITION(&mFeatures, asyncGarbageCleanup, true);
    // reset sometimes gets blocked by mutex lock inside vulkan driver and runs in small core while
    // main thread gets blocked by command pool lock. FOr now dont call reset in garbage clean up
    // thread on ARM proprietary driver.
    ANGLE_FEATURE_CONDITION(&mFeatures, asyncCommandBufferReset,
                            mFeatures.asyncGarbageCleanup.enabled && !isARMProprietary);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsYUVSamplerConversion,
                            mSamplerYcbcrConversionFeatures.samplerYcbcrConversion != VK_FALSE);

    ANGLE_FEATURE_CONDITION(&mFeatures, preferCachedNoncoherentForDynamicStreamBufferUsage,
                            IsMeteorLake(mPhysicalDeviceProperties.deviceID));

    // The compute shader used to generate mipmaps needs -
    // 1. subgroup quad operations in compute shader stage.
    // 2. subgroup operations that can use extended types.
    // 3. 256-wide workgroup.
    //
    // Furthermore, VK_IMAGE_USAGE_STORAGE_BIT is detrimental to performance on many platforms, on
    // which this path is not enabled.  Platforms that are known to have better performance with
    // this path are:
    //
    // - AMD
    // - Nvidia
    // - Samsung
    //
    // Additionally, this path is disabled on buggy drivers:
    //
    // - AMD/Windows: Unfortunately the trybots use ancient AMD cards and drivers.
    const bool supportsSubgroupQuadOpsInComputeShader =
        (mSubgroupProperties.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) &&
        (mSubgroupProperties.supportedOperations & VK_SUBGROUP_FEATURE_QUAD_BIT);

    const uint32_t maxComputeWorkGroupInvocations =
        mPhysicalDeviceProperties.limits.maxComputeWorkGroupInvocations;

    ANGLE_FEATURE_CONDITION(&mFeatures, allowGenerateMipmapWithCompute,
                            supportsSubgroupQuadOpsInComputeShader &&
                                mSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes &&
                                maxComputeWorkGroupInvocations >= 256 &&
                                ((isAMD && !IsWindows()) || isNvidia || isSamsung));

    bool isAdreno540 = mPhysicalDeviceProperties.deviceID == angle::kDeviceID_Adreno540;
    ANGLE_FEATURE_CONDITION(&mFeatures, forceMaxUniformBufferSize16KB,
                            isQualcommProprietary && isAdreno540);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsImageFormatList,
        ExtensionFound(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSwapchainMutableFormat,
        useVulkanSwapchain == UseVulkanSwapchain::Yes &&
            mFeatures.supportsImageFormatList.enabled &&
            ExtensionFound(VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSamplerMirrorClampToEdge,
        ExtensionFound(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, deviceExtensionNames));

    // Emulation of GL_EXT_multisampled_render_to_texture is only really useful on tiling hardware,
    // but is exposed on any configuration deployed on Android, such as Samsung's AMD-based GPU.
    //
    // During testing, it was also discovered that emulation triggers bugs on some platforms:
    //
    // - Swiftshader:
    //   * Failure on mac: http://anglebug.com/40644747
    //   * OOM: http://crbug.com/1263046
    // - Intel on windows: http://anglebug.com/42263602
    // - AMD on windows: http://crbug.com/1132366
    // - Old ARM proprietary drivers on Android fail multiple tests, though newer drivers don't
    //   (although they support MSRTSS and emulation is unnecessary)
    //
    ANGLE_FEATURE_CONDITION(&mFeatures, allowMultisampledRenderToTextureEmulation,
                            (isTileBasedRenderer && !isARMProprietary) || isSamsung);
    ANGLE_FEATURE_CONDITION(&mFeatures, enableMultisampledRenderToTexture,
                            mFeatures.supportsMultisampledRenderToSingleSampled.enabled ||
                                (mFeatures.supportsDepthStencilResolve.enabled &&
                                 mFeatures.allowMultisampledRenderToTextureEmulation.enabled));

    // Currently we enable cube map arrays based on the imageCubeArray Vk feature.
    // TODO: Check device caps for full cube map array support. http://anglebug.com/42263705
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsImageCubeArray,
                            mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsPipelineStatisticsQuery,
                            mPhysicalDeviceFeatures.pipelineStatisticsQuery == VK_TRUE);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, allowPipelineStatisticsForPrimitivesGeneratedQuery,
        mFeatures.supportsPipelineStatisticsQuery.enabled && (isSamsung || isQualcommProprietary));

    ANGLE_FEATURE_CONDITION(&mFeatures, destroyOldSwapchainInSharedPresentMode, IsAndroid());

    // vkCmdClearAttachments races with draw calls on Qualcomm hardware as observed on Pixel2 and
    // Pixel4.  https://issuetracker.google.com/issues/166809097
    ANGLE_FEATURE_CONDITION(
        &mFeatures, preferDrawClearOverVkCmdClearAttachments,
        isQualcommProprietary && driverVersion < angle::VersionTriple(512, 762, 12));

    // R32F imageAtomicExchange emulation is done if shaderImageFloat32Atomics feature is not
    // supported.
    ANGLE_FEATURE_CONDITION(&mFeatures, emulateR32fImageAtomicExchange,
                            mShaderAtomicFloatFeatures.shaderImageFloat32Atomics != VK_TRUE);

    // Whether non-conformant configurations and extensions should be exposed.
    ANGLE_FEATURE_CONDITION(&mFeatures, exposeNonConformantExtensionsAndVersions,
                            kExposeNonConformantExtensionsAndVersions);

    // http://issuetracker.google.com/376899587
    // Currently some testing platforms do not fully support ES 3.2 due to lack of certain features
    // or extensions. For the purpose of testing coverage, we would still enable ES 3.2 on these
    // platforms. However, once a listed test platform is updated to a version that does support
    // ES 3.2, it should be unlisted.
    ANGLE_FEATURE_CONDITION(&mFeatures, exposeES32ForTesting,
                            mFeatures.exposeNonConformantExtensionsAndVersions.enabled &&
                                (isSoftwareRenderer || (IsWindows() && isIntel)));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsMemoryBudget,
        ExtensionFound(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, deviceExtensionNames));

    // TODO: Delete these two feature flags (https://issuetracker.google.com/422507974). More
    // frequent submission may help benchmark score improvement, and in certain cases helps real
    // performance as well (for things like bufferSubData able to go down faster path), but it
    // increases power consumption by keeping GPU powered up longer for real world applications that
    // runs in vsync mode. For now the feature is just disabled so that people can still do
    // comparison if needed. They should be deleted in future.
    ANGLE_FEATURE_CONDITION(&mFeatures, preferSubmitAtFBOBoundary, false);
    // This is relevant only if preferSubmitAtFBOBoundary is enabled
    ANGLE_FEATURE_CONDITION(&mFeatures, forceSubmitExceptionsAtFBOBoundary,
                            mFeatures.preferSubmitAtFBOBoundary.enabled && !isQualcommProprietary);

    // The number of minimum write commands in the command buffer to trigger one submission of
    // pending commands at draw call time
    if (isARMProprietary)
    {
        mMinRPWriteCommandCountToEarlySubmit = 128;
    }

    // In order to support immutable samplers tied to external formats, we need to overallocate
    // descriptor counts for such immutable samplers
    ANGLE_FEATURE_CONDITION(&mFeatures, useMultipleDescriptorsForExternalFormats, true);

    // http://anglebug.com/42265147
    // When creating a surface with the format GL_RGB8, override the format to be GL_RGBA8, since
    // Android prevents creating swapchain images with VK_FORMAT_R8G8B8_UNORM.
    // Do this for all platforms, since few (none?) IHVs support 24-bit formats with their HW
    // natively anyway.
    ANGLE_FEATURE_CONDITION(&mFeatures, overrideSurfaceFormatRGB8ToRGBA8, true);

    // We set the following when there is color framebuffer fetch:
    //
    // - VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_EXT
    // - VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_COLOR_ACCESS_BIT_EXT
    //
    // and the following with depth/stencil framebuffer fetch:
    //
    // - VK_PIPELINE_DEPTH_STENCIL_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_DEPTH_ACCESS_BIT_EXT
    // -
    // VK_PIPELINE_DEPTH_STENCIL_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_STENCIL_ACCESS_BIT_EXT
    //
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsRasterizationOrderAttachmentAccess,
        mRasterizationOrderAttachmentAccessFeatures.rasterizationOrderColorAttachmentAccess ==
            VK_TRUE);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, addFramebufferFetchBarrierOnUseMidRenderPass,
        mFeatures.supportsRasterizationOrderAttachmentAccess.enabled && isQualcomm);

    // The VK_EXT_surface_maintenance1 and VK_EXT_swapchain_maintenance1 extensions are used for a
    // variety of improvements:
    //
    // - Recycling present semaphores
    // - Avoiding swapchain recreation when present modes change
    // - Amortizing the cost of memory allocation for swapchain creation over multiple frames
    //
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsSwapchainMaintenance1,
                            mSwapchainMaintenance1Features.swapchainMaintenance1 == VK_TRUE &&
                                useVulkanSwapchain == UseVulkanSwapchain::Yes);

    // ANI crashes on NVIDIA/Wayland on a swapchain with deferred memory allocation.
    // http://anglebug.com/499347835
    ANGLE_FEATURE_CONDITION(
        &mFeatures, swapchainDeferredMemoryAllocation,
        mFeatures.supportsSwapchainMaintenance1.enabled && !(IsWayland() && isNvidia));

    // The VK_EXT_legacy_dithering extension enables dithering support without emulation
    // Disable the usage of VK_EXT_legacy_dithering on ARM until the driver bug
    // http://issuetracker.google.com/293136916, http://issuetracker.google.com/292282210 are fixed.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsLegacyDithering,
                            mDitheringFeatures.legacyDithering == VK_TRUE);

    // Applications on Android have come to rely on hardware dithering, and visually regress without
    // it.  On desktop GPUs, OpenGL's dithering is a no-op.  The following setting mimics that
    // behavior.  Dithering is also currently not enabled on SwiftShader, but can be as needed
    // (which would require Chromium and Capture/Replay test expectations updates).
    ANGLE_FEATURE_CONDITION(&mFeatures, emulateDithering,
                            IsAndroid() && !mFeatures.supportsLegacyDithering.enabled);

    // Enable additional blend factors when dithering for Samsung
    ANGLE_FEATURE_CONDITION(&mFeatures, enableAdditionalBlendFactorsForDithering, isSamsung);

    ANGLE_FEATURE_CONDITION(&mFeatures, adjustClearColorPrecision,
                            IsAndroid() && mFeatures.supportsLegacyDithering.enabled &&
                                isARMProprietary && driverVersion < angle::VersionTriple(50, 0, 0));

    // ANGLE always exposes framebuffer fetch because too many apps assume it's there.  See comments
    // on |mIsColorFramebufferFetchCoherent| for details.  Non-coherent framebuffer fetch is always
    // supported by Vulkan.
    //
    // Without exposeNonConformantExtensionsAndVersions, this feature is disable on Intel/windows
    // due to lack of input attachment support for swapchain images, and Intel/mesa before mesa
    // 22.0 for the same reason.  Without VK_GOOGLE_surfaceless_query, there is no way to
    // automatically deduce this support.
    //
    // http://issuetracker.google.com/376899587
    // Advanced blend emulation depends on this functionality, lack of which prevents support for
    // ES 3.2; exposeNonConformantExtensionsAndVersions is used to force this.
    const bool supportsFramebufferFetchInSurface =
        IsAndroid() || !isIntel ||
        (isIntel && IsLinux() && driverVersion >= angle::VersionTriple(22, 0, 0)) ||
        mFeatures.exposeNonConformantExtensionsAndVersions.enabled;

    // Emulating framebuffer fetch causes significant perf regressions on samsung
    const bool framebufferFetchEmulationCausesPerfRegression = isSamsung;

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsShaderFramebufferFetch,
        (supportsFramebufferFetchInSurface && !framebufferFetchEmulationCausesPerfRegression));
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsShaderFramebufferFetchNonCoherent,
        (supportsFramebufferFetchInSurface && !framebufferFetchEmulationCausesPerfRegression));

    // On ARM hardware, framebuffer-fetch-like behavior on Vulkan is known to be coherent even
    // without the Vulkan extension.
    //
    // On IMG hardware, similarly framebuffer-fetch-like behavior on Vulkan is known to be coherent,
    // but the Vulkan extension cannot be exposed.  This is because the Vulkan extension guarantees
    // coherence when accessing all samples of a pixel from any other sample, but IMG hardware is
    // _not_ coherent in that case.  This is not a problem for GLES because the invocation for each
    // sample can only access values for the same sample by reading "the current color value",
    // unlike Vulkan-GLSL's |subpassLoad()| which takes a sample index.
    mIsColorFramebufferFetchCoherent =
        isARM || isPowerVR || mFeatures.supportsRasterizationOrderAttachmentAccess.enabled;

    // Support EGL_KHR_lock_surface3 extension.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsLockSurfaceExtension, IsAndroid());

    // http://anglebug.com/42265370
    // Android needs swapbuffers to update image and present to display.
    ANGLE_FEATURE_CONDITION(&mFeatures, swapbuffersOnFlushOrFinishWithSingleBuffer, IsAndroid());

    // Workaround a Qualcomm imprecision with dithering
    ANGLE_FEATURE_CONDITION(&mFeatures, roundOutputAfterDithering, isQualcomm);

    // GL_KHR_blend_equation_advanced is emulated when the equivalent Vulkan extension is not
    // usable.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, emulateAdvancedBlendEquations,
        !mFeatures.supportsBlendOperationAdvanced.enabled && supportsFramebufferFetchInSurface);

    // GL_KHR_blend_equation_advanced_coherent ensures that the blending operations are performed in
    // API primitive order.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsBlendOperationAdvancedCoherent,
        mFeatures.supportsBlendOperationAdvanced.enabled &&
            mBlendOperationAdvancedFeatures.advancedBlendCoherentOperations == VK_TRUE);

    // http://anglebug.com/42265410
    // Android expects VkPresentRegionsKHR rectangles with a bottom-left origin, while spec
    // states they should have a top-left origin.
    ANGLE_FEATURE_CONDITION(&mFeatures, bottomLeftOriginPresentRegionRectangles, IsAndroid());

    // Use VMA for image suballocation.
    ANGLE_FEATURE_CONDITION(&mFeatures, useVmaForImageSuballocation, true);

    // Use larger size for DynamicBuffer objects used as streaming vertex buffers (currently limited
    // to GLES1).
    ANGLE_FEATURE_CONDITION(&mFeatures, useLargeSizeForDynamicBuffers,
                            mPhysicalDeviceProperties.limits.maxMemoryAllocationCount <=
                                kAllocationCountThresholdForDynamicVertexDataSize);

    // Some platforms perform better using BGR565 than RGB565.
    bool isBGR565Renderable = hasImageFormatFeatureBits(angle::FormatID::B5G6R5_UNORM,
                                                        VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT);
    ANGLE_FEATURE_CONDITION(&mFeatures, preferBGR565ToRGB565, isBGR565Renderable && isQualcomm);

    // Emit SPIR-V 1.4 when supported.  The following old drivers have various bugs with SPIR-V 1.4:
    //
    // - Nvidia drivers - Crashes when creating pipelines, not using any SPIR-V 1.4 features.  Known
    //                    good since at least version 525.  http://anglebug.com/343249127
    // - Qualcomm drivers - Crashes when creating pipelines in the presence of OpCopyLogical with
    //                      some types.
    //                      Known good since at least version 512.829.0.
    //                      http://anglebug.com/343218484
    // - ARM proprietary drivers - Fail tests when OpSelect uses a scalar to select between vectors.
    //                             Known good since at least version 47.
    //                             http://anglebug.com/343218491
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSPIRV14,
        ExtensionFound(VK_KHR_SPIRV_1_4_EXTENSION_NAME, deviceExtensionNames) &&
            !(isNvidia && driverVersion < angle::VersionTriple(525, 0, 0)) &&
            !(isQualcommProprietary && driverVersion < angle::VersionTriple(512, 829, 0)) &&
            !(isARMProprietary && driverVersion < angle::VersionTriple(47, 0, 0)));

    // Retain debug info in SPIR-V blob.
    ANGLE_FEATURE_CONDITION(&mFeatures, retainSPIRVDebugInfo, getEnableValidationLayers());

    // For discrete GPUs, most of device local memory is host invisible. We should not force the
    // host visible flag for them and result in allocation failure.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, preferDeviceLocalMemoryHostVisible,
        canPreferDeviceLocalMemoryHostVisible(mPhysicalDeviceProperties.deviceType));

    // Multiple dynamic state issues on ARM have been fixed.
    // http://issuetracker.google.com/285124778
    // http://issuetracker.google.com/285196249
    // http://issuetracker.google.com/286224923
    // http://issuetracker.google.com/287318431
    //
    // On Pixel devices, the issues have been fixed since r44, but on others since r44p1.
    //
    // Regressions have been detected using r46 on older architectures, which are fixed in r51.
    // http://issuetracker.google.com/336411904
    const bool isARMExtendedDynamicStateBuggy =
        isARMProprietary &&
        (driverVersion < angle::VersionTriple(44, 1, 0) ||
         (isMaliJobManagerBasedGPU && driverVersion >= angle::VersionTriple(46, 0, 0) &&
          driverVersion < angle::VersionTriple(51, 0, 0)));

    // Intel driver has issues with VK_EXT_vertex_input_dynamic_state
    // http://anglebug.com/42265637#comment9
    //
    // On ARM proprietary drivers prior to r48, |vkCmdBindVertexBuffers2| applies strides to the
    // wrong index, according to the errata:
    // https://developer.arm.com/documentation/SDEN-3735689/0100/?lang=en
    //
    // On Qualcomm proprietary drivers prior to 777, this feature had a bug
    // (http://anglebug.com/381384988). Further data also shows that disable vertexInputDynamicState
    // has less overall CPU overhead.
    //
    // Use of vertexInputDynamicState on PowerVR devices is disabled for performance reasons
    // (http://issuetracker.google.com/469320616).
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsVertexInputDynamicState,
        mVertexInputDynamicStateFeatures.vertexInputDynamicState == VK_TRUE &&
            !(IsWindows() && isIntel) &&
            !(isARMProprietary && driverVersion < angle::VersionTriple(48, 0, 0)) &&
            !isQualcommProprietary && !isPowerVR);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsExtendedDynamicState,
                            mExtendedDynamicStateFeatures.extendedDynamicState == VK_TRUE &&
                                !isARMExtendedDynamicStateBuggy);

    // VK_EXT_vertex_input_dynamic_state enables dynamic state for the full vertex input state. As
    // such, when available use supportsVertexInputDynamicState instead of
    // useVertexInputBindingStrideDynamicState.
    //
    // Vertex input binding stride is buggy for Windows/Intel drivers before 100.9684
    // (https://anglebug.com/42266992).
    //
    // |vkCmdBindVertexBuffers2| applies strides to the wrong index on ARM proprietary drivers prior
    // to r48, according to the errata:
    // https://developer.arm.com/documentation/SDEN-3735689/0100/?lang=en
    const bool isVertexInputBindingStrideBuggy =
        (IsWindows() && isIntel && driverVersion < angle::VersionTriple(100, 9684, 0)) ||
        (isARMProprietary && driverVersion < angle::VersionTriple(48, 0, 0));
    ANGLE_FEATURE_CONDITION(&mFeatures, useVertexInputBindingStrideDynamicState,
                            mFeatures.supportsExtendedDynamicState.enabled &&
                                !mFeatures.supportsVertexInputDynamicState.enabled &&
                                !isVertexInputBindingStrideBuggy);
    // On ARM proprietary drivers prior to r52, |vkCmdSetCullMode| incorrectly culls non-triangle
    // topologies, according to the errata:
    // https://developer.arm.com/documentation/SDEN-3735689/0100/?lang=en
    ANGLE_FEATURE_CONDITION(
        &mFeatures, useCullModeDynamicState,
        mFeatures.supportsExtendedDynamicState.enabled && !isARMExtendedDynamicStateBuggy &&
            !(isARMProprietary && driverVersion < angle::VersionTriple(52, 0, 0)));
    ANGLE_FEATURE_CONDITION(&mFeatures, useDepthCompareOpDynamicState,
                            mFeatures.supportsExtendedDynamicState.enabled);
    ANGLE_FEATURE_CONDITION(&mFeatures, useDepthTestEnableDynamicState,
                            mFeatures.supportsExtendedDynamicState.enabled);
    ANGLE_FEATURE_CONDITION(
        &mFeatures, useDepthWriteEnableDynamicState,
        mFeatures.supportsExtendedDynamicState.enabled && !isARMExtendedDynamicStateBuggy);
    ANGLE_FEATURE_CONDITION(&mFeatures, useFrontFaceDynamicState,
                            mFeatures.supportsExtendedDynamicState.enabled);
    // On ARM proprietary drivers, there seems to be a bug with primitive topology dynamic state in
    // combination with geometry shaders.
    //
    // On Samsung, it was observed that the combination of primitive topology dynamic state
    // _enabled_ and primitive restart dynamic state _disabled_ is buggy.  However, the feature is
    // not disabled because primitive restart is not disabled outside tests.
    //
    // Also seeing rendering issues on PowerVR (http://b/472763050)
    ANGLE_FEATURE_CONDITION(
        &mFeatures, usePrimitiveTopologyDynamicState,
        mFeatures.supportsExtendedDynamicState.enabled && !isARMProprietary && !isPowerVR);
    ANGLE_FEATURE_CONDITION(&mFeatures, useStencilOpDynamicState,
                            mFeatures.supportsExtendedDynamicState.enabled);
    ANGLE_FEATURE_CONDITION(&mFeatures, useStencilTestEnableDynamicState,
                            mFeatures.supportsExtendedDynamicState.enabled);

    // vkCmdBindVertexBuffers2EXT() requires extended dynamic state.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsBindVertexBuffers2,
                            mFeatures.supportsExtendedDynamicState.enabled);

    // Providing vkCmdBindVertexBuffers2() with a pointer to the sizes of the bound buffers can help
    // with syncval issues and robustness.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, forceSizePointerForBoundVertexBuffers,
        mEnableValidationLayers && mFeatures.supportsBindVertexBuffers2.enabled);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsExtendedDynamicState2,
                            mExtendedDynamicState2Features.extendedDynamicState2 == VK_TRUE &&
                                !isARMExtendedDynamicStateBuggy);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, usePrimitiveRestartEnableDynamicState,
        mFeatures.supportsExtendedDynamicState2.enabled && !isARMExtendedDynamicStateBuggy);
    ANGLE_FEATURE_CONDITION(&mFeatures, useRasterizerDiscardEnableDynamicState,
                            mFeatures.supportsExtendedDynamicState2.enabled);
    ANGLE_FEATURE_CONDITION(&mFeatures, useDepthBiasEnableDynamicState,
                            mFeatures.supportsExtendedDynamicState2.enabled);

    // Disabled on Intel/Mesa due to driver bug (crbug.com/1379201).  This bug is fixed since Mesa
    // 22.2.0.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsLogicOpDynamicState,
        mFeatures.supportsExtendedDynamicState2.enabled &&
            mExtendedDynamicState2Features.extendedDynamicState2LogicOp == VK_TRUE &&
            !(IsLinux() && isIntel && driverVersion < angle::VersionTriple(22, 2, 0)) &&
            !(IsAndroid() && isGalaxyS23));

    // Older Samsung drivers with version < 24.0.0 have a bug in imageless framebuffer support.
    const bool isSamsungDriverWithImagelessFramebufferBug =
        isSamsung && driverVersion < angle::VersionTriple(24, 0, 0);
    // Qualcomm with imageless framebuffers, vkCreateFramebuffer loops forever (512.801).
    // http://issuetracker.google.com/369693310
    //
    // On some devices, crashes are seen with vkCmdBeginRenderPass, likely due to imageless
    // framebuffers (512.805).  http://crbug.com/415968761
    const bool isQualcommWithImagelessFramebufferBug =
        isQualcommProprietary && driverVersion < angle::VersionTriple(512, 806, 0);
    // Some ARM-based phones with the 38.0 and 38.1 ARM proprietary driver crash when creating
    // imageless framebuffers.
    const bool isARMProprietaryWithImagelessFramebufferBug =
        isARMProprietary && driverVersion >= angle::VersionTriple(38, 0, 0) &&
        driverVersion < angle::VersionTriple(38, 2, 0);
    // PowerVR with imageless framebuffer spends enormous amounts of time in framebuffer destruction
    // and creation. ANGLE doesn't cache imageless framebuffers, instead adding them to garbage
    // collection, expecting them to be lightweight.
    // http://issuetracker.google.com/372273294
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsImagelessFramebuffer,
                            mImagelessFramebufferFeatures.imagelessFramebuffer == VK_TRUE &&
                                !isSamsungDriverWithImagelessFramebufferBug &&
                                !isARMProprietaryWithImagelessFramebufferBug &&
                                !isQualcommWithImagelessFramebufferBug && !isPowerVR);

    if (ExtensionFound(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, deviceExtensionNames))
    {
        queryAndCacheFragmentShadingRates();
    }

    // Support GL_QCOM_shading_rate extension
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsFragmentShadingRate,
                            canSupportFragmentShadingRate());

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsPrimitiveFragmentShadingRate,
                            mFragmentShadingRateFeatures.primitiveFragmentShadingRate == VK_TRUE);

    // Support QCOM foveated rendering extensions.
    // Gated on supportsImagelessFramebuffer and supportsRenderPassLoadStoreOpNone
    // to reduce code complexity.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsFoveatedRendering,
                            mFeatures.supportsImagelessFramebuffer.enabled &&
                                mFeatures.supportsRenderPassLoadStoreOpNone.enabled &&
                                mFeatures.supportsFragmentShadingRate.enabled &&
                                canSupportFoveatedRendering());

    // Force CPU based generation of fragment shading rate attachment data if
    // VkPhysicalDeviceFeatures::shaderStorageImageExtendedFormats is not supported
    ANGLE_FEATURE_CONDITION(&mFeatures, generateFragmentShadingRateAttchementWithCpu,
                            mPhysicalDeviceFeatures.shaderStorageImageExtendedFormats != VK_TRUE);

    // We can use the interlock to support GL_ANGLE_shader_pixel_local_storage_coherent.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsFragmentShaderPixelInterlock,
        mFragmentShaderInterlockFeatures.fragmentShaderPixelInterlock == VK_TRUE);

    // The VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT behavior is used by
    // ANGLE, which requires the robustBufferAccess feature to be available.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsPipelineRobustness,
                            mPipelineRobustnessFeatures.pipelineRobustness == VK_TRUE &&
                                mPhysicalDeviceFeatures.robustBufferAccess);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsPipelineProtectedAccess,
                            mPipelineProtectedAccessFeatures.pipelineProtectedAccess == VK_TRUE &&
                                mProtectedMemoryFeatures.protectedMemory == VK_TRUE);

    // VK_EXT_graphics_pipeline_library is available on NVIDIA drivers earlier
    // than version 531, but there are transient visual glitches with rendering
    // on those earlier versions.  http://anglebug.com/42266655
    //
    // On RADV, creating graphics pipeline can crash in the driver.  http://crbug.com/1497512
    //
    // Some unacceptable performance degradation has been observed on device with ARM proprietary
    // driver when graphics pipeline is enabled, therefore it's recommended to disable it until the
    // problematic area gets addressed and fixed. http://anglebug.com/404581992
    //
    // PowerVR graphics pipeline support is in development and not yet tested with ANGLE.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsGraphicsPipelineLibrary,
                            mGraphicsPipelineLibraryFeatures.graphicsPipelineLibrary == VK_TRUE &&
                                (!isNvidia || driverVersion >= angle::VersionTriple(531, 0, 0)) &&
                                !isRADV && !isARMProprietary && !isPowerVR);

    // When VK_EXT_graphics_pipeline_library is not used:
    //
    //   The following drivers are known to key the pipeline cache blobs with vertex input and
    //   fragment output state, causing draw-time pipeline creation to miss the cache regardless of
    //   warm up:
    //
    //     - ARM proprietary drivers
    //     - Imagination drivers
    //
    //   The following drivers are instead known to _not_ include said state, and hit the cache at
    //   draw time.
    //
    //     - SwiftShader
    //     - Open source Qualcomm drivers
    //
    //   The situation is unknown for other drivers.
    //
    //   Additionally, numerous tests that previously never created a Vulkan pipeline fail or crash
    //   on proprietary Qualcomm drivers when they do during cache warm up.  On Intel/Linux, one
    //   trace shows flakiness with this.
    //
    // When VK_EXT_graphics_pipeline_library is used, warm up is always enabled as the chances of
    // blobs being reused is very high.
    const bool libraryBlobsAreReusedByMonolithicPipelines = !isARMProprietary && !isPowerVR;
    ANGLE_FEATURE_CONDITION(
        &mFeatures, warmUpPipelineCacheAtLink,
        mFeatures.supportsGraphicsPipelineLibrary.enabled ||
            (libraryBlobsAreReusedByMonolithicPipelines && !isQualcommProprietary &&
             !(IsLinux() && isIntel) && !(IsChromeOS() && isSwiftShader)));

    // On SwiftShader, no data is retrieved from the pipeline cache, so there is no reason to
    // serialize it or put it in the blob cache.
    // For Windows NVIDIA Vulkan driver, Vulkan pipeline cache will only generate one
    // single huge cache for one process shared by all graphics pipelines in the same process,
    // which can be huge. zlib take long time to compress it.
    ANGLE_FEATURE_CONDITION(&mFeatures, skipPipelineCacheSerialization, isSwiftShader || isNvidia);

    // Practically all drivers still prefer to do cross-stage linking.
    // graphicsPipelineLibraryFastLinking allows them to quickly produce working pipelines, but it
    // is typically not as efficient as complete pipelines.
    //
    // Unfortunately, the monolithic pipeline is not required to produce the exact same output as
    // linked-pipelines, which violates OpenGL ES rules:
    //
    //   Rule 4 The same vertex or fragment shader will produce the same result when run multiple
    //   times with the same input. The wording 'the same shader' means a program object that is
    //   populated with the same source strings, which are compiled and then linked, possibly
    //   multiple times, and which program object is then executed using the same GL state vector.
    //   Invariance is relaxed for shaders with side effects, such as image stores, image atomic
    //   operations, or accessing atomic counters (see section A.5)
    //
    // For that reason, this feature is disabled by default.  An application that does not rely on
    // the above rule and would like to benefit from the gains may override this.
    ANGLE_FEATURE_CONDITION(&mFeatures, preferMonolithicPipelinesOverLibraries,
                            mFeatures.supportsGraphicsPipelineLibrary.enabled && false);

    // To avoid memory bloating due to using pipeline caches per program, the pipeline cache in the
    // renderer can be used.
    ANGLE_FEATURE_CONDITION(&mFeatures, preferGlobalPipelineCache,
                            isNvidia || (isAMD && !isRADV) || isSamsung || isQualcommProprietary);

    // Whether the pipeline caches should merge into the global pipeline cache.  This should only be
    // enabled on platforms if:
    //
    // - preferGlobalPipelineCache is not enabled
    // - VK_EXT_graphics_pipeline_library is not supported.  In that case, only the program's cache
    //   used during warm up is merged into the global cache for later monolithic pipeline creation.
    // - VK_EXT_graphics_pipeline_library is supported, monolithic pipelines are preferred, and the
    //   driver is able to reuse blobs from partial pipelines when creating monolithic pipelines.
    ANGLE_FEATURE_CONDITION(&mFeatures, mergeProgramPipelineCachesToGlobalCache,
                            !mFeatures.preferGlobalPipelineCache.enabled &&
                                (!mFeatures.supportsGraphicsPipelineLibrary.enabled ||
                                 (mFeatures.preferMonolithicPipelinesOverLibraries.enabled &&
                                  libraryBlobsAreReusedByMonolithicPipelines)));

    // Whether all accesses to pipeline cache need to be externally synchronized. This should only
    // be enabled on platforms if:
    //
    // - mergeProgramPipelineCachesToGlobalCache is enabled
    // - preferMonolithicPipelinesOverLibraries is enabled
    // - stale nvidia drivers
    ANGLE_FEATURE_CONDITION(&mFeatures, externallySynchronizePipelineCacheAccess,
                            mFeatures.mergeProgramPipelineCachesToGlobalCache.enabled ||
                                mFeatures.preferMonolithicPipelinesOverLibraries.enabled ||
                                (isNvidia && driverVersion < angle::VersionTriple(441, 0, 0)));

    ANGLE_FEATURE_CONDITION(&mFeatures, enableAsyncPipelineCacheCompression, true);

    // Enable using an extra submit fence for the command batches. In case there is an external
    // fence during the main submission, this extra fence will be used for an empty submission right
    // after it.
    ANGLE_FEATURE_CONDITION(&mFeatures, enableExtraSubmitFence, false);

    // Sync monolithic pipelines to the blob cache occasionally on platforms that would benefit from
    // it:
    //
    // - VK_EXT_graphics_pipeline_library is not supported, and the program cache is not warmed up:
    //   If the pipeline cache is being warmed up at link time, the blobs corresponding to each
    //   program is individually retrieved and stored in the blob cache already.
    // - VK_EXT_graphics_pipeline_library is supported, but monolithic pipelines are still
    //   preferred, and the cost of syncing the large cache is acceptable.
    //
    // Otherwise monolithic pipelines are recreated on every run.
    const bool hasNoPipelineWarmUp = !mFeatures.supportsGraphicsPipelineLibrary.enabled &&
                                     !mFeatures.warmUpPipelineCacheAtLink.enabled;
    const bool canSyncLargeMonolithicCache =
        mFeatures.supportsGraphicsPipelineLibrary.enabled &&
        mFeatures.preferMonolithicPipelinesOverLibraries.enabled &&
        (!IsAndroid() || mFeatures.enableAsyncPipelineCacheCompression.enabled);
    ANGLE_FEATURE_CONDITION(&mFeatures, syncMonolithicPipelinesToBlobCache,
                            !mFeatures.skipPipelineCacheSerialization.enabled &&
                                (hasNoPipelineWarmUp || canSyncLargeMonolithicCache));

    // Enable the feature on Samsung by default, because it has big blob cache.
    ANGLE_FEATURE_CONDITION(&mFeatures, useDualPipelineBlobCacheSlots, isSamsung);

    // Disable by default, because currently it is uncommon that blob cache supports storing
    // zero sized blobs (or erasing blobs).
    ANGLE_FEATURE_CONDITION(&mFeatures, useEmptyBlobsToEraseOldPipelineCacheFromBlobCache, false);

    // Assume that platform has blob cache that has LRU eviction.
    ANGLE_FEATURE_CONDITION(&mFeatures, hasBlobCacheThatEvictsOldItemsFirst, true);
    // Also assume that platform blob cache evicts only minimum number of items when it has LRU,
    // in which case verification is not required.
    ANGLE_FEATURE_CONDITION(&mFeatures, verifyPipelineCacheInBlobCache,
                            !mFeatures.hasBlobCacheThatEvictsOldItemsFirst.enabled);

    // On ARM proprietary driver, dynamic state for stencil write mask doesn't work correctly in the
    // presence of discard or alpha to coverage, if the static state provided when creating the
    // pipeline has a value of 0. Fixed in r43p0 release.
    ANGLE_FEATURE_CONDITION(&mFeatures, useNonZeroStencilWriteMaskStaticState,
                            isARMProprietary && driverVersion < angle::VersionTriple(43, 0, 0));

    // On some vendors per-sample shading is not enabled despite the presence of a Sample
    // decoration. Guard against this by parsing shader for "sample" decoration and explicitly
    // enabling per-sample shading pipeline state.
    ANGLE_FEATURE_CONDITION(&mFeatures, explicitlyEnablePerSampleShading, !isQualcommProprietary);

    // Force to create swapchain with continuous refresh on shared present. Disabled by default.
    // Only enable it on integrations without EGL_FRONT_BUFFER_AUTO_REFRESH_ANDROID passthrough.
    ANGLE_FEATURE_CONDITION(&mFeatures, forceContinuousRefreshOnSharedPresent, false);

    // Enable setting frame timestamp surface attribute on Android platform.
    // Frame timestamp is enabled by calling into "vkGetPastPresentationTimingGOOGLE"
    // which, on Android platforms, makes the necessary ANativeWindow API calls.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsTimestampSurfaceAttribute,
                            IsAndroid() && ExtensionFound(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
                                                          deviceExtensionNames));

    // Only enable VK_EXT_host_image_copy on hardware where identicalMemoryTypeRequirements is set.
    // That lets ANGLE avoid having to fallback to non-host-copyable image allocations if the
    // host-copyable one fails due to out-of-that-specific-kind-of-memory.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsHostImageCopy,
                            mHostImageCopyFeatures.hostImageCopy == VK_TRUE &&
                                mHostImageCopyProperties.identicalMemoryTypeRequirements);
    // Software renderers always benefit from host-image-copy, because that's just memcpy.
    ANGLE_FEATURE_CONDITION(&mFeatures, allowHostImageCopyAfterInitialUpload, isSoftwareRenderer);

    // 1) host vk driver does not natively support ETC format.
    // 2) host vk driver supports BC format.
    // 3) host vk driver supports subgroup instructions: clustered, shuffle.
    //    * This limitation can be removed if necessary.
    // 4) host vk driver has maxTexelBufferSize >= 64M.
    //    * Usually on desktop device the limit is more than 128M. we may switch to dynamic
    //    decide cpu or gpu upload texture based on texture size.
    constexpr VkSubgroupFeatureFlags kRequiredSubgroupOp =
        VK_SUBGROUP_FEATURE_SHUFFLE_BIT | VK_SUBGROUP_FEATURE_CLUSTERED_BIT;
    static constexpr bool kSupportTranscodeEtcToBc = false;
    static constexpr uint32_t kMaxTexelBufferSize  = 64 * 1024 * 1024;
    const VkPhysicalDeviceLimits &limitsVk         = mPhysicalDeviceProperties.limits;
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsComputeTranscodeEtcToBc,
                            !mPhysicalDeviceFeatures.textureCompressionETC2 &&
                                kSupportTranscodeEtcToBc &&
                                (mSubgroupProperties.supportedOperations & kRequiredSubgroupOp) ==
                                    kRequiredSubgroupOp &&
                                (limitsVk.maxTexelBufferElements >= kMaxTexelBufferSize));

    // Limit GL_MAX_SHADER_STORAGE_BLOCK_SIZE to 256MB on older ARM hardware.
    ANGLE_FEATURE_CONDITION(&mFeatures, limitMaxStorageBufferSize, isMaliJobManagerBasedGPU);

    // http://anglebug.com/42265782
    // Flushing mutable textures causes flakes in perf tests using Windows/Intel GPU. Failures are
    // due to lost context/device.
    // http://b/278600575
    // Flushing mutable texture is disabled for discrete GPUs to mitigate possible VRAM OOM.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, mutableMipmapTextureUpload,
        canPreferDeviceLocalMemoryHostVisible(mPhysicalDeviceProperties.deviceType));

    // Allow passthrough of EGL colorspace attributes on Android platform and for vendors that
    // are known to support wide color gamut.
    ANGLE_FEATURE_CONDITION(&mFeatures, eglColorspaceAttributePassthrough,
                            IsAndroid() && isSamsung);

    // GBM does not have a VkSurface hence it does not support presentation through a Vulkan queue.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsPresentation,
                            nativeWindowSystem != angle::NativeWindowSystem::Gbm);

    // For tiled renderer, the renderpass query result may not available until the entire renderpass
    // is completed. This may cause a bubble in the application thread waiting result to be
    // available. When this feature flag is enabled, we will issue an immediate flush when we detect
    // there is switch from query enabled draw to query disabled draw. Since most apps uses bunch of
    // query back to back, this should only introduce one extra flush per frame.
    // https://issuetracker.google.com/250706693
    ANGLE_FEATURE_CONDITION(&mFeatures, preferSubmitOnAnySamplesPassedQueryEnd,
                            isTileBasedRenderer && !isQualcommProprietary);

    // ARM proprietary driver appears having a bug that if we did not wait for submission to
    // complete, but call vkGetQueryPoolResults(VK_QUERY_RESULT_WAIT_BIT), it may result
    // VK_NOT_READY. https://issuetracker.google.com/253522366
    //
    // Workaround for nvidia earlier version driver which appears having a bug that On older nvidia
    // driver, vkGetQueryPoolResult() with VK_QUERY_RESULT_WAIT_BIT may result in incorrect result.
    // In that case we force into CPU wait for submission to complete. http://anglebug.com/42265186
    ANGLE_FEATURE_CONDITION(
        &mFeatures, forceWaitForSubmissionToCompleteForQueryResult,
        isARMProprietary || (isNvidia && driverVersion < angle::VersionTriple(470, 0, 0)));

    // Some ARM proprietary drivers may not free memory in "vkFreeCommandBuffers()" without
    // VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT flag.
    ANGLE_FEATURE_CONDITION(&mFeatures, useResetCommandBufferBitForSecondaryPools,
                            isARMProprietary);

    // Intel, Mali and AMD mesa drivers need depthBiasConstantFactor to be doubled to align with GL.
    ANGLE_FEATURE_CONDITION(&mFeatures, doubleDepthBiasConstantFactor,
                            (isIntel && !IsWindows()) || isMesaPanVK || isRADV || isNvidia);

    // Required to pass android.media.codec.cts.EncodeDecodeTest
    // https://issuetracker.google.com/246218584
    ANGLE_FEATURE_CONDITION(
        &mFeatures, mapUnspecifiedColorSpaceToPassThrough,
        ExtensionFound(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, mEnabledInstanceExtensions));

    ANGLE_FEATURE_CONDITION(&mFeatures, enablePipelineCacheDataCompression, true);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsTimelineSemaphore,
                            mTimelineSemaphoreFeatures.timelineSemaphore == VK_TRUE);

#if defined(ANGLE_PLATFORM_ANDROID)
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsExternalFormatResolve,
                            mExternalFormatResolveFeatures.externalFormatResolve == VK_TRUE);

    // We can fully support GL_EXT_YUV_target iff we have support for
    // VK_ANDROID_external_format_resolve.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsYuvTarget,
                            mFeatures.supportsExternalFormatResolve.enabled);

#else
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsExternalFormatResolve, false);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsYuvTarget, false);
#endif

    // VkEvent has much bigger overhead. Until we know that it helps desktop GPUs, we restrict it to
    // TBRs. Also enabled for testing drivers to get more test coverage in bots.
    ANGLE_FEATURE_CONDITION(&mFeatures, useVkEventForImageBarrier,
                            isTileBasedRenderer || isSoftwareRenderer);
    ANGLE_FEATURE_CONDITION(&mFeatures, useVkEventForBufferBarrier,
                            isTileBasedRenderer || isSoftwareRenderer);

    // VkEvent is better than pipeline barriers on Mali GPUs with vertex sync deferred mode enabled.
    // Be unable to query GPU features currently, we restrict it to Mali non-JobManagerBased GPUs.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, isVertexSyncDeferred,
        mFeatures.useVkEventForBufferBarrier.enabled && isARM && !isMaliJobManagerBasedGPU);

    // vkCmdResetEvent adds extra GPU overhead and ARM prefers CPU overhead of creating/destroying
    // VkEvent instead of GPU overhead associated with vkCmdResetEvent.
    ANGLE_FEATURE_CONDITION(&mFeatures, recycleVkEvent, isSwiftShader);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDynamicRendering,
                            mDynamicRenderingFeatures.dynamicRendering == VK_TRUE);

    // Don't enable VK_KHR_maintenance5 without VK_KHR_dynamic_rendering
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsMaintenance5,
                            mFeatures.supportsDynamicRendering.enabled &&
                                mMaintenance5Features.maintenance5 == VK_TRUE);

    // Disabled on Nvidia driver due to a bug with attachment location mapping, resulting in
    // incorrect rendering in the presence of gaps in locations.  http://anglebug.com/372883691.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsDynamicRenderingLocalRead,
        mFeatures.supportsDynamicRendering.enabled &&
            mDynamicRenderingLocalReadFeatures.dynamicRenderingLocalRead == VK_TRUE && !isNvidia);

    // Using dynamic rendering when VK_KHR_dynamic_rendering_local_read is available, because that's
    // needed for framebuffer fetch, MSRTT and advanced blend emulation.
    //
    // VK_EXT_legacy_dithering needs to be at version 2 and VK_KHR_maintenance5 to be usable with
    // dynamic rendering.  If only version 1 is exposed, it's not sacrificied for dynamic rendering
    // and render pass objects are continued to be used.
    //
    // Using dynamic rendering when emulating GL_EXT_multisampled_render_to_texture is
    // supported. Except for SwiftShader, which crashes when binding non-attachment textures as
    // input attachments.
    //
    // Use of dynamic rendering is disabled on older ARM proprietary drivers due to driver bugs
    // (http://issuetracker.google.com/356051947).
    //
    // Use of dynamic rendering is disabled on older Qualcomm drivers due to driver bugs
    // (http://crbug.com/415738891).
    //
    // Use of dynamic rendering on PowerVR devices is disabled for performance reasons
    // (http://issuetracker.google.com/372273294).
    const bool hasLegacyDitheringV1 =
        mFeatures.supportsLegacyDithering.enabled &&
        (mLegacyDitheringVersion < 2 || !mFeatures.supportsMaintenance5.enabled);
    ANGLE_FEATURE_CONDITION(
        &mFeatures, preferDynamicRendering,
        mFeatures.supportsDynamicRendering.enabled &&
            mFeatures.supportsDynamicRenderingLocalRead.enabled && !hasLegacyDitheringV1 &&
            !isSwiftShader &&
            !(isARMProprietary && driverVersion < angle::VersionTriple(52, 0, 0)) &&
            !(isQualcommProprietary && driverVersion < angle::VersionTriple(512, 801, 0)) &&
            !isPowerVR);

    // On tile-based renderers, breaking the render pass is costly.  Changing into and out of
    // framebuffer fetch causes the render pass to break so that the layout of the color attachments
    // can be adjusted.  On such hardware, the switch to framebuffer fetch mode is made permanent so
    // such render pass breaks don't happen.
    //
    // This only applies to legacy render passes; with dynamic rendering there is no render pass
    // break when switching framebuffer fetch usage.
    ANGLE_FEATURE_CONDITION(&mFeatures, permanentlySwitchToFramebufferFetchMode,
                            isTileBasedRenderer && !mFeatures.preferDynamicRendering.enabled);

    // Vulkan supports depth/stencil input attachments same as it does with color.
    // GL_ARM_shader_framebuffer_fetch_depth_stencil requires coherent behavior however, so this
    // extension is exposed only where coherent framebuffer fetch is available.
    //
    // Additionally, the implementation assumes VK_KHR_dynamic_rendering_local_read to avoid
    // complications with VkRenderPass objects.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsShaderFramebufferFetchDepthStencil,
        mFeatures.supportsShaderFramebufferFetch.enabled &&
            mRasterizationOrderAttachmentAccessFeatures.rasterizationOrderDepthAttachmentAccess ==
                VK_TRUE &&
            mRasterizationOrderAttachmentAccessFeatures.rasterizationOrderStencilAttachmentAccess ==
                VK_TRUE &&
            mFeatures.preferDynamicRendering.enabled);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsSynchronization2,
                            mSynchronization2Features.synchronization2 == VK_TRUE);

    // Disable descriptorSet cache for testing drivers to ensure the code path gets tested.
    ANGLE_FEATURE_CONDITION(&mFeatures, descriptorSetCache, !isSoftwareRenderer);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsImageCompressionControl,
                            mImageCompressionControlFeatures.imageCompressionControl == VK_TRUE);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsImageCompressionControlSwapchain,
        mImageCompressionControlSwapchainFeatures.imageCompressionControlSwapchain == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsAstcSliced3d, isARM);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsTextureCompressionAstcHdr,
        mTextureCompressionASTCHDRFeatures.textureCompressionASTC_HDR == VK_TRUE);

    // Not all hardware can support 3D textures with ASTC HDR (http://anglebug.com/416095435)
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsAstcHdr3dTextures,
        mFeatures.supportsTextureCompressionAstcHdr.enabled && CanSupportAstcHdr3D(this));

    // http://anglebug.com/42264006
    // GL_EXT_clip_cull_distance also adds features to geometry and tessellation shaders, which are
    // currently disabled.
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsClipCullDistanceInGSAndTS, false);

    // http://anglebug.com/442950569
    // feature is currently experimental (i.e. dont default enable even if device supports it)
    // keep it as an opt-in override instead
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsBufferDeviceAddress, false);

    // Disable memory report feature overrides if extension is not supported.
    if ((mFeatures.logMemoryReportCallbacks.enabled || mFeatures.logMemoryReportStats.enabled) &&
        !mMemoryReportFeatures.deviceMemoryReport)
    {
        WARN() << "Disabling the following feature(s) because driver does not support "
                  "VK_EXT_device_memory_report extension:";
        if (getFeatures().logMemoryReportStats.enabled)
        {
            WARN() << "\tlogMemoryReportStats";
            mFeatures.logMemoryReportStats.applyOverride(false);
        }
        if (getFeatures().logMemoryReportCallbacks.enabled)
        {
            WARN() << "\tlogMemoryReportCallbacks";
            mFeatures.logMemoryReportCallbacks.applyOverride(false);
        }
    }

    // Check if VK implementation needs to strip-out non-semantic reflection info from shader module
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsShaderNonSemanticInfo,
        ExtensionFound(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, deviceExtensionNames));

    // Unconditionally enable GL_ANGLE_rgbx_internal_format extension
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsAngleRgbxInternalFormat, true);

    // Don't expose GL_APPLE_clip_distance on Samsung devices
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsAppleClipDistance, !isSamsung);

    // Force enable sample usage for AHB images for Samsung
    ANGLE_FEATURE_CONDITION(&mFeatures, forceSampleUsageForAhbBackedImages, isSamsung);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsAstcDecodeMode,
        ExtensionFound(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsAstcDecodeModeRgb9e5,
                            mPhysicalDeviceAstcDecodeFeatures.decodeModeSharedExponent == VK_TRUE &&
                                mFeatures.supportsAstcDecodeMode.enabled);

    // Disable uniformAndStorageBuffer16BitAccess and supportsShaderFloat16 on Samsung devices
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supports16BitUniformAndStorageBuffer,
        m16BitStorageFeatures.uniformAndStorageBuffer16BitAccess == VK_TRUE && !isSamsung);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsShaderFloat16,
                            mShaderFloat16Int8Features.shaderFloat16 == VK_TRUE && !isSamsung);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsShaderDemoteToHelperInvocation,
        mShaderDemoteToHelperInvocationFeatures.shaderDemoteToHelperInvocation == VK_TRUE);

    // http://anglebug.com/440941211:
    // Disable the feature on Windows Intel because some shaders using 16-bit floats crash
    // http://anglebug.com/443302350
    // http://anglebug.com/443302625
    // Temporarily disable the feature on PowerVR while the above 2 bugs are under investigations
    // http://anglebug.com/446159597
    // Disable the feature on Samsung devices
    // https://b.corp.google.com/issues/446844410
    // Disable on driver implementations other than ARM proprietary until the performance impact on
    // them is verified.
    ANGLE_FEATURE_CONDITION(&mFeatures, convertLowpAndMediumpFloatUniformsTo16Bits,
                            mFeatures.supports16BitUniformAndStorageBuffer.enabled &&
                                mFeatures.supportsShaderFloat16.enabled && isARMProprietary);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsUnifiedImageLayouts,
                            mUnifiedImageLayoutsFeatures.unifiedImageLayouts == VK_TRUE);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsGlobalPriority,
        ExtensionFound(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, deviceExtensionNames));

    // REALTIME priority is not permitted on most operating systems.  This feature is limited to
    // Android for now.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsGlobalPriorityQuery,
        mFeatures.supportsGlobalPriority.enabled &&
            mPhysicalDeviceGlobalPriorityQueryFeatures.globalPriorityQuery == VK_TRUE &&
            IsAndroid());

    // There are use cases where synchronization is not performed properly when texture is modified
    // between different contexts. To avoid rendering artifacts, force submit staged updates.
    ANGLE_FEATURE_CONDITION(&mFeatures, forceSubmitImmutableTextureUpdates, true);

    // Don't expose these 2 extensions on Samsung devices -
    // 1. GL_ANGLE_shader_pixel_local_storage
    // 2. GL_ANGLE_shader_pixel_local_storage_coherent
    ANGLE_FEATURE_CONDITION(&mFeatures, supportShaderPixelLocalStorageAngle, !isSamsung);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportFragmentShadingRateExtExtensions,
                            mFeatures.supportsFragmentShadingRate.enabled && !isSamsung);

    // http://issuetracker.google.com/372273294
    // Older Unity titles are calling glClear on depth/stencil followed by a call to
    // glInvalidateFramebuffer, but expect the contents of the framebuffer to still be valid.
    // In this case, we can't drop the clears that we've deferred.
    ANGLE_FEATURE_CONDITION(&mFeatures, dropDepthStencilClearOnInvalidate, false);

    // VK_QCOM_tile_memory_heap is available. Earlier qualcomm driver has a bug with copying stencil
    // data from tile memory.
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsTileMemoryHeap,
        mTileMemoryHeapFeatures.tileMemoryHeap == VK_TRUE &&
            !(isQualcommProprietary && driverVersion < angle::VersionTriple(512, 868, 1)));

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsAstc3d,
                            mTextureCompressionASTC3DFeatures.textureCompressionASTC_3D == VK_TRUE);

    // This feature flag shows reduced CPU instruction.
    // Older Samsung drivers with version < 25.0.0 have a bug in vertex attribute packing that
    // causes failures in some deqp tests when enableMergeClientAttribBuffer feature is enabled.
    const bool isSamsungDriverWithVertexAttributePackingBug =
        isSamsung && driverVersion < angle::VersionTriple(25, 0, 0);
    ANGLE_FEATURE_CONDITION(&mFeatures, enableMergeClientAttribBuffer,
                            !isSamsungDriverWithVertexAttributePackingBug);

    // Enable this feature to avoid image allocation overhead when repeatedly uploading the same
    // texture that has already been uploaded, outside a render pass.
    ANGLE_FEATURE_CONDITION(&mFeatures, avoidImageGhostOutsideRenderPass, !isARM);
}

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Features specific to OpenCL backend
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
void Renderer::initOpenCLFeatures(const vk::ExtensionNameList &deviceExtensionNames,
                                  const angle::FeatureOverrides &featureOverrides)
{
    const bool isSamsung = IsSamsung(mPhysicalDeviceProperties.vendorID);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsExternalMemoryHost,
        ExtensionFound(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, deviceExtensionNames));

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsUniformBufferStandardLayout,
        mUniformBufferStandardLayoutFeatures.uniformBufferStandardLayout == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsShaderFloat16,
                            mShaderFloat16Int8Features.shaderFloat16 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsShaderInt8,
                            mShaderFloat16Int8Features.shaderInt8 == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsShaderIntegerDotProduct,
                            mShaderIntegerDotProductFeatures.shaderIntegerDotProduct == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsShaderFloat64,
                            mPhysicalDeviceFeatures.shaderFloat64 == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supportsShaderAtomicInt64,
                            mShaderAtomicInt64Features.shaderBufferInt64Atomics == VK_TRUE &&
                                mShaderAtomicInt64Features.shaderSharedInt64Atomics == VK_TRUE);

    // Rounding features from VK_KHR_float_controls extension
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDenormFtzFp16,
                            mFloatControlProperties.shaderDenormFlushToZeroFloat16 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDenormFtzFp32,
                            mFloatControlProperties.shaderDenormFlushToZeroFloat32 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDenormFtzFp64,
                            mFloatControlProperties.shaderDenormFlushToZeroFloat64 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDenormPreserveFp16,
                            mFloatControlProperties.shaderDenormPreserveFloat16 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDenormPreserveFp32,
                            mFloatControlProperties.shaderDenormPreserveFloat32 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsDenormPreserveFp64,
                            mFloatControlProperties.shaderDenormPreserveFloat64 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsRoundingModeRteFp16,
                            mFloatControlProperties.shaderRoundingModeRTEFloat16 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsRoundingModeRteFp32,
                            mFloatControlProperties.shaderRoundingModeRTEFloat32 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsRoundingModeRteFp64,
                            mFloatControlProperties.shaderRoundingModeRTEFloat64 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsRoundingModeRtzFp16,
                            mFloatControlProperties.shaderRoundingModeRTZFloat16 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsRoundingModeRtzFp32,
                            mFloatControlProperties.shaderRoundingModeRTZFloat32 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(&mFeatures, supportsRoundingModeRtzFp64,
                            mFloatControlProperties.shaderRoundingModeRTZFloat64 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSignedZeroInfNanPreserveFp16,
        mFloatControlProperties.shaderSignedZeroInfNanPreserveFloat16 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSignedZeroInfNanPreserveFp32,
        mFloatControlProperties.shaderSignedZeroInfNanPreserveFloat32 == VK_TRUE);
    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsSignedZeroInfNanPreserveFp64,
        mFloatControlProperties.shaderSignedZeroInfNanPreserveFloat64 == VK_TRUE);

    // 8bit storage features
    ANGLE_FEATURE_CONDITION(&mFeatures, supports8BitStorageBuffer,
                            m8BitStorageFeatures.storageBuffer8BitAccess == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supports8BitUniformAndStorageBuffer,
                            m8BitStorageFeatures.uniformAndStorageBuffer8BitAccess == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supports8BitPushConstant,
                            m8BitStorageFeatures.storagePushConstant8 == VK_TRUE);

    // 16bit storage features
    ANGLE_FEATURE_CONDITION(&mFeatures, supports16BitStorageBuffer,
                            m16BitStorageFeatures.storageBuffer16BitAccess == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supports16BitUniformAndStorageBuffer,
                            m16BitStorageFeatures.uniformAndStorageBuffer16BitAccess == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supports16BitPushConstant,
                            m16BitStorageFeatures.storagePushConstant16 == VK_TRUE);

    ANGLE_FEATURE_CONDITION(&mFeatures, supports16BitInputOutput,
                            m16BitStorageFeatures.storageInputOutput16 == VK_TRUE);

    // Enable the use of below native kernels
    // Each builtin kernel gets its own feature and condition, for now a single feature
    // condition is setup
    ANGLE_FEATURE_CONDITION(&mFeatures, usesNativeBuiltinClKernel, isSamsung);

    ANGLE_FEATURE_CONDITION(&mFeatures, debugClDumpCommandStream, false);

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsAmdShaderCoreProperties,
        ExtensionFound(VK_AMD_SHADER_CORE_PROPERTIES_EXTENSION_NAME, deviceExtensionNames));

    // Set limits to expose to OpenCL.
    // This information cannot yet be queried from the Vulkan device.
    if (isSamsung && mFeatures.supportsShaderFloat64.enabled)
    {
        mNativeVectorWidthDouble    = 1;
        mPreferredVectorWidthDouble = 1;
    }
    if (isSamsung && mFeatures.supportsShaderFloat16.enabled)
    {
        mNativeVectorWidthHalf    = 2;
        mPreferredVectorWidthHalf = 8;
    }

    // The OpenCL extension cl_khr_subgroups needs support for
    // Basic - for subgroup size and related ops and barrier ops
    // Vote - for subgroup all/any ops
    // Arithmetic - for subgroup reduce and scan ops
    constexpr VkSubgroupFeatureFlags kRequiredSubgroupBits = VK_SUBGROUP_FEATURE_BASIC_BIT |
                                                             VK_SUBGROUP_FEATURE_VOTE_BIT |
                                                             VK_SUBGROUP_FEATURE_ARITHMETIC_BIT;

    ANGLE_FEATURE_CONDITION(
        &mFeatures, supportsClKhrSubgroups,
        (mSubgroupProperties.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0 &&
            (mSubgroupProperties.supportedOperations & kRequiredSubgroupBits) ==
                kRequiredSubgroupBits);
}

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// All features set in this function must use the applyOverride method
// so that the value set is sticky
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
void Renderer::appBasedFeatureOverrides(const vk::ExtensionNameList &extensions) {}

angle::Result Renderer::initPipelineCache(vk::ErrorContext *context,
                                          vk::PipelineCache *pipelineCache,
                                          bool *success)
{
    angle::MemoryBuffer initialData;
    if (!mFeatures.disablePipelineCacheLoadForTesting.enabled)
    {
        ANGLE_TRY(GetAndDecompressPipelineCacheVk(context, mGlobalOps, &initialData, success));
    }

    VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};

    pipelineCacheCreateInfo.sType           = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
    pipelineCacheCreateInfo.flags           = 0;
    pipelineCacheCreateInfo.initialDataSize = *success ? initialData.size() : 0;
    pipelineCacheCreateInfo.pInitialData    = *success ? initialData.data() : nullptr;

    ANGLE_VK_TRY(context, pipelineCache->init(mDevice, pipelineCacheCreateInfo));

    return angle::Result::Continue;
}

angle::Result Renderer::ensurePipelineCacheInitialized(vk::ErrorContext *context)
{
    // If it is initialized already, there is nothing to do
    if (mPipelineCacheInitialized)
    {
        return angle::Result::Continue;
    }

    std::unique_lock<angle::SimpleMutex> lock(mPipelineCacheMutex);

    // If another thread initialized it first don't redo it
    if (mPipelineCacheInitialized)
    {
        return angle::Result::Continue;
    }

    // We should now create the pipeline cache with the blob cache pipeline data.
    bool loadedFromBlobCache = false;
    ANGLE_TRY(initPipelineCache(context, &mPipelineCache, &loadedFromBlobCache));
    if (loadedFromBlobCache)
    {
        ANGLE_TRY(getLockedPipelineCacheDataIfNew(context, &mPipelineCacheSizeAtLastSync,
                                                  mPipelineCacheSizeAtLastSync, nullptr));
    }

    mPipelineCacheInitialized = true;

    return angle::Result::Continue;
}

size_t Renderer::getNextPipelineCacheBlobCacheSlotIndex(size_t *previousSlotIndexOut)
{
    if (previousSlotIndexOut != nullptr)
    {
        *previousSlotIndexOut = mCurrentPipelineCacheBlobCacheSlotIndex;
    }
    if (getFeatures().useDualPipelineBlobCacheSlots.enabled)
    {
        mCurrentPipelineCacheBlobCacheSlotIndex = 1 - mCurrentPipelineCacheBlobCacheSlotIndex;
    }
    return mCurrentPipelineCacheBlobCacheSlotIndex;
}

size_t Renderer::updatePipelineCacheChunkCount(size_t chunkCount)
{
    const size_t previousChunkCount = mPipelineCacheChunkCount;
    mPipelineCacheChunkCount        = chunkCount;
    return previousChunkCount;
}

angle::Result Renderer::getPipelineCache(vk::ErrorContext *context,
                                         vk::PipelineCacheAccess *pipelineCacheOut)
{
    ANGLE_TRY(ensurePipelineCacheInitialized(context));

    angle::SimpleMutex *pipelineCacheMutex =
        context->getFeatures().externallySynchronizePipelineCacheAccess.enabled
            ? &mPipelineCacheMutex
            : nullptr;

    pipelineCacheOut->init(&mPipelineCache, pipelineCacheMutex);
    return angle::Result::Continue;
}

angle::Result Renderer::mergeIntoPipelineCache(vk::ErrorContext *context,
                                               const vk::PipelineCache &pipelineCache)
{
    // It is an error to call into this method when the feature is disabled.
    ASSERT(context->getFeatures().mergeProgramPipelineCachesToGlobalCache.enabled);

    vk::PipelineCacheAccess globalCache;
    ANGLE_TRY(getPipelineCache(context, &globalCache));

    globalCache.merge(this, pipelineCache);

    return angle::Result::Continue;
}

const gl::Caps &Renderer::getNativeCaps() const
{
    ensureCapsInitialized();
    return mNativeCaps;
}

const gl::TextureCapsMap &Renderer::getNativeTextureCaps() const
{
    ensureCapsInitialized();
    return mNativeTextureCaps;
}

const gl::Extensions &Renderer::getNativeExtensions() const
{
    ensureCapsInitialized();
    return mNativeExtensions;
}

const gl::Limitations &Renderer::getNativeLimitations() const
{
    ensureCapsInitialized();
    return mNativeLimitations;
}

const ShPixelLocalStorageOptions &Renderer::getNativePixelLocalStorageOptions() const
{
    return mNativePLSOptions;
}

void Renderer::initializeFrontendFeatures(angle::FrontendFeatures *features) const
{
    const bool isSwiftShader =
        IsSwiftshader(mPhysicalDeviceProperties.vendorID, mPhysicalDeviceProperties.deviceID);
    const bool isSamsung = IsSamsung(mPhysicalDeviceProperties.vendorID);

    // Hopefully-temporary work-around for a crash on SwiftShader.  An Android process is turning
    // off GL error checking, and then asking ANGLE to write past the end of a buffer.
    // https://issuetracker.google.com/issues/220069903
    ANGLE_FEATURE_CONDITION(features, forceGlErrorChecking, (IsAndroid() && isSwiftShader));

    // Disable shader and program caching on Samsung devices.
    ANGLE_FEATURE_CONDITION(features, cacheCompiledShader, !isSamsung);
    ANGLE_FEATURE_CONDITION(features, disableProgramCaching, isSamsung);

    // https://issuetracker.google.com/292285899
    ANGLE_FEATURE_CONDITION(features, uncurrentEglSurfaceUponSurfaceDestroy, true);

    // The Vulkan backend's handling of compile and link is thread-safe
    ANGLE_FEATURE_CONDITION(features, compileJobIsThreadSafe, true);
    ANGLE_FEATURE_CONDITION(features, linkJobIsThreadSafe, true);
    // Always run the link's warm up job in a thread.  It's an optimization only, and does not block
    // the link resolution.
    ANGLE_FEATURE_CONDITION(features, alwaysRunLinkSubJobsThreaded, true);

    // Enable the program binary blob compression for glGetProgramBinary and glGetProgramiv.  It
    // will be decompressed when loading the program binary by glProgramBinary.
    ANGLE_FEATURE_CONDITION(features, compressProgramBinaryBlob, isSamsung);
}

angle::Result Renderer::getLockedPipelineCacheDataIfNew(vk::ErrorContext *context,
                                                        size_t *pipelineCacheSizeOut,
                                                        size_t lastSyncSize,
                                                        std::vector<uint8_t> *pipelineCacheDataOut)
{
    // Because this function may call |getCacheData| twice, |mPipelineCacheMutex| is not passed to
    // |PipelineAccessCache|, and is expected to be locked once **by the caller**.
    mPipelineCacheMutex.assertLocked();

    vk::PipelineCacheAccess globalCache;
    globalCache.init(&mPipelineCache, nullptr);

    ANGLE_VK_TRY(context, globalCache.getCacheData(context, pipelineCacheSizeOut, nullptr));

    // If the cache data is unchanged since last sync, don't retrieve the data.  Also, make sure we
    // will receive enough data to hold the pipeline cache header Table 7.  Layout for pipeline
    // cache header version VK_PIPELINE_CACHE_HEADER_VERSION_ONE.
    const size_t kPipelineCacheHeaderSize = 16 + VK_UUID_SIZE;
    if (*pipelineCacheSizeOut <= lastSyncSize || *pipelineCacheSizeOut < kPipelineCacheHeaderSize ||
        pipelineCacheDataOut == nullptr)
    {
        return angle::Result::Continue;
    }

    pipelineCacheDataOut->resize(*pipelineCacheSizeOut);
    VkResult result =
        globalCache.getCacheData(context, pipelineCacheSizeOut, pipelineCacheDataOut->data());
    if (ANGLE_UNLIKELY(result == VK_INCOMPLETE))
    {
        WARN()
            << "Received VK_INCOMPLETE when retrieving pipeline cache data, which should be "
               "impossible as the size query was previously done under the same lock, but this is "
               "a recoverable error";
    }
    else
    {
        ANGLE_VK_TRY(context, result);
    }

    // If vkGetPipelineCacheData ends up writing fewer bytes than requested, shrink the buffer to
    // avoid leaking garbage memory and potential rejection of the data by subsequent
    // vkCreatePipelineCache call.  Some drivers may ignore entire buffer if there padding present.
    ASSERT(*pipelineCacheSizeOut <= pipelineCacheDataOut->size());
    pipelineCacheDataOut->resize(*pipelineCacheSizeOut);

    return angle::Result::Continue;
}

angle::Result Renderer::syncPipelineCacheVk(const gl::Context *contextGL)
{
    // Skip syncing until pipeline cache is initialized.
    if (!mPipelineCacheInitialized)
    {
        return angle::Result::Continue;
    }
    ASSERT(mPipelineCache.valid());

    if (!mFeatures.syncMonolithicPipelinesToBlobCache.enabled)
    {
        return angle::Result::Continue;
    }

    if (--mPipelineCacheVkUpdateTimeout > 0)
    {
        return angle::Result::Continue;
    }

    mPipelineCacheVkUpdateTimeout =
        mFeatures.syncPipelineCacheToBlobCacheEveryFrame.enabled ? 1 : kPipelineCacheVkUpdatePeriod;

    ContextVk *contextVk = vk::GetImpl(contextGL);

    // Use worker thread pool to complete compression.
    // If the last task hasn't been finished, skip the syncing.
    if (mCompressEvent && !mCompressEvent->isReady())
    {
        ANGLE_PERF_WARNING(contextVk->getDebug(), GL_DEBUG_SEVERITY_LOW,
                           "Skip syncing pipeline cache data when the last task is not ready.");
        return angle::Result::Continue;
    }

    size_t pipelineCacheSize = 0;
    std::vector<uint8_t> pipelineCacheData;
    {
        std::unique_lock<angle::SimpleMutex> lock(mPipelineCacheMutex);
        ANGLE_TRY(getLockedPipelineCacheDataIfNew(
            contextVk, &pipelineCacheSize, mPipelineCacheSizeAtLastSync, &pipelineCacheData));
    }
    if (pipelineCacheData.empty())
    {
        return angle::Result::Continue;
    }
    mPipelineCacheSizeAtLastSync = pipelineCacheSize;

    vk::GlobalOps *globalOps = getGlobalOps();
    ASSERT(globalOps);
    if (mFeatures.enableAsyncPipelineCacheCompression.enabled)
    {
        // zlib compression ratio normally ranges from 2:1 to 5:1. Set kMaxTotalSize to 64M to
        // ensure the size can fit into the 32MB blob cache limit on supported platforms.
        constexpr size_t kMaxTotalSize = 64 * 1024 * 1024;

        // Create task to compress.
        mCompressEvent = contextGL->getWorkerThreadPool()->postWorkerTask(
            std::make_shared<CompressAndStorePipelineCacheTask>(
                globalOps, this, std::move(pipelineCacheData), kMaxTotalSize));
    }
    else
    {
        // If enableAsyncPipelineCacheCompression is disabled, to avoid the risk, set kMaxTotalSize
        // to 64k.
        constexpr size_t kMaxTotalSize = 64 * 1024;
        CompressAndStorePipelineCacheVk(globalOps, this, pipelineCacheData, kMaxTotalSize);
    }

    return angle::Result::Continue;
}

angle::Result Renderer::onFrameBoundary(const gl::Context *contextGL)
{
    ASSERT(contextGL);
    return syncPipelineCacheVk(contextGL);
}

// These functions look at the mandatory format for support, and fallback to querying the device (if
// necessary) to test the availability of the bits.
bool Renderer::hasLinearImageFormatFeatureBits(angle::FormatID formatID,
                                               const VkFormatFeatureFlags featureBits) const
{
    return hasFormatFeatureBits<&VkFormatProperties::linearTilingFeatures>(formatID, featureBits);
}

VkFormatFeatureFlags Renderer::getLinearImageFormatFeatureBits(
    angle::FormatID formatID,
    const VkFormatFeatureFlags featureBits) const
{
    return getFormatFeatureBits<&VkFormatProperties::linearTilingFeatures>(formatID, featureBits);
}

VkFormatFeatureFlags Renderer::getImageFormatFeatureBits(
    angle::FormatID formatID,
    const VkFormatFeatureFlags featureBits) const
{
    return getFormatFeatureBits<&VkFormatProperties::optimalTilingFeatures>(formatID, featureBits);
}

VkFormatFeatureFlags Renderer::getBufferFormatFeatureBits(
    angle::FormatID formatID,
    const VkFormatFeatureFlags featureBits) const
{
    return getFormatFeatureBits<&VkFormatProperties::bufferFeatures>(formatID, featureBits);
}

bool Renderer::hasImageFormatFeatureBits(angle::FormatID formatID,
                                         const VkFormatFeatureFlags featureBits) const
{
    return hasFormatFeatureBits<&VkFormatProperties::optimalTilingFeatures>(formatID, featureBits);
}

bool Renderer::hasBufferFormatFeatureBits(angle::FormatID formatID,
                                          const VkFormatFeatureFlags featureBits) const
{
    return hasFormatFeatureBits<&VkFormatProperties::bufferFeatures>(formatID, featureBits);
}

void Renderer::outputVmaStatString()
{
    // Output the VMA stats string
    // This JSON string can be passed to VmaDumpVis.py to generate a visualization of the
    // allocations the VMA has performed.
    char *statsString;
    mAllocator.buildStatsString(&statsString, true);
    INFO() << std::endl << statsString << std::endl;
    mAllocator.freeStatsString(statsString);
}

angle::Result Renderer::queueSubmitOneOff(vk::ErrorContext *context,
                                          vk::ScopedPrimaryCommandBuffer &&scopedCommandBuffer,
                                          vk::ProtectionType protectionType,
                                          egl::ContextPriority priority,
                                          VkSemaphore waitSemaphore,
                                          VkPipelineStageFlags waitSemaphoreStageMasks,
                                          QueueSerial *queueSerialOut)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "Renderer::queueSubmitOneOff");
    DeviceScoped<PrimaryCommandBuffer> commandBuffer = scopedCommandBuffer.unlockAndRelease();
    PrimaryCommandBuffer &primary                    = commandBuffer.get();

    // Allocate a one off SerialIndex and generate a QueueSerial and then use it and release the
    // index.
    vk::ScopedQueueSerialIndex index;
    ANGLE_TRY(allocateScopedQueueSerialIndex(&index));
    QueueSerial submitQueueSerial(index.get(), generateQueueSerial(index.get()));

    ANGLE_TRY(mCommandQueue.queueSubmitOneOff(context, protectionType, priority,
                                              primary.getHandle(), waitSemaphore,
                                              waitSemaphoreStageMasks, submitQueueSerial));

    mSubmittedResourceUse.setQueueSerial(submitQueueSerial);
    *queueSerialOut = submitQueueSerial;
    if (primary.valid())
    {
        mOneOffCommandPoolMap[protectionType].releaseCommandBuffer(submitQueueSerial,
                                                                   std::move(primary));
    }

    ANGLE_TRY(mCommandQueue.postSubmitCheck(context));

    return angle::Result::Continue;
}

angle::Result Renderer::queueSubmitWaitSemaphore(vk::ErrorContext *context,
                                                 egl::ContextPriority priority,
                                                 const vk::Semaphore &waitSemaphore,
                                                 VkPipelineStageFlags waitSemaphoreStageMasks,
                                                 QueueSerial submitQueueSerial)
{
    ANGLE_TRY(mCommandQueue.queueSubmitOneOff(context, vk::ProtectionType::Unprotected, priority,
                                              VK_NULL_HANDLE, waitSemaphore.getHandle(),
                                              waitSemaphoreStageMasks, submitQueueSerial));
    mSubmittedResourceUse.setQueueSerial(submitQueueSerial);
    return angle::Result::Continue;
}

template <VkFormatFeatureFlags VkFormatProperties::*features>
VkFormatFeatureFlags Renderer::getFormatFeatureBits(angle::FormatID formatID,
                                                    const VkFormatFeatureFlags featureBits) const
{
    ASSERT(formatID != angle::FormatID::NONE);
    VkFormatProperties &deviceProperties = mFormatProperties[formatID];

    if (deviceProperties.bufferFeatures == kInvalidFormatFeatureFlags)
    {
        // If we don't have the actual device features, see if the requested features are mandatory.
        // If so, there's no need to query the device.
        const VkFormatProperties &mandatoryProperties = vk::GetMandatoryFormatSupport(formatID);
        if (IsMaskFlagSet(mandatoryProperties.*features, featureBits))
        {
            return featureBits;
        }

        if (vk::IsYUVExternalFormat(formatID))
        {
            const vk::ExternalYuvFormatInfo &externalFormatInfo =
                mExternalFormatTable.getExternalFormatInfo(formatID);
            deviceProperties.optimalTilingFeatures = externalFormatInfo.formatFeatures;
        }
        else
        {
            VkFormat vkFormat = vk::GetVkFormatFromFormatID(this, formatID);
            ASSERT(vkFormat != VK_FORMAT_UNDEFINED);

            if (vkFormat == VK_FORMAT_A8_UNORM &&
                (!mFeatures.supportsMaintenance5.enabled ||
                 mGlobalOps->getFrontendApi() != GlobalOps::Api::OpenCL))
            {
                // TODO: VK_FORMAT_A8_UNORM currently only available in (VK_KHR_maintenance5 +
                // OpenCL) usage - GLES should go to fallback format (R8_UNORM)
                // http://anglebug.com/42266715
                return 0;
            }

            // Otherwise query the format features and cache it.
            vkGetPhysicalDeviceFormatProperties(mPhysicalDevice, vkFormat, &deviceProperties);
            // Workaround for some Android devices that don't indicate filtering
            // support on D16_UNORM and they should.
            if (mFeatures.forceD16TexFilter.enabled && vkFormat == VK_FORMAT_D16_UNORM)
            {
                deviceProperties.*features |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
            }
        }
    }

    return deviceProperties.*features & featureBits;
}

template <VkFormatFeatureFlags VkFormatProperties::*features>
bool Renderer::hasFormatFeatureBits(angle::FormatID formatID,
                                    const VkFormatFeatureFlags featureBits) const
{
    return IsMaskFlagSet(getFormatFeatureBits<features>(formatID, featureBits), featureBits);
}

bool Renderer::haveSameFormatFeatureBits(angle::FormatID formatID1, angle::FormatID formatID2) const
{
    if (formatID1 == angle::FormatID::NONE || formatID2 == angle::FormatID::NONE)
    {
        return false;
    }

    constexpr VkFormatFeatureFlags kImageUsageFeatureBits =
        VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
        VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT;

    VkFormatFeatureFlags fmt1LinearFeatureBits =
        getLinearImageFormatFeatureBits(formatID1, kImageUsageFeatureBits);
    VkFormatFeatureFlags fmt1OptimalFeatureBits =
        getImageFormatFeatureBits(formatID1, kImageUsageFeatureBits);

    return hasLinearImageFormatFeatureBits(formatID2, fmt1LinearFeatureBits) &&
           hasImageFormatFeatureBits(formatID2, fmt1OptimalFeatureBits);
}

void Renderer::cleanupGarbage(bool *anyGarbageCleanedOut)
{
    bool anyCleaned = false;

    // Clean up general garbage
    anyCleaned = mSharedGarbageList.cleanupSubmittedGarbage(this) > 0 || anyCleaned;

    // Clean up suballocation garbages
    anyCleaned = mSuballocationGarbageList.cleanupSubmittedGarbage(this) > 0 || anyCleaned;

    // Note: do this after clean up mSuballocationGarbageList so that we will have more chances to
    // find orphaned blocks being empty.
    anyCleaned = mOrphanedBufferBlockList.pruneEmptyBufferBlocks(this) > 0 || anyCleaned;

    // Clean up RefCountedEvent that are done resetting
    anyCleaned = mRefCountedEventRecycler.cleanupResettingEvents(this) > 0 || anyCleaned;

    if (anyGarbageCleanedOut != nullptr)
    {
        *anyGarbageCleanedOut = anyCleaned;
    }
}

void Renderer::cleanupPendingSubmissionGarbage()
{
    // Check if pending garbage is still pending. If not, move them to the garbage list.
    mSharedGarbageList.cleanupUnsubmittedGarbage(this);
    mSuballocationGarbageList.cleanupUnsubmittedGarbage(this);
}

uint64_t Renderer::getMaxFenceWaitTimeNs() const
{
    constexpr uint64_t kMaxFenceWaitTimeNs = std::numeric_limits<uint64_t>::max();

    return kMaxFenceWaitTimeNs;
}

VkDeviceSize Renderer::padVertexAttribBufferSizeIfNeeded(VkDeviceSize bufferSize)
{
    // When the padding feature is disabled, the max attribute stride should remain 0.
    ASSERT(mFeatures.padBuffersToMaxVertexAttribStride.enabled ^ (mMaxVertexAttribStride == 0));
    return bufferSize + mMaxVertexAttribStride;
}

void Renderer::setGlobalDebugAnnotator(bool *installedAnnotatorOut)
{
    // Install one of two DebugAnnotator classes:
    //
    // 1) The global class enables basic ANGLE debug functionality (e.g. Vulkan validation errors
    //    will cause dEQP tests to fail).
    //
    // 2) The DebugAnnotatorVk class processes OpenGL ES commands that the application uses.  It is
    //    installed for the following purposes:
    //
    //    1) To enable calling the vkCmd*DebugUtilsLabelEXT functions in order to communicate to
    //       debuggers (e.g. AGI) the OpenGL ES commands that the application uses.  In addition to
    //       simply installing DebugAnnotatorVk, also enable calling vkCmd*DebugUtilsLabelEXT.
    //
    //    2) To enable logging to Android logcat the OpenGL ES commands that the application uses.
    bool installDebugAnnotatorVk = false;

    // Enable calling the vkCmd*DebugUtilsLabelEXT functions if the vkCmd*DebugUtilsLabelEXT
    // functions exist, and if the kEnableDebugMarkersVarName environment variable is set.
    if (vkCmdBeginDebugUtilsLabelEXT)
    {
        // Use the GetAndSet variant to improve future lookup times
        std::string enabled = angle::GetAndSetEnvironmentVarOrUnCachedAndroidProperty(
            kEnableDebugMarkersVarName, kEnableDebugMarkersPropertyName);
        if (!enabled.empty() && enabled.compare("0") != 0)
        {
            mAngleDebuggerMode      = true;
            installDebugAnnotatorVk = true;
        }
    }
#if defined(ANGLE_ENABLE_TRACE_ANDROID_LOGCAT)
    // Only install DebugAnnotatorVk to log all API commands to Android's logcat.
    installDebugAnnotatorVk = true;
#endif

    {
        if (installDebugAnnotatorVk)
        {
            std::unique_lock<angle::SimpleMutex> lock(gl::GetDebugMutex());
            gl::InitializeDebugAnnotations(&mAnnotator);
        }
    }

    *installedAnnotatorOut = installDebugAnnotatorVk;
}

void Renderer::reloadVolkIfNeeded() const
{
#if defined(ANGLE_SHARED_LIBVULKAN)
    if ((mInstance != VK_NULL_HANDLE) && (volkGetLoadedInstance() != mInstance))
    {
        volkLoadInstance(mInstance);
    }

    if ((mDevice != VK_NULL_HANDLE) && (volkGetLoadedDevice() != mDevice))
    {
        volkLoadDevice(mDevice);
    }

    initializeInstanceExtensionEntryPointsFromCore();
    initializeDeviceExtensionEntryPointsFromCore();
#endif  // defined(ANGLE_SHARED_LIBVULKAN)
}

void Renderer::initializeInstanceExtensionEntryPointsFromCore() const
{
    // Initialize extension entry points from core ones.  In some cases, such as VMA, the extension
    // entry point is unconditionally used.
    InitGetPhysicalDeviceProperties2KHRFunctionsFromCore();
    if (mFeatures.supportsExternalFenceCapabilities.enabled)
    {
        InitExternalFenceCapabilitiesFunctionsFromCore();
    }
    if (mFeatures.supportsExternalSemaphoreCapabilities.enabled)
    {
        InitExternalSemaphoreCapabilitiesFunctionsFromCore();
    }
}

void Renderer::initializeDeviceExtensionEntryPointsFromCore() const
{
    if (mFeatures.supportsGetMemoryRequirements2.enabled)
    {
        InitGetMemoryRequirements2KHRFunctionsFromCore();
    }
    if (mFeatures.supportsBindMemory2.enabled)
    {
        InitBindMemory2KHRFunctionsFromCore();
    }
    if (mFeatures.supportsYUVSamplerConversion.enabled)
    {
        InitSamplerYcbcrKHRFunctionsFromCore();
    }
    if (mFeatures.supportsMaintenance5.enabled)
    {
        InitGetImageSubresourceLayoutEXTFunctionFromKHR();
    }
}

angle::Result Renderer::submitCommands(vk::ErrorContext *context,
                                       const vk::Semaphore *signalSemaphore,
                                       const vk::SharedExternalFence *externalFence,
                                       const QueueSerial &submitQueueSerial,
                                       CommandsState &&commandsState)
{
    ASSERT(signalSemaphore == nullptr || signalSemaphore->valid());
    const VkSemaphore signalVkSemaphore =
        signalSemaphore ? signalSemaphore->getHandle() : VK_NULL_HANDLE;

    vk::SharedExternalFence externalFenceCopy;
    if (externalFence != nullptr)
    {
        externalFenceCopy = *externalFence;
    }

    ANGLE_TRY(mCommandQueue.submitCommands(context, signalVkSemaphore, std::move(externalFenceCopy),
                                           submitQueueSerial, std::move(commandsState)));

    ANGLE_TRY(mCommandQueue.postSubmitCheck(context));

    return angle::Result::Continue;
}

angle::Result Renderer::insertOneOffSubmitDebugMarker(vk::ErrorContext *context,
                                                      vk::ProtectionType protectionType,
                                                      egl::ContextPriority priority,
                                                      QueueSubmitReason reason)
{
    if (!enableDebugUtils() && !angleDebuggerMode())
    {
        return angle::Result::Continue;
    }

    ScopedPrimaryCommandBuffer scopedCommandBuffer(getDevice());
    ANGLE_TRY(getCommandBufferOneOff(context, protectionType, &scopedCommandBuffer));
    PrimaryCommandBuffer &commandBuffer = scopedCommandBuffer.get();
    insertSubmitDebugMarkerInCommandBuffer(commandBuffer, reason);
    ANGLE_VK_TRY(context, commandBuffer.end());

    QueueSerial queueSerial;
    return queueSubmitOneOff(context, std::move(scopedCommandBuffer), protectionType, priority,
                             VK_NULL_HANDLE, 0, &queueSerial);
}

void Renderer::insertSubmitDebugMarkerInCommandBuffer(PrimaryCommandBuffer &commandBuffer,
                                                      QueueSubmitReason reason)
{
    if (!enableDebugUtils() && !angleDebuggerMode())
    {
        return;
    }

    VkDebugUtilsLabelEXT labelInfo;
    vk::MakeDebugUtilsLabel(GL_DEBUG_SOURCE_API, kQueueSubmitReason[reason], &labelInfo);
    commandBuffer.insertDebugUtilsLabelEXT(labelInfo);
}

angle::Result Renderer::submitPriorityDependency(vk::ErrorContext *context,
                                                 vk::ProtectionTypes protectionTypes,
                                                 egl::ContextPriority srcContextPriority,
                                                 egl::ContextPriority dstContextPriority,
                                                 SerialIndex index)
{
    RendererScoped<vk::ReleasableResource<vk::Semaphore>> semaphore(this);
    ANGLE_VK_TRY(context, semaphore.get().get().init(mDevice, VK_SEMAPHORE_TYPE_BINARY));

    // First, submit already flushed commands / wait semaphores into the source Priority VkQueue.
    // Commands that are in the Secondary Command Buffers will be flushed into the new VkQueue.

    // Submit commands and attach Signal Semaphore.
    ASSERT(protectionTypes.any());
    while (protectionTypes.any())
    {
        vk::ProtectionType protectionType = protectionTypes.first();
        protectionTypes.reset(protectionType);
        CommandsState commandsState(this, protectionType, srcContextPriority);

        QueueSerial queueSerial(index, generateQueueSerial(index));
        // Submit semaphore only if this is the last submission (all into the same VkQueue).
        const vk::Semaphore *signalSemaphore = nullptr;
        if (protectionTypes.none())
        {
            // Update QueueSerial to collect semaphore using the latest possible queueSerial.
            semaphore.get().setQueueSerial(queueSerial);
            signalSemaphore = &semaphore.get().get();
        }

        ANGLE_TRY(insertOneOffSubmitDebugMarker(context, protectionType, srcContextPriority,
                                                QueueSubmitReason::ContextPriorityChange));
        ANGLE_TRY(submitCommands(context, signalSemaphore, nullptr, queueSerial,
                                 std::move(commandsState)));
        mSubmittedResourceUse.setQueueSerial(queueSerial);
    }

    // Submit only Wait Semaphore into the destination Priority (VkQueue).
    QueueSerial queueSerial(index, generateQueueSerial(index));
    semaphore.get().setQueueSerial(queueSerial);

    ANGLE_TRY(insertOneOffSubmitDebugMarker(context, ProtectionType::Unprotected,
                                            dstContextPriority, QueueSubmitReason::WaitSemaphore));
    ANGLE_TRY(queueSubmitWaitSemaphore(context, dstContextPriority, semaphore.get().get(),
                                       VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, queueSerial));

    return angle::Result::Continue;
}

void Renderer::handleDeviceLost()
{
    mCommandQueue.handleDeviceLost(this);
}

angle::Result Renderer::finishResourceUse(vk::ErrorContext *context, const vk::ResourceUse &use)
{
    return mCommandQueue.finishResourceUse(context, use, getMaxFenceWaitTimeNs());
}

angle::Result Renderer::finishQueueSerial(vk::ErrorContext *context, const QueueSerial &queueSerial)
{
    ASSERT(queueSerial.valid());
    return mCommandQueue.finishQueueSerial(context, queueSerial, getMaxFenceWaitTimeNs());
}

angle::Result Renderer::waitForResourceUseToFinishWithUserTimeout(vk::ErrorContext *context,
                                                                  const vk::ResourceUse &use,
                                                                  uint64_t timeout,
                                                                  VkResult *result)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "Renderer::waitForResourceUseToFinishWithUserTimeout");
    return mCommandQueue.waitForResourceUseToFinishWithUserTimeout(context, use, timeout, result);
}

VkResult Renderer::queuePresent(vk::ErrorContext *context,
                                egl::ContextPriority priority,
                                const VkPresentInfoKHR &presentInfo)
{
    VkResult result = mCommandQueue.queuePresent(priority, presentInfo);

    if (getFeatures().logMemoryReportStats.enabled)
    {
        mMemoryReport.logMemoryReportStats();
    }

    return result;
}

template <typename CommandBufferHelperT, typename RecyclerT>
angle::Result Renderer::getCommandBufferImpl(vk::ErrorContext *context,
                                             vk::SecondaryCommandPool *commandPool,
                                             RecyclerT *recycler,
                                             CommandBufferHelperT **commandBufferHelperOut)
{
    return recycler->getCommandBufferHelper(context, commandPool, commandBufferHelperOut);
}

angle::Result Renderer::getOutsideRenderPassCommandBufferHelper(
    vk::ErrorContext *context,
    vk::SecondaryCommandPool *commandPool,
    vk::OutsideRenderPassCommandBufferHelper **commandBufferHelperOut)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "Renderer::getOutsideRenderPassCommandBufferHelper");
    return getCommandBufferImpl(context, commandPool, &mOutsideRenderPassCommandBufferRecycler,
                                commandBufferHelperOut);
}

angle::Result Renderer::getRenderPassCommandBufferHelper(
    vk::ErrorContext *context,
    vk::SecondaryCommandPool *commandPool,
    vk::RenderPassCommandBufferHelper **commandBufferHelperOut)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "Renderer::getRenderPassCommandBufferHelper");
    return getCommandBufferImpl(context, commandPool, &mRenderPassCommandBufferRecycler,
                                commandBufferHelperOut);
}

void Renderer::recycleOutsideRenderPassCommandBufferHelper(
    vk::OutsideRenderPassCommandBufferHelper **commandBuffer)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "Renderer::recycleOutsideRenderPassCommandBufferHelper");
    mOutsideRenderPassCommandBufferRecycler.recycleCommandBufferHelper(commandBuffer);
}

void Renderer::recycleRenderPassCommandBufferHelper(
    vk::RenderPassCommandBufferHelper **commandBuffer)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "Renderer::recycleRenderPassCommandBufferHelper");
    mRenderPassCommandBufferRecycler.recycleCommandBufferHelper(commandBuffer);
}

void Renderer::logCacheStats() const
{
    if (!vk::kOutputCumulativePerfCounters)
    {
        return;
    }

    std::unique_lock<angle::SimpleMutex> localLock(mCacheStatsMutex);

    int cacheType = 0;
    INFO() << "Vulkan object cache hit ratios: ";
    for (const CacheStats &stats : mVulkanCacheStats)
    {
        INFO() << "    CacheType " << cacheType++ << ": " << stats.getHitRatio();
    }
}

angle::Result Renderer::getFormatDescriptorCountForVkFormat(vk::ErrorContext *context,
                                                            VkFormat format,
                                                            uint32_t *descriptorCountOut)
{
    if (mVkFormatDescriptorCountMap.count(format) == 0)
    {
        // Query device for descriptor count with basic values for most of
        // VkPhysicalDeviceImageFormatInfo2 members.
        VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {};
        imageFormatInfo.sType  = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
        imageFormatInfo.format = format;
        imageFormatInfo.type   = VK_IMAGE_TYPE_2D;
        imageFormatInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
        imageFormatInfo.usage  = VK_IMAGE_USAGE_SAMPLED_BIT;
        imageFormatInfo.flags  = 0;

        VkImageFormatProperties imageFormatProperties                            = {};
        VkSamplerYcbcrConversionImageFormatProperties ycbcrImageFormatProperties = {};
        ycbcrImageFormatProperties.sType =
            VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES;

        VkImageFormatProperties2 imageFormatProperties2 = {};
        imageFormatProperties2.sType                 = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
        imageFormatProperties2.pNext                 = &ycbcrImageFormatProperties;
        imageFormatProperties2.imageFormatProperties = imageFormatProperties;

        ANGLE_VK_TRY(context, vkGetPhysicalDeviceImageFormatProperties2(
                                  mPhysicalDevice, &imageFormatInfo, &imageFormatProperties2));

        mVkFormatDescriptorCountMap[format] =
            ycbcrImageFormatProperties.combinedImageSamplerDescriptorCount;
    }

    ASSERT(descriptorCountOut);
    *descriptorCountOut = mVkFormatDescriptorCountMap[format];
    return angle::Result::Continue;
}

angle::Result Renderer::getFormatDescriptorCountForExternalFormat(vk::ErrorContext *context,
                                                                  uint64_t format,
                                                                  uint32_t *descriptorCountOut)
{
    ASSERT(descriptorCountOut);

    // TODO: need to query for external formats as well once spec is fixed.
    // http://anglebug.com/42264669
    ANGLE_VK_CHECK(context, getFeatures().useMultipleDescriptorsForExternalFormats.enabled,
                   VK_ERROR_INCOMPATIBLE_DRIVER);

    // Vulkan spec has a gap in that there is no mechanism available to query the immutable
    // sampler descriptor count of an external format. For now, return a default value.
    constexpr uint32_t kExternalFormatDefaultDescriptorCount = 4;
    *descriptorCountOut = kExternalFormatDefaultDescriptorCount;
    return angle::Result::Continue;
}

void Renderer::onAllocateHandle(vk::HandleType handleType)
{
    std::unique_lock<angle::SimpleMutex> localLock(mActiveHandleCountsMutex);
    mActiveHandleCounts.onAllocate(handleType);
}

void Renderer::onDeallocateHandle(vk::HandleType handleType, uint32_t count)
{
    std::unique_lock<angle::SimpleMutex> localLock(mActiveHandleCountsMutex);
    mActiveHandleCounts.onDeallocate(handleType, count);
}

VkDeviceSize Renderer::getPreferredInitialBufferBlockSize(uint32_t memoryTypeIndex) const
{
    // Try not to exceed 1/64 of heap size to begin with.
    const VkDeviceSize heapSize = getMemoryProperties().getHeapSizeForMemoryType(memoryTypeIndex);
    return std::min(heapSize / 64, mPreferredInitialBufferBlockSize);
}

VkDeviceSize Renderer::getPreferredLargeBufferBlockSize(uint32_t memoryTypeIndex) const
{
    // Try not to exceed 1/64 of heap size to begin with.
    const VkDeviceSize heapSize = getMemoryProperties().getHeapSizeForMemoryType(memoryTypeIndex);
    return std::min(heapSize / 64, mPreferredLargeHeapBlockSize);
}

angle::Result Renderer::allocateScopedQueueSerialIndex(vk::ScopedQueueSerialIndex *indexOut)
{
    SerialIndex index;
    ANGLE_TRY(allocateQueueSerialIndex(&index));
    indexOut->init(index, &mQueueSerialIndexAllocator);
    return angle::Result::Continue;
}

angle::Result Renderer::allocateQueueSerialIndex(SerialIndex *serialIndexOut)
{
    *serialIndexOut = mQueueSerialIndexAllocator.allocate();
    if (*serialIndexOut == kInvalidQueueSerialIndex)
    {
        return angle::Result::Stop;
    }
    return angle::Result::Continue;
}

void Renderer::releaseQueueSerialIndex(SerialIndex index)
{
    mQueueSerialIndexAllocator.release(index);
}

angle::Result Renderer::cleanupSomeGarbage(ErrorContext *context, bool *anyGarbageCleanedOut)
{
    return mCommandQueue.cleanupSomeGarbage(context, 0, anyGarbageCleanedOut);
}

void Renderer::logFeatures() const
{
    INFO() << "List of all Renderer.mFeatures:";
    for (const auto &featureInfo : mFeatures.getFeatures())
    {
        INFO() << "\tmFeatures." << featureInfo.second->name << ": " << featureInfo.second->enabled;
    }
}

// static
const char *Renderer::GetVulkanObjectTypeName(VkObjectType type)
{
    return GetVkObjectTypeName(type);
}

ImageMemorySuballocator::ImageMemorySuballocator() {}
ImageMemorySuballocator::~ImageMemorySuballocator() {}

void ImageMemorySuballocator::destroy(Renderer *renderer) {}

VkResult ImageMemorySuballocator::allocateAndBindMemory(
    ErrorContext *context,
    Image *image,
    const VkImageCreateInfo *imageCreateInfo,
    VkMemoryPropertyFlags requiredFlags,
    VkMemoryPropertyFlags preferredFlags,
    const VkMemoryRequirements *memoryRequirements,
    const bool allocateDedicatedMemory,
    MemoryAllocationType memoryAllocationType,
    Allocation *allocationOut,
    VkMemoryPropertyFlags *memoryFlagsOut,
    uint32_t *memoryTypeIndexOut,
    VkDeviceSize *sizeOut)
{
    ASSERT(image && image->valid());
    ASSERT(allocationOut && !allocationOut->valid());
    Renderer *renderer         = context->getRenderer();
    const Allocator &allocator = renderer->getAllocator();

    // The required size must not be greater than the maximum allocation size allowed by the driver.
    if (memoryRequirements->size > renderer->getMaxMemoryAllocationSize())
    {
        renderer->getMemoryAllocationTracker()->onExceedingMaxMemoryAllocationSize(
            memoryRequirements->size);
        return VK_ERROR_OUT_OF_DEVICE_MEMORY;
    }

    // Avoid device-local and host-visible combinations if possible. Here, "preferredFlags" is
    // expected to be the same as "requiredFlags" except in the device-local bit.
    ASSERT((preferredFlags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ==
           (requiredFlags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));

    uint32_t memoryTypeBits = memoryRequirements->memoryTypeBits;
    if ((requiredFlags & preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0)
    {
        memoryTypeBits = GetMemoryTypeBitsExcludingHostVisible(renderer, preferredFlags,
                                                               memoryRequirements->memoryTypeBits);
    }

    // Allocate and bind memory for the image. Try allocating on the device first.
    VkResult result = vma::AllocateAndBindMemoryForImage(
        allocator.getHandle(), &image->mHandle, requiredFlags, preferredFlags, memoryTypeBits,
        allocateDedicatedMemory, &allocationOut->mHandle, memoryTypeIndexOut, sizeOut);

    // We need to get the property flags of the allocated memory if successful.
    if (result == VK_SUCCESS)
    {
        *memoryFlagsOut =
            renderer->getMemoryProperties().getMemoryType(*memoryTypeIndexOut).propertyFlags;

        renderer->onMemoryAlloc(memoryAllocationType, *sizeOut, *memoryTypeIndexOut,
                                allocationOut->getHandle());
    }
    return result;
}

VkResult ImageMemorySuballocator::mapMemoryAndInitWithNonZeroValue(Renderer *renderer,
                                                                   Allocation *allocation,
                                                                   VkDeviceSize size,
                                                                   int value,
                                                                   VkMemoryPropertyFlags flags)
{
    ASSERT(allocation && allocation->valid());
    const Allocator &allocator = renderer->getAllocator();

    void *mappedMemoryData;
    VkResult result = vma::MapMemory(allocator.getHandle(), allocation->mHandle, &mappedMemoryData);
    if (result != VK_SUCCESS)
    {
        return result;
    }

    memset(mappedMemoryData, value, static_cast<size_t>(size));
    vma::UnmapMemory(allocator.getHandle(), allocation->mHandle);

    // If the memory type is not host coherent, we perform an explicit flush.
    if ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
    {
        vma::FlushAllocation(allocator.getHandle(), allocation->mHandle, 0, VK_WHOLE_SIZE);
    }

    return VK_SUCCESS;
}

bool ImageMemorySuballocator::needsDedicatedMemory(VkDeviceSize size) const
{
    return size >= kImageSizeThresholdForDedicatedMemoryAllocation;
}

}  // namespace vk
}  // namespace rx
