/******************************************************************************
 * 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 "../vk_core.h"
#include "../vk_debug.h"
#include "api/replay/version.h"

static char fakeRenderDocUUID[VK_UUID_SIZE] = {};

void MakeFakeUUID()
{
  // Assign a fake UUID, so that we get SPIR-V instead of cached shader data, etc.
  if(fakeRenderDocUUID[0] == 0)
  {
    // The start is "rdoc", and the end is the time that this call was first made
    //
    //     0123456789ABCDEF
    //     rdocyymmddHHMMSS
    //
    // We pass size+1 so that there's room for a null terminator (the UUID doesn't
    // need a null terminator as it's a fixed size non-string array)
    rdcstr uuid = StringFormat::sntimef(Timing::GetUTCTime(), "rdoc%y%m%d%H%M%S");
    RDCASSERT(uuid.size() == sizeof(fakeRenderDocUUID));
    memcpy(fakeRenderDocUUID, uuid.c_str(), RDCMIN((size_t)VK_UUID_SIZE, uuid.size()));
  }
}

void ClampPhysDevAPIVersion(VkPhysicalDeviceProperties *pProperties, VkPhysicalDevice physicalDevice)
{
  // for Vulkan 1.3 bufferDeviceAddress is core. If the bufferDeviceAddressCaptureReplay feature is
  // not available, we can't support it so we must clamp to version 1.2 for that physical device.
  if(pProperties->apiVersion >= VK_API_VERSION_1_3)
  {
    // for 1.1 this is core so we should definitely have this function.
    if(ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2 != NULL)
    {
      VkPhysicalDeviceFeatures2 features = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};

      // similarly this struct must be valid if the device is 1.3
      VkPhysicalDeviceVulkan12Features vk12 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};

      features.pNext = &vk12;

      ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), &features);

      if(vk12.bufferDeviceAddressCaptureReplay == VK_FALSE)
      {
        RDCWARN(
            "Vulkan feature bufferDeviceAddressCaptureReplay is not available. Clamping physical "
            "device %s from reported version %d.%d to 1.2",
            pProperties->deviceName, VK_VERSION_MAJOR(pProperties->apiVersion),
            VK_VERSION_MINOR(pProperties->apiVersion));

        pProperties->apiVersion = VK_API_VERSION_1_2;
      }
    }
    else
    {
      // if we don't have GPDP2 the application has not initialised the instance at 1.3+
      // let's clamp the version just to be safe since we can't check, and this will help protect
      // against buggy applications
      pProperties->apiVersion = VK_API_VERSION_1_2;
    }
  }

  // clamp to highest supported API version, currently vulkan 1.4, because loader no longer does this for us
  if(pProperties->apiVersion > VK_API_VERSION_1_4)
    pProperties->apiVersion = VK_API_VERSION_1_4;
}

void WrappedVulkan::PatchImageCreateInfo(VkImageCreateInfo *info, VkFormat *newViewFormatsTempMem)
{
  info->usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;

  if(IsCaptureMode(m_State))
  {
    info->usage |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    info->usage &= ~VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
  }

  if(IsYUVFormat(info->format))
    info->flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;

  if(info->samples != VK_SAMPLE_COUNT_1_BIT)
  {
    info->usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
    info->flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;

    if(IsCaptureMode(m_State))
    {
      if(!IsDepthOrStencilFormat(info->format))
      {
        if(GetDebugManager() && GetShaderCache()->IsBuffer2MSSupported())
          info->usage |= VK_IMAGE_USAGE_STORAGE_BIT;
      }
      else
      {
        info->usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
      }
    }
  }

  info->flags &= ~VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT;

  info->flags |= DefaultImageCreateFlags();

  VkImageStencilUsageCreateInfo *separateStencilUsage =
      (VkImageStencilUsageCreateInfo *)FindNextStruct(
          info, VK_STRUCTURE_TYPE_IMAGE_STENCIL_USAGE_CREATE_INFO);
  if(separateStencilUsage)
  {
    separateStencilUsage->stencilUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;

    if(IsCaptureMode(m_State))
    {
      info->usage |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
      info->usage &= ~VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
    }

    if(info->samples != VK_SAMPLE_COUNT_1_BIT)
    {
      separateStencilUsage->stencilUsage |=
          VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
    }
  }

  // similarly for the image format list for MSAA textures, add the UINT cast format we will need
  if(info->samples != VK_SAMPLE_COUNT_1_BIT)
  {
    VkImageFormatListCreateInfo *formatListInfo = (VkImageFormatListCreateInfo *)FindNextStruct(
        info, VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);

    if(formatListInfo)
    {
      uint32_t bs = (uint32_t)GetByteSize(1, 1, 1, info->format, 0);

      VkFormat msaaCopyFormat = VK_FORMAT_UNDEFINED;
      if(bs == 1)
        msaaCopyFormat = VK_FORMAT_R8_UINT;
      else if(bs == 2)
        msaaCopyFormat = VK_FORMAT_R16_UINT;
      else if(bs == 4)
        msaaCopyFormat = VK_FORMAT_R32_UINT;
      else if(bs == 8)
        msaaCopyFormat = VK_FORMAT_R32G32_UINT;
      else if(bs == 16)
        msaaCopyFormat = VK_FORMAT_R32G32B32A32_UINT;

      const VkFormat *oldFmts = formatListInfo->pViewFormats;
      formatListInfo->pViewFormats = newViewFormatsTempMem;

      bool needAdded = true;
      uint32_t i = 0;
      for(; i < formatListInfo->viewFormatCount; i++)
      {
        newViewFormatsTempMem[i] = oldFmts[i];
        if(newViewFormatsTempMem[i] == msaaCopyFormat)
          needAdded = false;
      }

      if(needAdded)
      {
        newViewFormatsTempMem[i] = msaaCopyFormat;
        formatListInfo->viewFormatCount++;
      }

      newViewFormatsTempMem += formatListInfo->viewFormatCount;
    }
  }
}

void WrappedVulkan::vkGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice,
                                                VkPhysicalDeviceFeatures *pFeatures)
{
  ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures(Unwrap(physicalDevice), pFeatures);
}

void WrappedVulkan::vkGetPhysicalDeviceFormatProperties(VkPhysicalDevice physicalDevice,
                                                        VkFormat format,
                                                        VkFormatProperties *pFormatProperties)
{
  ObjDisp(physicalDevice)
      ->GetPhysicalDeviceFormatProperties(Unwrap(physicalDevice), format, pFormatProperties);

  // we require all these properties at minimum for an image to be created, since we add these to
  // any usage. Fortunately, in the formats the spec requires an implementation to support,
  // optimalTiledFeatures must contain all these and more, so we can safely remove support for any
  // format that only includes a subset.
  uint32_t minRequiredMask = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;

  const InstanceDeviceInfo &exts = GetExtensions(GetRecord(physicalDevice));

  // transfer src/dst bits were added in KHR_maintenance1. Before then we assume that if
  // SAMPLED_IMAGE_BIT was present it's safe to add the transfer bits too.
  if(exts.ext_KHR_maintenance1)
    minRequiredMask |= VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT;

  if((pFormatProperties->linearTilingFeatures & minRequiredMask) != minRequiredMask)
    pFormatProperties->linearTilingFeatures = 0;
  if((pFormatProperties->optimalTilingFeatures & minRequiredMask) != minRequiredMask)
    pFormatProperties->optimalTilingFeatures = 0;

  // don't report support for DISJOINT_BIT_KHR binding
  pFormatProperties->linearTilingFeatures &= ~VK_FORMAT_FEATURE_DISJOINT_BIT;
  pFormatProperties->optimalTilingFeatures &= ~VK_FORMAT_FEATURE_DISJOINT_BIT;
}

void WrappedVulkan::vkGetPhysicalDeviceFormatProperties2(VkPhysicalDevice physicalDevice,
                                                         VkFormat format,
                                                         VkFormatProperties2 *pFormatProperties)
{
  ObjDisp(physicalDevice)
      ->GetPhysicalDeviceFormatProperties2(Unwrap(physicalDevice), format, pFormatProperties);

  // we require transfer source and dest these properties at minimum for an image to be created,
  // since we add these to
  // any usage. Fortunately, in the formats the spec requires an implementation to support,
  // optimalTiledFeatures must contain all these and more, so we can safely remove support for any
  // format that only includes a subset.
  uint32_t minRequiredMask = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;

  const InstanceDeviceInfo &exts = GetExtensions(GetRecord(physicalDevice));

  // transfer src/dst bits were added in KHR_maintenance1. Before then we assume that if
  // SAMPLED_IMAGE_BIT was present it's safe to add the transfer bits too.
  if(exts.ext_KHR_maintenance1)
    minRequiredMask |= VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT;

  if((pFormatProperties->formatProperties.linearTilingFeatures & minRequiredMask) != minRequiredMask)
    pFormatProperties->formatProperties.linearTilingFeatures = 0;
  if((pFormatProperties->formatProperties.optimalTilingFeatures & minRequiredMask) != minRequiredMask)
    pFormatProperties->formatProperties.optimalTilingFeatures = 0;

  // don't report support for DISJOINT_BIT_KHR binding
  pFormatProperties->formatProperties.linearTilingFeatures &= ~VK_FORMAT_FEATURE_DISJOINT_BIT;
  pFormatProperties->formatProperties.optimalTilingFeatures &= ~VK_FORMAT_FEATURE_DISJOINT_BIT;

  VkDrmFormatModifierPropertiesListEXT *drm = (VkDrmFormatModifierPropertiesListEXT *)FindNextStruct(
      pFormatProperties, VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT);
  if(drm && drm->pDrmFormatModifierProperties)
  {
    for(uint32_t i = 0; i < drm->drmFormatModifierCount; i++)
    {
      drm->pDrmFormatModifierProperties[i].drmFormatModifierTilingFeatures &=
          ~VK_FORMAT_FEATURE_DISJOINT_BIT;
    }
  }
  VkDrmFormatModifierPropertiesList2EXT *drm2 =
      (VkDrmFormatModifierPropertiesList2EXT *)FindNextStruct(
          pFormatProperties, VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_2_EXT);
  if(drm2 && drm2->pDrmFormatModifierProperties)
  {
    for(uint32_t i = 0; i < drm2->drmFormatModifierCount; i++)
    {
      drm2->pDrmFormatModifierProperties[i].drmFormatModifierTilingFeatures &=
          ~VK_FORMAT_FEATURE_DISJOINT_BIT;
    }
  }
}

VkResult WrappedVulkan::vkGetPhysicalDeviceImageFormatProperties(
    VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling,
    VkImageUsageFlags usage, VkImageCreateFlags flags,
    VkImageFormatProperties *pImageFormatProperties)
{
  // we're going to add these usage bits implicitly on image create, so ensure we get an accurate
  // response by adding them here. It's OK to add these, since these can't make a required format
  // suddenly report as unsupported (all required formats must support these usages), so it can only
  // make an optional format unsupported which is what we want.
  usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
           VK_IMAGE_USAGE_SAMPLED_BIT;

  VkResult vkr =
      ObjDisp(physicalDevice)
          ->GetPhysicalDeviceImageFormatProperties(Unwrap(physicalDevice), format, type, tiling,
                                                   usage, flags, pImageFormatProperties);

  if(vkr == VK_SUCCESS)
  {
    // check that the format is one we allow to be supported - if not we return an error to be
    // consistent.
    VkFormatProperties props;
    vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
    if(props.linearTilingFeatures == 0 && props.optimalTilingFeatures == 0)
    {
      RDCEraseEl(*pImageFormatProperties);
      return VK_ERROR_FORMAT_NOT_SUPPORTED;
    }
  }

  return vkr;
}

VkResult WrappedVulkan::vkGetPhysicalDeviceImageFormatProperties2(
    VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo,
    VkImageFormatProperties2 *pImageFormatProperties)
{
  // we're going to add these usage bits implicitly on image create, so ensure we get an accurate
  // response by adding them here. It's OK to add these, since these can't make a required format
  // suddenly report as unsupported (all required formats must support these usages), so it can only
  // make an optional format unsupported which is what we want.
  VkPhysicalDeviceImageFormatInfo2 info = *pImageFormatInfo;
  info.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
                VK_IMAGE_USAGE_SAMPLED_BIT;

  VkResult vkr = ObjDisp(physicalDevice)
                     ->GetPhysicalDeviceImageFormatProperties2(Unwrap(physicalDevice), &info,
                                                               pImageFormatProperties);

  if(vkr == VK_SUCCESS)
  {
    // check that the format is one we allow to be supported - if not we return an error to be
    // consistent.
    VkFormatProperties props;
    vkGetPhysicalDeviceFormatProperties(physicalDevice, pImageFormatInfo->format, &props);
    if(props.linearTilingFeatures == 0 && props.optimalTilingFeatures == 0)
    {
      RDCEraseEl(pImageFormatProperties->imageFormatProperties);
      return VK_ERROR_FORMAT_NOT_SUPPORTED;
    }
  }

  return vkr;
}

void WrappedVulkan::vkGetPhysicalDeviceSparseImageFormatProperties(
    VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type,
    VkSampleCountFlagBits samples, VkImageUsageFlags usage, VkImageTiling tiling,
    uint32_t *pPropertyCount, VkSparseImageFormatProperties *pProperties)
{
  ObjDisp(physicalDevice)
      ->GetPhysicalDeviceSparseImageFormatProperties(Unwrap(physicalDevice), format, type, samples,
                                                     usage, tiling, pPropertyCount, pProperties);
}

void WrappedVulkan::vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice,
                                                  VkPhysicalDeviceProperties *pProperties)
{
  ObjDisp(physicalDevice)->GetPhysicalDeviceProperties(Unwrap(physicalDevice), pProperties);

  ClampPhysDevAPIVersion(pProperties, physicalDevice);
}

void WrappedVulkan::vkGetPhysicalDeviceQueueFamilyProperties(
    VkPhysicalDevice physicalDevice, uint32_t *pCount, VkQueueFamilyProperties *pQueueFamilyProperties)
{
  // report the actual physical device properties - this will be remapped on replay if necessary
  ObjDisp(physicalDevice)
      ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), pCount,
                                               pQueueFamilyProperties);

  // remove any protected bits that might be set
  if(pCount && pQueueFamilyProperties)
  {
    for(uint32_t i = 0; i < *pCount; i++)
      pQueueFamilyProperties[i].queueFlags &= ~VK_QUEUE_PROTECTED_BIT;
  }
}

void WrappedVulkan::vkGetPhysicalDeviceMemoryProperties(
    VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties *pMemoryProperties)
{
  ObjDisp(physicalDevice)->GetPhysicalDeviceMemoryProperties(Unwrap(physicalDevice), pMemoryProperties);
}

void WrappedVulkan::vkGetImageSubresourceLayout(VkDevice device, VkImage image,
                                                const VkImageSubresource *pSubresource,
                                                VkSubresourceLayout *pLayout)
{
  ObjDisp(device)->GetImageSubresourceLayout(Unwrap(device), Unwrap(image), pSubresource, pLayout);
}

void WrappedVulkan::vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer,
                                                  VkMemoryRequirements *pMemoryRequirements)
{
  // if we have cached memory requirements, use them. These were fetched at create time (which is
  // still valid, they don't change over the lifetime of the resource) and may be slightly more
  // pessimistic for the case of external memory bound resources. See vkCreateBuffer/vkCreateImage
  if(IsCaptureMode(m_State) && GetRecord(buffer)->resInfo)
    *pMemoryRequirements = GetRecord(buffer)->resInfo->memreqs;
  else
    ObjDisp(device)->GetBufferMemoryRequirements(Unwrap(device), Unwrap(buffer), pMemoryRequirements);
}

void WrappedVulkan::vkGetImageMemoryRequirements(VkDevice device, VkImage image,
                                                 VkMemoryRequirements *pMemoryRequirements)
{
  // if we have cached memory requirements, use them. These were fetched at create time (which is
  // still valid, they don't change over the lifetime of the resource) and may be slightly more
  // pessimistic for the case of external memory bound resources. See vkCreateBuffer/vkCreateImage
  if(IsCaptureMode(m_State) && GetRecord(image)->resInfo)
    *pMemoryRequirements = GetRecord(image)->resInfo->memreqs;
  else
    ObjDisp(device)->GetImageMemoryRequirements(Unwrap(device), Unwrap(image), pMemoryRequirements);

  // AMD can have some variability in the returned size, so we need to pad the reported size to
  // allow for this. The variability isn't quite clear, but for now we assume aligning size to
  // alignment * 4 should be sufficient (adding on a fixed padding won't help the problem as it
  // won't remove the variability, nor will adding then aligning for the same reason).
  if(GetDriverInfo().AMDUnreliableImageMemoryRequirements() && pMemoryRequirements->size > 0)
  {
    VkMemoryRequirements &memreq = *pMemoryRequirements;

    VkDeviceSize oldsize = memreq.size;
    memreq.size = AlignUp(memreq.size, memreq.alignment * 4);

    // if it's already 'super aligned', then bump it up a little. We assume that this case
    // represents the low-end of the variation range, and other variations will be a little higher.
    // The other alternative is the variations are all lower and this one happened to be super
    // aligned, which I think (arbitrarily really) is less likely.
    if(oldsize == memreq.size)
      memreq.size = AlignUp(memreq.size + 1, memreq.alignment * 4);

    RDCDEBUG(
        "Padded image memory requirements from %llu to %llu (base alignment %llu) (%f%% increase)",
        oldsize, memreq.size, memreq.alignment,
        (100.0 * double(memreq.size - oldsize)) / double(oldsize));
  }
}

void WrappedVulkan::vkGetImageSparseMemoryRequirements(
    VkDevice device, VkImage image, uint32_t *pNumRequirements,
    VkSparseImageMemoryRequirements *pSparseMemoryRequirements)
{
  ObjDisp(device)->GetImageSparseMemoryRequirements(Unwrap(device), Unwrap(image), pNumRequirements,
                                                    pSparseMemoryRequirements);
}

void WrappedVulkan::vkGetDeviceBufferMemoryRequirements(VkDevice device,
                                                        const VkDeviceBufferMemoryRequirements *pInfo,
                                                        VkMemoryRequirements2 *pMemoryRequirements)
{
  byte *tempMem = GetTempMemory(GetNextPatchSize(pInfo));
  VkDeviceBufferMemoryRequirements *unwrappedInfo = UnwrapStructAndChain(m_State, tempMem, pInfo);

  VkBufferCreateInfo *info = (VkBufferCreateInfo *)unwrappedInfo->pCreateInfo;

  // patch the create info the same as we would for vkCreateBuffer
  uint64_t usage = GetBufferUsageFlags(info);
  usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
  usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
  SetBufferUsageFlags(info, usage);

  if(IsCaptureMode(m_State) && (usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT))
    info->flags |= VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT;

  ObjDisp(device)->GetDeviceBufferMemoryRequirements(Unwrap(device), unwrappedInfo,
                                                     pMemoryRequirements);

  // if the buffer is external, create a non-external and return the worst case memory requirements
  // so that the memory allocated is sufficient for us on replay when the buffer is non-external
  bool isExternal = FindNextStruct(unwrappedInfo->pCreateInfo,
                                   VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO) != NULL;

  if(isExternal)
  {
    bool removed =
        RemoveNextStruct(unwrappedInfo, VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO);

    RDCASSERTMSG("Couldn't find next struct indicating external memory", removed);

    VkMemoryRequirements2 nonExternalReq = {};
    nonExternalReq.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;

    ObjDisp(device)->GetDeviceBufferMemoryRequirements(Unwrap(device), unwrappedInfo,
                                                       &nonExternalReq);

    pMemoryRequirements->memoryRequirements.size =
        RDCMAX(pMemoryRequirements->memoryRequirements.size, nonExternalReq.memoryRequirements.size);
    pMemoryRequirements->memoryRequirements.alignment =
        RDCMAX(pMemoryRequirements->memoryRequirements.alignment,
               nonExternalReq.memoryRequirements.alignment);

    if((pMemoryRequirements->memoryRequirements.memoryTypeBits &
        nonExternalReq.memoryRequirements.memoryTypeBits) == 0)
    {
      RDCWARN(
          "External buffer shares no memory types with non-external buffer. This buffer "
          "will not be replayable.");
    }
    else
    {
      pMemoryRequirements->memoryRequirements.memoryTypeBits &=
          nonExternalReq.memoryRequirements.memoryTypeBits;
    }
  }
}

void WrappedVulkan::vkGetDeviceImageMemoryRequirements(VkDevice device,
                                                       const VkDeviceImageMemoryRequirements *pInfo,
                                                       VkMemoryRequirements2 *pMemoryRequirements)
{
  size_t tempMemSize = GetNextPatchSize(pInfo);

  // reserve space for a patched view format list if necessary
  if(pInfo->pCreateInfo->samples != VK_SAMPLE_COUNT_1_BIT)
  {
    const VkImageFormatListCreateInfo *formatListInfo =
        (const VkImageFormatListCreateInfo *)FindNextStruct(
            pInfo->pCreateInfo, VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);

    if(formatListInfo)
      tempMemSize += sizeof(VkFormat) * (formatListInfo->viewFormatCount + 1);
  }

  byte *tempMem = GetTempMemory(tempMemSize);
  VkDeviceImageMemoryRequirements *unwrappedInfo = UnwrapStructAndChain(m_State, tempMem, pInfo);

  VkImageCreateInfo *info = (VkImageCreateInfo *)unwrappedInfo->pCreateInfo;

  PatchImageCreateInfo(info, (VkFormat *)tempMem);

  ObjDisp(device)->GetDeviceImageMemoryRequirements(Unwrap(device), unwrappedInfo,
                                                    pMemoryRequirements);

  // if the image is external, create a non-external and return the worst case memory requirements
  // so that the memory allocated is sufficient for us on replay when the image is non-external
  bool isExternal = FindNextStruct(unwrappedInfo->pCreateInfo,
                                   VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO) != NULL;

  if(isExternal)
  {
    bool removed =
        RemoveNextStruct(unwrappedInfo, VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO);

    RDCASSERTMSG("Couldn't find next struct indicating external memory", removed);

    VkMemoryRequirements2 nonExternalReq = {};
    nonExternalReq.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;

    ObjDisp(device)->GetDeviceImageMemoryRequirements(Unwrap(device), unwrappedInfo, &nonExternalReq);

    pMemoryRequirements->memoryRequirements.size =
        RDCMAX(pMemoryRequirements->memoryRequirements.size, nonExternalReq.memoryRequirements.size);
    pMemoryRequirements->memoryRequirements.alignment =
        RDCMAX(pMemoryRequirements->memoryRequirements.alignment,
               nonExternalReq.memoryRequirements.alignment);

    if((pMemoryRequirements->memoryRequirements.memoryTypeBits &
        nonExternalReq.memoryRequirements.memoryTypeBits) == 0)
    {
      RDCWARN(
          "External image shares no memory types with non-external image. This image "
          "will not be replayable.");
    }
    else
    {
      pMemoryRequirements->memoryRequirements.memoryTypeBits &=
          nonExternalReq.memoryRequirements.memoryTypeBits;
    }
  }
}

void WrappedVulkan::vkGetDeviceImageSparseMemoryRequirements(
    VkDevice device, const VkDeviceImageMemoryRequirements *pInfo,
    uint32_t *pSparseMemoryRequirementCount,
    VkSparseImageMemoryRequirements2 *pSparseMemoryRequirements)
{
  byte *tempMem = GetTempMemory(GetNextPatchSize(pInfo));
  VkDeviceImageMemoryRequirements *unwrappedInfo = UnwrapStructAndChain(m_State, tempMem, pInfo);

  ObjDisp(device)->GetDeviceImageSparseMemoryRequirements(
      Unwrap(device), unwrappedInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
}

void WrappedVulkan::vkGetBufferMemoryRequirements2(VkDevice device,
                                                   const VkBufferMemoryRequirementsInfo2 *pInfo,
                                                   VkMemoryRequirements2 *pMemoryRequirements)
{
  VkBufferMemoryRequirementsInfo2 unwrappedInfo = *pInfo;
  unwrappedInfo.buffer = Unwrap(unwrappedInfo.buffer);
  ObjDisp(device)->GetBufferMemoryRequirements2(Unwrap(device), &unwrappedInfo, pMemoryRequirements);

  // if we have cached memory requirements, use them. These were fetched at create time (which is
  // still valid, they don't change over the lifetime of the resource) and may be slightly more
  // pessimistic for the case of external memory bound resources. See vkCreateBuffer/vkCreateImage
  if(IsCaptureMode(m_State) && GetRecord(pInfo->buffer)->resInfo)
    pMemoryRequirements->memoryRequirements = GetRecord(pInfo->buffer)->resInfo->memreqs;
}

void WrappedVulkan::vkGetImageMemoryRequirements2(VkDevice device,
                                                  const VkImageMemoryRequirementsInfo2 *pInfo,
                                                  VkMemoryRequirements2 *pMemoryRequirements)
{
  VkImageMemoryRequirementsInfo2 unwrappedInfo = *pInfo;
  unwrappedInfo.image = Unwrap(unwrappedInfo.image);
  ObjDisp(device)->GetImageMemoryRequirements2(Unwrap(device), &unwrappedInfo, pMemoryRequirements);

  // if we have cached memory requirements, use them. These were fetched at create time (which is
  // still valid, they don't change over the lifetime of the resource) and may be slightly more
  // pessimistic for the case of external memory bound resources. See vkCreateBuffer/vkCreateImage
  if(IsCaptureMode(m_State) && GetRecord(pInfo->image)->resInfo)
    pMemoryRequirements->memoryRequirements = GetRecord(pInfo->image)->resInfo->memreqs;

  // don't do remapping here on replay.
  if(IsReplayMode(m_State))
    return;

  // AMD can have some variability in the returned size, so we need to pad the reported size to
  // allow for this. The variability isn't quite clear, but for now we assume aligning size to
  // alignment * 4 should be sufficient (adding on a fixed padding won't help the problem as it
  // won't remove the variability, nor will adding then aligning for the same reason).
  if(GetDriverInfo().AMDUnreliableImageMemoryRequirements() &&
     pMemoryRequirements->memoryRequirements.size > 0)
  {
    VkMemoryRequirements &memreq = pMemoryRequirements->memoryRequirements;

    VkDeviceSize oldsize = memreq.size;
    memreq.size = AlignUp(memreq.size, memreq.alignment * 4);

    // if it's already 'super aligned', then bump it up a little. We assume that this case
    // represents the low-end of the variation range, and other variations will be a little higher.
    // The other alternative is the variations are all lower and this one happened to be super
    // aligned, which I think (arbitrarily really) is less likely.
    if(oldsize == memreq.size)
      memreq.size = AlignUp(memreq.size + 1, memreq.alignment * 4);

    RDCDEBUG(
        "Padded image memory requirements from %llu to %llu (base alignment %llu) (%f%% increase)",
        oldsize, memreq.size, memreq.alignment,
        (100.0 * double(memreq.size - oldsize)) / double(oldsize));
  }
}

void WrappedVulkan::vkGetImageSparseMemoryRequirements2(
    VkDevice device, const VkImageSparseMemoryRequirementsInfo2 *pInfo,
    uint32_t *pSparseMemoryRequirementCount,
    VkSparseImageMemoryRequirements2 *pSparseMemoryRequirements)
{
  VkImageSparseMemoryRequirementsInfo2 unwrappedInfo = *pInfo;
  unwrappedInfo.image = Unwrap(unwrappedInfo.image);
  ObjDisp(device)->GetImageSparseMemoryRequirements2(
      Unwrap(device), &unwrappedInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
}

void WrappedVulkan::vkGetDeviceMemoryCommitment(VkDevice device, VkDeviceMemory memory,
                                                VkDeviceSize *pCommittedMemoryInBytes)
{
  ObjDisp(device)->GetDeviceMemoryCommitment(Unwrap(device), Unwrap(memory), pCommittedMemoryInBytes);
}

void WrappedVulkan::vkGetRenderAreaGranularity(VkDevice device, VkRenderPass renderPass,
                                               VkExtent2D *pGranularity)
{
  return ObjDisp(device)->GetRenderAreaGranularity(Unwrap(device), Unwrap(renderPass), pGranularity);
}

VkResult WrappedVulkan::vkGetPipelineCacheData(VkDevice device, VkPipelineCache pipelineCache,
                                               size_t *pDataSize, void *pData)
{
  return ObjDisp(device)->GetPipelineCacheData(Unwrap(device), Unwrap(pipelineCache), pDataSize,
                                               pData);
}

VkResult WrappedVulkan::vkMergePipelineCaches(VkDevice device, VkPipelineCache destCache,
                                              uint32_t srcCacheCount,
                                              const VkPipelineCache *pSrcCaches)
{
  // do nothing, our pipeline caches are always dummies
  rdcarray<VkPipelineCache> unwrappedPipelineCaches;

  unwrappedPipelineCaches.reserve(srcCacheCount);
  for(uint32_t cacheIndex = 0; cacheIndex < srcCacheCount; cacheIndex++)
  {
    unwrappedPipelineCaches.push_back(Unwrap(pSrcCaches[cacheIndex]));
  }

  return ObjDisp(device)->MergePipelineCaches(Unwrap(device), Unwrap(destCache), srcCacheCount,
                                              unwrappedPipelineCaches.data());
}

VkResult WrappedVulkan::vkGetPhysicalDeviceExternalImageFormatPropertiesNV(
    VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling,
    VkImageUsageFlags usage, VkImageCreateFlags flags,
    VkExternalMemoryHandleTypeFlagsNV externalHandleType,
    VkExternalImageFormatPropertiesNV *pExternalImageFormatProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceExternalImageFormatPropertiesNV(Unwrap(physicalDevice), format, type,
                                                         tiling, usage, flags, externalHandleType,
                                                         pExternalImageFormatProperties);
}

#if defined(VK_USE_PLATFORM_WIN32_KHR)
VkResult WrappedVulkan::vkGetMemoryWin32HandleNV(VkDevice device, VkDeviceMemory memory,
                                                 VkExternalMemoryHandleTypeFlagsNV handleType,
                                                 HANDLE *pHandle)
{
  return ObjDisp(device)->GetMemoryWin32HandleNV(Unwrap(device), Unwrap(memory), handleType, pHandle);
}

VkResult WrappedVulkan::vkGetMemoryWin32HandleKHR(
    VkDevice device, const VkMemoryGetWin32HandleInfoKHR *pGetWin32HandleInfo, HANDLE *pHandle)
{
  VkMemoryGetWin32HandleInfoKHR unwrappedInfo = *pGetWin32HandleInfo;
  unwrappedInfo.memory = Unwrap(unwrappedInfo.memory);
  return ObjDisp(device)->GetMemoryWin32HandleKHR(Unwrap(device), &unwrappedInfo, pHandle);
}

VkResult WrappedVulkan::vkGetMemoryWin32HandlePropertiesKHR(
    VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, HANDLE handle,
    VkMemoryWin32HandlePropertiesKHR *pMemoryWin32HandleProperties)
{
  return ObjDisp(device)->GetMemoryWin32HandlePropertiesKHR(Unwrap(device), handleType, handle,
                                                            pMemoryWin32HandleProperties);
}
#endif

VkResult WrappedVulkan::vkGetMemoryFdKHR(VkDevice device, const VkMemoryGetFdInfoKHR *pGetFdInfo,
                                         int *pFd)
{
  VkMemoryGetFdInfoKHR unwrappedInfo = *pGetFdInfo;
  unwrappedInfo.memory = Unwrap(unwrappedInfo.memory);
  return ObjDisp(device)->GetMemoryFdKHR(Unwrap(device), &unwrappedInfo, pFd);
}

VkResult WrappedVulkan::vkGetMemoryFdPropertiesKHR(VkDevice device,
                                                   VkExternalMemoryHandleTypeFlagBits handleType,
                                                   int fd,
                                                   VkMemoryFdPropertiesKHR *pMemoryFdProperties)
{
  return ObjDisp(device)->GetMemoryFdPropertiesKHR(Unwrap(device), handleType, fd,
                                                   pMemoryFdProperties);
}

void WrappedVulkan::vkGetPhysicalDeviceExternalBufferProperties(
    VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo *pExternalBufferInfo,
    VkExternalBufferProperties *pExternalBufferProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceExternalBufferProperties(Unwrap(physicalDevice), pExternalBufferInfo,
                                                  pExternalBufferProperties);
}

void WrappedVulkan::vkGetPhysicalDeviceExternalSemaphoreProperties(
    VkPhysicalDevice physicalDevice,
    const VkPhysicalDeviceExternalSemaphoreInfo *pExternalSemaphoreInfo,
    VkExternalSemaphoreProperties *pExternalSemaphoreProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceExternalSemaphoreProperties(Unwrap(physicalDevice), pExternalSemaphoreInfo,
                                                     pExternalSemaphoreProperties);
}

void WrappedVulkan::vkGetPhysicalDeviceExternalFenceProperties(
    VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalFenceInfo *pExternalFenceInfo,
    VkExternalFenceProperties *pExternalFenceProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceExternalFenceProperties(Unwrap(physicalDevice), pExternalFenceInfo,
                                                 pExternalFenceProperties);
}

void WrappedVulkan::vkGetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice,
                                                 VkPhysicalDeviceFeatures2 *pFeatures)
{
  ObjDisp(physicalDevice)->GetPhysicalDeviceFeatures2(Unwrap(physicalDevice), pFeatures);

  // if the user is requesting protected memory, make sure it's reported as NOT supported
  VkPhysicalDeviceProtectedMemoryFeatures *protectedMem =
      (VkPhysicalDeviceProtectedMemoryFeatures *)FindNextStruct(
          pFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES);

  if(protectedMem)
  {
    RDCWARN("Forcibly disabling support for protected memory");
    protectedMem->protectedMemory = VK_FALSE;
  }

  // in Vulkan 1.2 buffer_device_address can be used without an extension, so we can't hide the
  // extension when capture/replay is not supported. Instead we hide the feature bit here.
  VkPhysicalDeviceVulkan12Features *vulkan12 = (VkPhysicalDeviceVulkan12Features *)FindNextStruct(
      pFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);

  if(vulkan12)
  {
    if(vulkan12->bufferDeviceAddressCaptureReplay == VK_FALSE)
    {
      RDCWARN(
          "VkPhysicalDeviceVulkan12Features::bufferDeviceAddressCaptureReplay is false, "
          "can't support capture of bufferDeviceAddress");
      vulkan12->bufferDeviceAddress = vulkan12->bufferDeviceAddressMultiDevice = VK_FALSE;
    }
  }

  // Vulkan 1.2 also promoted the old core extension query. This would have been invalid to use
  // before since we would have hidden the extension itself, but on vulkan 1.2 it is valid so we
  // intercept it unconditionally
  VkPhysicalDeviceBufferDeviceAddressFeatures *bda =
      (VkPhysicalDeviceBufferDeviceAddressFeatures *)FindNextStruct(
          pFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES);

  if(bda)
  {
    if(bda->bufferDeviceAddressCaptureReplay == VK_FALSE)
    {
      RDCWARN(
          "VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddressCaptureReplay is false, "
          "can't support capture of bufferDeviceAddress");
      bda->bufferDeviceAddress = bda->bufferDeviceAddressMultiDevice = VK_FALSE;
    }
  }

  // we don't want to report support for mesh shaders + multiview
  VkPhysicalDeviceMeshShaderFeaturesEXT *mesh =
      (VkPhysicalDeviceMeshShaderFeaturesEXT *)FindNextStruct(
          pFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT);

  if(mesh)
  {
    if(mesh->multiviewMeshShader)
    {
      RDCWARN("Disabling support for multiview + mesh shaders");
      mesh->multiviewMeshShader = VK_FALSE;
    }
  }

  // report features depending on extensions not supported in RenderDoc as not supported
  VkPhysicalDeviceExtendedDynamicState3FeaturesEXT *dynState3 =
      (VkPhysicalDeviceExtendedDynamicState3FeaturesEXT *)FindNextStruct(
          pFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT);

#define DISABLE_EDS3_FEATURE(feature)                                                 \
  if(dynState3->feature == VK_TRUE)                                                   \
  {                                                                                   \
    RDCWARN("Forcibly disabling support for physical device feature '" #feature "'"); \
    dynState3->feature = VK_FALSE;                                                    \
  }

  if(dynState3)
  {
    // need VK_EXT_blend_operation_advanced
    DISABLE_EDS3_FEATURE(extendedDynamicState3ColorBlendAdvanced);
    // need VK_NV_clip_space_w_scaling
    DISABLE_EDS3_FEATURE(extendedDynamicState3ViewportWScalingEnable);
    // need VK_NV_viewport_swizzle
    DISABLE_EDS3_FEATURE(extendedDynamicState3ViewportSwizzle);
    // need VK_NV_fragment_coverage_to_color
    DISABLE_EDS3_FEATURE(extendedDynamicState3CoverageToColorEnable);
    DISABLE_EDS3_FEATURE(extendedDynamicState3CoverageToColorLocation);
    // need VK_NV_framebuffer_mixed_samples
    DISABLE_EDS3_FEATURE(extendedDynamicState3CoverageModulationMode);
    DISABLE_EDS3_FEATURE(extendedDynamicState3CoverageModulationTableEnable);
    DISABLE_EDS3_FEATURE(extendedDynamicState3CoverageModulationTable);
    // need VK_NV_coverage_reduction_mode
    DISABLE_EDS3_FEATURE(extendedDynamicState3CoverageReductionMode);
    // need VK_NV_representative_fragment_test
    DISABLE_EDS3_FEATURE(extendedDynamicState3RepresentativeFragmentTestEnable);
    // VK_NV_shading_rate_image
    DISABLE_EDS3_FEATURE(extendedDynamicState3ShadingRateImageEnable);
  }

#undef DISABLE_EDS3_FEATURE

  // we don't want to report support for acceleration structure host commands
  VkPhysicalDeviceAccelerationStructureFeaturesKHR *accStruct =
      (VkPhysicalDeviceAccelerationStructureFeaturesKHR *)FindNextStruct(
          pFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR);

  if(accStruct && accStruct->accelerationStructureHostCommands)
  {
    RDCWARN("Disabling support for acceleration structure host commands and indirect builds");
    accStruct->accelerationStructureHostCommands = VK_FALSE;
    accStruct->accelerationStructureIndirectBuild = VK_FALSE;
  }
}

void WrappedVulkan::vkGetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice,
                                                   VkPhysicalDeviceProperties2 *pProperties)
{
  ObjDisp(physicalDevice)->GetPhysicalDeviceProperties2(Unwrap(physicalDevice), pProperties);

  ClampPhysDevAPIVersion(&pProperties->properties, physicalDevice);

  // Internal RenderDoc UUID for:
  //
  // * Shader object binary, so we always get SPIR-V
  // * Optimal image layout, so we never get VK_HOST_IMAGE_COPY_MEMCPY_BIT
  VkPhysicalDeviceShaderObjectPropertiesEXT *shaderObject =
      (VkPhysicalDeviceShaderObjectPropertiesEXT *)FindNextStruct(
          pProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_PROPERTIES_EXT);
  VkPhysicalDeviceHostImageCopyProperties *hostImageCopy =
      (VkPhysicalDeviceHostImageCopyProperties *)FindNextStruct(
          pProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_PROPERTIES);
  VkPhysicalDeviceVulkan14Properties *vulkan14 = (VkPhysicalDeviceVulkan14Properties *)FindNextStruct(
      pProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES);

  if(shaderObject || hostImageCopy || vulkan14)
  {
    MakeFakeUUID();
  }

  if(shaderObject)
  {
    memcpy(shaderObject->shaderBinaryUUID, fakeRenderDocUUID, VK_UUID_SIZE);
  }
  if(hostImageCopy)
  {
    memcpy(hostImageCopy->optimalTilingLayoutUUID, fakeRenderDocUUID, VK_UUID_SIZE);
  }
  if(vulkan14)
  {
    memcpy(vulkan14->optimalTilingLayoutUUID, fakeRenderDocUUID, VK_UUID_SIZE);
  }

  VkPhysicalDeviceDescriptorBufferPropertiesEXT *descBufferProperties =
      (VkPhysicalDeviceDescriptorBufferPropertiesEXT *)FindNextStruct(
          pProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT);

  // we force the use of robustness, so tweak sizes to be robust sizes as the application doesn't
  // know we forced robustness
  if(descBufferProperties)
  {
    descBufferProperties->uniformBufferDescriptorSize =
        descBufferProperties->robustUniformBufferDescriptorSize;
    descBufferProperties->storageBufferDescriptorSize =
        descBufferProperties->robustStorageBufferDescriptorSize;
    descBufferProperties->uniformTexelBufferDescriptorSize =
        descBufferProperties->robustUniformTexelBufferDescriptorSize;
    descBufferProperties->storageTexelBufferDescriptorSize =
        descBufferProperties->robustStorageTexelBufferDescriptorSize;

    // we also may need to shrink some range/address space limits to allow us to expand buffers. We
    // checked that this should be valid at extension filter time

    // calculate the maximum descriptor size according to the spec
    size_t maxResourceDescriptorSize = 0;
#define CALC_MAX_SIZE(prop) \
  maxResourceDescriptorSize = RDCMAX(maxResourceDescriptorSize, descBufferProperties->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);

    VkDeviceSize reservedDescriptorSize =
        AlignUp(VkDeviceSize(maxResourceDescriptorSize * 2),
                descBufferProperties->descriptorBufferOffsetAlignment);

    descBufferProperties->maxResourceDescriptorBufferRange -= reservedDescriptorSize;

    descBufferProperties->descriptorBufferAddressSpaceSize -=
        ExpectedMaxNumDescriptorBuffers * reservedDescriptorSize;
    descBufferProperties->resourceDescriptorBufferAddressSpaceSize -=
        ExpectedMaxNumDescriptorBuffers * reservedDescriptorSize;
  }
}

void WrappedVulkan::vkGetPhysicalDeviceQueueFamilyProperties2(
    VkPhysicalDevice physicalDevice, uint32_t *pCount,
    VkQueueFamilyProperties2 *pQueueFamilyProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceQueueFamilyProperties2(Unwrap(physicalDevice), pCount,
                                                pQueueFamilyProperties);
}

void WrappedVulkan::vkGetPhysicalDeviceMemoryProperties2(
    VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2 *pMemoryProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceMemoryProperties2(Unwrap(physicalDevice), pMemoryProperties);
}

void WrappedVulkan::vkGetPhysicalDeviceSparseImageFormatProperties2(
    VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2 *pFormatInfo,
    uint32_t *pPropertyCount, VkSparseImageFormatProperties2 *pProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceSparseImageFormatProperties2(Unwrap(physicalDevice), pFormatInfo,
                                                      pPropertyCount, pProperties);
}

VkResult WrappedVulkan::vkGetShaderInfoAMD(VkDevice device, VkPipeline pipeline,
                                           VkShaderStageFlagBits shaderStage,
                                           VkShaderInfoTypeAMD infoType, size_t *pInfoSize,
                                           void *pInfo)
{
  return ObjDisp(device)->GetShaderInfoAMD(Unwrap(device), Unwrap(pipeline), shaderStage, infoType,
                                           pInfoSize, pInfo);
}

void WrappedVulkan::vkGetDescriptorSetLayoutSupport(VkDevice device,
                                                    const VkDescriptorSetLayoutCreateInfo *pCreateInfo,
                                                    VkDescriptorSetLayoutSupport *pSupport)
{
  VkDescriptorSetLayoutCreateInfo unwrapped = UnwrapInfo(pCreateInfo);
  return ObjDisp(device)->GetDescriptorSetLayoutSupport(Unwrap(device), &unwrapped, pSupport);
}

VkResult WrappedVulkan::vkEnumeratePhysicalDeviceGroups(
    VkInstance instance, uint32_t *pPhysicalDeviceGroupCount,
    VkPhysicalDeviceGroupProperties *pPhysicalDeviceGroupProperties)
{
  // We ignore the 'real' physical device groups, and report one group per physical device.
  // We use our internal enumerate function to make sure we handle wrapping the objects.
  RDCASSERT(pPhysicalDeviceGroupCount);

  // Total number of available physical device groups.
  uint32_t physicalDevicesNumber = 0;
  vkEnumeratePhysicalDevices(instance, &physicalDevicesNumber, NULL);

  // vkEnumeratePhysicalDeviceGroups - Return number of available physical device groups.
  if(pPhysicalDeviceGroupProperties == NULL)
  {
    *pPhysicalDeviceGroupCount = physicalDevicesNumber;
    return VK_SUCCESS;
  }

  // vkEnumeratePhysicalDeviceGroups - Query properties of available physical device groups.

  // Number of physical device groups to query.
  *pPhysicalDeviceGroupCount = RDCMIN(*pPhysicalDeviceGroupCount, physicalDevicesNumber);

  rdcarray<VkPhysicalDevice> physicalDevices;
  physicalDevices.resize(*pPhysicalDeviceGroupCount);
  vkEnumeratePhysicalDevices(instance, pPhysicalDeviceGroupCount, physicalDevices.data());

  // List one group per device.
  for(uint32_t i = 0; i < *pPhysicalDeviceGroupCount; i++)
  {
    RDCEraseEl(pPhysicalDeviceGroupProperties[i]);
    pPhysicalDeviceGroupProperties[i].sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES;
    pPhysicalDeviceGroupProperties[i].physicalDeviceCount = 1;
    pPhysicalDeviceGroupProperties[i].physicalDevices[0] = physicalDevices[i];
    pPhysicalDeviceGroupProperties[i].subsetAllocation = VK_FALSE;
  }

  if(*pPhysicalDeviceGroupCount < physicalDevicesNumber)
    return VK_INCOMPLETE;

  return VK_SUCCESS;
}

void WrappedVulkan::vkGetDeviceGroupPeerMemoryFeatures(VkDevice device, uint32_t heapIndex,
                                                       uint32_t localDeviceIndex,
                                                       uint32_t remoteDeviceIndex,
                                                       VkPeerMemoryFeatureFlags *pPeerMemoryFeatures)
{
  return ObjDisp(device)->GetDeviceGroupPeerMemoryFeatures(
      Unwrap(device), heapIndex, localDeviceIndex, remoteDeviceIndex, pPeerMemoryFeatures);
}

VkResult WrappedVulkan::vkCreateValidationCacheEXT(VkDevice device,
                                                   const VkValidationCacheCreateInfoEXT *pCreateInfo,
                                                   const VkAllocationCallbacks *,
                                                   VkValidationCacheEXT *pValidationCache)
{
  return ObjDisp(device)->CreateValidationCacheEXT(Unwrap(device), pCreateInfo, NULL,
                                                   pValidationCache);
}

void WrappedVulkan::vkDestroyValidationCacheEXT(VkDevice device, VkValidationCacheEXT validationCache,
                                                const VkAllocationCallbacks *)
{
  return ObjDisp(device)->DestroyValidationCacheEXT(Unwrap(device), validationCache, NULL);
}

VkResult WrappedVulkan::vkMergeValidationCachesEXT(VkDevice device, VkValidationCacheEXT dstCache,
                                                   uint32_t srcCacheCount,
                                                   const VkValidationCacheEXT *pSrcCaches)
{
  return ObjDisp(device)->MergeValidationCachesEXT(Unwrap(device), dstCache, srcCacheCount,
                                                   pSrcCaches);
}

VkResult WrappedVulkan::vkGetValidationCacheDataEXT(VkDevice device,
                                                    VkValidationCacheEXT validationCache,
                                                    size_t *pDataSize, void *pData)
{
  return ObjDisp(device)->GetValidationCacheDataEXT(Unwrap(device), validationCache, pDataSize,
                                                    pData);
}
void WrappedVulkan::vkGetPhysicalDeviceMultisamplePropertiesEXT(
    VkPhysicalDevice physicalDevice, VkSampleCountFlagBits samples,
    VkMultisamplePropertiesEXT *pMultisampleProperties)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceMultisamplePropertiesEXT(Unwrap(physicalDevice), samples,
                                                  pMultisampleProperties);
}

VkResult WrappedVulkan::vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(
    VkPhysicalDevice physicalDevice, uint32_t *pTimeDomainCount, VkTimeDomainKHR *pTimeDomains)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceCalibrateableTimeDomainsEXT(Unwrap(physicalDevice), pTimeDomainCount,
                                                     pTimeDomains);
}

VkResult WrappedVulkan::vkGetCalibratedTimestampsEXT(VkDevice device, uint32_t timestampCount,
                                                     const VkCalibratedTimestampInfoKHR *pTimestampInfos,
                                                     uint64_t *pTimestamps, uint64_t *pMaxDeviation)
{
  return ObjDisp(device)->GetCalibratedTimestampsEXT(Unwrap(device), timestampCount,
                                                     pTimestampInfos, pTimestamps, pMaxDeviation);
}

VkResult WrappedVulkan::vkGetPhysicalDeviceCalibrateableTimeDomainsKHR(
    VkPhysicalDevice physicalDevice, uint32_t *pTimeDomainCount, VkTimeDomainKHR *pTimeDomains)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceCalibrateableTimeDomainsKHR(Unwrap(physicalDevice), pTimeDomainCount,
                                                     pTimeDomains);
}

VkResult WrappedVulkan::vkGetCalibratedTimestampsKHR(VkDevice device, uint32_t timestampCount,
                                                     const VkCalibratedTimestampInfoKHR *pTimestampInfos,
                                                     uint64_t *pTimestamps, uint64_t *pMaxDeviation)
{
  return ObjDisp(device)->GetCalibratedTimestampsKHR(Unwrap(device), timestampCount,
                                                     pTimestampInfos, pTimestamps, pMaxDeviation);
}

VkDeviceAddress WrappedVulkan::vkGetBufferDeviceAddressEXT(VkDevice device,
                                                           const VkBufferDeviceAddressInfoEXT *pInfo)
{
  VkBufferDeviceAddressInfoEXT unwrappedInfo = *pInfo;
  unwrappedInfo.buffer = Unwrap(unwrappedInfo.buffer);
  return ObjDisp(device)->GetBufferDeviceAddressEXT(Unwrap(device), &unwrappedInfo);
}

VkResult WrappedVulkan::vkGetPipelineExecutablePropertiesKHR(
    VkDevice device, const VkPipelineInfoKHR *pPipelineInfo, uint32_t *pExecutableCount,
    VkPipelineExecutablePropertiesKHR *pProperties)
{
  VkPipelineInfoKHR unwrappedInfo = *pPipelineInfo;
  unwrappedInfo.pipeline = Unwrap(unwrappedInfo.pipeline);
  return ObjDisp(device)->GetPipelineExecutablePropertiesKHR(Unwrap(device), &unwrappedInfo,
                                                             pExecutableCount, pProperties);
}

VkResult WrappedVulkan::vkGetPipelineExecutableStatisticsKHR(
    VkDevice device, const VkPipelineExecutableInfoKHR *pExecutableInfo, uint32_t *pStatisticCount,
    VkPipelineExecutableStatisticKHR *pStatistics)
{
  VkPipelineExecutableInfoKHR unwrappedInfo = *pExecutableInfo;
  unwrappedInfo.pipeline = Unwrap(unwrappedInfo.pipeline);
  return ObjDisp(device)->GetPipelineExecutableStatisticsKHR(Unwrap(device), &unwrappedInfo,
                                                             pStatisticCount, pStatistics);
}

VkResult WrappedVulkan::vkGetPipelineExecutableInternalRepresentationsKHR(
    VkDevice device, const VkPipelineExecutableInfoKHR *pExecutableInfo,
    uint32_t *pInternalRepresentationCount,
    VkPipelineExecutableInternalRepresentationKHR *pInternalRepresentations)
{
  VkPipelineExecutableInfoKHR unwrappedInfo = *pExecutableInfo;
  unwrappedInfo.pipeline = Unwrap(unwrappedInfo.pipeline);
  return ObjDisp(device)->GetPipelineExecutableInternalRepresentationsKHR(
      Unwrap(device), &unwrappedInfo, pInternalRepresentationCount, pInternalRepresentations);
}

VkDeviceAddress WrappedVulkan::vkGetBufferDeviceAddress(VkDevice device,
                                                        VkBufferDeviceAddressInfo *pInfo)
{
  VkBufferDeviceAddressInfo unwrappedInfo = *pInfo;
  unwrappedInfo.buffer = Unwrap(unwrappedInfo.buffer);
  return ObjDisp(device)->GetBufferDeviceAddress(Unwrap(device), &unwrappedInfo);
}

uint64_t WrappedVulkan::vkGetBufferOpaqueCaptureAddress(VkDevice device,
                                                        VkBufferDeviceAddressInfo *pInfo)
{
  VkBufferDeviceAddressInfo unwrappedInfo = *pInfo;
  unwrappedInfo.buffer = Unwrap(unwrappedInfo.buffer);
  return ObjDisp(device)->GetBufferOpaqueCaptureAddress(Unwrap(device), &unwrappedInfo);
}

uint64_t WrappedVulkan::vkGetDeviceMemoryOpaqueCaptureAddress(
    VkDevice device, VkDeviceMemoryOpaqueCaptureAddressInfo *pInfo)
{
  VkDeviceMemoryOpaqueCaptureAddressInfo unwrappedInfo = *pInfo;
  unwrappedInfo.memory = Unwrap(unwrappedInfo.memory);
  return ObjDisp(device)->GetDeviceMemoryOpaqueCaptureAddress(Unwrap(device), &unwrappedInfo);
}

VkResult WrappedVulkan::vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice,
                                                          uint32_t *pToolCount,
                                                          VkPhysicalDeviceToolProperties *pToolProperties)
{
  // check how many tools are downstream. The function pointer will be NULL if no-one else supports
  // this extension except us.
  uint32_t downstreamCount = 0;
  if(ObjDisp(physicalDevice)->GetPhysicalDeviceToolProperties != NULL)
    ObjDisp(physicalDevice)
        ->GetPhysicalDeviceToolProperties(Unwrap(physicalDevice), &downstreamCount, NULL);

  // if we're just enumerating, pToolProperties is NULL, so set the tool count and return
  if(pToolCount && pToolProperties == NULL)
  {
    *pToolCount = downstreamCount + 1;
    return VK_SUCCESS;
  }

  // otherwise we expect both to be non-NULL
  if(pToolCount == NULL || pToolProperties == NULL)
    return VK_INCOMPLETE;

  // this is how much space is in the array, don't forget it
  uint32_t availableCount = *pToolCount;

  VkResult vkr = VK_SUCCESS;

  if(ObjDisp(physicalDevice)->GetPhysicalDeviceToolProperties != NULL)
  {
    // call downstream to populate the array (up to what's available)
    // this writes up to availableCount properties into pToolProperties, and sets the number written
    // in pToolCount
    vkr = ObjDisp(physicalDevice)
              ->GetPhysicalDeviceToolProperties(Unwrap(physicalDevice), pToolCount, pToolProperties);
  }
  else
  {
    // nothing written downstream
    *pToolCount = 0;
  }

  // if available isn't enough, return VK_INCOMPLETE now
  if(vkr == VK_INCOMPLETE || availableCount < downstreamCount + 1)
    return VK_INCOMPLETE;

  // otherwise we write our own properties in after any downstream properties, then increment
  // pToolCount

  VkPhysicalDeviceToolProperties &props = *(pToolProperties + *pToolCount);

  const rdcstr name = "RenderDoc"_lit;
  const rdcstr version = StringFormat::Fmt(
      "%s (%s)", FULL_VERSION_STRING, GitVersionHash[0] == 'N' ? "Unknown revision" : GitVersionHash);
  const rdcstr description = "Debugging capture layer for RenderDoc"_lit;

  RDCASSERTMSG("Name is too long for VkPhysicalDeviceToolProperties",
               name.length() < sizeof(props.name));
  RDCASSERTMSG("Version is too long for VkPhysicalDeviceToolProperties",
               version.length() < sizeof(props.version));
  RDCASSERTMSG("Description is too long for VkPhysicalDeviceToolProperties",
               description.length() < sizeof(props.description));

  memcpy(props.name, name.c_str(), name.length() + 1);
  memcpy(props.version, version.c_str(), version.length() + 1);
  props.purposes = VK_TOOL_PURPOSE_TRACING_BIT | VK_TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT |
                   VK_TOOL_PURPOSE_MODIFYING_FEATURES_BIT;
  memcpy(props.description, description.c_str(), description.length() + 1);
  // do not tell people about the layer
  RDCEraseEl(props.layer);

  (*pToolCount)++;
  return VK_SUCCESS;
}

VkResult WrappedVulkan::vkGetPhysicalDeviceFragmentShadingRatesKHR(
    VkPhysicalDevice physicalDevice, uint32_t *pFragmentShadingRateCount,
    VkPhysicalDeviceFragmentShadingRateKHR *pFragmentShadingRates)
{
  return ObjDisp(physicalDevice)
      ->GetPhysicalDeviceFragmentShadingRatesKHR(Unwrap(physicalDevice), pFragmentShadingRateCount,
                                                 pFragmentShadingRates);
}

uint32_t WrappedVulkan::vkGetDeferredOperationMaxConcurrencyKHR(VkDevice device,
                                                                VkDeferredOperationKHR operation)
{
  return ObjDisp(device)->GetDeferredOperationMaxConcurrencyKHR(Unwrap(device), operation);
}

VkResult WrappedVulkan::vkGetDeferredOperationResultKHR(VkDevice device,
                                                        VkDeferredOperationKHR operation)
{
  return ObjDisp(device)->GetDeferredOperationResultKHR(Unwrap(device), operation);
}

void WrappedVulkan::vkGetAccelerationStructureBuildSizesKHR(
    VkDevice device, VkAccelerationStructureBuildTypeKHR buildType,
    const VkAccelerationStructureBuildGeometryInfoKHR *pBuildInfo,
    const uint32_t *pMaxPrimitiveCounts, VkAccelerationStructureBuildSizesInfoKHR *pSizeInfo)
{
  VkAccelerationStructureBuildGeometryInfoKHR unwrapped = *pBuildInfo;
  unwrapped.srcAccelerationStructure = Unwrap(unwrapped.srcAccelerationStructure);
  unwrapped.dstAccelerationStructure = Unwrap(unwrapped.dstAccelerationStructure);

  ObjDisp(device)->GetAccelerationStructureBuildSizesKHR(Unwrap(device), buildType, &unwrapped,
                                                         pMaxPrimitiveCounts, pSizeInfo);
}

VkDeviceAddress WrappedVulkan::vkGetAccelerationStructureDeviceAddressKHR(
    VkDevice device, const VkAccelerationStructureDeviceAddressInfoKHR *pInfo)
{
  VkAccelerationStructureDeviceAddressInfoKHR info = *pInfo;
  info.accelerationStructure = Unwrap(info.accelerationStructure);
  return ObjDisp(device)->GetAccelerationStructureDeviceAddressKHR(Unwrap(device), &info);
}

void WrappedVulkan::vkGetDeviceAccelerationStructureCompatibilityKHR(
    VkDevice device, const VkAccelerationStructureVersionInfoKHR *pVersionInfo,
    VkAccelerationStructureCompatibilityKHR *pCompatibility)
{
  *pCompatibility = VK_ACCELERATION_STRUCTURE_COMPATIBILITY_INCOMPATIBLE_KHR;
}

VkResult WrappedVulkan::vkGetShaderBinaryDataEXT(VkDevice device, VkShaderEXT shader,
                                                 size_t *pDataSize, void *pData)
{
  // renderdoc doesn't support shader binaries, but should comply with the spec
  // so we return four NULL bytes if this function is called and would otherwise
  // return a valid binary
  size_t totalSize = 4;

  if(pDataSize && !pData)
    *pDataSize = totalSize;

  if(pDataSize && pData)
  {
    if(*pDataSize < totalSize)
    {
      memset(pData, 0, *pDataSize);
      return VK_INCOMPLETE;
    }

    // empty bytes
    memset(pData, 0, 4);
  }

  // we don't want the application to use shader binaries at all, and especially
  // don't want to return any data for future use. We thus return a technically
  // valid but empty shader binary. Our UUID changes every run so in theory the
  // application should never provide an old binary.
  return VK_SUCCESS;
}

VkResult WrappedVulkan::vkGetRayTracingShaderGroupHandlesKHR(VkDevice device, VkPipeline pipeline,
                                                             uint32_t firstGroup, uint32_t groupCount,
                                                             size_t dataSize, void *pData)
{
  return ObjDisp(device)->GetRayTracingShaderGroupHandlesKHR(
      Unwrap(device), Unwrap(pipeline), firstGroup, groupCount, dataSize, pData);
}

VkResult WrappedVulkan::vkGetRayTracingCaptureReplayShaderGroupHandlesKHR(
    VkDevice device, VkPipeline pipeline, uint32_t firstGroup, uint32_t groupCount, size_t dataSize,
    void *pData)
{
  return ObjDisp(device)->GetRayTracingCaptureReplayShaderGroupHandlesKHR(
      Unwrap(device), Unwrap(pipeline), firstGroup, groupCount, dataSize, pData);
}

VkDeviceSize WrappedVulkan::vkGetRayTracingShaderGroupStackSizeKHR(
    VkDevice device, VkPipeline pipeline, uint32_t group, VkShaderGroupShaderKHR groupShader)
{
  return ObjDisp(device)->GetRayTracingShaderGroupStackSizeKHR(Unwrap(device), Unwrap(pipeline),
                                                               group, groupShader);
}

void WrappedVulkan::vkGetDeviceImageSubresourceLayout(VkDevice device,
                                                      const VkDeviceImageSubresourceInfo *pInfo,
                                                      VkSubresourceLayout2 *pLayout)
{
  size_t tempMemSize = GetNextPatchSize(pInfo);

  // reserve space for a patched view format list if necessary
  if(pInfo->pCreateInfo->samples != VK_SAMPLE_COUNT_1_BIT)
  {
    const VkImageFormatListCreateInfo *formatListInfo =
        (const VkImageFormatListCreateInfo *)FindNextStruct(
            pInfo->pCreateInfo, VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);

    if(formatListInfo)
      tempMemSize += sizeof(VkFormat) * (formatListInfo->viewFormatCount + 1);
  }

  byte *tempMem = GetTempMemory(tempMemSize);
  VkDeviceImageSubresourceInfo *unwrappedInfo = UnwrapStructAndChain(m_State, tempMem, pInfo);

  PatchImageCreateInfo((VkImageCreateInfo *)unwrappedInfo->pCreateInfo, (VkFormat *)tempMem);

  ObjDisp(device)->GetDeviceImageSubresourceLayout(Unwrap(device), unwrappedInfo, pLayout);
}

void WrappedVulkan::vkGetImageSubresourceLayout2(VkDevice device, VkImage image,
                                                 const VkImageSubresource2 *pSubresource,
                                                 VkSubresourceLayout2 *pLayout)
{
  ObjDisp(device)->GetImageSubresourceLayout2(Unwrap(device), Unwrap(image), pSubresource, pLayout);

  // RenderDoc removes calls with VK_HOST_IMAGE_COPY_MEMCPY_BIT flag, so the
  // VkSubresourceHostMemcpySize struct chained to VkSubresourceLayout2 is overriden to
  // provide a fixed size.
  VkSubresourceHostMemcpySize *memcpySize = (VkSubresourceHostMemcpySize *)FindNextStruct(
      pLayout, VK_STRUCTURE_TYPE_SUBRESOURCE_HOST_MEMCPY_SIZE);
  if(memcpySize)
    memcpySize->size = 64;
}

void WrappedVulkan::vkGetRenderingAreaGranularity(VkDevice device,
                                                  const VkRenderingAreaInfo *pRenderingAreaInfo,
                                                  VkExtent2D *pGranularity)
{
  ObjDisp(device)->GetRenderingAreaGranularity(Unwrap(device), pRenderingAreaInfo, pGranularity);
}

void WrappedVulkan::vkGetImageSubresourceLayout2EXT(VkDevice device, VkImage image,
                                                    const VkImageSubresource2 *pSubresource,
                                                    VkSubresourceLayout2 *pLayout)
{
  ObjDisp(device)->GetImageSubresourceLayout2(Unwrap(device), Unwrap(image), pSubresource, pLayout);

  // RenderDoc removes calls with VK_HOST_IMAGE_COPY_MEMCPY_BIT flag, so the
  // VkSubresourceHostMemcpySize struct chained to VkSubresourceLayout2 is overriden to
  // provide a fixed size.
  VkSubresourceHostMemcpySize *memcpySize = (VkSubresourceHostMemcpySize *)FindNextStruct(
      pLayout, VK_STRUCTURE_TYPE_SUBRESOURCE_HOST_MEMCPY_SIZE);
  if(memcpySize)
    memcpySize->size = 64;
}

void WrappedVulkan::vkGetDescriptorSetLayoutSizeEXT(VkDevice device, VkDescriptorSetLayout layout,
                                                    VkDeviceSize *pLayoutSizeInBytes)
{
  ObjDisp(device)->GetDescriptorSetLayoutSizeEXT(Unwrap(device), Unwrap(layout), pLayoutSizeInBytes);
}

void WrappedVulkan::vkGetDescriptorSetLayoutBindingOffsetEXT(VkDevice device,
                                                             VkDescriptorSetLayout layout,
                                                             uint32_t binding, VkDeviceSize *pOffset)
{
  ObjDisp(device)->GetDescriptorSetLayoutBindingOffsetEXT(Unwrap(device), Unwrap(layout), binding,
                                                          pOffset);
}

VkResult WrappedVulkan::vkGetBufferOpaqueCaptureDescriptorDataEXT(
    VkDevice device, const VkBufferCaptureDescriptorDataInfoEXT *pInfo, void *pData)
{
  VkBufferCaptureDescriptorDataInfoEXT unwrappedInfo = *pInfo;
  unwrappedInfo.buffer = Unwrap(unwrappedInfo.buffer);

  return ObjDisp(device)->GetBufferOpaqueCaptureDescriptorDataEXT(Unwrap(device), &unwrappedInfo,
                                                                  pData);
}

VkResult WrappedVulkan::vkGetImageOpaqueCaptureDescriptorDataEXT(
    VkDevice device, const VkImageCaptureDescriptorDataInfoEXT *pInfo, void *pData)
{
  VkImageCaptureDescriptorDataInfoEXT unwrappedInfo = *pInfo;
  unwrappedInfo.image = Unwrap(unwrappedInfo.image);

  return ObjDisp(device)->GetImageOpaqueCaptureDescriptorDataEXT(Unwrap(device), &unwrappedInfo,
                                                                 pData);
}

VkResult WrappedVulkan::vkGetImageViewOpaqueCaptureDescriptorDataEXT(
    VkDevice device, const VkImageViewCaptureDescriptorDataInfoEXT *pInfo, void *pData)
{
  VkImageViewCaptureDescriptorDataInfoEXT unwrappedInfo = *pInfo;
  unwrappedInfo.imageView = Unwrap(unwrappedInfo.imageView);

  return ObjDisp(device)->GetImageViewOpaqueCaptureDescriptorDataEXT(Unwrap(device), &unwrappedInfo,
                                                                     pData);
}

VkResult WrappedVulkan::vkGetSamplerOpaqueCaptureDescriptorDataEXT(
    VkDevice device, const VkSamplerCaptureDescriptorDataInfoEXT *pInfo, void *pData)
{
  VkSamplerCaptureDescriptorDataInfoEXT unwrappedInfo = *pInfo;
  unwrappedInfo.sampler = Unwrap(unwrappedInfo.sampler);

  return ObjDisp(device)->GetSamplerOpaqueCaptureDescriptorDataEXT(Unwrap(device), &unwrappedInfo,
                                                                   pData);
}

VkResult WrappedVulkan::vkGetAccelerationStructureOpaqueCaptureDescriptorDataEXT(
    VkDevice device, const VkAccelerationStructureCaptureDescriptorDataInfoEXT *pInfo, void *pData)
{
  VkAccelerationStructureCaptureDescriptorDataInfoEXT unwrappedInfo = *pInfo;
  unwrappedInfo.accelerationStructure = Unwrap(unwrappedInfo.accelerationStructure);

  return ObjDisp(device)->GetAccelerationStructureOpaqueCaptureDescriptorDataEXT(
      Unwrap(device), &unwrappedInfo, pData);
}

VkResult WrappedVulkan::vkGetImageDrmFormatModifierPropertiesEXT(
    VkDevice device, VkImage image, VkImageDrmFormatModifierPropertiesEXT *pProperties)
{
  return ObjDisp(device)->GetImageDrmFormatModifierPropertiesEXT(Unwrap(device), Unwrap(image),
                                                                 pProperties);
}
