/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2015-2026 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include <algorithm>
#include "../vk_core.h"
#include "../vk_debug.h"
#include "../vk_rendertext.h"
#include "../vk_replay.h"
#include "../vk_shader_cache.h"
#include "api/replay/version.h"
#include "core/settings.h"
#include "driver/ihv/nv/nv_aftermath.h"
#include "strings/string_utils.h"

RDOC_CONFIG(
    bool, Vulkan_Debug_ReplaceAppInfo, true,
    "By default we have no choice but to replace VkApplicationInfo to safely work on all drivers. "
    "This behaviour can be disabled with this flag, which lets it through both during capture and "
    "on replay.");

RDOC_CONFIG(bool, Vulkan_Debug_EnableGPUVA, false,
            "Enable GPU Validation when enabling Vulkan validation.");

// intercept and overwrite the application info if present. We must use the same appinfo on
// capture and replay, and the safer default is not to replay as if we were the original app but
// with a slightly different workload. So instead we trample what the app reported and put in our
// own info.
static VkApplicationInfo renderdocAppInfo = {
    VK_STRUCTURE_TYPE_APPLICATION_INFO,
    NULL,
    "RenderDoc Capturing App",
    VK_MAKE_VERSION(RENDERDOC_VERSION_MAJOR, RENDERDOC_VERSION_MINOR, 0),
    "RenderDoc",
    VK_MAKE_VERSION(RENDERDOC_VERSION_MAJOR, RENDERDOC_VERSION_MINOR, 0),
    VK_API_VERSION_1_0,
};

static bool equivalent(const VkQueueFamilyProperties &a, const VkQueueFamilyProperties &b)
{
  return a.timestampValidBits == b.timestampValidBits &&
         a.minImageTransferGranularity.width == b.minImageTransferGranularity.width &&
         a.minImageTransferGranularity.height == b.minImageTransferGranularity.height &&
         a.minImageTransferGranularity.depth == b.minImageTransferGranularity.depth &&
         a.queueFlags == b.queueFlags;
}

// we store the index in the loader table, since it won't be dereferenced and other parts of the
// code expect to copy it into a wrapped object
static VkPhysicalDevice MakePhysicalDeviceHandleFromIndex(uint32_t physDeviceIndex)
{
  static uintptr_t loaderTable[32];
  loaderTable[physDeviceIndex] = (0x100 + physDeviceIndex);
  return VkPhysicalDevice(&loaderTable[physDeviceIndex]);
}

static uint32_t GetPhysicalDeviceIndexFromHandle(VkPhysicalDevice physicalDevice)
{
  return uint32_t((uintptr_t)LayerDisp(physicalDevice) - 0x100);
}

static bool CheckTransferGranularity(VkExtent3D required, VkExtent3D check)
{
  // if the required granularity is (0,0,0) then any is fine - the requirement is always satisfied.
  if(required.width == required.height && required.height == required.depth && required.depth == 0)
    return true;

  // otherwise, each dimension must be <= the required dimension (i.e. more fine-grained) to support
  // any copies we might do.
  return check.width <= required.width && check.height <= required.height &&
         check.depth <= required.depth;
}

// vk_dispatchtables.cpp
void InitDeviceTable(VkDevice dev, PFN_vkGetDeviceProcAddr gpa);
void InitInstanceTable(VkInstance inst, PFN_vkGetInstanceProcAddr gpa);

// Init/shutdown order:
//
// On capture, WrappedVulkan is new'd and delete'd before vkCreateInstance() and after
// vkDestroyInstance()
// On replay,  WrappedVulkan is new'd and delete'd before Initialise()       and after Shutdown()
//
// The class constructor and destructor handle only *non-API* work. All API objects must be created
// and
// torn down in the latter functions (vkCreateInstance/vkDestroyInstance during capture, and
// Initialise/Shutdown during replay).
//
// Note that during capture we have vkDestroyDevice before vkDestroyDevice that does most of the
// work.
//
// Also we assume correctness from the application, that all objects are destroyed before the device
// and
// instance are destroyed. We only clean up after our own objects.

static void StripUnwantedLayers(rdcarray<rdcstr> &Layers)
{
  Layers.removeIf([](const rdcstr &layer) {
    // don't try and create our own layer on replay!
    if(layer == RENDERDOC_VULKAN_LAYER_NAME)
    {
      return true;
    }

    // don't enable tracing or dumping layers just in case they
    // came along with the application
    if(layer == "VK_LAYER_LUNARG_api_dump" || layer == "VK_LAYER_LUNARG_vktrace")
    {
      return true;
    }

    // also remove the framerate monitor layer as it's buggy and doesn't do anything
    // in our case
    if(layer == "VK_LAYER_LUNARG_monitor")
    {
      return true;
    }

    // remove the optimus layer just in case it was explicitly enabled.
    if(layer == "VK_LAYER_NV_optimus")
    {
      return true;
    }

    // filter out validation layers
    if(layer == "VK_LAYER_LUNARG_standard_validation" || layer == "VK_LAYER_KHRONOS_validation" ||
       layer == "VK_LAYER_LUNARG_core_validation" || layer == "VK_LAYER_LUNARG_device_limits" ||
       layer == "VK_LAYER_LUNARG_image" || layer == "VK_LAYER_LUNARG_object_tracker" ||
       layer == "VK_LAYER_LUNARG_parameter_validation" || layer == "VK_LAYER_LUNARG_swapchain" ||
       layer == "VK_LAYER_GOOGLE_threading" || layer == "VK_LAYER_GOOGLE_unique_objects" ||
       layer == "VK_LAYER_LUNARG_assistant_layer")
    {
      return true;
    }

    return false;
  });
}

static void StripUnwantedExtensions(rdcarray<rdcstr> &Extensions)
{
  // strip out any WSI/direct display extensions. We'll add the ones we want for creating windows
  // on the current platforms below, and we don't replay any of the WSI functionality
  // directly so these extensions aren't needed
  Extensions.removeIf([](const rdcstr &ext) {
    // remove surface extensions
    if(ext == "VK_KHR_xlib_surface" || ext == "VK_KHR_xcb_surface" ||
       ext == "VK_KHR_wayland_surface" || ext == "VK_KHR_mir_surface" ||
       ext == "VK_MVK_macos_surface" || ext == "VK_KHR_android_surface" ||
       ext == "VK_KHR_win32_surface" || ext == "VK_EXT_headless_surface")
    {
      return true;
    }

    // remove direct display extensions
    if(ext == "VK_KHR_display" || ext == "VK_EXT_direct_mode_display" ||
       ext == "VK_EXT_acquire_xlib_display" || ext == "VK_EXT_display_surface_counter" ||
       ext == "VK_EXT_acquire_drm_display")
    {
      return true;
    }

    // remove platform-specific external extensions, as we don't replay external objects. We leave
    // the base extensions since they're widely supported and we don't strip all uses of e.g.
    // feature structs.
    if(ext == "VK_KHR_external_fence_fd" || ext == "VK_KHR_external_fence_win32" ||
       ext == "VK_KHR_external_memory_fd" || ext == "VK_KHR_external_memory_win32" ||
       ext == "VK_KHR_external_semaphore_fd" || ext == "VK_KHR_external_semaphore_win32" ||
       ext == "VK_KHR_win32_keyed_mutex" || ext == "VK_EXT_external_memory_dma_buf")
    {
      return true;
    }

    // remove WSI-only extensions
    if(ext == "VK_GOOGLE_display_timing" || ext == "VK_KHR_display_swapchain" ||
       ext == "VK_EXT_display_control" || ext == "VK_KHR_present_id" ||
       ext == "VK_KHR_present_id2" || ext == "VK_KHR_present_wait" ||
       ext == "VK_KHR_present_wait2" || ext == "VK_KHR_present_mode_fifo_latest_ready" ||
       ext == "VK_EXT_present_mode_fifo_latest_ready" || ext == "VK_EXT_surface_maintenance1" ||
       ext == "VK_EXT_swapchain_maintenance1" || ext == "VK_KHR_surface_maintenance1" ||
       ext == "VK_KHR_swapchain_maintenance1" || ext == "VK_EXT_hdr_metadata" ||
       ext == "VK_KHR_get_display_properties2")
      return true;

    // remove fullscreen exclusive extension
    if(ext == "VK_EXT_full_screen_exclusive")
      return true;

    // we do not replay any drm features
    if(ext == "VK_EXT_image_drm_format_modifier")
      return true;

    // this is debug only, nothing to capture, so nothing to replay
    if(ext == "VK_EXT_tooling_info" || ext == "VK_EXT_private_data" ||
       ext == "VK_EXT_validation_features" || ext == "VK_EXT_validation_cache" ||
       ext == "VK_EXT_validation_flags")
      return true;

    // these are debug only and will be added (if supported) as optional
    if(ext == "VK_EXT_debug_utils" || ext == "VK_EXT_debug_marker")
      return true;

    return false;
  });
}

RDResult WrappedVulkan::Initialise(VkInitParams &params, uint64_t sectionVersion,
                                   const ReplayOptions &opts)
{
  m_InitParams = params;
  m_SectionVersion = sectionVersion;
  m_ReplayOptions = opts;

  if(!m_Replay->IsRemoteProxy())
    Threading::JobSystem::Init();

  m_ResourceManager->SetOptimisationLevel(m_ReplayOptions.optimisation);

  StripUnwantedLayers(params.Layers);
  StripUnwantedExtensions(params.Extensions);

  std::set<rdcstr> supportedLayers;

  {
    uint32_t count = 0;
    GetInstanceDispatchTable(NULL)->EnumerateInstanceLayerProperties(&count, NULL);

    VkLayerProperties *props = new VkLayerProperties[count];
    GetInstanceDispatchTable(NULL)->EnumerateInstanceLayerProperties(&count, props);

    for(uint32_t e = 0; e < count; e++)
      supportedLayers.insert(props[e].layerName);

    SAFE_DELETE_ARRAY(props);
  }

  if(m_ReplayOptions.apiValidation)
  {
    const char KhronosValidation[] = "VK_LAYER_KHRONOS_validation";
    const char LunarGValidation[] = "VK_LAYER_LUNARG_standard_validation";

    if(supportedLayers.find(KhronosValidation) != supportedLayers.end())
    {
      RDCLOG("Enabling %s layer for API validation", KhronosValidation);
      params.Layers.push_back(KhronosValidation);
      m_LayersEnabled[VkCheckLayer_unique_objects] = true;
    }
    else if(supportedLayers.find(LunarGValidation) != supportedLayers.end())
    {
      RDCLOG("Enabling %s layer for API validation", LunarGValidation);
      params.Layers.push_back(LunarGValidation);
      m_LayersEnabled[VkCheckLayer_unique_objects] = true;
    }
    else
    {
      RDCLOG("API validation layers are not available, check you have the Vulkan SDK installed");
    }
  }

  // complain about any missing layers, but remove them from the list and continue
  params.Layers.removeIf([&supportedLayers](const rdcstr &layer) {
    if(supportedLayers.find(layer) == supportedLayers.end())
    {
      RDCERR("Capture used layer '%s' which is not available, continuing without it", layer.c_str());
      return true;
    }

    return false;
  });

  std::set<rdcstr> supportedExtensions;

  for(size_t i = 0; i <= params.Layers.size(); i++)
  {
    const char *pLayerName = (i == 0 ? NULL : params.Layers[i - 1].c_str());

    uint32_t count = 0;
    GetInstanceDispatchTable(NULL)->EnumerateInstanceExtensionProperties(pLayerName, &count, NULL);

    VkExtensionProperties *props = new VkExtensionProperties[count];
    GetInstanceDispatchTable(NULL)->EnumerateInstanceExtensionProperties(pLayerName, &count, props);

    for(uint32_t e = 0; e < count; e++)
      supportedExtensions.insert(props[e].extensionName);

    SAFE_DELETE_ARRAY(props);
  }

  if(!m_Replay->IsRemoteProxy())
  {
    size_t i = 0;
    for(const rdcstr &ext : supportedExtensions)
    {
      RDCLOG("Inst Ext %u: %s", i, ext.c_str());
      i++;
    }

    i = 0;
    for(const rdcstr &layer : supportedLayers)
    {
      RDCLOG("Inst Layer %u: %s", i, layer.c_str());
      i++;
    }
  }

  AddRequiredExtensions(true, params.Extensions, supportedExtensions);

  // after 1.1, VK_KHR_get_physical_device_properties2 is promoted to core, but enable it if it's
  // reported as available, just in case.
  if(params.APIVersion >= VK_API_VERSION_1_1)
  {
    if(supportedExtensions.find(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) !=
       supportedExtensions.end())
    {
      if(!params.Extensions.contains(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
        params.Extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
    }
  }
  else
  {
    if(supportedExtensions.find(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) ==
       supportedExtensions.end())
    {
      RDCWARN("Unsupported required instance extension for AMD performance counters '%s'",
              VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
    }
    else
    {
      if(!params.Extensions.contains(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
        params.Extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
    }
  }

  // enable device group extension if on 1.0, so we can use BDA. Shuts the validation layers up
  if(params.APIVersion <= VK_API_VERSION_1_0)
  {
    if(supportedExtensions.find(VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME) !=
       supportedExtensions.end())
    {
      if(!params.Extensions.contains(VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME))
        params.Extensions.push_back(VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME);
    }
  }

  // verify that extensions are supported
  for(size_t i = 0; i < params.Extensions.size(); i++)
  {
    if(supportedExtensions.find(params.Extensions[i]) == supportedExtensions.end())
    {
      RETURN_ERROR_RESULT(ResultCode::APIHardwareUnsupported,
                          "Capture requires instance extension '%s' which is not supported\n",
                          params.Extensions[i].c_str());
    }
  }

  // we always want debug extensions if it available, and not already enabled
  if(supportedExtensions.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != supportedExtensions.end() &&
     !params.Extensions.contains(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
  {
    if(!m_Replay->IsRemoteProxy())
      RDCLOG("Enabling VK_EXT_debug_utils");
    params.Extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
  }
  else if(supportedExtensions.find(VK_EXT_DEBUG_REPORT_EXTENSION_NAME) != supportedExtensions.end() &&
          !params.Extensions.contains(VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
  {
    if(!m_Replay->IsRemoteProxy())
      RDCLOG("Enabling VK_EXT_debug_report");
    params.Extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
  }

  VkValidationFeaturesEXT featuresEXT = {VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT};
  VkValidationFeatureDisableEXT disableFeatures[] = {VK_VALIDATION_FEATURE_DISABLE_SHADERS_EXT};
  featuresEXT.disabledValidationFeatureCount = ARRAY_COUNT(disableFeatures);
  featuresEXT.pDisabledValidationFeatures = disableFeatures;

  // enable this to get GPU-based validation, where available, whenever we enable API validation
  if(m_ReplayOptions.apiValidation && Vulkan_Debug_EnableGPUVA())
  {
    VkValidationFeatureEnableEXT enableFeatures[] = {
        VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,
        VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT};
    featuresEXT.enabledValidationFeatureCount = ARRAY_COUNT(enableFeatures);
    featuresEXT.pEnabledValidationFeatures = enableFeatures;
  }

  VkValidationFlagsEXT flagsEXT = {VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT};
  VkValidationCheckEXT disableChecks[] = {VK_VALIDATION_CHECK_SHADERS_EXT};
  flagsEXT.disabledValidationCheckCount = ARRAY_COUNT(disableChecks);
  flagsEXT.pDisabledValidationChecks = disableChecks;

  void *instNext = NULL;

  if(supportedExtensions.find(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME) != supportedExtensions.end() &&
     !params.Extensions.contains(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME))
  {
    if(!m_Replay->IsRemoteProxy())
      RDCLOG("Enabling VK_EXT_validation_features");
    params.Extensions.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);

    instNext = &featuresEXT;
  }
  else if(supportedExtensions.find(VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME) !=
              supportedExtensions.end() &&
          !params.Extensions.contains(VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME))
  {
    if(!m_Replay->IsRemoteProxy())
      RDCLOG("Enabling VK_EXT_validation_flags");
    params.Extensions.push_back(VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME);

    instNext = &flagsEXT;
  }

  const char **layerscstr = new const char *[params.Layers.size()];
  for(size_t i = 0; i < params.Layers.size(); i++)
    layerscstr[i] = params.Layers[i].c_str();

  const char **extscstr = new const char *[params.Extensions.size()];
  for(size_t i = 0; i < params.Extensions.size(); i++)
    extscstr[i] = params.Extensions[i].c_str();

  VkInstanceCreateInfo instinfo = {
      VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
      instNext,
      0,
      &renderdocAppInfo,
      (uint32_t)params.Layers.size(),
      layerscstr,
      (uint32_t)params.Extensions.size(),
      extscstr,
  };

  if(params.APIVersion >= VK_API_VERSION_1_0)
    renderdocAppInfo.apiVersion = params.APIVersion;

  m_EnabledExtensions.vulkanVersion = renderdocAppInfo.apiVersion;

  if(!Vulkan_Debug_ReplaceAppInfo())
  {
    // if we're not replacing the app info, set renderdocAppInfo's parameters to the ones from the
    // capture
    renderdocAppInfo.pEngineName = params.EngineName.c_str();
    renderdocAppInfo.engineVersion = params.EngineVersion;
    renderdocAppInfo.pApplicationName = params.AppName.c_str();
    renderdocAppInfo.applicationVersion = params.AppVersion;
  }

  m_Instance = VK_NULL_HANDLE;

  VkResult ret = GetInstanceDispatchTable(NULL)->CreateInstance(&instinfo, NULL, &m_Instance);

#undef CheckExt
#define CheckExt(name, ver)                                                                           \
  if(!strcmp(instinfo.ppEnabledExtensionNames[i], "VK_" #name) || renderdocAppInfo.apiVersion >= ver) \
  {                                                                                                   \
    m_EnabledExtensions.ext_##name = true;                                                            \
  }

  for(uint32_t i = 0; i < instinfo.enabledExtensionCount; i++)
  {
    CheckInstanceExts();
  }

  SAFE_DELETE_ARRAY(layerscstr);
  SAFE_DELETE_ARRAY(extscstr);

  if(ret != VK_SUCCESS)
  {
    RETURN_ERROR_RESULT(ResultCode::APIHardwareUnsupported, "Vulkan instance creation returned %s",
                        ToStr(ret).c_str());
  }

  RDCASSERTEQUAL(ret, VK_SUCCESS);

  GetResourceManager()->WrapResource(params.InstanceID, m_Instance, m_Instance);

  // we'll add the chunk later when we re-process it.
  if(params.InstanceID != ResourceId())
  {
    AddResource(params.InstanceID, ResourceType::Device, "Instance");
    GetReplay()->GetResourceDesc(params.InstanceID).initialisationChunks.clear();
  }

  InitInstanceExtensionTables(m_Instance, &m_EnabledExtensions);

  m_DbgReportCallback = VK_NULL_HANDLE;
  m_DbgUtilsCallback = VK_NULL_HANDLE;
  m_PhysicalDevice = VK_NULL_HANDLE;
  m_Device = VK_NULL_HANDLE;
  m_QueueFamilyIdx = ~0U;
  m_PrevQueue = m_Queue = VK_NULL_HANDLE;
  m_InternalCmds.Reset();

  if(ObjDisp(m_Instance)->CreateDebugUtilsMessengerEXT)
  {
    VkDebugUtilsMessengerCreateInfoEXT debugInfo = {};
    debugInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
    debugInfo.pfnUserCallback = &DebugUtilsCallbackStatic;
    debugInfo.pUserData = this;
    debugInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                            VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                            VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
    debugInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                                VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;

    ObjDisp(m_Instance)
        ->CreateDebugUtilsMessengerEXT(Unwrap(m_Instance), &debugInfo, NULL, &m_DbgUtilsCallback);
  }
  else if(ObjDisp(m_Instance)->CreateDebugReportCallbackEXT)
  {
    VkDebugReportCallbackCreateInfoEXT debugInfo = {};
    debugInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
    debugInfo.pfnCallback = &DebugReportCallbackStatic;
    debugInfo.pUserData = this;
    debugInfo.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT |
                      VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT;

    ObjDisp(m_Instance)
        ->CreateDebugReportCallbackEXT(Unwrap(m_Instance), &debugInfo, NULL, &m_DbgReportCallback);
  }

  uint32_t count = 0;

  VkResult vkr = ObjDisp(m_Instance)->EnumeratePhysicalDevices(Unwrap(m_Instance), &count, NULL);
  CHECK_VKR(this, vkr);

  if(count == 0)
  {
    RETURN_ERROR_RESULT(ResultCode::APIHardwareUnsupported,
                        "No physical devices exist in this vulkan instance");
  }

  m_ReplayPhysicalDevices.resize(count);
  m_ReplayPhysicalDevicesUsed.resize(count);
  m_OriginalPhysicalDevices.resize(count);

  vkr = ObjDisp(m_Instance)
            ->EnumeratePhysicalDevices(Unwrap(m_Instance), &count, &m_ReplayPhysicalDevices[0]);
  CHECK_VKR(this, vkr);

  // these are only used internally, since due to physical device remapping the actual registered
  // resource is created as a fake physical device and that's where we register them by their
  // capture-time ID. These can safely be created with replay-only IDs.
  for(uint32_t i = 0; i < count; i++)
    GetResourceManager()->WrapResource(ResourceId(), m_Instance, m_ReplayPhysicalDevices[i]);

#if ENABLED(RDOC_WIN32)
  if(GetModuleHandleA("nvoglv64.dll"))
    LoadLibraryA("nvoglv64.dll");
#endif

  return ResultCode::Succeeded;
}

VkResult WrappedVulkan::vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo,
                                         const VkAllocationCallbacks *, VkInstance *pInstance)
{
  RDCASSERT(pCreateInfo);

  // don't support any extensions for this createinfo
  RDCASSERT(pCreateInfo->pApplicationInfo == NULL || pCreateInfo->pApplicationInfo->pNext == NULL);

  const bool internalInstance =
      (pCreateInfo->pApplicationInfo && pCreateInfo->pApplicationInfo->pApplicationName &&
       rdcstr(pCreateInfo->pApplicationInfo->pApplicationName) == "RenderDoc forced instance");

  VkLayerInstanceCreateInfo *layerCreateInfo = (VkLayerInstanceCreateInfo *)pCreateInfo->pNext;

  // step through the chain of pNext until we get to the link info
  while(layerCreateInfo && (layerCreateInfo->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO ||
                            layerCreateInfo->function != VK_LAYER_LINK_INFO))
  {
    layerCreateInfo = (VkLayerInstanceCreateInfo *)layerCreateInfo->pNext;
  }
  RDCASSERT(layerCreateInfo);

  if(layerCreateInfo == NULL)
  {
    RDCERR("Couldn't find loader instance create info, which is required. Incompatible loader?");
    return VK_ERROR_INITIALIZATION_FAILED;
  }

  PFN_vkGetInstanceProcAddr gpa = layerCreateInfo->u.pLayerInfo->pfnNextGetInstanceProcAddr;
  // move chain on for next layer
  layerCreateInfo->u.pLayerInfo = layerCreateInfo->u.pLayerInfo->pNext;

  PFN_vkCreateInstance createFunc = (PFN_vkCreateInstance)gpa(VK_NULL_HANDLE, "vkCreateInstance");

  VkInstanceCreateInfo modifiedCreateInfo;
  modifiedCreateInfo = *pCreateInfo;

  // Android is a bad platform with a bad loader that it custom wrote, and implicit layers get added
  // to the create info's layer list even if they weren't specified, so it's impossible to tell if
  // it's an application making a mistake or the platform messing up. Disable this protection on Android
#if DISABLED(RDOC_ANDROID)
  for(uint32_t i = 0; i < modifiedCreateInfo.enabledLayerCount; i++)
  {
    if(rdcstr(modifiedCreateInfo.ppEnabledLayerNames[i]) == RENDERDOC_VULKAN_LAYER_NAME)
    {
      // see if any debug report callbacks were passed in the pNext chain
      VkDebugReportCallbackCreateInfoEXT *report =
          (VkDebugReportCallbackCreateInfoEXT *)pCreateInfo->pNext;

      rdcstr msg =
          "RenderDoc's layer should NEVER be activated manually. Do not include it in "
          "vkCreateInstance's instance layers.";

      RDCERR("%s", msg.c_str());

      while(report)
      {
        if(report->sType == VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT)
          report->pfnCallback(VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT,
                              0, 1, 1, "RDOC", msg.c_str(), report->pUserData);

        report = (VkDebugReportCallbackCreateInfoEXT *)report->pNext;
      }

      // or debug utils callbacks
      VkDebugUtilsMessengerCreateInfoEXT *messenger =
          (VkDebugUtilsMessengerCreateInfoEXT *)pCreateInfo->pNext;

      VkDebugUtilsMessengerCallbackDataEXT messengerData = {};

      messengerData.messageIdNumber = 1;
      messengerData.pMessageIdName = NULL;
      messengerData.pMessage = msg.c_str();
      messengerData.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT;

      while(messenger)
      {
        if(messenger->sType == VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT)
          messenger->pfnUserCallback(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
                                     VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, &messengerData,
                                     messenger->pUserData);

        messenger = (VkDebugUtilsMessengerCreateInfoEXT *)messenger->pNext;
      }

      return VK_ERROR_INITIALIZATION_FAILED;
    }
  }
#endif

  for(uint32_t i = 0; i < modifiedCreateInfo.enabledExtensionCount; i++)
  {
    if(!IsSupportedExtension(modifiedCreateInfo.ppEnabledExtensionNames[i]))
    {
      RDCERR("RenderDoc does not support instance extension '%s'.",
             modifiedCreateInfo.ppEnabledExtensionNames[i]);
      RDCERR(
          "For KHR/EXT extensions file an issue on github to request support: "
          "https://github.com/baldurk/renderdoc");

      // see if any debug report callbacks were passed in the pNext chain
      VkDebugReportCallbackCreateInfoEXT *report =
          (VkDebugReportCallbackCreateInfoEXT *)pCreateInfo->pNext;

      rdcstr msg = StringFormat::Fmt("RenderDoc does not support requested instance extension: %s.",
                                     modifiedCreateInfo.ppEnabledExtensionNames[i]);

      while(report)
      {
        if(report->sType == VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT)
          report->pfnCallback(VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT,
                              0, 1, 1, "RDOC", msg.c_str(), report->pUserData);

        report = (VkDebugReportCallbackCreateInfoEXT *)report->pNext;
      }

      // or debug utils callbacks
      VkDebugUtilsMessengerCreateInfoEXT *messenger =
          (VkDebugUtilsMessengerCreateInfoEXT *)pCreateInfo->pNext;

      VkDebugUtilsMessengerCallbackDataEXT messengerData = {};

      messengerData.messageIdNumber = 1;
      messengerData.pMessageIdName = NULL;
      messengerData.pMessage = msg.c_str();
      messengerData.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT;

      while(messenger)
      {
        if(messenger->sType == VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT)
          messenger->pfnUserCallback(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
                                     VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, &messengerData,
                                     messenger->pUserData);

        messenger = (VkDebugUtilsMessengerCreateInfoEXT *)messenger->pNext;
      }

      return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
  }

  const char **addedExts = NULL;

  if(!internalInstance)
  {
    addedExts = new const char *[modifiedCreateInfo.enabledExtensionCount + 2];

    bool hasDebugReport = false, hasDebugUtils = false, hasGPDP2 = false;
    for(uint32_t i = 0; i < modifiedCreateInfo.enabledExtensionCount; i++)
    {
      addedExts[i] = modifiedCreateInfo.ppEnabledExtensionNames[i];
      if(!strcmp(addedExts[i], VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
        hasDebugReport = true;
      if(!strcmp(addedExts[i], VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
        hasDebugUtils = true;
      if(!strcmp(addedExts[i], VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
        hasGPDP2 = true;
    }

    rdcarray<VkExtensionProperties> supportedExts;

    // enumerate what instance extensions are available
    void *module = LoadVulkanLibrary();
    if(module)
    {
      PFN_vkEnumerateInstanceExtensionProperties enumInstExts =
          (PFN_vkEnumerateInstanceExtensionProperties)Process::GetFunctionAddress(
              module, "vkEnumerateInstanceExtensionProperties");

      if(enumInstExts)
      {
        uint32_t numSupportedExts = 0;
        enumInstExts(NULL, &numSupportedExts, NULL);

        supportedExts.resize(numSupportedExts);
        enumInstExts(NULL, &numSupportedExts, &supportedExts[0]);
      }
    }

    if(supportedExts.empty())
      RDCWARN(
          "Couldn't load vkEnumerateInstanceExtensionProperties in vkCreateInstance to enumerate "
          "instance extensions");

    // always enable GPDP2 if it's available
    if(!hasGPDP2)
    {
      for(const VkExtensionProperties &ext : supportedExts)
      {
        if(!strcmp(ext.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
        {
          addedExts[modifiedCreateInfo.enabledExtensionCount++] =
              VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME;
          break;
        }
      }
    }

    // always enable debug report/utils, if it's available
    if(!hasDebugUtils)
    {
      for(const VkExtensionProperties &ext : supportedExts)
      {
        if(!strcmp(ext.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
        {
          addedExts[modifiedCreateInfo.enabledExtensionCount++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
          break;
        }
      }
    }
    else if(!hasDebugReport)
    {
      for(const VkExtensionProperties &ext : supportedExts)
      {
        if(!strcmp(ext.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
        {
          addedExts[modifiedCreateInfo.enabledExtensionCount++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
          break;
        }
      }
    }

    modifiedCreateInfo.ppEnabledExtensionNames = addedExts;
  }

  bool brokenGetDeviceProcAddr = false;

  // override applicationInfo with RenderDoc's, but preserve apiVersion
  if(modifiedCreateInfo.pApplicationInfo)
  {
    if(modifiedCreateInfo.pApplicationInfo->pEngineName &&
       strlower(modifiedCreateInfo.pApplicationInfo->pEngineName) == "idtech")
      brokenGetDeviceProcAddr = true;

    if(modifiedCreateInfo.pApplicationInfo->apiVersion >= VK_API_VERSION_1_0)
      renderdocAppInfo.apiVersion = modifiedCreateInfo.pApplicationInfo->apiVersion;

    if(Vulkan_Debug_ReplaceAppInfo())
    {
      modifiedCreateInfo.pApplicationInfo = &renderdocAppInfo;
    }
  }

  for(uint32_t i = 0; i < modifiedCreateInfo.enabledLayerCount; i++)
  {
    if(!strcmp(modifiedCreateInfo.ppEnabledLayerNames[i], "VK_LAYER_LUNARG_standard_validation") ||
       !strcmp(modifiedCreateInfo.ppEnabledLayerNames[i], "VK_LAYER_KHRONOS_validation") ||
       !strcmp(modifiedCreateInfo.ppEnabledLayerNames[i], "VK_LAYER_GOOGLE_unique_objects"))
    {
      m_LayersEnabled[VkCheckLayer_unique_objects] = true;
    }
  }

  // if we forced on API validation, it's also available
  m_LayersEnabled[VkCheckLayer_unique_objects] |= RenderDoc::Inst().GetCaptureOptions().apiValidation;

  VkResult ret = createFunc(&modifiedCreateInfo, NULL, pInstance);

  m_Instance = *pInstance;

  InitInstanceTable(m_Instance, gpa);

  GetResourceManager()->WrapResource(ResourceId(), m_Instance, m_Instance);

  *pInstance = m_Instance;

  // should only be called during capture
  RDCASSERT(IsCaptureMode(m_State));

  m_InitParams.Set(pCreateInfo, GetResID(m_Instance));
  VkResourceRecord *record = GetResourceManager()->AddResourceRecord(m_Instance);

  record->instDevInfo = new InstanceDeviceInfo();

  record->instDevInfo->brokenGetDeviceProcAddr = brokenGetDeviceProcAddr;

  record->instDevInfo->vulkanVersion = VK_API_VERSION_1_0;

  // whether or not we're using it, we updated the apiVersion in renderdocAppInfo
  if(renderdocAppInfo.apiVersion > VK_API_VERSION_1_0)
    record->instDevInfo->vulkanVersion = renderdocAppInfo.apiVersion;

  std::set<rdcstr> availablePhysDeviceFunctions;

  {
    uint32_t count = 0;
    ObjDisp(m_Instance)->EnumeratePhysicalDevices(Unwrap(m_Instance), &count, NULL);

    rdcarray<VkPhysicalDevice> physDevs;
    physDevs.resize(count);
    ObjDisp(m_Instance)->EnumeratePhysicalDevices(Unwrap(m_Instance), &count, physDevs.data());

    rdcarray<VkExtensionProperties> exts;
    for(VkPhysicalDevice p : physDevs)
    {
      ObjDisp(m_Instance)->EnumerateDeviceExtensionProperties(p, NULL, &count, NULL);

      exts.resize(count);
      ObjDisp(m_Instance)->EnumerateDeviceExtensionProperties(p, NULL, &count, exts.data());

      for(const VkExtensionProperties &e : exts)
      {
        availablePhysDeviceFunctions.insert(e.extensionName);
      }
    }
    // we don't bother wrapping these, they're temporary handles
  }

// an extension is available if:
// * it's enabled in the instance creation
// * it's promoted in the selected vulkan version
// * it's a device extension and available on at least one physical device
#undef CheckExt
#define CheckExt(name, ver)                                                                \
  if(record->instDevInfo->vulkanVersion >= ver ||                                          \
     availablePhysDeviceFunctions.find("VK_" #name) != availablePhysDeviceFunctions.end()) \
  {                                                                                        \
    record->instDevInfo->ext_##name = true;                                                \
  }
  CheckInstanceExts();
#undef CheckExt
#define CheckExt(name, ver)                                               \
  if(!strcmp(modifiedCreateInfo.ppEnabledExtensionNames[i], "VK_" #name)) \
  {                                                                       \
    record->instDevInfo->ext_##name = true;                               \
  }
  for(uint32_t i = 0; i < modifiedCreateInfo.enabledExtensionCount; i++)
  {
    CheckInstanceExts();
  }

  SAFE_DELETE_ARRAY(addedExts);

  InitInstanceExtensionTables(m_Instance, record->instDevInfo);

  // don't register a frame capturer for our internal instance on android
  if(internalInstance)
  {
    RDCDEBUG("Not registering internal instance as frame capturer");
  }
  else
  {
    RenderDoc::Inst().AddDeviceFrameCapturer(LayerDisp(m_Instance), this);
  }

  m_DbgReportCallback = VK_NULL_HANDLE;
  m_DbgUtilsCallback = VK_NULL_HANDLE;
  m_PhysicalDevice = VK_NULL_HANDLE;
  m_Device = VK_NULL_HANDLE;
  m_QueueFamilyIdx = ~0U;
  m_PrevQueue = m_Queue = VK_NULL_HANDLE;
  m_InternalCmds.Reset();

  if(ObjDisp(m_Instance)->CreateDebugUtilsMessengerEXT)
  {
    VkDebugUtilsMessengerCreateInfoEXT debugInfo = {};
    debugInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
    debugInfo.pfnUserCallback = &DebugUtilsCallbackStatic;
    debugInfo.pUserData = this;
    debugInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                            VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                            VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
    debugInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                                VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;

    ObjDisp(m_Instance)
        ->CreateDebugUtilsMessengerEXT(Unwrap(m_Instance), &debugInfo, NULL, &m_DbgUtilsCallback);
  }
  else if(ObjDisp(m_Instance)->CreateDebugReportCallbackEXT)
  {
    VkDebugReportCallbackCreateInfoEXT debugInfo = {};
    debugInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
    debugInfo.pNext = NULL;
    debugInfo.pfnCallback = &DebugReportCallbackStatic;
    debugInfo.pUserData = this;
    debugInfo.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT |
                      VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT;

    ObjDisp(m_Instance)
        ->CreateDebugReportCallbackEXT(Unwrap(m_Instance), &debugInfo, NULL, &m_DbgReportCallback);
  }

  if(ret == VK_SUCCESS)
  {
    RDCLOG("Initialised capture layer in Vulkan instance.");
  }

  return ret;
}

void WrappedVulkan::Shutdown()
{
  if(!m_Replay->IsRemoteProxy())
  {
    Threading::JobSystem::Shutdown();
    GetResourceManager()->ResolveDeferredWrappers();
  }

  // flush out any pending commands/semaphores
  SubmitCmds();
  SubmitSemaphores();
  FlushQ();

  // idle the device as well so that external queues are idle.
  if(m_Device)
  {
    VkResult vkr = ObjDisp(m_Device)->DeviceWaitIdle(Unwrap(m_Device));
    CHECK_VKR(this, vkr);
  }

  // since we didn't create proper registered resources for our command buffers,
  // they won't be taken down properly with the pool. So we release them (just our
  // data) here.
  for(size_t i = 0; i < m_InternalCmds.freecmds.size(); i++)
    GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.freecmds[i]);

  if(m_IndirectCommandBuffer != VK_NULL_HANDLE)
    GetResourceManager()->ReleaseWrappedResource(m_IndirectCommandBuffer);

  // destroy the pool
  if(m_Device != VK_NULL_HANDLE && m_InternalCmds.cmdpool != VK_NULL_HANDLE)
  {
    ObjDisp(m_Device)->DestroyCommandPool(Unwrap(m_Device), Unwrap(m_InternalCmds.cmdpool), NULL);
    GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.cmdpool);
  }

  for(size_t i = 0; i < m_InternalCmds.freesems.size(); i++)
  {
    ObjDisp(m_Device)->DestroySemaphore(Unwrap(m_Device), Unwrap(m_InternalCmds.freesems[i]), NULL);
    GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.freesems[i]);
  }

  for(size_t i = 0; i < m_ExternalQueues.size(); i++)
  {
    if(m_ExternalQueues[i].pool != VK_NULL_HANDLE)
    {
      for(size_t x = 0; x < ARRAY_COUNT(m_ExternalQueues[i].ring); x++)
      {
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].acquire);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].release);

        ObjDisp(m_Device)->DestroySemaphore(Unwrap(m_Device),
                                            Unwrap(m_ExternalQueues[i].ring[x].fromext), NULL);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].fromext);

        ObjDisp(m_Device)->DestroySemaphore(Unwrap(m_Device),
                                            Unwrap(m_ExternalQueues[i].ring[x].toext), NULL);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].toext);

        ObjDisp(m_Device)->DestroyFence(Unwrap(m_Device), Unwrap(m_ExternalQueues[i].ring[x].fence),
                                        NULL);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].fence);
      }

      ObjDisp(m_Device)->DestroyCommandPool(Unwrap(m_Device), Unwrap(m_ExternalQueues[i].pool), NULL);
      GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].pool);
    }
  }

  FreeAllMemory(MemoryScope::InitialContents);
  FreeAllMemory(MemoryScope::InitialContentsFirstApplyOnly);

  if(m_MemoryFreeThread)
  {
    Threading::JoinThread(m_MemoryFreeThread);
    Threading::CloseThread(m_MemoryFreeThread);
    m_MemoryFreeThread = 0;
  }

  // we do more in Shutdown than the equivalent vkDestroyInstance since on replay there's
  // no explicit vkDestroyDevice, we destroy the device here then the instance

  // destroy the physical devices manually because due to remapping the may have leftover
  // refcounts
  for(size_t i = 0; i < m_ReplayPhysicalDevices.size(); i++)
  {
    if(m_ReplayPhysicalDevices[i] != VK_NULL_HANDLE)
      GetResourceManager()->ReleaseWrappedResource(m_ReplayPhysicalDevices[i]);
  }

  m_ASManager->Cleanup();

  m_Replay->DestroyResources();

  m_IndirectBuffer.Destroy();
  m_IndirectBufferCB.Destroy();

  // destroy debug manager and any objects it created
  SAFE_DELETE(m_DebugManager);
  SAFE_DELETE(m_ShaderCache);

  if(m_Instance && ObjDisp(m_Instance)->DestroyDebugReportCallbackEXT &&
     m_DbgReportCallback != VK_NULL_HANDLE)
    ObjDisp(m_Instance)->DestroyDebugReportCallbackEXT(Unwrap(m_Instance), m_DbgReportCallback, NULL);

  if(m_Instance && ObjDisp(m_Instance)->DestroyDebugUtilsMessengerEXT &&
     m_DbgUtilsCallback != VK_NULL_HANDLE)
    ObjDisp(m_Instance)->DestroyDebugUtilsMessengerEXT(Unwrap(m_Instance), m_DbgUtilsCallback, NULL);

  // need to store the unwrapped device and instance to destroy the
  // API object after resource manager shutdown
  VkInstance inst = Unwrap(m_Instance);
  VkDevice dev = Unwrap(m_Device);

  const VkDevDispatchTable *vt = m_Device != VK_NULL_HANDLE ? ObjDisp(m_Device) : NULL;
  const VkInstDispatchTable *vit = m_Instance != VK_NULL_HANDLE ? ObjDisp(m_Instance) : NULL;

  // this destroys the wrapped objects for the devices and instances
  m_ResourceManager->Shutdown();

  delete GetWrapped(m_Device);
  delete GetWrapped(m_Instance);

  m_PhysicalDevice = VK_NULL_HANDLE;
  m_Device = VK_NULL_HANDLE;
  m_Instance = VK_NULL_HANDLE;

  m_ReplayPhysicalDevices.clear();
  m_PhysicalDevices.clear();

  for(size_t i = 0; i < m_QueueFamilies.size(); i++)
    delete[] m_QueueFamilies[i];

  m_QueueFamilies.clear();

  // finally destroy device then instance
  if(vt)
    vt->DestroyDevice(dev, NULL);
  if(vit)
    vit->DestroyInstance(inst, NULL);
}

void WrappedVulkan::vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks *)
{
  if(instance == VK_NULL_HANDLE)
    return;

  RDCASSERT(m_Instance == instance);

  if(ObjDisp(m_Instance)->DestroyDebugReportCallbackEXT && m_DbgReportCallback != VK_NULL_HANDLE)
    ObjDisp(m_Instance)->DestroyDebugReportCallbackEXT(Unwrap(m_Instance), m_DbgReportCallback, NULL);

  if(ObjDisp(m_Instance)->DestroyDebugUtilsMessengerEXT && m_DbgUtilsCallback != VK_NULL_HANDLE)
    ObjDisp(m_Instance)->DestroyDebugUtilsMessengerEXT(Unwrap(m_Instance), m_DbgUtilsCallback, NULL);

  // the device should already have been destroyed, assuming that the
  // application is well behaved. If not, we just leak.

  ObjDisp(m_Instance)->DestroyInstance(Unwrap(m_Instance), NULL);
  RenderDoc::Inst().RemoveDeviceFrameCapturer(LayerDisp(m_Instance));

  GetResourceManager()->ReleaseWrappedResource(m_Instance);
  m_Instance = VK_NULL_HANDLE;
}

template <typename SerialiserType>
bool WrappedVulkan::Serialise_vkEnumeratePhysicalDevices(SerialiserType &ser, VkInstance instance,
                                                         uint32_t *pPhysicalDeviceCount,
                                                         VkPhysicalDevice *pPhysicalDevices)
{
  SERIALISE_ELEMENT(instance);
  SERIALISE_ELEMENT_LOCAL(PhysicalDeviceIndex, *pPhysicalDeviceCount);
  SERIALISE_ELEMENT_LOCAL(PhysicalDevice, GetResID(*pPhysicalDevices))
      .TypedAs("VkPhysicalDevice"_lit)
      .Important();

  uint32_t legacyUnused_memIdxMap[VK_MAX_MEMORY_TYPES] = {0};
  // not used at the moment but useful for reference and might be used
  // in the future
  VkPhysicalDeviceProperties physProps = {};
  VkPhysicalDeviceMemoryProperties memProps = {};
  VkPhysicalDeviceFeatures physFeatures = {};
  uint32_t queueCount = 0;
  VkQueueFamilyProperties queueProps[16] = {};

  VkPhysicalDeviceDriverProperties driverProps = {
      VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
  };

  if(ser.IsWriting())
  {
    ObjDisp(instance)->GetPhysicalDeviceProperties(Unwrap(*pPhysicalDevices), &physProps);
    ObjDisp(instance)->GetPhysicalDeviceMemoryProperties(Unwrap(*pPhysicalDevices), &memProps);
    ObjDisp(instance)->GetPhysicalDeviceFeatures(Unwrap(*pPhysicalDevices), &physFeatures);

    ObjDisp(instance)->GetPhysicalDeviceQueueFamilyProperties(Unwrap(*pPhysicalDevices),
                                                              &queueCount, NULL);

    if(queueCount > 16)
    {
      RDCERR("More than 16 queue families");
      queueCount = 16;
    }

    ObjDisp(instance)->GetPhysicalDeviceQueueFamilyProperties(Unwrap(*pPhysicalDevices),
                                                              &queueCount, queueProps);

    GetPhysicalDeviceDriverProperties(ObjDisp(instance), Unwrap(*pPhysicalDevices), driverProps);
  }

  SERIALISE_ELEMENT(legacyUnused_memIdxMap).Hidden();    // was never used
  SERIALISE_ELEMENT(physProps);
  SERIALISE_ELEMENT(memProps);
  SERIALISE_ELEMENT(physFeatures);
  SERIALISE_ELEMENT(queueCount);
  SERIALISE_ELEMENT(queueProps);

  // serialisation of the driver properties was added in 0x10
  if(ser.VersionAtLeast(0x10))
  {
    SERIALISE_ELEMENT(driverProps);

    // we don't need any special handling if this is missing - the properties will be empty which is
    // the same as a new capture if we can't query the properties
  }

  VkPhysicalDevice pd = VK_NULL_HANDLE;

  SERIALISE_CHECK_READ_ERRORS();

  if(IsReplayingAndReading())
  {
    bool firstTime = true;

    for(bool used : m_ReplayPhysicalDevicesUsed)
      if(used)
        firstTime = false;

    {
      if(PhysicalDeviceIndex >= m_OriginalPhysicalDevices.size())
        m_OriginalPhysicalDevices.resize(PhysicalDeviceIndex + 1);

      m_OriginalPhysicalDevices[PhysicalDeviceIndex].props = physProps;
      m_OriginalPhysicalDevices[PhysicalDeviceIndex].driverProps = driverProps;
      m_OriginalPhysicalDevices[PhysicalDeviceIndex].memProps = memProps;
      m_OriginalPhysicalDevices[PhysicalDeviceIndex].availFeatures = physFeatures;
      m_OriginalPhysicalDevices[PhysicalDeviceIndex].queueCount = queueCount;
      memcpy(m_OriginalPhysicalDevices[PhysicalDeviceIndex].queueProps, queueProps,
             sizeof(queueProps));
    }

    // match up physical devices to those available on replay as best as possible. In general
    // hopefully the most common case is when there's a precise match, and maybe the order changed.
    //
    // If more GPUs were present on replay than during capture, we map many-to-one which might have
    // bad side-effects, but this is as good as we can do.

    uint32_t bestIdx = 0;
    VkPhysicalDeviceProperties bestPhysProps = {};
    VkPhysicalDeviceDriverProperties bestDriverProps = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
    };

    rdcarray<VkPhysicalDeviceProperties> compPhysPropsArray;
    rdcarray<VkPhysicalDeviceDriverProperties> compDriverPropsArray;

    compPhysPropsArray.resize(m_ReplayPhysicalDevices.size());
    compDriverPropsArray.resize(m_ReplayPhysicalDevices.size());

    // first cache all the physical device data to compare against
    for(uint32_t i = 0; i < (uint32_t)m_ReplayPhysicalDevices.size(); i++)
    {
      VkPhysicalDeviceProperties &compPhysProps = compPhysPropsArray[i];
      RDCEraseEl(compPhysProps);
      VkPhysicalDeviceDriverProperties &compDriverProps = compDriverPropsArray[i];
      RDCEraseEl(compDriverProps);
      compDriverProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;

      pd = m_ReplayPhysicalDevices[i];

      // find the best possible match for this physical device
      ObjDisp(pd)->GetPhysicalDeviceProperties(Unwrap(pd), &compPhysProps);

      GetPhysicalDeviceDriverProperties(ObjDisp(pd), Unwrap(pd), compDriverProps);

      if(firstTime)
      {
        VkDriverInfo runningVersion(compPhysProps, compDriverProps);

        RDCLOG("Replay has physical device %u available:", i);
        RDCLOG("   - %s (ver %u.%u patch 0x%x) - %04x:%04x", compPhysProps.deviceName,
               runningVersion.Major(), runningVersion.Minor(), runningVersion.Patch(),
               compPhysProps.vendorID, compPhysProps.deviceID);

        if(compDriverProps.driverID != 0)
        {
          RDCLOG(
              "   - %s driver: %s (%s) - CTS %d.%d.%d.%d", ToStr(compDriverProps.driverID).c_str(),
              compDriverProps.driverName, compDriverProps.driverInfo,
              compDriverProps.conformanceVersion.major, compDriverProps.conformanceVersion.minor,
              compDriverProps.conformanceVersion.subminor, compDriverProps.conformanceVersion.patch);
        }
      }
    }

    // if we're forcing use of a GPU, try to find that one
    bool forced = false;
    if(m_ReplayOptions.forceGPUVendor != GPUVendor::Unknown)
    {
      for(uint32_t i = 0; i < (uint32_t)m_ReplayPhysicalDevices.size(); i++)
      {
        const VkPhysicalDeviceProperties &compPhysProps = compPhysPropsArray[i];
        const VkPhysicalDeviceDriverProperties &compDriverProps = compDriverPropsArray[i];

        VkDriverInfo bestInfo(bestPhysProps, bestDriverProps);
        VkDriverInfo compInfo(compPhysProps, compDriverProps);

        // an exact vendorID match is a better match than not
        if(compInfo.Vendor() == m_ReplayOptions.forceGPUVendor &&
           bestInfo.Vendor() != m_ReplayOptions.forceGPUVendor)
        {
          bestIdx = i;
          bestPhysProps = compPhysProps;
          bestDriverProps = compDriverProps;
          forced = true;
          continue;
        }
        else if(compInfo.Vendor() != m_ReplayOptions.forceGPUVendor)
        {
          continue;
        }

        // ditto deviceID
        if(compPhysProps.deviceID == m_ReplayOptions.forceGPUDeviceID &&
           bestPhysProps.deviceID != m_ReplayOptions.forceGPUDeviceID)
        {
          bestIdx = i;
          bestPhysProps = compPhysProps;
          bestDriverProps = compDriverProps;
          forced = true;
          continue;
        }
        else if(compPhysProps.deviceID != m_ReplayOptions.forceGPUDeviceID)
        {
          continue;
        }

        // driver matching. Only do this if we have a driver name to look at
        if(compDriverProps.driverID && !m_ReplayOptions.forceGPUDriverName.empty())
        {
          rdcstr compHumanDriverName = HumanDriverName(compDriverProps.driverID);
          rdcstr bestHumanDriverName = HumanDriverName(bestDriverProps.driverID);

          // check for a better driverID match
          if(compHumanDriverName == m_ReplayOptions.forceGPUDriverName &&
             bestHumanDriverName != m_ReplayOptions.forceGPUDriverName)
          {
            bestIdx = i;
            bestPhysProps = compPhysProps;
            bestDriverProps = compDriverProps;
            forced = true;
            continue;
          }
        }
      }
    }

    if(forced)
    {
      RDCLOG("Forcing use of physical device");
    }
    else
    {
      bestIdx = 0;
      RDCEraseEl(bestPhysProps);
      RDCEraseEl(bestDriverProps);

      for(uint32_t i = 0; i < (uint32_t)m_ReplayPhysicalDevices.size(); i++)
      {
        const VkPhysicalDeviceProperties &compPhysProps = compPhysPropsArray[i];
        const VkPhysicalDeviceDriverProperties &compDriverProps = compDriverPropsArray[i];

        pd = m_ReplayPhysicalDevices[i];

        // the first is the best at the start
        if(i == 0)
        {
          bestPhysProps = compPhysProps;
          bestDriverProps = compDriverProps;
          continue;
        }

        // an exact vendorID match is a better match than not
        if(compPhysProps.vendorID == physProps.vendorID &&
           bestPhysProps.vendorID != physProps.vendorID)
        {
          bestIdx = i;
          bestPhysProps = compPhysProps;
          bestDriverProps = compDriverProps;
          continue;
        }
        else if(compPhysProps.vendorID != physProps.vendorID)
        {
          continue;
        }

        // ditto deviceID
        if(compPhysProps.deviceID == physProps.deviceID &&
           bestPhysProps.deviceID != physProps.deviceID)
        {
          bestIdx = i;
          bestPhysProps = compPhysProps;
          bestDriverProps = compDriverProps;
          continue;
        }
        else if(compPhysProps.deviceID != physProps.deviceID)
        {
          continue;
        }

        // driver matching. Only do this if both capture and replay gave us valid driver info to
        // compare
        if(compDriverProps.driverID && driverProps.driverID)
        {
          // check for a better driverID match
          if(compDriverProps.driverID == driverProps.driverID &&
             bestDriverProps.driverID != driverProps.driverID)
          {
            bestIdx = i;
            bestPhysProps = compPhysProps;
            bestDriverProps = compDriverProps;
            continue;
          }
          else if(compDriverProps.driverID != driverProps.driverID)
          {
            continue;
          }
        }

        // if we have an exact driver version match, prefer that
        if(compPhysProps.driverVersion == physProps.driverVersion &&
           bestPhysProps.driverVersion != physProps.driverVersion)
        {
          bestIdx = i;
          bestPhysProps = compPhysProps;
          bestDriverProps = compDriverProps;
          continue;
        }
        else if(compPhysProps.driverVersion != physProps.driverVersion)
        {
          continue;
        }

        // if we have multiple identical devices, which isn't uncommon, favour the one
        // that hasn't been assigned
        if(m_ReplayPhysicalDevicesUsed[bestIdx] && !m_ReplayPhysicalDevicesUsed[i])
        {
          bestIdx = i;
          bestPhysProps = compPhysProps;
          continue;
        }

        // this device isn't any better, ignore it
      }
    }

    {
      VkDriverInfo capturedVersion(physProps, driverProps);

      RDCLOG("Found capture physical device %u:", PhysicalDeviceIndex);
      RDCLOG("   - %s (ver %u.%u patch 0x%x) - %04x:%04x", physProps.deviceName,
             capturedVersion.Major(), capturedVersion.Minor(), capturedVersion.Patch(),
             physProps.vendorID, physProps.deviceID);

      if(driverProps.driverID != 0)
      {
        RDCLOG("   - %s driver: %s (%s) - CTS %d.%d.%d.%d", ToStr(driverProps.driverID).c_str(),
               driverProps.driverName, driverProps.driverInfo, driverProps.conformanceVersion.major,
               driverProps.conformanceVersion.minor, driverProps.conformanceVersion.subminor,
               driverProps.conformanceVersion.patch);
      }

      RDCLOG("Mapping during replay to %s physical device %u", forced ? "forced" : "best-match",
             bestIdx);
    }

    pd = m_ReplayPhysicalDevices[bestIdx];

    // we want to preserve the separate physical devices until we actually need the real handle,
    // so don't remap multiple capture-time physical devices to one replay-time physical device
    // yet. See below in Serialise_vkCreateDevice where this is decoded.
    // Note this allocation is pooled so we don't have to explicitly delete it.
    {
      VkPhysicalDevice fakeDevice = MakePhysicalDeviceHandleFromIndex(PhysicalDeviceIndex);

      WrappedVkPhysicalDevice *wrapped = new WrappedVkPhysicalDevice(fakeDevice, PhysicalDevice);

      GetResourceManager()->AddResource(PhysicalDevice, wrapped);

      if(IsReplayMode(m_State))
        GetResourceManager()->AddWrapper(wrapped, ToTypedHandle(fakeDevice));

      fakeDevice = (VkPhysicalDevice)wrapped;
    }

    AddResource(PhysicalDevice, ResourceType::Device, "Physical Device");
    DerivedResource(m_Instance, PhysicalDevice);

    if(PhysicalDeviceIndex >= m_PhysicalDevices.size())
      m_PhysicalDevices.resize(PhysicalDeviceIndex + 1);
    m_PhysicalDevices[PhysicalDeviceIndex] = pd;

    if(m_ReplayPhysicalDevicesUsed[bestIdx])
    {
      // error if we're remapping multiple physical devices to the same best match
      RDCWARN(
          "Mapping multiple capture-time physical devices to a single replay-time physical device."
          "This means the HW has changed between capture and replay and may cause bugs.");
    }

    m_ReplayPhysicalDevicesUsed[bestIdx] = true;
  }

  return true;
}

VkResult WrappedVulkan::vkEnumeratePhysicalDevices(VkInstance instance,
                                                   uint32_t *pPhysicalDeviceCount,
                                                   VkPhysicalDevice *pPhysicalDevices)
{
  uint32_t count;

  VkResult vkr = ObjDisp(instance)->EnumeratePhysicalDevices(Unwrap(instance), &count, NULL);

  if(vkr != VK_SUCCESS)
    return vkr;

  VkPhysicalDevice *devices = new VkPhysicalDevice[count];

  SERIALISE_TIME_CALL(
      vkr = ObjDisp(instance)->EnumeratePhysicalDevices(Unwrap(instance), &count, devices));
  CHECK_VKR(this, vkr);

  m_PhysicalDevices.resize(count);

  for(uint32_t i = 0; i < count; i++)
  {
    // it's perfectly valid for enumerate type functions to return the same handle
    // each time. If that happens, we will already have a wrapper created so just
    // return the wrapped object to the user and do nothing else
    if(m_PhysicalDevices[i] != VK_NULL_HANDLE)
    {
      GetWrapped(m_PhysicalDevices[i])->RewrapObject(devices[i]);
      devices[i] = m_PhysicalDevices[i];
    }
    else
    {
      GetResourceManager()->WrapResource(ResourceId(), instance, devices[i]);

      if(IsCaptureMode(m_State))
      {
        // add the record first since it's used in the serialise function below to fetch
        // the memory indices
        VkResourceRecord *record = GetResourceManager()->AddResourceRecord(devices[i]);
        RDCASSERT(record);

        VkResourceRecord *instrecord = GetRecord(instance);

        VkPhysicalDeviceProperties physProps;

        ObjDisp(devices[i])->GetPhysicalDeviceProperties(Unwrap(devices[i]), &physProps);

        VkPhysicalDeviceDriverProperties driverProps = {};
        GetPhysicalDeviceDriverProperties(ObjDisp(devices[i]), Unwrap(devices[i]), driverProps);

        VkDriverInfo capturedVersion(physProps, driverProps);

        RDCLOG("physical device %u: %s (ver %u.%u patch 0x%x) - %04x:%04x", i, physProps.deviceName,
               capturedVersion.Major(), capturedVersion.Minor(), capturedVersion.Patch(),
               physProps.vendorID, physProps.deviceID);

        m_PhysicalDevices[i] = devices[i];

        {
          CACHE_THREAD_SERIALISER();

          SCOPED_SERIALISE_CHUNK(VulkanChunk::vkEnumeratePhysicalDevices);
          Serialise_vkEnumeratePhysicalDevices(ser, instance, &i, &devices[i]);

          record->AddChunk(scope.Get());
        }

        instrecord->AddParent(record);

        // copy the instance's setup directly
        record->instDevInfo = new InstanceDeviceInfo(*instrecord->instDevInfo);

        // treat physical devices as pool members of the instance (ie. freed when the instance dies)
        {
          instrecord->LockChunks();
          instrecord->pooledChildren.push_back(record);
          instrecord->UnlockChunks();
        }
      }
    }
  }

  VkResult result = VK_SUCCESS;

  if(pPhysicalDevices)
  {
    if(count > *pPhysicalDeviceCount)
    {
      count = *pPhysicalDeviceCount;
      result = VK_INCOMPLETE;
    }
    memcpy(pPhysicalDevices, devices, count * sizeof(VkPhysicalDevice));
  }
  *pPhysicalDeviceCount = count;

  SAFE_DELETE_ARRAY(devices);

  return result;
}

bool WrappedVulkan::SelectGraphicsComputeQueue(const rdcarray<VkQueueFamilyProperties> &queueProps,
                                               VkDeviceCreateInfo &createInfo,
                                               uint32_t &queueFamilyIndex)
{
  // storage for if we need to change the requested queues
  static rdcarray<VkDeviceQueueCreateInfo> modQueues;

  bool found = false;

  // we need graphics, and if there is a graphics queue there must be a graphics & compute queue.
  const VkQueueFlags search = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT);

  // for queue priorities, if we need it
  static const float one = 1.0f;

  for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++)
  {
    uint32_t idx = createInfo.pQueueCreateInfos[i].queueFamilyIndex;
    RDCASSERT(idx < queueProps.size());

    // this requested queue is one we can use too
    if((queueProps[idx].queueFlags & search) == search &&
       createInfo.pQueueCreateInfos[i].queueCount > 0)
    {
      queueFamilyIndex = idx;
      found = true;
      break;
    }
  }

  // if we didn't find it, search for which queue family we should add a request for
  if(!found)
  {
    RDCDEBUG("App didn't request a queue family we can use - adding our own");

    for(uint32_t i = 0; i < queueProps.size(); i++)
    {
      if((queueProps[i].queueFlags & search) == search)
      {
        queueFamilyIndex = i;
        found = true;
        break;
      }
    }

    if(!found)
    {
      SET_ERROR_RESULT(
          m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
          "Can't add a queue with required properties for RenderDoc! Unsupported configuration");
      return false;
    }

    // we found the queue family, add it
    modQueues.resize(createInfo.queueCreateInfoCount + 1);
    for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++)
      modQueues[i] = createInfo.pQueueCreateInfos[i];

    modQueues[createInfo.queueCreateInfoCount].queueFamilyIndex = queueFamilyIndex;
    modQueues[createInfo.queueCreateInfoCount].queueCount = 1;
    modQueues[createInfo.queueCreateInfoCount].pQueuePriorities = &one;
    modQueues[createInfo.queueCreateInfoCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    modQueues[createInfo.queueCreateInfoCount].pNext = NULL;
    modQueues[createInfo.queueCreateInfoCount].flags = 0;

    createInfo.pQueueCreateInfos = modQueues.data();
    createInfo.queueCreateInfoCount++;
  }

  return true;
}

void WrappedVulkan::SendUserDebugMessage(const rdcstr &msg)
{
  VkDebugUtilsMessengerCallbackDataEXT messengerData = {};

  messengerData.messageIdNumber = 1;
  messengerData.pMessageIdName = NULL;
  messengerData.pMessage = msg.c_str();
  messengerData.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT;

  {
    SCOPED_LOCK(m_CallbacksLock);

    for(UserDebugReportCallbackData *cb : m_ReportCallbacks)
    {
      cb->createInfo.pfnCallback(VK_DEBUG_REPORT_ERROR_BIT_EXT,
                                 VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT, 0, 1, 1, "RDOC",
                                 msg.c_str(), cb->createInfo.pUserData);
    }

    for(UserDebugUtilsCallbackData *cb : m_UtilsCallbacks)
    {
      cb->createInfo.pfnUserCallback(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
                                     VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, &messengerData,
                                     cb->createInfo.pUserData);
    }
  }
}

template <typename SerialiserType>
bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevice physicalDevice,
                                             const VkDeviceCreateInfo *pCreateInfo,
                                             const VkAllocationCallbacks *pAllocator,
                                             VkDevice *pDevice)
{
  SERIALISE_ELEMENT(physicalDevice).Important();
  SERIALISE_ELEMENT_LOCAL(CreateInfo, *pCreateInfo).Important();
  SERIALISE_ELEMENT_OPT(pAllocator);
  SERIALISE_ELEMENT_LOCAL(Device, GetResID(*pDevice)).TypedAs("VkDevice"_lit);

  if(ser.VersionLess(0xD))
  {
    uint32_t supportedQueueFamily;    // no longer used
    SERIALISE_ELEMENT(supportedQueueFamily).Hidden();
  }

  SERIALISE_CHECK_READ_ERRORS();

  if(IsReplayingAndReading())
  {
    // kept around only to call DerivedResource below, as this is the resource that actually has an
    // capture time resource ID.
    VkPhysicalDevice origPhysDevice = physicalDevice;

    // see above in Serialise_vkEnumeratePhysicalDevices where this is encoded
    uint32_t physicalDeviceIndex = GetPhysicalDeviceIndexFromHandle(Unwrap(physicalDevice));
    physicalDevice = m_PhysicalDevices[physicalDeviceIndex];

    RDCLOG("Creating replay device from physical device %u", physicalDeviceIndex);

    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceProperties(Unwrap(physicalDevice), &m_PhysicalDeviceData.props);

    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceMemoryProperties(Unwrap(physicalDevice), &m_PhysicalDeviceData.memProps);

    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceFeatures(Unwrap(physicalDevice), &m_PhysicalDeviceData.availFeatures);

    GetPhysicalDeviceDriverProperties(ObjDisp(physicalDevice), Unwrap(physicalDevice),
                                      m_PhysicalDeviceData.driverProps);

    m_PhysicalDeviceData.driverInfo =
        VkDriverInfo(m_PhysicalDeviceData.props, m_PhysicalDeviceData.driverProps, true);

    rdcarray<VkDeviceQueueGlobalPriorityCreateInfo *> queuePriorities;

    for(uint32_t i = 0; i < CreateInfo.queueCreateInfoCount; i++)
    {
      VkDeviceQueueGlobalPriorityCreateInfo *queuePrio =
          (VkDeviceQueueGlobalPriorityCreateInfo *)FindNextStruct(
              &CreateInfo.pQueueCreateInfos[i],
              VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO);

      if(queuePrio)
        queuePriorities.push_back(queuePrio);
    }

    const PhysicalDeviceData &origData = m_OriginalPhysicalDevices[physicalDeviceIndex];

    m_OrigPhysicalDeviceData = origData;
    m_OrigPhysicalDeviceData.driverInfo = VkDriverInfo(origData.props, origData.driverProps, false);

    // we must make any modifications locally, so the free of pointers
    // in the serialised VkDeviceCreateInfo don't double-free
    VkDeviceCreateInfo createInfo = CreateInfo;

    rdcarray<rdcstr> Extensions;
    for(uint32_t i = 0; i < createInfo.enabledExtensionCount; i++)
    {
      Extensions.push_back(createInfo.ppEnabledExtensionNames[i]);
    }

    StripUnwantedExtensions(Extensions);

    std::set<rdcstr> supportedExtensions;

    {
      uint32_t count = 0;
      ObjDisp(physicalDevice)
          ->EnumerateDeviceExtensionProperties(Unwrap(physicalDevice), NULL, &count, NULL);

      VkExtensionProperties *props = new VkExtensionProperties[count];
      ObjDisp(physicalDevice)
          ->EnumerateDeviceExtensionProperties(Unwrap(physicalDevice), NULL, &count, props);

      for(uint32_t e = 0; e < count; e++)
        supportedExtensions.insert(props[e].extensionName);

      SAFE_DELETE_ARRAY(props);
    }

    AddRequiredExtensions(false, Extensions, supportedExtensions);

    VkPhysicalDeviceProperties physProps;
    ObjDisp(physicalDevice)->GetPhysicalDeviceProperties(Unwrap(physicalDevice), &physProps);

    // Drop VK_KHR_driver_properties if it's not available, but add it if it is
    bool driverPropsSupported = (supportedExtensions.find(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME) !=
                                 supportedExtensions.end());
    if(driverPropsSupported)
    {
      if(!Extensions.contains(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME))
        Extensions.push_back(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME);
    }
    else
    {
      Extensions.removeOne(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME);
    }

    for(size_t i = 0; i < Extensions.size(); i++)
    {
      if(supportedExtensions.find(Extensions[i]) == supportedExtensions.end())
      {
        SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
                         "Capture requires extension '%s' which is not supported\n"
                         "\n%s",
                         Extensions[i].c_str(), GetPhysDeviceCompatString(false, false).c_str());
        return false;
      }
    }

    // enable VK_EXT_debug_marker if it's available, to replay markers to the driver/any other
    // layers that might be listening
    if(supportedExtensions.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != supportedExtensions.end())
    {
      Extensions.push_back(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
      RDCLOG("Enabling VK_EXT_debug_marker");
    }

    // enable VK_AMD_SHADER_INFO_EXTENSION_NAME if it's available, to fetch shader disassembly
    if(supportedExtensions.find(VK_AMD_SHADER_INFO_EXTENSION_NAME) != supportedExtensions.end())
    {
      Extensions.push_back(VK_AMD_SHADER_INFO_EXTENSION_NAME);
      RDCLOG("Enabling VK_AMD_shader_info");
    }

    // enable VK_AMD_gpa_interface if it's available, for AMD counter support
    if(supportedExtensions.find("VK_AMD_gpa_interface") != supportedExtensions.end())
    {
      Extensions.push_back("VK_AMD_gpa_interface");
      RDCLOG("Enabling VK_AMD_gpa_interface");
    }

    // enable VK_AMD_shader_core_properties if it's available, for AMD counter support
    if(supportedExtensions.find(VK_AMD_SHADER_CORE_PROPERTIES_EXTENSION_NAME) !=
       supportedExtensions.end())
    {
      Extensions.push_back(VK_AMD_SHADER_CORE_PROPERTIES_EXTENSION_NAME);
      RDCLOG("Enabling VK_AMD_shader_core_properties");
    }

    // enable VK_MVK_moltenvk if it's available, for detecting/controlling moltenvk.
    // Currently this is used opaquely (extension present or not) rather than using anything the
    // extension provides.
    if(supportedExtensions.find("VK_MVK_moltenvk") != supportedExtensions.end())
    {
      Extensions.push_back("VK_MVK_moltenvk");
      RDCLOG("Enabling VK_MVK_moltenvk");
    }

    // enable VK_KHR_driver_properties if it's available, to match up to capture-time
    if(supportedExtensions.find(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME) != supportedExtensions.end())
    {
      Extensions.push_back(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME);
      RDCLOG("Enabling VK_KHR_driver_properties");
    }

    // enable VK_KHR_shader_non_semantic_info if it's available, to enable debug printf
    if(supportedExtensions.find(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME) !=
       supportedExtensions.end())
    {
      Extensions.push_back(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME);
      RDCLOG("Enabling VK_KHR_shader_non_semantic_info");
    }

    bool pipeExec = false;

    // enable VK_KHR_pipeline_executable_properties if it's available, to fetch disassembly and
    // statistics
    if(supportedExtensions.find(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME) !=
       supportedExtensions.end())
    {
      pipeExec = true;
      Extensions.push_back(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
      RDCLOG("Enabling VK_KHR_pipeline_executable_properties");
    }

    bool xfb = false;

    // enable VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME if it's available, to fetch mesh output in
    // tessellation/geometry stages
    if(supportedExtensions.find(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME) != supportedExtensions.end())
    {
      xfb = true;
      Extensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
      RDCLOG("Enabling VK_EXT_transform_feedback extension");
    }
    else
    {
      RDCWARN(
          "VK_EXT_transform_feedback extension not available, mesh output from "
          "geometry/tessellation stages will not be available");
    }

    bool scalarBlock = false;

    // enable VK_EXT_scalar_block_layout if it's available, to fetch mesh output in the mesh stage
    if(supportedExtensions.find(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME) !=
       supportedExtensions.end())
    {
      scalarBlock = true;
      Extensions.push_back(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME);
      RDCLOG("Enabling VK_EXT_scalar_block_layout extension");
    }
    else
    {
      const VkPhysicalDeviceMeshShaderFeaturesEXT *meshFeats =
          (const VkPhysicalDeviceMeshShaderFeaturesEXT *)FindNextStruct(
              &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT);

      if(meshFeats && meshFeats->meshShader)
        RDCWARN(
            "VK_EXT_scalar_block_layout extension not available, mesh output from "
            "mesh stage will not be available");
    }

    // enable VK_KHR_shader_subgroup_uniform_control_flow if it's available, to make subgroup
    // debugging more reliable/spec-clean.
    // if we can't get it, we'll just emit the same code anyway and hope it compiles to something sensible
    if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 1, 0))
    {
      if(supportedExtensions.find(VK_KHR_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_EXTENSION_NAME) !=
         supportedExtensions.end())
      {
        Extensions.push_back(VK_KHR_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_EXTENSION_NAME);
        RDCLOG("Enabling VK_KHR_shader_subgroup_uniform_control_flow extension");
      }
    }

    bool KHRbuffer = false, EXTbuffer = false;

    if(supportedExtensions.find(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) !=
       supportedExtensions.end())
    {
      Extensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
      RDCLOG("Enabling VK_KHR_buffer_device_address");

      if(!Extensions.contains(VK_KHR_DEVICE_GROUP_EXTENSION_NAME))
        Extensions.push_back(VK_KHR_DEVICE_GROUP_EXTENSION_NAME);

      KHRbuffer = true;
    }
    else if(supportedExtensions.find(VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) !=
            supportedExtensions.end())
    {
      Extensions.push_back(VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
      RDCLOG("Enabling VK_EXT_buffer_device_address");

      EXTbuffer = true;
    }
    else
    {
      RDCWARN(
          "VK_[KHR|EXT]_buffer_device_address not available, feedback from "
          "bindless shader access will use less reliable fallback");
    }

    bool perfQuery = false;

    if(supportedExtensions.find(VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME) != supportedExtensions.end())
    {
      perfQuery = true;
      Extensions.push_back(VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME);
      RDCLOG("Enabling VK_KHR_performance_query");
    }

    VkDevice device;

    rdcarray<VkQueueFamilyProperties> queueProps;

    {
      uint32_t qCount = 0;
      ObjDisp(physicalDevice)
          ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), &qCount, NULL);

      queueProps.resize(qCount);
      ObjDisp(physicalDevice)
          ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), &qCount,
                                                   queueProps.data());
    }

    // to aid the search algorithm below, we apply implied transfer bit onto the queue properties.
    for(VkQueueFamilyProperties &q : queueProps)
    {
      if(q.queueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))
        q.queueFlags |= VK_QUEUE_TRANSFER_BIT;
    }

    uint32_t origQCount = origData.queueCount;
    const VkQueueFamilyProperties *origprops = origData.queueProps;

    // create queue remapping
    for(uint32_t origQIndex = 0; origQIndex < origQCount; origQIndex++)
    {
      m_QueueRemapping[origQIndex].resize(origprops[origQIndex].queueCount);
      RDCLOG("Capture describes queue family %u:", origQIndex);
      RDCLOG("   - %u queues available with %s", origprops[origQIndex].queueCount,
             ToStr(VkQueueFlagBits(origprops[origQIndex].queueFlags)).c_str());
      RDCLOG("     %u timestamp bits (%u,%u,%u) granularity",
             origprops[origQIndex].timestampValidBits,
             origprops[origQIndex].minImageTransferGranularity.width,
             origprops[origQIndex].minImageTransferGranularity.height,
             origprops[origQIndex].minImageTransferGranularity.depth);

      // find the best queue family to map to. We try and find the closest match that is at least
      // good enough. We want to try and preserve families that were separate before but we need to
      // ensure the remapped queue family is at least as good as it was at capture time.
      uint32_t destFamily = 0;

      if(origQIndex < queueProps.size() && equivalent(origprops[origQIndex], queueProps[origQIndex]))
      {
        destFamily = origQIndex;
        RDCLOG(" (identity match)");
      }
      else
      {
        // we categorise the original queue as one of four types: universal
        // (graphics/compute/transfer), graphics/transfer only (rare), compute-only
        // (compute/transfer) or transfer-only (transfer). We try first to find an exact match, then
        // move progressively up the priority list to find a broader and broader match.
        // We don't care about sparse binding - it's just treated as a requirement.
        enum class SearchType
        {
          Other,
          Universal,
          GraphicsTransfer,
          ComputeTransfer,
          GraphicsOrComputeTransfer,
          TransferOnly,
        } search;

        VkQueueFlags mask = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT);

        switch(origprops[origQIndex].queueFlags & mask)
        {
          case VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT:
          case VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT:
            search = SearchType::Universal;
            break;
          case VK_QUEUE_GRAPHICS_BIT:
          case VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT:
            search = SearchType::GraphicsTransfer;
            break;
          case VK_QUEUE_COMPUTE_BIT:
          case VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT:
            search = SearchType::ComputeTransfer;
            break;
          case VK_QUEUE_TRANSFER_BIT: search = SearchType::TransferOnly; break;
          default:
            search = SearchType::Other;
            // video queue, NV optical flow, some type of queue we don't handle
            break;
        }

        bool needSparse = (origprops[origQIndex].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0;
        VkExtent3D needGranularity = origprops[origQIndex].minImageTransferGranularity;

        while(search != SearchType::Other)
        {
          bool found = false;

          for(uint32_t replayQIndex = 0; replayQIndex < queueProps.size(); replayQIndex++)
          {
            // ignore queues that couldn't satisfy the required transfer granularity
            if(!CheckTransferGranularity(needGranularity,
                                         queueProps[replayQIndex].minImageTransferGranularity))
              continue;

            // ignore queues that don't have sparse binding, if we need that
            if(needSparse &&
               ((queueProps[replayQIndex].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) == 0))
              continue;

            switch(search)
            {
              case SearchType::Other: break;
              case SearchType::Universal:
                if((queueProps[replayQIndex].queueFlags & mask) ==
                   (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT))
                {
                  destFamily = replayQIndex;
                  found = true;
                }
                break;
              case SearchType::GraphicsTransfer:
                if((queueProps[replayQIndex].queueFlags & mask) ==
                   (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT))
                {
                  destFamily = replayQIndex;
                  found = true;
                }
                break;
              case SearchType::ComputeTransfer:
                if((queueProps[replayQIndex].queueFlags & mask) ==
                   (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT))
                {
                  destFamily = replayQIndex;
                  found = true;
                }
                break;
              case SearchType::GraphicsOrComputeTransfer:
                if((queueProps[replayQIndex].queueFlags & mask) ==
                       (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT) ||
                   (queueProps[replayQIndex].queueFlags & mask) ==
                       (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT))
                {
                  destFamily = replayQIndex;
                  found = true;
                }
                break;
              case SearchType::TransferOnly:
                if((queueProps[replayQIndex].queueFlags & mask) == VK_QUEUE_TRANSFER_BIT)
                {
                  destFamily = replayQIndex;
                  found = true;
                }
                break;
            }

            if(found)
              break;
          }

          if(found)
            break;

          // no such queue family found, fall back to the next type of queue to search for
          switch(search)
          {
            case SearchType::Other: break;
            case SearchType::Universal: search = SearchType::Other; break;
            case SearchType::GraphicsTransfer:
            case SearchType::ComputeTransfer:
            case SearchType::GraphicsOrComputeTransfer:
              // if we didn't find a graphics or compute (and transfer) queue, we have to look for a
              // universal one
              search = SearchType::Universal;
              break;
            case SearchType::TransferOnly:
              // when falling back from looking for a transfer-only queue, we consider either
              // graphics-only or compute-only as better candidates before universal
              search = SearchType::GraphicsOrComputeTransfer;
              break;
          }
        }
      }

      RDCLOG("Remapping to queue family %u:", destFamily);
      RDCLOG("   - %u queues available with %s", queueProps[destFamily].queueCount,
             ToStr(VkQueueFlagBits(queueProps[destFamily].queueFlags)).c_str());
      RDCLOG("     %u timestamp bits (%u,%u,%u) granularity",
             queueProps[destFamily].timestampValidBits,
             queueProps[destFamily].minImageTransferGranularity.width,
             queueProps[destFamily].minImageTransferGranularity.height,
             queueProps[destFamily].minImageTransferGranularity.depth);

      // loop over the queues, wrapping around if necessary to provide enough queues. The idea being
      // an application is more likely to use early queues than later ones, so if there aren't
      // enough queues in the family then we should prioritise giving unique queues to the early
      // indices
      for(uint32_t q = 0; q < origprops[origQIndex].queueCount; q++)
      {
        m_QueueRemapping[origQIndex][q] = {destFamily, q % queueProps[destFamily].queueCount};
      }
    }

    VkDeviceQueueCreateInfo *queueCreateInfos =
        (VkDeviceQueueCreateInfo *)createInfo.pQueueCreateInfos;

    // now apply the remapping to the requested queues
    for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++)
    {
      VkDeviceQueueCreateInfo &queueCreate = (VkDeviceQueueCreateInfo &)queueCreateInfos[i];

      uint32_t queueFamily = queueCreate.queueFamilyIndex;
      queueFamily = m_QueueRemapping[queueFamily][0].family;
      queueCreate.queueFamilyIndex = queueFamily;
      uint32_t queueCount = RDCMIN(queueCreate.queueCount, queueProps[queueFamily].queueCount);

      if(queueCount < queueCreate.queueCount)
        RDCWARN("Truncating queue family request from %u queues to %u queues",
                queueCreate.queueCount, queueCount);

      queueCreate.queueCount = queueCount;
    }

    // remove any duplicates that have been created
    rdcarray<VkDeviceQueueCreateInfo> queueInfos;

    for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++)
    {
      VkDeviceQueueCreateInfo &queue1 = (VkDeviceQueueCreateInfo &)queueCreateInfos[i];

      // if we already have this one in the list, continue
      bool already = false;
      for(const VkDeviceQueueCreateInfo &queue2 : queueInfos)
      {
        if(queue1.queueFamilyIndex == queue2.queueFamilyIndex)
        {
          already = true;
          break;
        }
      }

      if(already)
        continue;

      // get the 'biggest' queue allocation from all duplicates. That way we ensure we have enough
      // queues in the queue family to satisfy any remap.
      VkDeviceQueueCreateInfo biggest = queue1;

      for(uint32_t j = i + 1; j < createInfo.queueCreateInfoCount; j++)
      {
        VkDeviceQueueCreateInfo &queue2 = (VkDeviceQueueCreateInfo &)queueCreateInfos[j];

        if(biggest.queueFamilyIndex == queue2.queueFamilyIndex)
        {
          if(queue2.queueCount > biggest.queueCount)
            biggest = queue2;
        }
      }

      queueInfos.push_back(biggest);
    }

    createInfo.queueCreateInfoCount = (uint32_t)queueInfos.size();
    createInfo.pQueueCreateInfos = queueInfos.data();

    uint32_t qFamilyIdx = 0;

    if(!SelectGraphicsComputeQueue(queueProps, createInfo, qFamilyIdx))
    {
      SET_ERROR_RESULT(
          m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
          "Can't add a queue with required properties for RenderDoc! Unsupported configuration");
      return false;
    }

    // remove structs from extensions that we have stripped but may still be referenced here,
    // to ensure we don't pass structs for disabled extensions.
    bool private_data = false;
    private_data |= RemoveNextStruct(&createInfo, VK_STRUCTURE_TYPE_DEVICE_PRIVATE_DATA_CREATE_INFO);
    private_data |=
        RemoveNextStruct(&createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIVATE_DATA_FEATURES);
    if(private_data)
    {
      RDCLOG("Removed VK_EXT_private_data structs from vkCreateDevice pNext chain");
    }

    bool present_exts = false;
    present_exts |=
        RemoveNextStruct(&createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR);
    present_exts |=
        RemoveNextStruct(&createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR);
    present_exts |=
        RemoveNextStruct(&createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_2_FEATURES_KHR);
    present_exts |=
        RemoveNextStruct(&createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_2_FEATURES_KHR);
    present_exts |= RemoveNextStruct(
        &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_KHR);
    if(present_exts)
    {
      RDCLOG("Removed VK_KHR_present_id/wait/latest_ready structs from vkCreateDevice pNext chain");
    }

    VkPhysicalDeviceFeatures enabledFeatures = {0};
    if(createInfo.pEnabledFeatures != NULL)
      enabledFeatures = *createInfo.pEnabledFeatures;

    VkPhysicalDeviceFeatures2 *enabledFeatures2 = (VkPhysicalDeviceFeatures2 *)FindNextStruct(
        &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2);

    // VkPhysicalDeviceFeatures2 takes priority
    if(enabledFeatures2)
      enabledFeatures = enabledFeatures2->features;
    else if(createInfo.pEnabledFeatures)
      enabledFeatures = *createInfo.pEnabledFeatures;

    VkPhysicalDeviceFeatures availFeatures = {0};
    ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures(Unwrap(physicalDevice), &availFeatures);

#define CHECK_PHYS_FEATURE(feature)                                            \
  if(enabledFeatures.feature && !availFeatures.feature)                        \
  {                                                                            \
    SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIHardwareUnsupported, \
                     "Capture requires physical device feature '" #feature     \
                     "' which is not supported\n\n%s",                         \
                     GetPhysDeviceCompatString(false, false).c_str());         \
    return false;                                                              \
  }

    CHECK_PHYS_FEATURE(robustBufferAccess);
    CHECK_PHYS_FEATURE(fullDrawIndexUint32);
    CHECK_PHYS_FEATURE(imageCubeArray);
    CHECK_PHYS_FEATURE(independentBlend);
    CHECK_PHYS_FEATURE(geometryShader);
    CHECK_PHYS_FEATURE(tessellationShader);
    CHECK_PHYS_FEATURE(sampleRateShading);
    CHECK_PHYS_FEATURE(dualSrcBlend);
    CHECK_PHYS_FEATURE(logicOp);
    CHECK_PHYS_FEATURE(multiDrawIndirect);
    CHECK_PHYS_FEATURE(drawIndirectFirstInstance);
    CHECK_PHYS_FEATURE(depthClamp);
    CHECK_PHYS_FEATURE(depthBiasClamp);
    CHECK_PHYS_FEATURE(fillModeNonSolid);
    CHECK_PHYS_FEATURE(depthBounds);
    CHECK_PHYS_FEATURE(wideLines);
    CHECK_PHYS_FEATURE(largePoints);
    CHECK_PHYS_FEATURE(alphaToOne);
    CHECK_PHYS_FEATURE(multiViewport);
    CHECK_PHYS_FEATURE(samplerAnisotropy);
    CHECK_PHYS_FEATURE(textureCompressionETC2);
    CHECK_PHYS_FEATURE(textureCompressionASTC_LDR);
    CHECK_PHYS_FEATURE(textureCompressionBC);
    CHECK_PHYS_FEATURE(occlusionQueryPrecise);
    CHECK_PHYS_FEATURE(pipelineStatisticsQuery);
    CHECK_PHYS_FEATURE(vertexPipelineStoresAndAtomics);
    CHECK_PHYS_FEATURE(fragmentStoresAndAtomics);
    CHECK_PHYS_FEATURE(shaderTessellationAndGeometryPointSize);
    CHECK_PHYS_FEATURE(shaderImageGatherExtended);
    CHECK_PHYS_FEATURE(shaderStorageImageExtendedFormats);
    CHECK_PHYS_FEATURE(shaderStorageImageMultisample);
    CHECK_PHYS_FEATURE(shaderStorageImageReadWithoutFormat);
    CHECK_PHYS_FEATURE(shaderStorageImageWriteWithoutFormat);
    CHECK_PHYS_FEATURE(shaderUniformBufferArrayDynamicIndexing);
    CHECK_PHYS_FEATURE(shaderSampledImageArrayDynamicIndexing);
    CHECK_PHYS_FEATURE(shaderStorageBufferArrayDynamicIndexing);
    CHECK_PHYS_FEATURE(shaderStorageImageArrayDynamicIndexing);
    CHECK_PHYS_FEATURE(shaderClipDistance);
    CHECK_PHYS_FEATURE(shaderCullDistance);
    CHECK_PHYS_FEATURE(shaderFloat64);
    CHECK_PHYS_FEATURE(shaderInt64);
    CHECK_PHYS_FEATURE(shaderInt16);
    CHECK_PHYS_FEATURE(shaderResourceResidency);
    CHECK_PHYS_FEATURE(shaderResourceMinLod);
    CHECK_PHYS_FEATURE(sparseBinding);
    CHECK_PHYS_FEATURE(sparseResidencyBuffer);
    CHECK_PHYS_FEATURE(sparseResidencyImage2D);
    CHECK_PHYS_FEATURE(sparseResidencyImage3D);
    CHECK_PHYS_FEATURE(sparseResidency2Samples);
    CHECK_PHYS_FEATURE(sparseResidency4Samples);
    CHECK_PHYS_FEATURE(sparseResidency8Samples);
    CHECK_PHYS_FEATURE(sparseResidency16Samples);
    CHECK_PHYS_FEATURE(sparseResidencyAliased);
    CHECK_PHYS_FEATURE(variableMultisampleRate);
    CHECK_PHYS_FEATURE(inheritedQueries);

#define BEGIN_PHYS_EXT_CHECK(struct, stype)                                                  \
  if(struct *ext = (struct *)FindNextStruct(&createInfo, stype))                             \
  {                                                                                          \
    struct avail = {stype};                                                                  \
    VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};    \
    availBase.pNext = &avail;                                                                \
    ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase); \
    const char *structName = #struct;

#define END_PHYS_EXT_CHECK() }

#define CHECK_PHYS_EXT_FEATURE(feature)                                            \
  if(ext->feature && !avail.feature)                                               \
  {                                                                                \
    SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIHardwareUnsupported,     \
                     "Capture requires physical device feature '" #feature         \
                     "' in struct '%s' which is not supported\n\n%s",              \
                     structName, GetPhysDeviceCompatString(false, false).c_str()); \
    return false;                                                                  \
  }

    VkPhysicalDeviceDescriptorIndexingFeatures descIndexingFeatures = {};
    VkPhysicalDeviceVulkan12Features vulkan12Features = {};
    VkPhysicalDeviceVulkan13Features vulkan13Features = {};
    VkPhysicalDeviceVulkan14Features vulkan14Features = {};
    VkPhysicalDeviceSynchronization2Features sync2 = {};

    if(ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2)
    {
      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVulkan11Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(storageBuffer16BitAccess);
        CHECK_PHYS_EXT_FEATURE(uniformAndStorageBuffer16BitAccess);
        CHECK_PHYS_EXT_FEATURE(storagePushConstant16);
        CHECK_PHYS_EXT_FEATURE(storageInputOutput16);
        CHECK_PHYS_EXT_FEATURE(multiview);
        CHECK_PHYS_EXT_FEATURE(multiviewGeometryShader);
        CHECK_PHYS_EXT_FEATURE(multiviewTessellationShader);
        CHECK_PHYS_EXT_FEATURE(variablePointersStorageBuffer);
        CHECK_PHYS_EXT_FEATURE(variablePointers);
        CHECK_PHYS_EXT_FEATURE(protectedMemory);
        CHECK_PHYS_EXT_FEATURE(samplerYcbcrConversion);
        CHECK_PHYS_EXT_FEATURE(shaderDrawParameters);

        m_MultiView |= ext->multiview != VK_FALSE;

        if(m_MultiView)
        {
          if(avail.multiviewGeometryShader)
            ext->multiviewGeometryShader = true;
          else
            RDCWARN(
                "multiviewGeometryShader = false, triangle size overlay with multiview "
                "unavailable");

          m_MultiViewGeometryShaders |= ext->multiviewGeometryShader != VK_FALSE;
        }
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVulkan12Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);
      {
        vulkan12Features = *ext;

        CHECK_PHYS_EXT_FEATURE(samplerMirrorClampToEdge);
        CHECK_PHYS_EXT_FEATURE(drawIndirectCount);
        CHECK_PHYS_EXT_FEATURE(storageBuffer8BitAccess);
        CHECK_PHYS_EXT_FEATURE(uniformAndStorageBuffer8BitAccess);
        CHECK_PHYS_EXT_FEATURE(storagePushConstant8);
        CHECK_PHYS_EXT_FEATURE(shaderBufferInt64Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderSharedInt64Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderFloat16);
        CHECK_PHYS_EXT_FEATURE(shaderInt8);
        CHECK_PHYS_EXT_FEATURE(descriptorIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderInputAttachmentArrayDynamicIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderUniformTexelBufferArrayDynamicIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageTexelBufferArrayDynamicIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderUniformBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderSampledImageArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageImageArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderInputAttachmentArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderUniformTexelBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageTexelBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingUniformBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingSampledImageUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingStorageImageUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingStorageBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingUniformTexelBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingStorageTexelBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingUpdateUnusedWhilePending);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingPartiallyBound);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingVariableDescriptorCount);
        CHECK_PHYS_EXT_FEATURE(runtimeDescriptorArray);
        CHECK_PHYS_EXT_FEATURE(samplerFilterMinmax);
        CHECK_PHYS_EXT_FEATURE(scalarBlockLayout);
        CHECK_PHYS_EXT_FEATURE(imagelessFramebuffer);
        CHECK_PHYS_EXT_FEATURE(uniformBufferStandardLayout);
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupExtendedTypes);
        CHECK_PHYS_EXT_FEATURE(separateDepthStencilLayouts);
        CHECK_PHYS_EXT_FEATURE(hostQueryReset);
        CHECK_PHYS_EXT_FEATURE(timelineSemaphore);
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddress);
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddressCaptureReplay);
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddressMultiDevice);
        CHECK_PHYS_EXT_FEATURE(vulkanMemoryModel);
        CHECK_PHYS_EXT_FEATURE(vulkanMemoryModelDeviceScope);
        CHECK_PHYS_EXT_FEATURE(vulkanMemoryModelAvailabilityVisibilityChains);
        CHECK_PHYS_EXT_FEATURE(shaderOutputViewportIndex);
        CHECK_PHYS_EXT_FEATURE(shaderOutputLayer);
        CHECK_PHYS_EXT_FEATURE(subgroupBroadcastDynamicId);

        m_SeparateDepthStencil |= (ext->separateDepthStencilLayouts != VK_FALSE);

        if(ext->bufferDeviceAddress && !avail.bufferDeviceAddressCaptureReplay)
        {
          SET_ERROR_RESULT(
              m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
              "Capture requires bufferDeviceAddress support, which is available, but "
              "bufferDeviceAddressCaptureReplay support is not available which is required to "
              "replay\n"
              "\n%s",
              GetPhysDeviceCompatString(false, false).c_str());
          return false;
        }
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVulkan13Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES);
      {
        vulkan13Features = *ext;

        CHECK_PHYS_EXT_FEATURE(robustImageAccess);
        CHECK_PHYS_EXT_FEATURE(inlineUniformBlock);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingInlineUniformBlockUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(pipelineCreationCacheControl);
        CHECK_PHYS_EXT_FEATURE(privateData);
        CHECK_PHYS_EXT_FEATURE(shaderDemoteToHelperInvocation);
        CHECK_PHYS_EXT_FEATURE(shaderTerminateInvocation);
        CHECK_PHYS_EXT_FEATURE(subgroupSizeControl);
        CHECK_PHYS_EXT_FEATURE(computeFullSubgroups);
        CHECK_PHYS_EXT_FEATURE(synchronization2);
        CHECK_PHYS_EXT_FEATURE(textureCompressionASTC_HDR);
        CHECK_PHYS_EXT_FEATURE(shaderZeroInitializeWorkgroupMemory);
        CHECK_PHYS_EXT_FEATURE(dynamicRendering);
        CHECK_PHYS_EXT_FEATURE(shaderIntegerDotProduct);
        CHECK_PHYS_EXT_FEATURE(maintenance4);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVulkan14Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES);
      {
        vulkan14Features = *ext;

        CHECK_PHYS_EXT_FEATURE(globalPriorityQuery);
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupRotate);
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupRotateClustered);
        CHECK_PHYS_EXT_FEATURE(shaderFloatControls2);
        CHECK_PHYS_EXT_FEATURE(shaderExpectAssume);
        CHECK_PHYS_EXT_FEATURE(rectangularLines);
        CHECK_PHYS_EXT_FEATURE(bresenhamLines);
        CHECK_PHYS_EXT_FEATURE(smoothLines);
        CHECK_PHYS_EXT_FEATURE(stippledRectangularLines);
        CHECK_PHYS_EXT_FEATURE(stippledBresenhamLines);
        CHECK_PHYS_EXT_FEATURE(stippledSmoothLines);
        CHECK_PHYS_EXT_FEATURE(vertexAttributeInstanceRateDivisor);
        CHECK_PHYS_EXT_FEATURE(vertexAttributeInstanceRateZeroDivisor);
        CHECK_PHYS_EXT_FEATURE(indexTypeUint8);
        CHECK_PHYS_EXT_FEATURE(dynamicRenderingLocalRead);
        CHECK_PHYS_EXT_FEATURE(maintenance5);
        CHECK_PHYS_EXT_FEATURE(maintenance6);
        CHECK_PHYS_EXT_FEATURE(pipelineProtectedAccess);
        CHECK_PHYS_EXT_FEATURE(pipelineRobustness);
        CHECK_PHYS_EXT_FEATURE(hostImageCopy);
        CHECK_PHYS_EXT_FEATURE(pushDescriptor);

        m_Maintenance5 |= ext->maintenance5 != VK_FALSE;
        m_Maintenance6 |= ext->maintenance6 != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDevice8BitStorageFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(storageBuffer8BitAccess);
        CHECK_PHYS_EXT_FEATURE(uniformAndStorageBuffer8BitAccess);
        CHECK_PHYS_EXT_FEATURE(storagePushConstant8);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDevice16BitStorageFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(storageBuffer16BitAccess);
        CHECK_PHYS_EXT_FEATURE(uniformAndStorageBuffer16BitAccess);
        CHECK_PHYS_EXT_FEATURE(storagePushConstant16);
        CHECK_PHYS_EXT_FEATURE(storageInputOutput16);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceASTCDecodeFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ASTC_DECODE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(decodeModeSharedExponent);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(attachmentFeedbackLoopLayout);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(fragmentShaderBarycentric);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMultiviewFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(multiview);
        CHECK_PHYS_EXT_FEATURE(multiviewGeometryShader);
        CHECK_PHYS_EXT_FEATURE(multiviewTessellationShader);

        m_MultiView |= ext->multiview != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceFragmentDensityMapFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(fragmentDensityMap);
        CHECK_PHYS_EXT_FEATURE(fragmentDensityMapDynamic);
        CHECK_PHYS_EXT_FEATURE(fragmentDensityMapNonSubsampledImages);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceFragmentDensityMap2FeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_2_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(fragmentDensityMapDeferred);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceFragmentDensityMapOffsetFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_OFFSET_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(fragmentDensityMapOffset);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceFragmentDensityMapLayeredFeaturesVALVE,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_LAYERED_FEATURES_VALVE);
      {
        CHECK_PHYS_EXT_FEATURE(fragmentDensityMapLayered);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceProtectedMemoryFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(protectedMemory);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceSamplerYcbcrConversionFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(samplerYcbcrConversion);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderAtomicInt64Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderBufferInt64Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderSharedInt64Atomics);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderDrawParametersFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderDrawParameters);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderImageFootprintFeaturesNV,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_FOOTPRINT_FEATURES_NV);
      {
        CHECK_PHYS_EXT_FEATURE(imageFootprint);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceTransformFeedbackFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(transformFeedback);
        CHECK_PHYS_EXT_FEATURE(geometryStreams);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVariablePointersFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(variablePointersStorageBuffer);
        CHECK_PHYS_EXT_FEATURE(variablePointers);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVertexAttributeDivisorFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(vertexAttributeInstanceRateDivisor);
        CHECK_PHYS_EXT_FEATURE(vertexAttributeInstanceRateZeroDivisor);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceVertexAttributeRobustnessFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_ROBUSTNESS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(vertexAttributeRobustness);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVulkanMemoryModelFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(vulkanMemoryModel);
        CHECK_PHYS_EXT_FEATURE(vulkanMemoryModelDeviceScope);
        CHECK_PHYS_EXT_FEATURE(vulkanMemoryModelAvailabilityVisibilityChains);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceConditionalRenderingFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(conditionalRendering);
        CHECK_PHYS_EXT_FEATURE(inheritedConditionalRendering);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceHostQueryResetFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(hostQueryReset);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceDepthClipControlFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(depthClipControl);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(primitiveTopologyListRestart);
        CHECK_PHYS_EXT_FEATURE(primitiveTopologyPatchListRestart);

        m_ListRestart = ext->primitiveTopologyListRestart != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(primitivesGeneratedQuery);
        CHECK_PHYS_EXT_FEATURE(primitivesGeneratedQueryWithRasterizerDiscard);
        CHECK_PHYS_EXT_FEATURE(primitivesGeneratedQueryWithNonZeroStreams);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(multisampledRenderToSingleSampled);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(rasterizationOrderColorAttachmentAccess);
        CHECK_PHYS_EXT_FEATURE(rasterizationOrderDepthAttachmentAccess);
        CHECK_PHYS_EXT_FEATURE(rasterizationOrderStencilAttachmentAccess);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceDepthClipEnableFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(depthClipEnable);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceYcbcrImageArraysFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_IMAGE_ARRAYS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(ycbcrImageArrays);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceBufferDeviceAddressFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddress);
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddressCaptureReplay);
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddressMultiDevice);

        if(ext->bufferDeviceAddress && !avail.bufferDeviceAddressCaptureReplay)
        {
          SET_ERROR_RESULT(
              m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
              "Capture requires bufferDeviceAddress support, which is available, but "
              "bufferDeviceAddressCaptureReplay support is not available which is required to "
              "replay\n"
              "\n%s",
              GetPhysDeviceCompatString(false, false).c_str());
          return false;
        }
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceBufferDeviceAddressFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddress);
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddressCaptureReplay);
        CHECK_PHYS_EXT_FEATURE(bufferDeviceAddressMultiDevice);

        if(ext->bufferDeviceAddress && !avail.bufferDeviceAddressCaptureReplay)
        {
          SET_ERROR_RESULT(
              m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
              "Capture requires bufferDeviceAddress support, which is available, but "
              "bufferDeviceAddressCaptureReplay support is not available which is required to "
              "replay\n"
              "\n%s",
              GetPhysDeviceCompatString(false, false).c_str());
          return false;
        }
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceDescriptorIndexingFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES);
      {
        descIndexingFeatures = *ext;

        CHECK_PHYS_EXT_FEATURE(shaderInputAttachmentArrayDynamicIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderUniformTexelBufferArrayDynamicIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageTexelBufferArrayDynamicIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderUniformBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderSampledImageArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageImageArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderInputAttachmentArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderUniformTexelBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(shaderStorageTexelBufferArrayNonUniformIndexing);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingUniformBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingSampledImageUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingStorageImageUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingStorageBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingUniformTexelBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingStorageTexelBufferUpdateAfterBind);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingUpdateUnusedWhilePending);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingPartiallyBound);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingVariableDescriptorCount);
        CHECK_PHYS_EXT_FEATURE(runtimeDescriptorArray);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceUniformBufferStandardLayoutFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(uniformBufferStandardLayout);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(fragmentShaderSampleInterlock);
        CHECK_PHYS_EXT_FEATURE(fragmentShaderPixelInterlock);
        CHECK_PHYS_EXT_FEATURE(fragmentShaderShadingRateInterlock);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceShaderDemoteToHelperInvocationFeatures,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderDemoteToHelperInvocation);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(texelBufferAlignment);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceIndexTypeUint8Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(indexTypeUint8);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceImagelessFramebufferFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(imagelessFramebuffer);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceSubgroupSizeControlFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(subgroupSizeControl);
        CHECK_PHYS_EXT_FEATURE(computeFullSubgroups);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(pipelineExecutableInfo);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceLineRasterizationFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(rectangularLines);
        CHECK_PHYS_EXT_FEATURE(bresenhamLines);
        CHECK_PHYS_EXT_FEATURE(smoothLines);
        CHECK_PHYS_EXT_FEATURE(stippledRectangularLines);
        CHECK_PHYS_EXT_FEATURE(stippledBresenhamLines);
        CHECK_PHYS_EXT_FEATURE(stippledSmoothLines);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupExtendedTypes);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderSubgroupRotateFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_ROTATE_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupRotate);
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupRotateClustered);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceCoherentMemoryFeaturesAMD,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD);
      {
        CHECK_PHYS_EXT_FEATURE(deviceCoherentMemory);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderClockFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CLOCK_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupClock);
        CHECK_PHYS_EXT_FEATURE(shaderDeviceClock);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMemoryPriorityFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(memoryPriority);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceScalarBlockLayoutFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(scalarBlockLayout);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderFloat16Int8Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderFloat16);
        CHECK_PHYS_EXT_FEATURE(shaderInt8);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderFloatControls2Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT_CONTROLS_2_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderFloatControls2);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceTimelineSemaphoreFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(timelineSemaphore);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(separateDepthStencilLayouts);

        m_SeparateDepthStencil |= (ext->separateDepthStencilLayouts != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDevicePerformanceQueryFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(performanceCounterQueryPools);
        CHECK_PHYS_EXT_FEATURE(performanceCounterMultipleQueryPools);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceInlineUniformBlockFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(inlineUniformBlock);
        CHECK_PHYS_EXT_FEATURE(descriptorBindingInlineUniformBlockUpdateAfterBind);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceCustomBorderColorFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(customBorderColors);
        CHECK_PHYS_EXT_FEATURE(customBorderColorWithoutFormat);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceRobustness2FeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(robustBufferAccess2);
        CHECK_PHYS_EXT_FEATURE(robustImageAccess2);
        CHECK_PHYS_EXT_FEATURE(nullDescriptor);

        m_NULLDescriptorsAllowed |= (ext->nullDescriptor != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDevicePipelineCreationCacheControlFeatures,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(pipelineCreationCacheControl);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceComputeShaderDerivativesFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COMPUTE_SHADER_DERIVATIVES_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(computeDerivativeGroupQuads);
        CHECK_PHYS_EXT_FEATURE(computeDerivativeGroupLinear);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceExtendedDynamicStateFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState);

        m_ExtendedDynState = (ext->extendedDynamicState != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderTerminateInvocationFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderTerminateInvocation);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceImageRobustnessFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(robustImageAccess);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderAtomicFloatFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat32Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat32AtomicAdd);
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat64Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat64AtomicAdd);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat32Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat32AtomicAdd);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat64Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat64AtomicAdd);
        CHECK_PHYS_EXT_FEATURE(shaderImageFloat32Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderImageFloat32AtomicAdd);
        CHECK_PHYS_EXT_FEATURE(sparseImageFloat32Atomics);
        CHECK_PHYS_EXT_FEATURE(sparseImageFloat32AtomicAdd);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderImageAtomicInt64FeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_ATOMIC_INT64_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(shaderImageInt64Atomics);
        CHECK_PHYS_EXT_FEATURE(sparseImageInt64Atomics);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceZeroInitializeWorkgroupMemoryFeatures,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ZERO_INITIALIZE_WORKGROUP_MEMORY_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderZeroInitializeWorkgroupMemory);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(workgroupMemoryExplicitLayout);
        CHECK_PHYS_EXT_FEATURE(workgroupMemoryExplicitLayoutScalarBlockLayout);
        CHECK_PHYS_EXT_FEATURE(workgroupMemoryExplicitLayout8BitAccess);
        CHECK_PHYS_EXT_FEATURE(workgroupMemoryExplicitLayout16BitAccess);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceSynchronization2Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES);
      {
        sync2 = *ext;

        CHECK_PHYS_EXT_FEATURE(synchronization2);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMaintenance4Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(maintenance4);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderIntegerDotProductFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderIntegerDotProduct);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(shaderSubgroupUniformControlFlow);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderAtomicFloat2FeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_2_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat16Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat16AtomicAdd);
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat16AtomicMinMax);
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat32AtomicMinMax);
        CHECK_PHYS_EXT_FEATURE(shaderBufferFloat64AtomicMinMax);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat16Atomics);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat16AtomicAdd);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat16AtomicMinMax);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat32AtomicMinMax);
        CHECK_PHYS_EXT_FEATURE(shaderSharedFloat64AtomicMinMax);
        CHECK_PHYS_EXT_FEATURE(shaderImageFloat32AtomicMinMax);
        CHECK_PHYS_EXT_FEATURE(sparseImageFloat32AtomicMinMax);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceYcbcr2Plane444FormatsFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_2_PLANE_444_FORMATS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(ycbcr2plane444Formats);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(formatRgba10x6WithoutYCbCrSampler);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceGlobalPriorityQueryFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(globalPriorityQuery);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceColorWriteEnableFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(colorWriteEnable);

        m_DynColorWrite = (ext->colorWriteEnable != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceExtendedDynamicState2FeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState2);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState2LogicOp);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState2PatchControlPoints);

        m_ExtendedDynState2 = (ext->extendedDynamicState2 != VK_FALSE);
        m_ExtendedDynState2Logic = (ext->extendedDynamicState2LogicOp != VK_FALSE);
        m_ExtendedDynState2CPs = (ext->extendedDynamicState2PatchControlPoints != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(vertexInputDynamicState);

        m_DynVertexInput = (ext->vertexInputDynamicState != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(graphicsPipelineLibrary);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceDynamicRenderingFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(dynamicRendering);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceDynamicRenderingLocalReadFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_LOCAL_READ_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(dynamicRenderingLocalRead);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(dynamicRenderingUnusedAttachments);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDevice4444FormatsFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(formatA4R4G4B4);
        CHECK_PHYS_EXT_FEATURE(formatA4B4G4R4);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceTextureCompressionASTCHDRFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(textureCompressionASTC_HDR);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceFragmentShadingRateFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(pipelineFragmentShadingRate);
        CHECK_PHYS_EXT_FEATURE(primitiveFragmentShadingRate);
        CHECK_PHYS_EXT_FEATURE(attachmentFragmentShadingRate);

        m_FragmentShadingRate = (ext->pipelineFragmentShadingRate != VK_FALSE) ||
                                (ext->primitiveFragmentShadingRate != VK_FALSE) ||
                                (ext->attachmentFragmentShadingRate != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMutableDescriptorTypeFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MUTABLE_DESCRIPTOR_TYPE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(mutableDescriptorType);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDevicePageableDeviceLocalMemoryFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PAGEABLE_DEVICE_LOCAL_MEMORY_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(pageableDeviceLocalMemory);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceSwapchainMaintenance1FeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(swapchainMaintenance1);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceBorderColorSwizzleFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BORDER_COLOR_SWIZZLE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(borderColorSwizzle);
        CHECK_PHYS_EXT_FEATURE(borderColorSwizzleFromImage);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceNonSeamlessCubeMapFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(nonSeamlessCubeMap);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceDepthClampZeroOneFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLAMP_ZERO_ONE_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(depthClampZeroOne);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceImageViewMinLodFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_VIEW_MIN_LOD_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(minLod);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceProvokingVertexFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(provokingVertexLast);
        CHECK_PHYS_EXT_FEATURE(transformFeedbackPreservesProvokingVertex);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(attachmentFeedbackLoopDynamicState);
        m_DynAttachmentLoop = ext->attachmentFeedbackLoopDynamicState != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceImage2DViewOf3DFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_2D_VIEW_OF_3D_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(image2DViewOf3D);
        CHECK_PHYS_EXT_FEATURE(sampler2DViewOf3D);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3TessellationDomainOrigin);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3DepthClampEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3PolygonMode);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3RasterizationSamples);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3SampleMask);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3AlphaToCoverageEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3AlphaToOneEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3LogicOpEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ColorBlendEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ColorBlendEquation);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ColorWriteMask);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3RasterizationStream);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ConservativeRasterizationMode);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ExtraPrimitiveOverestimationSize);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3DepthClipEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3SampleLocationsEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ColorBlendAdvanced);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ProvokingVertexMode);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3LineRasterizationMode);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3LineStippleEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3DepthClipNegativeOneToOne);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ViewportWScalingEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ViewportSwizzle);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3CoverageToColorEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3CoverageToColorLocation);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3CoverageModulationMode);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3CoverageModulationTableEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3CoverageModulationTable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3CoverageReductionMode);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3RepresentativeFragmentTestEnable);
        CHECK_PHYS_EXT_FEATURE(extendedDynamicState3ShadingRateImageEnable);

        m_ExtendedDynState3TesselDomain =
            (ext->extendedDynamicState3TessellationDomainOrigin != VK_FALSE);
        m_ExtendedDynState3DepthClampEnable =
            (ext->extendedDynamicState3DepthClampEnable != VK_FALSE);
        m_ExtendedDynState3PolyMode = (ext->extendedDynamicState3PolygonMode != VK_FALSE);
        m_ExtendedDynState3RastSamples = (ext->extendedDynamicState3RasterizationSamples != VK_FALSE);
        m_ExtendedDynState3SampleMask = (ext->extendedDynamicState3SampleMask != VK_FALSE);
        m_ExtendedDynState3AlphaToCover =
            (ext->extendedDynamicState3AlphaToCoverageEnable != VK_FALSE);
        m_ExtendedDynState3AlphaToOne = (ext->extendedDynamicState3AlphaToOneEnable != VK_FALSE);
        m_ExtendedDynState3LogicEnable = (ext->extendedDynamicState3LogicOpEnable != VK_FALSE);
        m_ExtendedDynState3CBEnable = (ext->extendedDynamicState3ColorBlendEnable != VK_FALSE);
        m_ExtendedDynState3CBEquation = (ext->extendedDynamicState3ColorBlendEquation != VK_FALSE);
        m_ExtendedDynState3WriteMask = (ext->extendedDynamicState3ColorWriteMask != VK_FALSE);
        m_ExtendedDynState3RastStream = (ext->extendedDynamicState3RasterizationStream != VK_FALSE);
        m_ExtendedDynState3ConservRast =
            (ext->extendedDynamicState3ConservativeRasterizationMode != VK_FALSE);
        m_ExtendedDynState3PrimOverest =
            (ext->extendedDynamicState3ExtraPrimitiveOverestimationSize != VK_FALSE);
        m_ExtendedDynState3DepthClip = (ext->extendedDynamicState3DepthClipEnable != VK_FALSE);
        m_ExtendedDynState3SampleLoc = (ext->extendedDynamicState3SampleLocationsEnable != VK_FALSE);
        m_ExtendedDynState3ProvokingVert =
            (ext->extendedDynamicState3ProvokingVertexMode != VK_FALSE);
        m_ExtendedDynState3LineRast = (ext->extendedDynamicState3LineRasterizationMode != VK_FALSE);
        m_ExtendedDynState3LineStipple = (ext->extendedDynamicState3LineStippleEnable != VK_FALSE);
        m_ExtendedDynState3DepthClipNeg =
            (ext->extendedDynamicState3DepthClipNegativeOneToOne != VK_FALSE);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMeshShaderFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(taskShader);
        CHECK_PHYS_EXT_FEATURE(meshShader);
        CHECK_PHYS_EXT_FEATURE(multiviewMeshShader);
        CHECK_PHYS_EXT_FEATURE(primitiveFragmentShadingRateMeshShader);
        CHECK_PHYS_EXT_FEATURE(meshShaderQueries);

        m_MeshShaders = ext->meshShader != VK_FALSE;
        m_TaskShaders = ext->taskShader != VK_FALSE;
        m_MeshQueries = avail.meshShaderQueries != VK_FALSE;

        if(avail.meshShaderQueries)
          ext->meshShaderQueries = true;
        else
          RDCWARN("meshShaderQueries = false, mesh shader performance counters unavailable");
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceAccelerationStructureFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(accelerationStructure)
        CHECK_PHYS_EXT_FEATURE(accelerationStructureCaptureReplay)
        CHECK_PHYS_EXT_FEATURE(accelerationStructureIndirectBuild)
        CHECK_PHYS_EXT_FEATURE(descriptorBindingAccelerationStructureUpdateAfterBind)

        if(ext->accelerationStructure && !avail.accelerationStructureCaptureReplay)
        {
          SET_ERROR_RESULT(
              m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
              "Capture requires accelerationStructure support, which is available, but "
              "accelerationStructureCaptureReplay support is not available which is required to "
              "replay\n"
              "\n%s",
              GetPhysDeviceCompatString(false, false).c_str());
          return false;
        }

        m_AccelerationStructures = ext->accelerationStructure != VK_FALSE;
        if(m_AccelerationStructures)
        {
          RDCLOG(
              "Ray tracing acceleration structures requested, allocating all device memory with "
              "VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT");
          ext->accelerationStructureCaptureReplay = VK_TRUE;
        }
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceNestedCommandBufferFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NESTED_COMMAND_BUFFER_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(nestedCommandBuffer);
        CHECK_PHYS_EXT_FEATURE(nestedCommandBufferRendering);
        CHECK_PHYS_EXT_FEATURE(nestedCommandBufferSimultaneousUse);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderObjectFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(shaderObject);

        m_ShaderObject = ext->shaderObject != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceShaderRelaxedExtendedInstructionFeaturesKHR,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_RELAXED_EXTENDED_INSTRUCTION_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(shaderRelaxedExtendedInstruction);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderExpectAssumeFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_EXPECT_ASSUME_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(shaderExpectAssume);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceShaderMaximalReconvergenceFeaturesKHR,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_MAXIMAL_RECONVERGENCE_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(shaderMaximalReconvergence);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderQuadControlFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_QUAD_CONTROL_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(shaderQuadControl);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceRayTracingPipelineFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(rayTracingPipeline);
        CHECK_PHYS_EXT_FEATURE(rayTracingPipelineShaderGroupHandleCaptureReplay);
        CHECK_PHYS_EXT_FEATURE(rayTracingPipelineShaderGroupHandleCaptureReplayMixed);
        CHECK_PHYS_EXT_FEATURE(rayTracingPipelineTraceRaysIndirect);
        CHECK_PHYS_EXT_FEATURE(rayTraversalPrimitiveCulling);

        if(ext->rayTracingPipeline && !avail.rayTracingPipelineShaderGroupHandleCaptureReplay)
        {
          SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
                           "Capture requires rayTracingPipeline support, which is available, but "
                           "rayTracingPipelineShaderGroupHandleCaptureReplay support is not "
                           "available which is required to replay\n"
                           "\n%s",
                           GetPhysDeviceCompatString(false, false).c_str());
          return false;
        }
        if(ext->rayTracingPipeline)
          ext->rayTracingPipelineShaderGroupHandleCaptureReplay = VK_TRUE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceRayTracingMaintenance1FeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MAINTENANCE_1_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(rayTracingMaintenance1);
        CHECK_PHYS_EXT_FEATURE(rayTracingPipelineTraceRaysIndirect2);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_POSITION_FETCH_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(rayTracingPositionFetch);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMaintenance5Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(maintenance5);
        m_Maintenance5 |= ext->maintenance5 != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceImageCompressionControlFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(imageCompressionControl);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(imageCompressionControlSwapchain);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceDescriptorBufferFeaturesEXT,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT);
      {
        CHECK_PHYS_EXT_FEATURE(descriptorBuffer);
        CHECK_PHYS_EXT_FEATURE(descriptorBufferCaptureReplay);
        CHECK_PHYS_EXT_FEATURE(descriptorBufferImageLayoutIgnored);
        CHECK_PHYS_EXT_FEATURE(descriptorBufferPushDescriptors);

        if(ext->descriptorBuffer && !avail.descriptorBufferCaptureReplay)
        {
          SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIHardwareUnsupported,
                           "Capture requires descriptorBuffer support, which is available, but "
                           "descriptorBufferCaptureReplay support is not "
                           "available which is required to replay\n"
                           "\n%s",
                           GetPhysDeviceCompatString(false, false).c_str());
          return false;
        }
        if(ext->descriptorBuffer)
          ext->descriptorBufferCaptureReplay = VK_TRUE;

        // enable the imageLayoutIgnored feature if available
        if(avail.descriptorBufferImageLayoutIgnored)
          ext->descriptorBufferImageLayoutIgnored = VK_TRUE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceShaderBfloat16FeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_BFLOAT16_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(shaderBFloat16Type);
        CHECK_PHYS_EXT_FEATURE(shaderBFloat16DotProduct);
        CHECK_PHYS_EXT_FEATURE(shaderBFloat16CooperativeMatrix);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceUnifiedImageLayoutsFeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFIED_IMAGE_LAYOUTS_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(unifiedImageLayouts);
        CHECK_PHYS_EXT_FEATURE(unifiedImageLayoutsVideo);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMaintenance6Features,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_6_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(maintenance6);
        m_Maintenance6 |= ext->maintenance6 != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMaintenance7FeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_7_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(maintenance7);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMaintenance8FeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_8_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(maintenance8);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceMaintenance9FeaturesKHR,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_9_FEATURES_KHR);
      {
        CHECK_PHYS_EXT_FEATURE(maintenance9);
        m_Maintenance9 |= ext->maintenance9 != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDevicePipelineRobustnessFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(pipelineRobustness);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDevicePipelineProtectedAccessFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROTECTED_ACCESS_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(pipelineProtectedAccess);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(VkPhysicalDeviceHostImageCopyFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES);
      {
        CHECK_PHYS_EXT_FEATURE(hostImageCopy);
      }
      END_PHYS_EXT_CHECK();

      BEGIN_PHYS_EXT_CHECK(
          VkPhysicalDeviceMultiviewPerViewViewportsFeaturesQCOM,
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_VIEWPORTS_FEATURES_QCOM);
      {
        CHECK_PHYS_EXT_FEATURE(multiviewPerViewViewports);
        m_MultiviewPerViewViewports |= ext->multiviewPerViewViewports != VK_FALSE;
      }
      END_PHYS_EXT_CHECK();
    }

    if(availFeatures.depthClamp)
      enabledFeatures.depthClamp = true;
    else
      RDCWARN(
          "depthClamp = false, overlays like highlight drawcall won't show depth-clipped pixels.");

    // we have a fallback for this case, so no warning
    if(availFeatures.fillModeNonSolid)
      enabledFeatures.fillModeNonSolid = true;

    // don't warn if this isn't available, we just won't report the counters
    if(availFeatures.pipelineStatisticsQuery)
      enabledFeatures.pipelineStatisticsQuery = true;

    if(availFeatures.geometryShader)
      enabledFeatures.geometryShader = true;
    else
      RDCWARN(
          "geometryShader = false, pixel history primitive ID and triangle size overlay will not "
          "be available, and local rendering on this device will not support lit mesh views.");

    // enable these features for simplicity, since we use them when available in the shader
    // debugging to simplify samples. If minlod isn't used then we omit it, and that's fine because
    // the application's shaders wouldn't have been using minlod. We use gatherExtended for gather
    // offsets, which means if it's not supported then we can't debug constant offsets properly
    // (because we pass offsets as uniforms), but that's not a big deal.
    if(availFeatures.shaderImageGatherExtended)
      enabledFeatures.shaderImageGatherExtended = true;
    if(availFeatures.shaderResourceMinLod)
      enabledFeatures.shaderResourceMinLod = true;
    if(availFeatures.imageCubeArray)
      enabledFeatures.imageCubeArray = true;

    bool descIndexingAllowsRBA = true;

    if(vulkan12Features.descriptorBindingUniformBufferUpdateAfterBind ||
       vulkan12Features.descriptorBindingStorageBufferUpdateAfterBind ||
       vulkan12Features.descriptorBindingUniformTexelBufferUpdateAfterBind ||
       vulkan12Features.descriptorBindingStorageTexelBufferUpdateAfterBind)
    {
      // if any update after bind feature is enabled, check robustBufferAccessUpdateAfterBind
      VkPhysicalDeviceVulkan12Properties vulkan12Props = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES,
      };

      VkPhysicalDeviceProperties2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
      availBase.pNext = &vulkan12Props;
      ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), &availBase);

      descIndexingAllowsRBA = vulkan12Props.robustBufferAccessUpdateAfterBind != VK_FALSE;
    }

    if(descIndexingFeatures.descriptorBindingUniformBufferUpdateAfterBind ||
       descIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind ||
       descIndexingFeatures.descriptorBindingUniformTexelBufferUpdateAfterBind ||
       descIndexingFeatures.descriptorBindingStorageTexelBufferUpdateAfterBind)
    {
      // if any update after bind feature is enabled, check robustBufferAccessUpdateAfterBind
      VkPhysicalDeviceDescriptorIndexingProperties descIndexingProps = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES,
      };

      VkPhysicalDeviceProperties2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
      availBase.pNext = &descIndexingProps;
      ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), &availBase);

      descIndexingAllowsRBA = descIndexingProps.robustBufferAccessUpdateAfterBind != VK_FALSE;
    }

    if(availFeatures.robustBufferAccess && !descIndexingAllowsRBA)
    {
      // if the feature is available but we can't use it, warn
      RDCWARN(
          "robustBufferAccess is available, but cannot be enabled due to "
          "robustBufferAccessUpdateAfterBind not being avilable and some UpdateAfterBind features "
          "being enabled. "
          "out of bounds access due to bugs in application or RenderDoc may cause crashes");
    }
    else
    {
      // either the feature is available, and we enable it, or it's not available at all.
      if(availFeatures.robustBufferAccess)
        enabledFeatures.robustBufferAccess = true;
      else
        RDCWARN(
            "robustBufferAccess = false, out of bounds access due to bugs in application or "
            "RenderDoc may cause crashes");
    }

    if(availFeatures.shaderInt64)
      enabledFeatures.shaderInt64 = true;
    else
      RDCWARN("shaderInt64 = false, feedback from shaders will use less reliable fallback.");

    if(availFeatures.shaderStorageImageWriteWithoutFormat)
      enabledFeatures.shaderStorageImageWriteWithoutFormat = true;
    else
      RDCWARN(
          "shaderStorageImageWriteWithoutFormat = false, save/load from 2DMS textures will not be "
          "possible");

    if(availFeatures.shaderStorageImageMultisample)
      enabledFeatures.shaderStorageImageMultisample = true;
    else
      RDCWARN(
          "shaderStorageImageMultisample = false, accurately replaying 2DMS textures will not be "
          "possible");

    if(availFeatures.occlusionQueryPrecise)
      enabledFeatures.occlusionQueryPrecise = true;
    else
      RDCWARN("occlusionQueryPrecise = false, samples passed counter will not be available");

    if(availFeatures.fragmentStoresAndAtomics)
      enabledFeatures.fragmentStoresAndAtomics = true;
    else
      RDCWARN(
          "fragmentStoresAndAtomics = false, quad overdraw overlay will not be available and "
          "feedback from shaders will not be fetched for fragment stage");

    if(availFeatures.vertexPipelineStoresAndAtomics)
      enabledFeatures.vertexPipelineStoresAndAtomics = true;
    else
      RDCWARN(
          "vertexPipelineStoresAndAtomics = false, feedback from shaders will not be fetched for "
          "vertex stages");

    if(availFeatures.sampleRateShading)
      enabledFeatures.sampleRateShading = true;
    else
      RDCWARN(
          "sampleRateShading = false, save/load from depth 2DMS textures will not be "
          "possible");

    // patch the enabled features
    if(enabledFeatures2)
      enabledFeatures2->features = enabledFeatures;
    else
      createInfo.pEnabledFeatures = &enabledFeatures;

    uint32_t numExts = 0;

    VkResult vkr =
        ObjDisp(physicalDevice)
            ->EnumerateDeviceExtensionProperties(Unwrap(physicalDevice), NULL, &numExts, NULL);
    CHECK_VKR(this, vkr);

    VkExtensionProperties *exts = new VkExtensionProperties[numExts];

    vkr = ObjDisp(physicalDevice)
              ->EnumerateDeviceExtensionProperties(Unwrap(physicalDevice), NULL, &numExts, exts);
    CHECK_VKR(this, vkr);

    for(uint32_t i = 0; i < numExts; i++)
      RDCLOG("Dev Ext %u: %s (%u)", i, exts[i].extensionName, exts[i].specVersion);

    SAFE_DELETE_ARRAY(exts);

    VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR pipeExecFeatures = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR,
    };

    if(pipeExec)
    {
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      availBase.pNext = &pipeExecFeatures;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      if(pipeExecFeatures.pipelineExecutableInfo)
      {
        // see if there's an existing struct
        VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR *existing =
            (VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR *)FindNextStruct(
                &createInfo,
                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR);

        if(existing)
        {
          // if so, make sure the feature is enabled
          existing->pipelineExecutableInfo = VK_TRUE;
        }
        else
        {
          // otherwise, add our own, and push it onto the pNext array
          pipeExecFeatures.pipelineExecutableInfo = VK_TRUE;

          pipeExecFeatures.pNext = (void *)createInfo.pNext;
          createInfo.pNext = &pipeExecFeatures;
        }
      }
      else
      {
        RDCWARN(
            "VK_KHR_pipeline_executable_properties is available, but the physical device feature "
            "is not. Disabling");

        Extensions.removeOne(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
      }
    }

    VkPhysicalDeviceTransformFeedbackFeaturesEXT xfbFeatures = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT,
    };

    // if we're enabling XFB, make sure we can enable the physical device feature
    if(xfb)
    {
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      availBase.pNext = &xfbFeatures;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      if(xfbFeatures.transformFeedback)
      {
        // see if there's an existing struct
        VkPhysicalDeviceTransformFeedbackFeaturesEXT *existing =
            (VkPhysicalDeviceTransformFeedbackFeaturesEXT *)FindNextStruct(
                &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT);

        if(existing)
        {
          // if so, make sure the feature is enabled
          existing->transformFeedback = VK_TRUE;
        }
        else
        {
          // otherwise, add our own, and push it onto the pNext array
          xfbFeatures.transformFeedback = VK_TRUE;
          xfbFeatures.geometryStreams = VK_FALSE;

          xfbFeatures.pNext = (void *)createInfo.pNext;
          createInfo.pNext = &xfbFeatures;
        }
      }
      else
      {
        RDCWARN(
            "VK_EXT_transform_feedback is available, but the physical device feature is not. "
            "Disabling");

        Extensions.removeOne(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
      }
    }

    VkPhysicalDevicePerformanceQueryFeaturesKHR perfFeatures = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR,
    };

    if(perfQuery)
    {
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      m_PhysicalDeviceData.performanceQueryFeatures.sType =
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR;
      availBase.pNext = &perfFeatures;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      m_PhysicalDeviceData.performanceQueryFeatures = perfFeatures;

      if(perfFeatures.performanceCounterQueryPools)
      {
        VkPhysicalDevicePerformanceQueryFeaturesKHR *existing =
            (VkPhysicalDevicePerformanceQueryFeaturesKHR *)FindNextStruct(
                &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR);

        if(existing)
        {
          existing->performanceCounterQueryPools = VK_TRUE;
        }
        else
        {
          perfFeatures.performanceCounterQueryPools = VK_TRUE;
          perfFeatures.performanceCounterMultipleQueryPools = VK_FALSE;

          perfFeatures.pNext = (void *)createInfo.pNext;
          createInfo.pNext = &perfFeatures;
        }
      }
      else
      {
        Extensions.removeOne(VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME);
      }
    }

    VkPhysicalDeviceScalarBlockLayoutFeatures scalarBlockFeatures = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES,
    };

    if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 2, 0))
    {
      VkPhysicalDeviceVulkan12Features avail12Features = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
      };
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      availBase.pNext = &avail12Features;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      if(avail12Features.scalarBlockLayout)
      {
        VkPhysicalDeviceVulkan12Features *existing =
            (VkPhysicalDeviceVulkan12Features *)FindNextStruct(
                &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);

        if(existing)
        {
          existing->scalarBlockLayout = VK_TRUE;
        }
        else
        {
          VkPhysicalDeviceScalarBlockLayoutFeatures *existingEXT =
              (VkPhysicalDeviceScalarBlockLayoutFeatures *)FindNextStruct(
                  &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES);

          if(existingEXT)
          {
            existingEXT->scalarBlockLayout = VK_TRUE;
          }
          else
          {
            // don't add a new VkPhysicalDeviceVulkan12Features to the pNext chain because if we do
            // we have to remove any components etc. Instead just add the individual
            // VkPhysicalDeviceScalarBlockLayoutFeaturesEXT
            scalarBlockFeatures.scalarBlockLayout = VK_TRUE;

            scalarBlockFeatures.pNext = (void *)createInfo.pNext;
            createInfo.pNext = &scalarBlockFeatures;
          }
        }
      }
    }
    else if(scalarBlock)
    {
      VkPhysicalDeviceScalarBlockLayoutFeatures scalarAvail = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES,
      };
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      availBase.pNext = &scalarAvail;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      if(scalarAvail.scalarBlockLayout)
      {
        // see if there's an existing struct
        VkPhysicalDeviceScalarBlockLayoutFeatures *existing =
            (VkPhysicalDeviceScalarBlockLayoutFeatures *)FindNextStruct(
                &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES);

        if(existing)
        {
          existing->scalarBlockLayout = VK_TRUE;
        }
        else
        {
          // otherwise, add our own, and push it onto the pNext array
          scalarBlockFeatures.scalarBlockLayout = VK_TRUE;

          scalarBlockFeatures.pNext = (void *)createInfo.pNext;
          createInfo.pNext = &scalarBlockFeatures;
        }
      }
      else
      {
        RDCWARN(
            "VK_EXT_scalar_block_layout is available, but the physical device feature "
            "is not. Disabling");

        Extensions.removeOne(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME);
      }
    }

    VkPhysicalDeviceBufferDeviceAddressFeaturesEXT bufAddrEXTFeatures = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT,
    };
    VkPhysicalDeviceBufferDeviceAddressFeatures bufAddrFeatures = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES,
    };

    if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 3, 0))
    {
      // VK_EXT_extended_dynamic_state and VK_EXT_extended_dynamic_state2 were unconditionally
      // promoted and considered implicitly enabled in vulkan 1.3
      m_ExtendedDynState = true;
      m_ExtendedDynState2 = true;
      // logic and patch CPs were not
    }

    if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 2, 0))
    {
      VkPhysicalDeviceVulkan12Features avail12Features = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
      };
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      availBase.pNext = &avail12Features;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      if(avail12Features.bufferDeviceAddress)
      {
        VkPhysicalDeviceVulkan12Features *existing =
            (VkPhysicalDeviceVulkan12Features *)FindNextStruct(
                &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);

        if(existing)
        {
          if(existing->bufferDeviceAddress)
            existing->bufferDeviceAddressCaptureReplay = VK_TRUE;
          existing->bufferDeviceAddress = VK_TRUE;
        }
        else
        {
          VkPhysicalDeviceBufferDeviceAddressFeatures *existingKHR =
              (VkPhysicalDeviceBufferDeviceAddressFeatures *)FindNextStruct(
                  &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES);
          VkPhysicalDeviceBufferDeviceAddressFeaturesEXT *existingEXT =
              (VkPhysicalDeviceBufferDeviceAddressFeaturesEXT *)FindNextStruct(
                  &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT);

          if(existingKHR)
          {
            if(existingKHR->bufferDeviceAddress)
              existingKHR->bufferDeviceAddressCaptureReplay = VK_TRUE;
            existingKHR->bufferDeviceAddress = VK_TRUE;
          }
          else if(existingEXT)
          {
            if(existingEXT->bufferDeviceAddress)
              existingEXT->bufferDeviceAddressCaptureReplay = VK_TRUE;
            existingEXT->bufferDeviceAddress = VK_TRUE;
          }
          else
          {
            // don't add a new VkPhysicalDeviceVulkan12Features to the pNext chain because if we do
            // we have to remove any components etc. Instead just add the individual
            // VkPhysicalDeviceBufferDeviceAddressFeaturesKHR
            bufAddrFeatures.bufferDeviceAddress = VK_TRUE;
            bufAddrFeatures.bufferDeviceAddressMultiDevice = VK_FALSE;

            bufAddrFeatures.pNext = (void *)createInfo.pNext;
            createInfo.pNext = &bufAddrFeatures;
          }
        }
      }
    }
    else if(KHRbuffer)
    {
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      availBase.pNext = &bufAddrFeatures;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      if(bufAddrFeatures.bufferDeviceAddress)
      {
        // see if there's an existing struct
        VkPhysicalDeviceBufferDeviceAddressFeatures *existing =
            (VkPhysicalDeviceBufferDeviceAddressFeatures *)FindNextStruct(
                &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES);

        if(existing)
        {
          if(existing->bufferDeviceAddress)
            existing->bufferDeviceAddressCaptureReplay = VK_TRUE;
          // if so, make sure the feature is enabled
          existing->bufferDeviceAddress = VK_TRUE;
        }
        else
        {
          // otherwise, add our own, and push it onto the pNext array
          bufAddrFeatures.bufferDeviceAddress = VK_TRUE;
          bufAddrFeatures.bufferDeviceAddressMultiDevice = VK_FALSE;

          bufAddrFeatures.pNext = (void *)createInfo.pNext;
          createInfo.pNext = &bufAddrFeatures;
        }
      }
      else
      {
        RDCWARN(
            "VK_KHR_buffer_device_address is available, but the physical device feature "
            "is not. Disabling");

        Extensions.removeOne(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
      }
    }
    else if(EXTbuffer)
    {
      VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
      availBase.pNext = &bufAddrEXTFeatures;
      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

      if(bufAddrEXTFeatures.bufferDeviceAddress)
      {
        // see if there's an existing struct
        VkPhysicalDeviceBufferDeviceAddressFeaturesEXT *existing =
            (VkPhysicalDeviceBufferDeviceAddressFeaturesEXT *)FindNextStruct(
                &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT);

        if(existing)
        {
          if(existing->bufferDeviceAddress)
            existing->bufferDeviceAddressCaptureReplay = VK_TRUE;

          // if so, make sure the feature is enabled
          existing->bufferDeviceAddress = VK_TRUE;
        }
        else
        {
          // otherwise, add our own, and push it onto the pNext array
          bufAddrEXTFeatures.bufferDeviceAddress = VK_TRUE;
          bufAddrEXTFeatures.bufferDeviceAddressMultiDevice = VK_FALSE;

          bufAddrEXTFeatures.pNext = (void *)createInfo.pNext;
          createInfo.pNext = &bufAddrEXTFeatures;
        }
      }
      else
      {
        RDCWARN(
            "VK_EXT_buffer_device_address is available, but the physical device feature "
            "is not. Disabling");

        Extensions.removeOne(VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
      }
    }

    rdcarray<const char *> layerArray;
    layerArray.resize(m_InitParams.Layers.size());
    for(size_t i = 0; i < m_InitParams.Layers.size(); i++)
      layerArray[i] = m_InitParams.Layers[i].c_str();

    createInfo.enabledLayerCount = 0;
    createInfo.ppEnabledLayerNames = NULL;

    byte *tempMem = GetTempMemory(GetNextPatchSize(createInfo.pNext));

    UnwrapNextChain(m_State, "VkDeviceCreateInfo", tempMem, (VkBaseInStructure *)&createInfo);

    VkDeviceGroupDeviceCreateInfo *device_group_info =
        (VkDeviceGroupDeviceCreateInfo *)FindNextStruct(
            &createInfo, VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO);
    // decode physical devices that are actually indices
    if(device_group_info)
    {
      VkPhysicalDevice *physDevs = (VkPhysicalDevice *)device_group_info->pPhysicalDevices;
      for(uint32_t i = 0; i < device_group_info->physicalDeviceCount; i++)
        physDevs[i] = Unwrap(m_PhysicalDevices[GetPhysicalDeviceIndexFromHandle(physDevs[i])]);
    }

    NVAftermath_Init();
    NVAftermath_EnableVK(supportedExtensions, Extensions, &createInfo.pNext);

    rdcarray<const char *> extArray;
    extArray.resize(Extensions.size());
    for(size_t i = 0; i < Extensions.size(); i++)
      extArray[i] = Extensions[i].c_str();

    createInfo.enabledExtensionCount = (uint32_t)extArray.size();
    createInfo.ppEnabledExtensionNames = extArray.data();

    vkr = GetDeviceDispatchTable(NULL)->CreateDevice(Unwrap(physicalDevice), &createInfo, NULL,
                                                     &device);

    if(vkr != VK_SUCCESS && !queuePriorities.empty())
    {
      RDCWARN("Failed to create logical device: %s. Reducing queue priorities", ToStr(vkr).c_str());

      for(VkDeviceQueueGlobalPriorityCreateInfo *q : queuePriorities)
      {
        // medium is considered the default if no priority is set otherwise
        if(q->globalPriority > VK_QUEUE_GLOBAL_PRIORITY_MEDIUM)
          q->globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM;
      }

      vkr = GetDeviceDispatchTable(NULL)->CreateDevice(Unwrap(physicalDevice), &createInfo, NULL,
                                                       &device);
    }

    if(vkr != VK_SUCCESS)
    {
      SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIReplayFailed,
                       "Error creating logical device, VkResult: %s", ToStr(vkr).c_str());
      return false;
    }

    GetResourceManager()->WrapResource(Device, device, device);

    AddResource(Device, ResourceType::Device, "Device");
    DerivedResource(origPhysDevice, Device);

// we unset the extension because it may be a 'shared' extension that's available at both instance
// and device. Only set it to enabled if it's really enabled for this device. This can happen with a
// device extension that is reported by another physical device than the one selected - it becomes
// available at instance level (e.g. for physical device queries) but is not available at *this*
// device level.
#undef CheckExt
#define CheckExt(name, ver) m_EnabledExtensions.ext_##name = false;

    CheckDeviceExts();

    uint32_t effectiveApiVersion = RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion);

#undef CheckExt
#define CheckExt(name, ver)                \
  if(effectiveApiVersion >= ver)           \
  {                                        \
    m_EnabledExtensions.ext_##name = true; \
  }
    CheckDeviceExts();

#undef CheckExt
#define CheckExt(name, ver)                                       \
  if(!strcmp(createInfo.ppEnabledExtensionNames[i], "VK_" #name)) \
  {                                                               \
    m_EnabledExtensions.ext_##name = true;                        \
  }
    for(uint32_t i = 0; i < createInfo.enabledExtensionCount; i++)
    {
      CheckDeviceExts();
    }

    // for cases where a promoted extension isn't supported as the extension itself, manually
    // disable them when the feature bit is false.

    if(effectiveApiVersion >= VK_MAKE_VERSION(1, 2, 0))
    {
      if(supportedExtensions.find(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) ==
             supportedExtensions.end() &&
         !vulkan12Features.bufferDeviceAddress)
        m_EnabledExtensions.ext_KHR_buffer_device_address = false;

      if(supportedExtensions.find(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME) ==
             supportedExtensions.end() &&
         !vulkan12Features.scalarBlockLayout)
        m_EnabledExtensions.ext_EXT_scalar_block_layout = false;

      if(supportedExtensions.find(VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME) ==
             supportedExtensions.end() &&
         !vulkan12Features.drawIndirectCount)
        m_EnabledExtensions.ext_KHR_draw_indirect_count = false;

      if(supportedExtensions.find(VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME) ==
             supportedExtensions.end() &&
         !vulkan12Features.samplerFilterMinmax)
        m_EnabledExtensions.ext_EXT_sampler_filter_minmax = false;

      // these features are required so this should never happen
      if(supportedExtensions.find(VK_KHR_SEPARATE_DEPTH_STENCIL_LAYOUTS_EXTENSION_NAME) ==
             supportedExtensions.end() &&
         !vulkan12Features.separateDepthStencilLayouts)
      {
        RDCWARN(
            "Required feature 'separateDepthStencilLayouts' not supported by 1.2 physical device.");
        m_EnabledExtensions.ext_KHR_separate_depth_stencil_layouts = false;
      }
    }

    // we also need to check for feature enablement - if an extension is promoted that doesn't mean
    // it's enabled

    if(m_EnabledExtensions.ext_KHR_synchronization2)
    {
      if(!vulkan13Features.synchronization2 && !sync2.synchronization2)
        m_EnabledExtensions.ext_KHR_synchronization2 = false;
    }

    InitInstanceExtensionTables(m_Instance, &m_EnabledExtensions);
    InitDeviceExtensionTables(device, &m_EnabledExtensions);

    RDCASSERT(m_Device == VK_NULL_HANDLE);    // MULTIDEVICE

    m_PhysicalDevice = physicalDevice;
    m_Device = device;

    m_QueueFamilyIdx = qFamilyIdx;

    if(m_InternalCmds.cmdpool == VK_NULL_HANDLE)
    {
      VkCommandPoolCreateInfo poolInfo = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, NULL,
                                          VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
                                          qFamilyIdx};
      vkr = ObjDisp(device)->CreateCommandPool(Unwrap(device), &poolInfo, NULL,
                                               &m_InternalCmds.cmdpool);
      CHECK_VKR(this, vkr);

      GetResourceManager()->WrapResource(ResourceId(), Unwrap(device), m_InternalCmds.cmdpool);
    }

    // for each queue family we've remapped to, ensure we have a command pool and command buffer on
    // that queue, and we'll also use the first queue that the application creates (or fetch our
    // own).
    for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++)
    {
      uint32_t qidx = createInfo.pQueueCreateInfos[i].queueFamilyIndex;
      m_ExternalQueues.resize(RDCMAX((uint32_t)m_ExternalQueues.size(), qidx + 1));

      // Resize also the image barriers, as ImageBarrierSequence::SetMaxQueueFamilyIndex
      // just sets the static MaxQueueFamilyIndex
      m_setupImageBarriers.ResizeForMaxQueueFamilyIndex(qidx);
      m_cleanupImageBarriers.ResizeForMaxQueueFamilyIndex(qidx);

      ImageBarrierSequence::SetMaxQueueFamilyIndex(qidx);

      VkCommandPoolCreateInfo poolInfo = {
          VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
          NULL,
          VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
          qidx,
      };
      vkr = ObjDisp(device)->CreateCommandPool(Unwrap(device), &poolInfo, NULL,
                                               &m_ExternalQueues[qidx].pool);
      CHECK_VKR(this, vkr);

      GetResourceManager()->WrapResource(ResourceId(), Unwrap(device), m_ExternalQueues[qidx].pool);

      VkCommandBufferAllocateInfo cmdInfo = {
          VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
          NULL,
          Unwrap(m_ExternalQueues[qidx].pool),
          VK_COMMAND_BUFFER_LEVEL_PRIMARY,
          1,
      };

      VkFenceCreateInfo fenceInfo = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, NULL,
                                     VK_FENCE_CREATE_SIGNALED_BIT};
      VkSemaphoreCreateInfo semInfo = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};

      for(size_t x = 0; x < ARRAY_COUNT(m_ExternalQueues[i].ring); x++)
      {
        vkr = ObjDisp(device)->AllocateCommandBuffers(Unwrap(device), &cmdInfo,
                                                      &m_ExternalQueues[qidx].ring[x].acquire);
        CHECK_VKR(this, vkr);

        if(m_SetDeviceLoaderData)
          m_SetDeviceLoaderData(device, m_ExternalQueues[qidx].ring[x].acquire);
        else
          SetDispatchTableOverMagicNumber(device, m_ExternalQueues[qidx].ring[x].acquire);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].acquire);

        vkr = ObjDisp(device)->AllocateCommandBuffers(Unwrap(device), &cmdInfo,
                                                      &m_ExternalQueues[qidx].ring[x].release);
        CHECK_VKR(this, vkr);

        if(m_SetDeviceLoaderData)
          m_SetDeviceLoaderData(device, m_ExternalQueues[qidx].ring[x].release);
        else
          SetDispatchTableOverMagicNumber(device, m_ExternalQueues[qidx].ring[x].release);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].release);

        vkr = ObjDisp(device)->CreateSemaphore(Unwrap(device), &semInfo, NULL,
                                               &m_ExternalQueues[qidx].ring[x].fromext);
        CHECK_VKR(this, vkr);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].fromext);

        vkr = ObjDisp(device)->CreateSemaphore(Unwrap(device), &semInfo, NULL,
                                               &m_ExternalQueues[qidx].ring[x].toext);
        CHECK_VKR(this, vkr);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].toext);

        vkr = ObjDisp(device)->CreateFence(Unwrap(device), &fenceInfo, NULL,
                                           &m_ExternalQueues[qidx].ring[x].fence);
        CHECK_VKR(this, vkr);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].fence);
      }
    }

    m_Replay->SetDriverInformation(m_PhysicalDeviceData.props, m_PhysicalDeviceData.driverProps);

    m_PhysicalDeviceData.enabledFeatures = enabledFeatures;

    const VkPhysicalDeviceDescriptorBufferFeaturesEXT *descBufFeats =
        (const VkPhysicalDeviceDescriptorBufferFeaturesEXT *)FindNextStruct(
            &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT);

    if(m_EnabledExtensions.ext_EXT_descriptor_buffer && descBufFeats && descBufFeats->descriptorBuffer)
    {
      m_DescriptorBuffers = true;

      // if any update after bind feature is enabled, check robustBufferAccessUpdateAfterBind
      m_DescriptorBufferProperties = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT,
      };

      VkPhysicalDeviceProperties2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
      availBase.pNext = &m_DescriptorBufferProperties;
      ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), &availBase);

      bool robustness = false;

      // VkPhysicalDeviceFeatures2 takes priority
      if(enabledFeatures2)
      {
        robustness = enabledFeatures2->features.robustBufferAccess != VK_FALSE;
      }
      else if(createInfo.pEnabledFeatures && createInfo.pEnabledFeatures->robustBufferAccess)
      {
        robustness = true;
      }

      // we should have forced on robustness
      RDCASSERT(robustness);

      m_DescriptorBufferProperties.uniformBufferDescriptorSize =
          m_DescriptorBufferProperties.robustUniformBufferDescriptorSize;
      m_DescriptorBufferProperties.storageBufferDescriptorSize =
          m_DescriptorBufferProperties.robustStorageBufferDescriptorSize;
      m_DescriptorBufferProperties.uniformTexelBufferDescriptorSize =
          m_DescriptorBufferProperties.robustUniformTexelBufferDescriptorSize;
      m_DescriptorBufferProperties.storageTexelBufferDescriptorSize =
          m_DescriptorBufferProperties.robustStorageTexelBufferDescriptorSize;

      // ensure our fixed-size is large enough for this driver.
      RDCASSERT(m_DescriptorBufferProperties.accelerationStructureCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.bufferCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.imageCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.imageViewCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.samplerCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);

      uint32_t maxResourceDescriptorSize = 0;
#define CALC_MAX_SIZE(prop)   \
  maxResourceDescriptorSize = \
      RDCMAX(maxResourceDescriptorSize, (uint32_t)m_DescriptorBufferProperties.prop);

      CALC_MAX_SIZE(storageImageDescriptorSize);
      CALC_MAX_SIZE(sampledImageDescriptorSize);
      CALC_MAX_SIZE(robustUniformTexelBufferDescriptorSize);
      CALC_MAX_SIZE(robustStorageTexelBufferDescriptorSize);
      CALC_MAX_SIZE(robustUniformBufferDescriptorSize);
      CALC_MAX_SIZE(robustStorageBufferDescriptorSize);
      CALC_MAX_SIZE(inputAttachmentDescriptorSize);
      CALC_MAX_SIZE(accelerationStructureDescriptorSize);

      m_ResourceDescriptorBufferReserveSize =
          AlignUp(maxResourceDescriptorSize * 2,
                  (uint32_t)m_DescriptorBufferProperties.descriptorBufferOffsetAlignment);

      m_IgnoreLayoutForDescriptors = descBufFeats->descriptorBufferImageLayoutIgnored != VK_FALSE;
    }

    // MoltenVK reports 0x3fffffff for this limit so just ignore that value if it comes up
    RDCASSERT(m_PhysicalDeviceData.props.limits.maxBoundDescriptorSets <
                      ARRAY_COUNT(BakedCmdBufferInfo::pushDescriptorID[0]) ||
                  m_PhysicalDeviceData.props.limits.maxBoundDescriptorSets >= 0x10000000,
              m_PhysicalDeviceData.props.limits.maxBoundDescriptorSets);

    m_PhysicalDeviceData.queueCount = (uint32_t)queueProps.size();
    for(size_t i = 0; i < queueProps.size(); i++)
      m_PhysicalDeviceData.queueProps[i] = queueProps[i];

    if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 1, 0))
    {
      VkPhysicalDeviceVulkan11Properties vulkan11Props = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES,
      };

      VkPhysicalDeviceProperties2 devProps2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
      devProps2.pNext = &vulkan11Props;
      ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), &devProps2);
      m_PhysicalDeviceData.maxMemoryAllocationSize = vulkan11Props.maxMemoryAllocationSize;
    }
    else
    {
      m_PhysicalDeviceData.maxMemoryAllocationSize = 0x80000000U;
    }

    VulkanCreationInfo::pushConstantDescriptorStorage = ResourceIDGen::GetNewUniqueID();
    VulkanCreationInfo::descriptorSetStorage.resize(
        m_PhysicalDeviceData.props.limits.maxBoundDescriptorSets);
    for(size_t i = 0; i < VulkanCreationInfo::descriptorSetStorage.size(); i++)
      VulkanCreationInfo::descriptorSetStorage[i] = ResourceIDGen::GetNewUniqueID();

    ChooseMemoryIndices();

    if(DescriptorBuffers())
    {
      VulkanCreationInfo::descriptorBufferStorage.resize(
          m_DescriptorBufferProperties.maxDescriptorBufferBindings);
      VulkanCreationInfo::inlineBufferStorage.resize(
          m_DescriptorBufferProperties.maxDescriptorBufferBindings);
      for(size_t i = 0; i < VulkanCreationInfo::descriptorBufferStorage.size(); i++)
      {
        VulkanCreationInfo::descriptorBufferStorage[i] = ResourceIDGen::GetNewUniqueID();
        VulkanCreationInfo::inlineBufferStorage[i] = ResourceIDGen::GetNewUniqueID();
      }

      EstimateDescriptorFormats();

      // indicate to any self-capture that we have reserved the descriptors
      if(ObjDisp(m_Device)->SetDebugUtilsObjectTagEXT)
      {
        VkDebugUtilsObjectTagInfoEXT tagInfo = {VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT};
        tagInfo.objectType = VK_OBJECT_TYPE_INSTANCE;
        tagInfo.objectHandle = uint64_t(Unwrap(m_Instance));
        tagInfo.tagName = RENDERDOC_DescriptorsReservation_UUID;
        tagInfo.tagSize = sizeof(bool);
        bool yes = true;
        tagInfo.pTag = &yes;

        vkr = ObjDisp(m_Device)->SetDebugUtilsObjectTagEXT(Unwrap(m_Device), &tagInfo);
      }
    }

    APIProps.vendor = GetDriverInfo().Vendor();

    // temporarily disable the debug message sink, to ignore any false positive messages from our
    // init
    ScopedDebugMessageSink *sink = GetDebugMessageSink();
    SetDebugMessageSink(NULL);

    m_ShaderCache = new VulkanShaderCache(this);

    SetDebugMessageSink(sink);
  }

  return true;
}

VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice,
                                       const VkDeviceCreateInfo *pCreateInfo,
                                       const VkAllocationCallbacks *, VkDevice *pDevice)
{
  VkDeviceCreateInfo createInfo = *pCreateInfo;

  for(uint32_t i = 0; i < createInfo.enabledExtensionCount; i++)
  {
    if(!IsSupportedExtension(createInfo.ppEnabledExtensionNames[i]))
    {
      RDCERR("RenderDoc does not support device extension '%s'.",
             createInfo.ppEnabledExtensionNames[i]);
      RDCERR(
          "For KHR/EXT extensions file an issue on github to request support: "
          "https://github.com/baldurk/renderdoc");

      SendUserDebugMessage(
          StringFormat::Fmt("RenderDoc does not support requested device extension: %s.",
                            createInfo.ppEnabledExtensionNames[i]));

      return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
  }

  if(m_Device != VK_NULL_HANDLE)
  {
    SendUserDebugMessage("RenderDoc does not support multiple simultaneous logical devices.");
    return VK_ERROR_INITIALIZATION_FAILED;
  }

  rdcarray<const char *> Extensions(createInfo.ppEnabledExtensionNames,
                                    createInfo.enabledExtensionCount);

  // enable VK_KHR_driver_properties if it's available
  {
    uint32_t count = 0;
    ObjDisp(physicalDevice)
        ->EnumerateDeviceExtensionProperties(Unwrap(physicalDevice), NULL, &count, NULL);

    VkExtensionProperties *props = new VkExtensionProperties[count];
    ObjDisp(physicalDevice)
        ->EnumerateDeviceExtensionProperties(Unwrap(physicalDevice), NULL, &count, props);

    for(uint32_t e = 0; e < count; e++)
    {
      if(!strcmp(props[e].extensionName, VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME))
      {
        Extensions.push_back(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME);
        break;
      }
    }

    SAFE_DELETE_ARRAY(props);
  }

  createInfo.ppEnabledExtensionNames = Extensions.data();
  createInfo.enabledExtensionCount = (uint32_t)Extensions.size();

  VkResult vkr = VK_SUCCESS;

  rdcarray<VkQueueFamilyProperties> queueProps;

  {
    uint32_t qCount = 0;
    ObjDisp(physicalDevice)->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), &qCount, NULL);

    queueProps.resize(qCount);
    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), &qCount, queueProps.data());
  }

  // find a queue that supports all capabilities, and if one doesn't exist, add it.
  uint32_t qFamilyIdx = 0;

  if(!SelectGraphicsComputeQueue(queueProps, createInfo, qFamilyIdx))
    return VK_ERROR_INITIALIZATION_FAILED;

  m_QueueFamilies.resize(createInfo.queueCreateInfoCount);
  m_QueueFamilyCounts.resize(createInfo.queueCreateInfoCount);
  m_QueueFamilyIndices.clear();
  for(size_t i = 0; i < createInfo.queueCreateInfoCount; i++)
  {
    uint32_t family = createInfo.pQueueCreateInfos[i].queueFamilyIndex;
    uint32_t count = createInfo.pQueueCreateInfos[i].queueCount;
    m_QueueFamilies.resize(RDCMAX(m_QueueFamilies.size(), size_t(family) + 1));
    m_QueueFamilyCounts.resize(RDCMAX(m_QueueFamilies.size(), size_t(family) + 1));

    m_QueueFamilies[family] = new VkQueue[count];
    m_QueueFamilyCounts[family] = count;
    for(uint32_t q = 0; q < count; q++)
      m_QueueFamilies[family][q] = VK_NULL_HANDLE;

    if(!m_QueueFamilyIndices.contains(family))
      m_QueueFamilyIndices.push_back(family);
  }

  VkLayerDeviceCreateInfo *layerCreateInfo = (VkLayerDeviceCreateInfo *)pCreateInfo->pNext;

  // step through the chain of pNext until we get to the link info
  while(layerCreateInfo && (layerCreateInfo->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO ||
                            layerCreateInfo->function != VK_LAYER_LINK_INFO))
  {
    layerCreateInfo = (VkLayerDeviceCreateInfo *)layerCreateInfo->pNext;
  }
  RDCASSERT(layerCreateInfo);

  if(layerCreateInfo == NULL)
  {
    RDCERR("Couldn't find loader device create info, which is required. Incompatible loader?");
    return VK_ERROR_INITIALIZATION_FAILED;
  }

  PFN_vkGetDeviceProcAddr gdpa = layerCreateInfo->u.pLayerInfo->pfnNextGetDeviceProcAddr;
  PFN_vkGetInstanceProcAddr gipa = layerCreateInfo->u.pLayerInfo->pfnNextGetInstanceProcAddr;
  // move chain on for next layer
  layerCreateInfo->u.pLayerInfo = layerCreateInfo->u.pLayerInfo->pNext;

  PFN_vkCreateDevice createFunc = (PFN_vkCreateDevice)gipa(Unwrap(m_Instance), "vkCreateDevice");

  // now search again through for the loader data callback (if it exists)
  layerCreateInfo = (VkLayerDeviceCreateInfo *)pCreateInfo->pNext;

  // step through the chain of pNext
  while(layerCreateInfo && (layerCreateInfo->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO ||
                            layerCreateInfo->function != VK_LOADER_DATA_CALLBACK))
  {
    layerCreateInfo = (VkLayerDeviceCreateInfo *)layerCreateInfo->pNext;
  }

  // if we found one (we might not - on old loaders), then store the func ptr for
  // use instead of SetDispatchTableOverMagicNumber
  if(layerCreateInfo)
  {
    RDCASSERT(m_SetDeviceLoaderData == layerCreateInfo->u.pfnSetDeviceLoaderData ||
                  m_SetDeviceLoaderData == NULL,
              (void *)m_SetDeviceLoaderData, (void *)layerCreateInfo->u.pfnSetDeviceLoaderData);
    m_SetDeviceLoaderData = layerCreateInfo->u.pfnSetDeviceLoaderData;
  }

  // patch enabled features needed at capture time

  VkPhysicalDeviceFeatures availFeatures;
  ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures(Unwrap(physicalDevice), &availFeatures);

  // default to all off. This is equivalent to createInfo.pEnabledFeatures == NULL
  VkPhysicalDeviceFeatures enabledFeatures = {0};

  // allocate and unwrap the next chain, so we can patch features if we need to, as well as removing
  // the loader info later when it comes time to serialise
  byte *tempMem = GetTempMemory(GetNextPatchSize(createInfo.pNext));

  UnwrapNextChain(m_State, "VkDeviceCreateInfo", tempMem, (VkBaseInStructure *)&createInfo);

  VkPhysicalDeviceFeatures2 *enabledFeatures2 = (VkPhysicalDeviceFeatures2 *)FindNextStruct(
      &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2);
  /*
  VkPhysicalDeviceVulkan11Features *enabledFeaturesVK11 =
      (VkPhysicalDeviceVulkan11Features *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES);
          */
  VkPhysicalDeviceVulkan12Features *enabledFeaturesVK12 =
      (VkPhysicalDeviceVulkan12Features *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);

  // VkPhysicalDeviceFeatures2 takes priority
  if(enabledFeatures2)
    enabledFeatures = enabledFeatures2->features;
  else if(createInfo.pEnabledFeatures)
    enabledFeatures = *createInfo.pEnabledFeatures;

  bool descIndexingAllowsRBA = true;

  VkPhysicalDeviceVulkan12Features *vulkan12Features =
      (VkPhysicalDeviceVulkan12Features *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);

  if(vulkan12Features && (vulkan12Features->descriptorBindingUniformBufferUpdateAfterBind ||
                          vulkan12Features->descriptorBindingStorageBufferUpdateAfterBind ||
                          vulkan12Features->descriptorBindingUniformTexelBufferUpdateAfterBind ||
                          vulkan12Features->descriptorBindingStorageTexelBufferUpdateAfterBind))
  {
    // if any update after bind feature is enabled, check robustBufferAccessUpdateAfterBind
    VkPhysicalDeviceVulkan12Properties vulkan12Props = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES,
    };

    VkPhysicalDeviceProperties2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
    availBase.pNext = &vulkan12Props;
    ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), &availBase);

    descIndexingAllowsRBA = vulkan12Props.robustBufferAccessUpdateAfterBind != VK_FALSE;
  }

  VkPhysicalDeviceDescriptorIndexingFeatures *descIndexingFeatures =
      (VkPhysicalDeviceDescriptorIndexingFeatures *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES);

  if(descIndexingFeatures &&
     (descIndexingFeatures->descriptorBindingUniformBufferUpdateAfterBind ||
      descIndexingFeatures->descriptorBindingStorageBufferUpdateAfterBind ||
      descIndexingFeatures->descriptorBindingUniformTexelBufferUpdateAfterBind ||
      descIndexingFeatures->descriptorBindingStorageTexelBufferUpdateAfterBind))
  {
    // if any update after bind feature is enabled, check robustBufferAccessUpdateAfterBind
    VkPhysicalDeviceDescriptorIndexingProperties descIndexingProps = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES,
    };

    VkPhysicalDeviceProperties2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
    availBase.pNext = &descIndexingProps;
    ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), &availBase);

    descIndexingAllowsRBA = descIndexingProps.robustBufferAccessUpdateAfterBind != VK_FALSE;
  }

  if(availFeatures.robustBufferAccess && !descIndexingAllowsRBA)
  {
    // if the feature is available but we can't use it, warn
    RDCWARN(
        "robustBufferAccess is available, but cannot be enabled due to "
        "robustBufferAccessUpdateAfterBind not being avilable and some UpdateAfterBind features "
        "being enabled. "
        "out of bounds access due to bugs in application or RenderDoc may cause crashes");

    for(const char *e : Extensions)
    {
      if(rdcstr(e) == VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME)
      {
        RDCERR("VK_EXT_descriptor_buffer is enabled without robust buffer access!");
      }
    }
  }
  else
  {
    // either the feature is available, and we enable it, or it's not available at all.
    if(availFeatures.robustBufferAccess)
      enabledFeatures.robustBufferAccess = true;
    else
      RDCWARN(
          "robustBufferAccess = false, out of bounds access due to bugs in application or "
          "RenderDoc may cause crashes");
  }

  // enable this feature as it's needed at capture time to save MSAA initial states
  if(availFeatures.shaderStorageImageWriteWithoutFormat)
    enabledFeatures.shaderStorageImageWriteWithoutFormat = true;
  else
    RDCWARN(
        "shaderStorageImageWriteWithoutFormat = false, multisampled textures will have empty "
        "contents at frame start.");

  // even though we don't actually do any multisampled stores, this is needed to be able to create
  // MSAA images with STORAGE_BIT usage
  if(availFeatures.shaderStorageImageMultisample)
    enabledFeatures.shaderStorageImageMultisample = true;
  else
    RDCWARN(
        "shaderStorageImageMultisample = false, multisampled textures will have empty "
        "contents at frame start.");

  // patch the enabled features
  if(enabledFeatures2)
    enabledFeatures2->features = enabledFeatures;
  else
    createInfo.pEnabledFeatures = &enabledFeatures;

  // we need to enable non-subsampled images because we're going to remove subsampled bit from
  // images, and we want to ensure that it's legal! We verified that this is OK before whitelisting
  // the extension

  VkPhysicalDeviceFragmentDensityMapFeaturesEXT *fragmentDensityMapFeatures =
      (VkPhysicalDeviceFragmentDensityMapFeaturesEXT *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT);
  if(fragmentDensityMapFeatures && !fragmentDensityMapFeatures->fragmentDensityMapNonSubsampledImages)
  {
    fragmentDensityMapFeatures->fragmentDensityMapNonSubsampledImages = true;
  }

  VkPhysicalDeviceBufferDeviceAddressFeaturesEXT *bufferAddressFeaturesEXT =
      (VkPhysicalDeviceBufferDeviceAddressFeaturesEXT *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT);
  VkPhysicalDeviceBufferDeviceAddressFeatures *bufferAddressFeaturesCoreKHR =
      (VkPhysicalDeviceBufferDeviceAddressFeatures *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES);

  // we must turn on bufferDeviceAddressCaptureReplay. We verified that this feature was available
  // before we whitelisted the extension/feature
  if(enabledFeaturesVK12 && enabledFeaturesVK12->bufferDeviceAddress)
    enabledFeaturesVK12->bufferDeviceAddressCaptureReplay = VK_TRUE;

  if(bufferAddressFeaturesCoreKHR)
    bufferAddressFeaturesCoreKHR->bufferDeviceAddressCaptureReplay = VK_TRUE;

  if(bufferAddressFeaturesEXT)
    bufferAddressFeaturesEXT->bufferDeviceAddressCaptureReplay = VK_TRUE;

  // check features that we care about at capture time

  const VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures *separateDepthStencilFeatures =
      (const VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES);

  if(separateDepthStencilFeatures)
    m_SeparateDepthStencil |= (separateDepthStencilFeatures->separateDepthStencilLayouts != VK_FALSE);

  // we need to enable acceleration structure capture/replay. We verified that this is OK before
  // whitelisting the extension

  VkPhysicalDeviceAccelerationStructureFeaturesKHR *accFeatures =
      (VkPhysicalDeviceAccelerationStructureFeaturesKHR *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR);
  if(accFeatures && accFeatures->accelerationStructure)
  {
    accFeatures->accelerationStructureCaptureReplay = VK_TRUE;
    m_AccelerationStructures = true;

    RDCLOG("Acceleration structures enabled, ALL MEMORY WILL BE MARKED AS BDA");
  }

  VkPhysicalDeviceRayTracingPipelineFeaturesKHR *rtpFeatures =
      (VkPhysicalDeviceRayTracingPipelineFeaturesKHR *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR);
  if(rtpFeatures && rtpFeatures->rayTracingPipeline)
  {
    rtpFeatures->rayTracingPipelineShaderGroupHandleCaptureReplay = VK_TRUE;
  }

  VkPhysicalDeviceDescriptorBufferFeaturesEXT *descBufFeatures =
      (VkPhysicalDeviceDescriptorBufferFeaturesEXT *)FindNextStruct(
          &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT);
  if(descBufFeatures && descBufFeatures->descriptorBuffer)
  {
    descBufFeatures->descriptorBufferCaptureReplay = VK_TRUE;
    m_DescriptorBuffers = true;

    VkPhysicalDeviceDescriptorBufferFeaturesEXT availDescBufFeatures = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT,
    };

    VkPhysicalDeviceFeatures2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
    availBase.pNext = &availDescBufFeatures;
    ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &availBase);

    // enable the imageLayoutIgnored feature if available
    if(availDescBufFeatures.descriptorBufferImageLayoutIgnored)
      descBufFeatures->descriptorBufferImageLayoutIgnored = VK_TRUE;

    RDCLOG("descriptor buffers enabled, ALL MEMORY WILL BE MARKED AS BDA");
  }

  VkResult ret;
  SERIALISE_TIME_CALL(ret = createFunc(Unwrap(physicalDevice), &createInfo, NULL, pDevice));

  if(ret == VK_SUCCESS)
  {
    InitDeviceTable(*pDevice, gdpa);

    RDCLOG("Created capture device from physical device %d",
           m_PhysicalDevices.indexOf(physicalDevice));

    ResourceId id = GetResourceManager()->WrapResource(ResourceId(), *pDevice, *pDevice);

    if(IsCaptureMode(m_State))
    {
      Chunk *chunk = NULL;

      VkDeviceCreateInfo serialiseCreateInfo = *pCreateInfo;

      // don't serialise out any of the pNext stuff for layer initialisation
      RemoveNextStruct(&serialiseCreateInfo, VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO);

      {
        CACHE_THREAD_SERIALISER();

        SCOPED_SERIALISE_CHUNK(VulkanChunk::vkCreateDevice);
        Serialise_vkCreateDevice(ser, physicalDevice, &serialiseCreateInfo, NULL, pDevice);

        chunk = scope.Get();
      }

      VkResourceRecord *record = GetResourceManager()->AddResourceRecord(*pDevice);
      RDCASSERT(record);

      record->AddChunk(chunk);

      record->instDevInfo = new InstanceDeviceInfo();

      record->instDevInfo->brokenGetDeviceProcAddr =
          GetRecord(m_Instance)->instDevInfo->brokenGetDeviceProcAddr;

      VkPhysicalDeviceProperties physProps;
      ObjDisp(physicalDevice)->GetPhysicalDeviceProperties(Unwrap(physicalDevice), &physProps);

      record->instDevInfo->vulkanVersion =
          RDCMIN(physProps.apiVersion, GetRecord(m_Instance)->instDevInfo->vulkanVersion);

#undef CheckExt
#define CheckExt(name, ver) \
  record->instDevInfo->ext_##name = GetRecord(m_Instance)->instDevInfo->ext_##name;

      // inherit extension enablement from instance, that way GetDeviceProcAddress can check
      // for enabled extensions for instance functions
      CheckInstanceExts();

// we unset the extension because it may be a 'shared' extension that's available at both instance
// and device. Only set it to enabled if it's really enabled for this device. This can happen with a
// device extension that is reported by another physical device than the one selected - it becomes
// available at instance level (e.g. for physical device queries) but is not available at *this*
// device level.
#undef CheckExt
#define CheckExt(name, ver) record->instDevInfo->ext_##name = false;

      CheckDeviceExts();

#undef CheckExt
#define CheckExt(name, ver)                                         \
  if(!strcmp(createInfo.ppEnabledExtensionNames[i], "VK_" #name) || \
     record->instDevInfo->vulkanVersion >= ver)                     \
  {                                                                 \
    record->instDevInfo->ext_##name = true;                         \
  }

      for(uint32_t i = 0; i < createInfo.enabledExtensionCount; i++)
      {
        CheckDeviceExts();
      }

      m_EnabledExtensions = *record->instDevInfo;

      InitDeviceExtensionTables(*pDevice, record->instDevInfo);
    }

    VkDevice device = *pDevice;

    RDCASSERT(m_Device == VK_NULL_HANDLE);    // MULTIDEVICE

    m_PhysicalDevice = physicalDevice;
    m_Device = device;

    m_QueueFamilyIdx = qFamilyIdx;

    if(m_InternalCmds.cmdpool == VK_NULL_HANDLE)
    {
      VkCommandPoolCreateInfo poolInfo = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, NULL,
                                          VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
                                          qFamilyIdx};
      vkr = ObjDisp(device)->CreateCommandPool(Unwrap(device), &poolInfo, NULL,
                                               &m_InternalCmds.cmdpool);
      CHECK_VKR(this, vkr);

      GetResourceManager()->WrapResource(ResourceId(), Unwrap(device), m_InternalCmds.cmdpool);
    }

    // for each queue family that isn't our own, create a command pool and command buffer on that
    // queue
    for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++)
    {
      uint32_t qidx = createInfo.pQueueCreateInfos[i].queueFamilyIndex;
      m_ExternalQueues.resize(RDCMAX((uint32_t)m_ExternalQueues.size(), qidx + 1));

      // Resize also the image barriers, as ImageBarrierSequence::SetMaxQueueFamilyIndex
      // just sets the static MaxQueueFamilyIndex
      m_setupImageBarriers.ResizeForMaxQueueFamilyIndex(qidx);
      m_cleanupImageBarriers.ResizeForMaxQueueFamilyIndex(qidx);

      ImageBarrierSequence::SetMaxQueueFamilyIndex(qidx);

      VkCommandPoolCreateInfo poolInfo = {
          VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
          NULL,
          VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
          qidx,
      };
      vkr = ObjDisp(device)->CreateCommandPool(Unwrap(device), &poolInfo, NULL,
                                               &m_ExternalQueues[qidx].pool);
      CHECK_VKR(this, vkr);

      GetResourceManager()->WrapResource(ResourceId(), Unwrap(device), m_ExternalQueues[qidx].pool);

      VkCommandBufferAllocateInfo cmdInfo = {
          VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
          NULL,
          Unwrap(m_ExternalQueues[qidx].pool),
          VK_COMMAND_BUFFER_LEVEL_PRIMARY,
          1,
      };

      VkFenceCreateInfo fenceInfo = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, NULL,
                                     VK_FENCE_CREATE_SIGNALED_BIT};
      VkSemaphoreCreateInfo semInfo = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};

      for(size_t x = 0; x < ARRAY_COUNT(m_ExternalQueues[i].ring); x++)
      {
        vkr = ObjDisp(device)->AllocateCommandBuffers(Unwrap(device), &cmdInfo,
                                                      &m_ExternalQueues[qidx].ring[x].acquire);
        CHECK_VKR(this, vkr);

        if(m_SetDeviceLoaderData)
          m_SetDeviceLoaderData(device, m_ExternalQueues[qidx].ring[x].acquire);
        else
          SetDispatchTableOverMagicNumber(device, m_ExternalQueues[qidx].ring[x].acquire);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].acquire);

        vkr = ObjDisp(device)->AllocateCommandBuffers(Unwrap(device), &cmdInfo,
                                                      &m_ExternalQueues[qidx].ring[x].release);
        CHECK_VKR(this, vkr);

        if(m_SetDeviceLoaderData)
          m_SetDeviceLoaderData(device, m_ExternalQueues[qidx].ring[x].release);
        else
          SetDispatchTableOverMagicNumber(device, m_ExternalQueues[qidx].ring[x].release);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].release);

        vkr = ObjDisp(device)->CreateSemaphore(Unwrap(device), &semInfo, NULL,
                                               &m_ExternalQueues[qidx].ring[x].fromext);
        CHECK_VKR(this, vkr);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].fromext);

        vkr = ObjDisp(device)->CreateSemaphore(Unwrap(device), &semInfo, NULL,
                                               &m_ExternalQueues[qidx].ring[x].toext);
        CHECK_VKR(this, vkr);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].toext);

        vkr = ObjDisp(device)->CreateFence(Unwrap(device), &fenceInfo, NULL,
                                           &m_ExternalQueues[qidx].ring[x].fence);
        CHECK_VKR(this, vkr);

        GetResourceManager()->WrapResource(ResourceId(), Unwrap(device),
                                           m_ExternalQueues[qidx].ring[x].fence);
      }
    }

    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceProperties(Unwrap(physicalDevice), &m_PhysicalDeviceData.props);

    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceMemoryProperties(Unwrap(physicalDevice), &m_PhysicalDeviceData.memProps);

    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceFeatures(Unwrap(physicalDevice), &m_PhysicalDeviceData.availFeatures);
    m_PhysicalDeviceData.enabledFeatures = enabledFeatures;

    GetPhysicalDeviceDriverProperties(ObjDisp(physicalDevice), Unwrap(physicalDevice),
                                      m_PhysicalDeviceData.driverProps);

    if(m_EnabledExtensions.ext_EXT_descriptor_buffer && descBufFeatures &&
       descBufFeatures->descriptorBuffer)
    {
      // if any update after bind feature is enabled, check robustBufferAccessUpdateAfterBind
      m_DescriptorBufferProperties = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT,
      };

      VkPhysicalDeviceProperties2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
      availBase.pNext = &m_DescriptorBufferProperties;
      ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), &availBase);

      bool robustness = false;

      // VkPhysicalDeviceFeatures2 takes priority
      if(enabledFeatures2)
      {
        robustness = enabledFeatures2->features.robustBufferAccess != VK_FALSE;
      }
      else if(createInfo.pEnabledFeatures && createInfo.pEnabledFeatures->robustBufferAccess)
      {
        robustness = true;
      }

      // we should have forced on robustness
      RDCASSERT(robustness);

      m_DescriptorBufferProperties.uniformBufferDescriptorSize =
          m_DescriptorBufferProperties.robustUniformBufferDescriptorSize;
      m_DescriptorBufferProperties.storageBufferDescriptorSize =
          m_DescriptorBufferProperties.robustStorageBufferDescriptorSize;
      m_DescriptorBufferProperties.uniformTexelBufferDescriptorSize =
          m_DescriptorBufferProperties.robustUniformTexelBufferDescriptorSize;
      m_DescriptorBufferProperties.storageTexelBufferDescriptorSize =
          m_DescriptorBufferProperties.robustStorageTexelBufferDescriptorSize;

      // ensure our fixed-size is large enough for this driver.
      RDCASSERT(m_DescriptorBufferProperties.accelerationStructureCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.bufferCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.imageCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.imageViewCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);
      RDCASSERT(m_DescriptorBufferProperties.samplerCaptureReplayDescriptorDataSize <=
                FixedOpaqueDescriptorCaptureSize);

      uint32_t maxResourceDescriptorSize = 0;
#define CALC_MAX_SIZE(prop)   \
  maxResourceDescriptorSize = \
      RDCMAX(maxResourceDescriptorSize, (uint32_t)m_DescriptorBufferProperties.prop);

      CALC_MAX_SIZE(storageImageDescriptorSize);
      CALC_MAX_SIZE(sampledImageDescriptorSize);
      CALC_MAX_SIZE(robustUniformTexelBufferDescriptorSize);
      CALC_MAX_SIZE(robustStorageTexelBufferDescriptorSize);
      CALC_MAX_SIZE(robustUniformBufferDescriptorSize);
      CALC_MAX_SIZE(robustStorageBufferDescriptorSize);
      CALC_MAX_SIZE(inputAttachmentDescriptorSize);
      CALC_MAX_SIZE(accelerationStructureDescriptorSize);

      m_ResourceDescriptorBufferReserveSize =
          AlignUp(maxResourceDescriptorSize * 2,
                  (uint32_t)m_DescriptorBufferProperties.descriptorBufferOffsetAlignment);

      m_IgnoreLayoutForDescriptors = descBufFeatures->descriptorBufferImageLayoutIgnored != VK_FALSE;

      VkPhysicalDeviceRobustness2FeaturesKHR *robustness2 =
          (VkPhysicalDeviceRobustness2FeaturesKHR *)FindNextStruct(
              &createInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_KHR);

      // record all the NULL descriptors
      if(IsCaptureMode(m_State) && robustness2 && robustness2->nullDescriptor)
      {
        VkResourceRecord *record = GetRecord(*pDevice);
        RDCASSERT(record);

        VkDescriptorGetInfoEXT getInfo = {
            VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT,
        };

        rdcarray<rdcpair<VkDescriptorType, size_t>> descriptorTypes = {
            {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
             m_DescriptorBufferProperties.sampledImageDescriptorSize},
            {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
             m_DescriptorBufferProperties.storageImageDescriptorSize},
            {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
             m_DescriptorBufferProperties.robustUniformTexelBufferDescriptorSize},
            {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
             m_DescriptorBufferProperties.robustStorageTexelBufferDescriptorSize},
            {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
             m_DescriptorBufferProperties.robustUniformBufferDescriptorSize},
            {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
             m_DescriptorBufferProperties.robustStorageBufferDescriptorSize},
        };

        if(m_AccelerationStructures)
        {
          descriptorTypes.push_back(
              {VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
               m_DescriptorBufferProperties.accelerationStructureDescriptorSize});
        }

        byte dummy[256];
        for(rdcpair<VkDescriptorType, size_t> typeAndSize : descriptorTypes)
        {
          getInfo.type = typeAndSize.first;

          vkGetDescriptorEXT(*pDevice, &getInfo, typeAndSize.second, dummy);
        }
      }

      m_NULLDescriptorPatternSaved = true;
    }

    m_PhysicalDeviceData.driverInfo =
        VkDriverInfo(m_PhysicalDeviceData.props, m_PhysicalDeviceData.driverProps, true);

    // hack for steamdeck, set soft memory limit to 200MB if it's not specified
    if(m_PhysicalDeviceData.driverProps.driverID == VK_DRIVER_ID_MESA_RADV &&
       m_PhysicalDeviceData.props.vendorID == 0x1002 && m_PhysicalDeviceData.props.deviceID == 0x163F)
    {
      CaptureOptions opts = RenderDoc::Inst().GetCaptureOptions();
      if(opts.softMemoryLimit == 0)
        opts.softMemoryLimit = 200;
      RenderDoc::Inst().SetCaptureOptions(opts);
      RDCLOG("Forcing 200MB soft memory limit");
    }

    m_PhysicalDeviceData.maxMemoryAllocationSize = 0;
    ChooseMemoryIndices();

    if(DescriptorBuffers())
      EstimateDescriptorFormats();

    m_PhysicalDeviceData.queueCount = (uint32_t)queueProps.size();
    for(size_t i = 0; i < queueProps.size(); i++)
      m_PhysicalDeviceData.queueProps[i] = queueProps[i];

    m_ShaderCache = new VulkanShaderCache(this);

    m_TextRenderer = new VulkanTextRenderer(this);

    m_DebugManager = new VulkanDebugManager(this);
  }

  FirstFrame();

  return ret;
}

void WrappedVulkan::vkDestroyDevice(VkDevice device, const VkAllocationCallbacks *)
{
  if(device == VK_NULL_HANDLE)
    return;

  if(m_MemoryFreeThread)
  {
    Threading::JoinThread(m_MemoryFreeThread);
    Threading::CloseThread(m_MemoryFreeThread);
    m_MemoryFreeThread = 0;
  }

  // flush out any pending commands/semaphores
  SubmitCmds();
  SubmitSemaphores();
  FlushQ();

  {
    SCOPED_LOCK(m_PendingCmdBufferCallbacksLock);

    for(VkPendingSubmissionCompleteCallbacks *pending : m_PendingCmdBufferCallbacks)
    {
      const VkResult vkr = ObjDisp(m_Device)->GetEventStatus(Unwrap(m_Device), pending->event);
      if(vkr == VK_EVENT_SET)
        pending->Release();
    }

    m_PendingCmdBufferCallbacks.clear();
  }

  // idle the device as well so that external queues are idle.
  VkResult vkr = ObjDisp(m_Device)->DeviceWaitIdle(Unwrap(m_Device));
  CHECK_VKR(this, vkr);

  // MULTIDEVICE this function will need to check if the device is the one we
  // used for debugmanager/cmd pool etc, and only remove child queues and
  // resources (instead of doing full resource manager shutdown).
  // Or will we have a debug manager per-device?
  RDCASSERT(m_Device == device);

  // delete all debug manager objects
  SAFE_DELETE(m_DebugManager);
  SAFE_DELETE(m_ShaderCache);
  SAFE_DELETE(m_TextRenderer);

  m_ASManager->Cleanup();

  // since we didn't create proper registered resources for our command buffers,
  // they won't be taken down properly with the pool. So we release them (just our
  // data) here.
  for(size_t i = 0; i < m_InternalCmds.freecmds.size(); i++)
    GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.freecmds[i]);

  if(m_IndirectCommandBuffer != VK_NULL_HANDLE)
    GetResourceManager()->ReleaseWrappedResource(m_IndirectCommandBuffer);

  // destroy our command pool
  if(m_InternalCmds.cmdpool != VK_NULL_HANDLE)
  {
    ObjDisp(m_Device)->DestroyCommandPool(Unwrap(m_Device), Unwrap(m_InternalCmds.cmdpool), NULL);
    GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.cmdpool);
  }

  for(size_t i = 0; i < m_InternalCmds.freesems.size(); i++)
  {
    ObjDisp(m_Device)->DestroySemaphore(Unwrap(m_Device), Unwrap(m_InternalCmds.freesems[i]), NULL);
    GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.freesems[i]);
  }

  for(size_t i = 0; i < m_ExternalQueues.size(); i++)
  {
    if(m_ExternalQueues[i].pool != VK_NULL_HANDLE)
    {
      for(size_t x = 0; x < ARRAY_COUNT(m_ExternalQueues[i].ring); x++)
      {
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].acquire);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].release);

        ObjDisp(m_Device)->DestroySemaphore(Unwrap(m_Device),
                                            Unwrap(m_ExternalQueues[i].ring[x].fromext), NULL);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].fromext);

        ObjDisp(m_Device)->DestroySemaphore(Unwrap(m_Device),
                                            Unwrap(m_ExternalQueues[i].ring[x].toext), NULL);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].toext);

        ObjDisp(m_Device)->DestroyFence(Unwrap(m_Device), Unwrap(m_ExternalQueues[i].ring[x].fence),
                                        NULL);
        GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].ring[x].fence);
      }

      ObjDisp(m_Device)->DestroyCommandPool(Unwrap(m_Device), Unwrap(m_ExternalQueues[i].pool), NULL);
      GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].pool);
    }
  }

  m_InternalCmds.Reset();

  m_QueueFamilyIdx = ~0U;
  m_PrevQueue = m_Queue = VK_NULL_HANDLE;

  m_QueueFamilies.clear();
  m_QueueFamilyCounts.clear();
  m_QueueFamilyIndices.clear();
  m_ExternalQueues.clear();

  // destroy the API device immediately. There should be no more
  // resources left in the resource manager device/physical device/instance.
  // Anything we created should be gone and anything the application created
  // should be deleted by now.
  // If there were any leaks, we will leak them ourselves in vkDestroyInstance
  // rather than try to delete API objects after the device has gone
  ObjDisp(m_Device)->DestroyDevice(Unwrap(m_Device), NULL);
  GetResourceManager()->ReleaseWrappedResource(m_Device);
  m_Device = VK_NULL_HANDLE;
  m_PhysicalDevice = VK_NULL_HANDLE;
}

template <typename SerialiserType>
bool WrappedVulkan::Serialise_vkDeviceWaitIdle(SerialiserType &ser, VkDevice device)
{
  SERIALISE_ELEMENT(device).Important();

  SERIALISE_CHECK_READ_ERRORS();

  if(IsReplayingAndReading())
  {
    ObjDisp(device)->DeviceWaitIdle(Unwrap(device));
  }

  return true;
}

VkResult WrappedVulkan::vkDeviceWaitIdle(VkDevice device)
{
  VkResult ret;
  SERIALISE_TIME_CALL(ret = ObjDisp(device)->DeviceWaitIdle(Unwrap(device)));

  if(IsActiveCapturing(m_State))
  {
    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CHUNK(VulkanChunk::vkDeviceWaitIdle);
    Serialise_vkDeviceWaitIdle(ser, device);

    m_FrameCaptureRecord->AddChunk(scope.Get());
  }

  return ret;
}

INSTANTIATE_FUNCTION_SERIALISED(VkResult, vkEnumeratePhysicalDevices, VkInstance instance,
                                uint32_t *pPhysicalDeviceCount, VkPhysicalDevice *pPhysicalDevices);

INSTANTIATE_FUNCTION_SERIALISED(VkResult, vkCreateDevice, VkPhysicalDevice physicalDevice,
                                const VkDeviceCreateInfo *pCreateInfo,
                                const VkAllocationCallbacks *, VkDevice *pDevice);

INSTANTIATE_FUNCTION_SERIALISED(VkResult, vkDeviceWaitIdle, VkDevice device);
