void UnwrapNextChain()

in renderdoc/driver/vulkan/vk_next_chains.cpp [1814:3033]


void UnwrapNextChain(CaptureState state, const char *structName, byte *&tempMem,
                     VkBaseInStructure *infoStruct)
{
  if(!infoStruct)
    return;

  NextChainFlags nextChainFlags;
  PreprocessNextChain(infoStruct, nextChainFlags);

  // during capture, this walks the pNext chain and either copies structs that can be passed
  // straight through, or copies and modifies any with vulkan objects that need to be unwrapped.
  //
  // during replay, we do the same thing to prepare for dispatching to the driver, but we also strip
  // out any structs we don't want to replay - e.g. external memory. This means the data is
  // serialised and available for future use and for user inspection, but isn't replayed when not
  // necesary.

  VkBaseInStructure *nextChainTail = infoStruct;
  const VkBaseInStructure *nextInput = (const VkBaseInStructure *)infoStruct->pNext;

#undef COPY_STRUCT_CAPTURE_ONLY
#define COPY_STRUCT_CAPTURE_ONLY(StructType, StructName)                            \
  case StructType:                                                                  \
  {                                                                                 \
    if(IsCaptureMode(state))                                                        \
      CopyNextChainedStruct(sizeof(StructName), tempMem, nextInput, nextChainTail); \
    break;                                                                          \
  }

#undef COPY_STRUCT
#define COPY_STRUCT(StructType, StructName)                                       \
  case StructType:                                                                \
  {                                                                               \
    CopyNextChainedStruct(sizeof(StructName), tempMem, nextInput, nextChainTail); \
    break;                                                                        \
  }

#undef UNWRAP_STRUCT_INNER
#define UNWRAP_STRUCT_INNER(StructType, StructName, ...)      \
  {                                                           \
    const StructName *in = (const StructName *)nextInput;     \
    StructName *out = (StructName *)tempMem;                  \
                                                              \
    /* copy the struct */                                     \
    *out = *in;                                               \
    /* abuse comma operator to unwrap all members */          \
    __VA_ARGS__;                                              \
                                                              \
    AppendModifiedChainedStruct(tempMem, out, nextChainTail); \
  }

#undef UNWRAP_STRUCT
#define UNWRAP_STRUCT(StructType, StructName, ...)           \
  case StructType:                                           \
  {                                                          \
    UNWRAP_STRUCT_INNER(StructType, StructName, __VA_ARGS__) \
    break;                                                   \
  }

#undef UNWRAP_STRUCT_CAPTURE_ONLY
#define UNWRAP_STRUCT_CAPTURE_ONLY(StructType, StructName, ...) \
  case StructType:                                              \
  {                                                             \
    if(IsCaptureMode(state))                                    \
    {                                                           \
      UNWRAP_STRUCT_INNER(StructType, StructName, __VA_ARGS__)  \
    }                                                           \
    break;                                                      \
  }

  // start with an empty chain. Every call to AppendModifiedChainedStruct / CopyNextChainedStruct
  // pushes on a new entry, but if there's only one entry in the list and it's one we want to skip,
  // this needs to start at NULL.
  nextChainTail->pNext = NULL;
  while(nextInput)
  {
    switch(nextInput->sType)
    {
      PROCESS_SIMPLE_STRUCTS();

      // complex structs to handle - require multiple allocations
      case VK_STRUCTURE_TYPE_BIND_SPARSE_INFO:
      {
        const VkBindSparseInfo *in = (const VkBindSparseInfo *)nextInput;
        VkBindSparseInfo *out = (VkBindSparseInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped arrays
        VkSemaphore *outWaitSemaphores = (VkSemaphore *)tempMem;
        tempMem += sizeof(VkSemaphore) * in->waitSemaphoreCount;
        VkSemaphore *outSignalSemaphores = (VkSemaphore *)tempMem;
        tempMem += sizeof(VkSemaphore) * in->signalSemaphoreCount;
        VkSparseBufferMemoryBindInfo *outBufferBinds = (VkSparseBufferMemoryBindInfo *)tempMem;
        tempMem += sizeof(VkSparseBufferMemoryBindInfo) * in->bufferBindCount;
        VkSparseImageOpaqueMemoryBindInfo *outImageOpaqueBinds =
            (VkSparseImageOpaqueMemoryBindInfo *)tempMem;
        tempMem += sizeof(VkSparseImageOpaqueMemoryBindInfo) * in->imageOpaqueBindCount;
        VkSparseImageMemoryBindInfo *outImageBinds = (VkSparseImageMemoryBindInfo *)tempMem;
        tempMem += sizeof(VkSparseImageMemoryBindInfo) * in->imageBindCount;

        *out = *in;

        out->pWaitSemaphores = outWaitSemaphores;
        out->pSignalSemaphores = outSignalSemaphores;
        out->pBufferBinds = outBufferBinds;
        out->pImageOpaqueBinds = outImageOpaqueBinds;
        out->pImageBinds = outImageBinds;

        for(uint32_t i = 0; i < in->waitSemaphoreCount; i++)
          outWaitSemaphores[i] = Unwrap(in->pWaitSemaphores[i]);
        for(uint32_t i = 0; i < in->signalSemaphoreCount; i++)
          outSignalSemaphores[i] = Unwrap(in->pSignalSemaphores[i]);

        VkSparseMemoryBind *outMemoryBinds = (VkSparseMemoryBind *)tempMem;

        for(uint32_t i = 0; i < in->bufferBindCount; i++)
        {
          outBufferBinds[i] = in->pBufferBinds[i];
          UnwrapInPlace(outBufferBinds[i].buffer);

          outBufferBinds[i].pBinds = outMemoryBinds;

          for(uint32_t b = 0; b < outBufferBinds[i].bindCount; b++)
          {
            outMemoryBinds[b] = in->pBufferBinds[i].pBinds[b];
            UnwrapInPlace(outMemoryBinds[b].memory);
          }

          outMemoryBinds += outBufferBinds[i].bindCount;
          tempMem += outBufferBinds[i].bindCount * sizeof(VkSparseMemoryBind);
        }

        for(uint32_t i = 0; i < in->imageOpaqueBindCount; i++)
        {
          outImageOpaqueBinds[i] = in->pImageOpaqueBinds[i];
          UnwrapInPlace(outImageOpaqueBinds[i].image);

          outImageOpaqueBinds[i].pBinds = outMemoryBinds;

          for(uint32_t b = 0; b < outBufferBinds[i].bindCount; b++)
          {
            outMemoryBinds[b] = in->pImageOpaqueBinds[i].pBinds[b];
            UnwrapInPlace(outMemoryBinds[b].memory);
          }

          outMemoryBinds += outImageOpaqueBinds[i].bindCount;
          tempMem += outImageOpaqueBinds[i].bindCount * sizeof(VkSparseMemoryBind);
        }

        VkSparseImageMemoryBind *outImageMemoryBinds = (VkSparseImageMemoryBind *)tempMem;

        for(uint32_t i = 0; i < in->imageBindCount; i++)
        {
          outImageBinds[i] = in->pImageBinds[i];
          UnwrapInPlace(outImageBinds[i].image);

          outImageBinds[i].pBinds = outImageMemoryBinds;

          for(uint32_t b = 0; b < outBufferBinds[i].bindCount; b++)
          {
            outImageMemoryBinds[b] = in->pImageBinds[i].pBinds[b];
            UnwrapInPlace(outImageMemoryBinds[b].memory);
          }

          outImageMemoryBinds += outImageBinds[i].bindCount;
          tempMem += outImageBinds[i].bindCount * sizeof(VkSparseMemoryBind);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2:
      {
        const VkBlitImageInfo2 *in = (const VkBlitImageInfo2 *)nextInput;
        VkBlitImageInfo2 *out = (VkBlitImageInfo2 *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkImageBlit2 *outRegions = (VkImageBlit2 *)tempMem;
        tempMem += sizeof(VkImageBlit2) * in->regionCount;

        *out = *in;
        UnwrapInPlace(out->srcImage);
        UnwrapInPlace(out->dstImage);

        out->pRegions = outRegions;
        for(uint32_t i = 0; i < in->regionCount; i++)
        {
          outRegions[i] = in->pRegions[i];
          UnwrapNextChain(state, "VkImageBlit2", tempMem, (VkBaseInStructure *)&outRegions[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_RENDERING_INFO:
      {
        const VkCommandBufferInheritanceRenderingInfo *in =
            (const VkCommandBufferInheritanceRenderingInfo *)nextInput;
        VkCommandBufferInheritanceRenderingInfo *out =
            (VkCommandBufferInheritanceRenderingInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkFormat *outFormats = (VkFormat *)tempMem;
        tempMem += sizeof(VkFormat) * in->colorAttachmentCount;

        *out = *in;

        out->pColorAttachmentFormats = outFormats;
        for(uint32_t i = 0; i < in->colorAttachmentCount; i++)
          outFormats[i] = in->pColorAttachmentFormats[i];

        break;
      }
      case VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO:
      {
        const VkComputePipelineCreateInfo *in = (const VkComputePipelineCreateInfo *)nextInput;
        VkComputePipelineCreateInfo *out = (VkComputePipelineCreateInfo *)tempMem;

        *out = *in;
        UnwrapInPlace(out->layout);
        UnwrapInPlace(out->stage.module);
        if(out->flags & VK_PIPELINE_CREATE_DERIVATIVE_BIT)
          UnwrapInPlace(out->basePipelineHandle);

        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        break;
      }
      case VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2:
      {
        const VkCopyBufferInfo2 *in = (const VkCopyBufferInfo2 *)nextInput;
        VkCopyBufferInfo2 *out = (VkCopyBufferInfo2 *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkBufferCopy2 *outRegions = (VkBufferCopy2 *)tempMem;
        tempMem += sizeof(VkBufferCopy2) * in->regionCount;

        *out = *in;
        UnwrapInPlace(out->srcBuffer);
        UnwrapInPlace(out->dstBuffer);

        out->pRegions = outRegions;
        for(uint32_t i = 0; i < in->regionCount; i++)
        {
          outRegions[i] = in->pRegions[i];
          UnwrapNextChain(state, "VkBufferCopy2", tempMem, (VkBaseInStructure *)&outRegions[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2:
      {
        const VkCopyBufferToImageInfo2 *in = (const VkCopyBufferToImageInfo2 *)nextInput;
        VkCopyBufferToImageInfo2 *out = (VkCopyBufferToImageInfo2 *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkBufferImageCopy2 *outRegions = (VkBufferImageCopy2 *)tempMem;
        tempMem += sizeof(VkBufferImageCopy2) * in->regionCount;

        *out = *in;
        UnwrapInPlace(out->srcBuffer);
        UnwrapInPlace(out->dstImage);

        out->pRegions = outRegions;
        for(uint32_t i = 0; i < in->regionCount; i++)
        {
          outRegions[i] = in->pRegions[i];
          UnwrapNextChain(state, "VkBufferImageCopy2", tempMem, (VkBaseInStructure *)&outRegions[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_COPY_IMAGE_TO_BUFFER_INFO_2:
      {
        const VkCopyImageToBufferInfo2 *in = (const VkCopyImageToBufferInfo2 *)nextInput;
        VkCopyImageToBufferInfo2 *out = (VkCopyImageToBufferInfo2 *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkBufferImageCopy2 *outRegions = (VkBufferImageCopy2 *)tempMem;
        tempMem += sizeof(VkBufferImageCopy2) * in->regionCount;

        *out = *in;
        UnwrapInPlace(out->srcImage);
        UnwrapInPlace(out->dstBuffer);

        out->pRegions = outRegions;
        for(uint32_t i = 0; i < in->regionCount; i++)
        {
          outRegions[i] = in->pRegions[i];
          UnwrapNextChain(state, "VkBufferImageCopy2", tempMem, (VkBaseInStructure *)&outRegions[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2:
      {
        const VkCopyImageInfo2 *in = (const VkCopyImageInfo2 *)nextInput;
        VkCopyImageInfo2 *out = (VkCopyImageInfo2 *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkImageCopy2 *outRegions = (VkImageCopy2 *)tempMem;
        tempMem += sizeof(VkImageCopy2) * in->regionCount;

        *out = *in;
        UnwrapInPlace(out->srcImage);
        UnwrapInPlace(out->dstImage);

        out->pRegions = outRegions;
        for(uint32_t i = 0; i < in->regionCount; i++)
        {
          outRegions[i] = in->pRegions[i];
          UnwrapNextChain(state, "VkImageCopy2", tempMem, (VkBaseInStructure *)&outRegions[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_DEPENDENCY_INFO:
      {
        const VkDependencyInfo *in = (const VkDependencyInfo *)nextInput;
        VkDependencyInfo *out = (VkDependencyInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped arrays
        VkMemoryBarrier2 *outMemoryBarriers = (VkMemoryBarrier2 *)tempMem;
        tempMem += sizeof(VkMemoryBarrier2) * in->memoryBarrierCount;
        VkBufferMemoryBarrier2 *outBufferBarriers = (VkBufferMemoryBarrier2 *)tempMem;
        tempMem += sizeof(VkBufferMemoryBarrier2) * in->bufferMemoryBarrierCount;
        VkImageMemoryBarrier2 *outImageBarriers = (VkImageMemoryBarrier2 *)tempMem;
        tempMem += sizeof(VkImageMemoryBarrier2) * in->imageMemoryBarrierCount;

        *out = *in;
        out->pMemoryBarriers = outMemoryBarriers;
        out->pBufferMemoryBarriers = outBufferBarriers;
        out->pImageMemoryBarriers = outImageBarriers;

        for(uint32_t i = 0; i < in->memoryBarrierCount; i++)
        {
          outMemoryBarriers[i] = in->pMemoryBarriers[i];
          UnwrapNextChain(state, "VkMemoryBarrier2", tempMem,
                          (VkBaseInStructure *)&outMemoryBarriers[i]);
        }

        for(uint32_t i = 0; i < in->bufferMemoryBarrierCount; i++)
        {
          outBufferBarriers[i] = in->pBufferMemoryBarriers[i];
          UnwrapInPlace(outBufferBarriers[i].buffer);
          UnwrapNextChain(state, "VkBufferMemoryBarrier2", tempMem,
                          (VkBaseInStructure *)&outBufferBarriers[i]);
        }

        for(uint32_t i = 0; i < in->imageMemoryBarrierCount; i++)
        {
          outImageBarriers[i] = in->pImageMemoryBarriers[i];
          UnwrapInPlace(outImageBarriers[i].image);
          UnwrapNextChain(state, "VkImageMemoryBarrier2", tempMem,
                          (VkBaseInStructure *)&outImageBarriers[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO:
      {
        const VkDescriptorSetAllocateInfo *in = (const VkDescriptorSetAllocateInfo *)nextInput;
        VkDescriptorSetAllocateInfo *out = (VkDescriptorSetAllocateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkDescriptorSetLayout *outLayouts = (VkDescriptorSetLayout *)tempMem;
        tempMem += sizeof(VkDescriptorSetLayout) * in->descriptorSetCount;

        *out = *in;
        UnwrapInPlace(out->descriptorPool);

        out->pSetLayouts = outLayouts;
        for(uint32_t i = 0; i < in->descriptorSetCount; i++)
          outLayouts[i] = Unwrap(in->pSetLayouts[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO:
      {
        const VkDescriptorSetLayoutCreateInfo *in =
            (const VkDescriptorSetLayoutCreateInfo *)nextInput;
        VkDescriptorSetLayoutCreateInfo *out = (VkDescriptorSetLayoutCreateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkDescriptorSetLayoutBinding *outBindings = (VkDescriptorSetLayoutBinding *)tempMem;
        tempMem += sizeof(VkDescriptorSetLayoutBinding) * in->bindingCount;
        VkSampler *outSamplers = (VkSampler *)tempMem;

        *out = *in;
        out->pBindings = outBindings;

        for(uint32_t i = 0; i < out->bindingCount; i++)
        {
          outBindings[i] = in->pBindings[i];

          if(outBindings[i].pImmutableSamplers)
          {
            outBindings[i].pImmutableSamplers = outSamplers;

            for(uint32_t d = 0; d < out->pBindings[i].descriptorCount; d++)
              outSamplers[d] = Unwrap(in->pBindings[i].pImmutableSamplers[d]);

            tempMem += sizeof(VkSampler) * out->pBindings[i].descriptorCount;
            outSamplers += out->pBindings[i].descriptorCount;
          }
        }

        break;
      }
      case VK_STRUCTURE_TYPE_DEVICE_BUFFER_MEMORY_REQUIREMENTS:
      {
        const VkDeviceBufferMemoryRequirements *in =
            (const VkDeviceBufferMemoryRequirements *)nextInput;
        VkDeviceBufferMemoryRequirements *out = (VkDeviceBufferMemoryRequirements *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        out->sType = VK_STRUCTURE_TYPE_DEVICE_BUFFER_MEMORY_REQUIREMENTS;
        out->pNext = in->pNext;

        out->pCreateInfo = AllocStructCopy(tempMem, in->pCreateInfo);
        UnwrapNextChain(state, "VkBufferCreateInfo", tempMem, (VkBaseInStructure *)out->pCreateInfo);

        break;
      }
      case VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO:
      {
        const VkDeviceGroupDeviceCreateInfo *in = (const VkDeviceGroupDeviceCreateInfo *)nextInput;
        VkDeviceGroupDeviceCreateInfo *out = (VkDeviceGroupDeviceCreateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkPhysicalDevice *outDevices = (VkPhysicalDevice *)tempMem;
        tempMem += sizeof(VkPhysicalDevice) * in->physicalDeviceCount;

        *out = *in;
        out->pPhysicalDevices = outDevices;

        for(uint32_t i = 0; i < in->physicalDeviceCount; i++)
          outDevices[i] = Unwrap(in->pPhysicalDevices[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_DEVICE_IMAGE_MEMORY_REQUIREMENTS:
      {
        const VkDeviceImageMemoryRequirements *in =
            (const VkDeviceImageMemoryRequirements *)nextInput;
        VkDeviceImageMemoryRequirements *out = (VkDeviceImageMemoryRequirements *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        out->sType = VK_STRUCTURE_TYPE_DEVICE_IMAGE_MEMORY_REQUIREMENTS;
        out->pNext = in->pNext;
        out->planeAspect = in->planeAspect;

        out->pCreateInfo = AllocStructCopy(tempMem, in->pCreateInfo);
        UnwrapNextChain(state, "VkImageCreateInfo", tempMem, (VkBaseInStructure *)out->pCreateInfo);

        break;
      }
      case VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO:
      {
        const VkFramebufferCreateInfo *in = (const VkFramebufferCreateInfo *)nextInput;
        VkFramebufferCreateInfo *out = (VkFramebufferCreateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkImageView *outAttachments = (VkImageView *)tempMem;
        tempMem += sizeof(VkImageView) * in->attachmentCount;

        *out = *in;
        UnwrapInPlace(out->renderPass);

        if((out->flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) == 0)
        {
          out->pAttachments = outAttachments;
          for(uint32_t i = 0; i < in->attachmentCount; i++)
            outAttachments[i] = Unwrap(in->pAttachments[i]);
        }

        break;
      }
      // this struct doesn't really need to be unwrapped but we allocate space for it since it
      // contains arrays that we will very commonly need to patch, to adjust image info/formats.
      // this saves us needing to iterate it outside and allocate extra space
      case VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO:
      {
        const VkFramebufferAttachmentsCreateInfo *in =
            (const VkFramebufferAttachmentsCreateInfo *)nextInput;
        VkFramebufferAttachmentsCreateInfo *out = (VkFramebufferAttachmentsCreateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkFramebufferAttachmentImageInfo *outAtts = (VkFramebufferAttachmentImageInfo *)tempMem;
        tempMem += sizeof(VkFramebufferAttachmentImageInfo) * in->attachmentImageInfoCount;

        *out = *in;
        out->pAttachmentImageInfos = outAtts;
        for(uint32_t i = 0; i < in->attachmentImageInfoCount; i++)
        {
          outAtts[i] = in->pAttachmentImageInfos[i];
          UnwrapNextChain(state, "VkFramebufferAttachmentImageInfo", tempMem,
                          (VkBaseInStructure *)&outAtts[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO:
      {
        const VkFramebufferAttachmentImageInfo *in =
            (const VkFramebufferAttachmentImageInfo *)nextInput;
        VkFramebufferAttachmentImageInfo *out = (VkFramebufferAttachmentImageInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        *out = *in;

        // allocate extra array
        if(in->viewFormatCount > 0)
        {
          VkFormat *outFormats = (VkFormat *)tempMem;
          tempMem += sizeof(VkFormat) * (in->viewFormatCount + 1);

          out->pViewFormats = outFormats;
          memcpy(outFormats, in->pViewFormats, sizeof(VkFormat) * in->viewFormatCount);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO:
      {
        const VkGraphicsPipelineCreateInfo *in = (const VkGraphicsPipelineCreateInfo *)nextInput;
        VkGraphicsPipelineCreateInfo *out = (VkGraphicsPipelineCreateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkPipelineShaderStageCreateInfo *outShaders = (VkPipelineShaderStageCreateInfo *)tempMem;
        tempMem += sizeof(VkPipelineShaderStageCreateInfo) * in->stageCount;

        out->sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
        out->pNext = in->pNext;
        out->flags = in->flags;
        out->stageCount = in->stageCount;
        out->pStages = outShaders;
        for(uint32_t i = 0; i < in->stageCount; i++)
        {
          outShaders[i].module = Unwrap(in->pStages[i].module);
          UnwrapNextChain(state, "VkPipelineShaderStageCreateInfo", tempMem,
                          (VkBaseInStructure *)&outShaders[i]);
        }

        out->pVertexInputState = AllocStructCopy(tempMem, in->pVertexInputState);
        UnwrapNextChain(state, "VkPipelineVertexInputStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pVertexInputState);
        out->pInputAssemblyState = AllocStructCopy(tempMem, in->pInputAssemblyState);
        UnwrapNextChain(state, "VkPipelineInputAssemblyStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pInputAssemblyState);
        out->pTessellationState = AllocStructCopy(tempMem, in->pTessellationState);
        UnwrapNextChain(state, "VkPipelineTessellationStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pTessellationState);
        out->pViewportState = AllocStructCopy(tempMem, in->pViewportState);
        UnwrapNextChain(state, "VkPipelineViewportStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pViewportState);
        out->pRasterizationState = AllocStructCopy(tempMem, in->pRasterizationState);
        UnwrapNextChain(state, "VkPipelineRasterizationStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pRasterizationState);
        out->pMultisampleState = AllocStructCopy(tempMem, in->pMultisampleState);
        UnwrapNextChain(state, "VkPipelineMultisampleStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pMultisampleState);
        out->pDepthStencilState = AllocStructCopy(tempMem, in->pDepthStencilState);
        UnwrapNextChain(state, "VkPipelineDepthStencilStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pDepthStencilState);
        out->pColorBlendState = AllocStructCopy(tempMem, in->pColorBlendState);
        UnwrapNextChain(state, "VkPipelineColorBlendStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pColorBlendState);
        out->pDynamicState = AllocStructCopy(tempMem, in->pDynamicState);
        UnwrapNextChain(state, "VkPipelineDynamicStateCreateInfo", tempMem,
                        (VkBaseInStructure *)out->pDynamicState);

        UnwrapInPlace(out->layout);
        UnwrapInPlace(out->renderPass);
        out->subpass = in->subpass;
        if(out->flags & VK_PIPELINE_CREATE_DERIVATIVE_BIT)
          UnwrapInPlace(out->basePipelineHandle);
        else
          out->basePipelineHandle = VK_NULL_HANDLE;
        out->basePipelineIndex = in->basePipelineIndex;

        break;
      }
      case VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO:
      {
        const VkPipelineLayoutCreateInfo *in = (const VkPipelineLayoutCreateInfo *)nextInput;
        VkPipelineLayoutCreateInfo *out = (VkPipelineLayoutCreateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkDescriptorSetLayout *outLayouts = (VkDescriptorSetLayout *)tempMem;
        tempMem += sizeof(VkDescriptorSetLayout) * in->setLayoutCount;

        *out = *in;

        out->pSetLayouts = outLayouts;
        for(uint32_t i = 0; i < in->setLayoutCount; i++)
          outLayouts[i] = Unwrap(in->pSetLayouts[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR:
      {
        const VkPipelineLibraryCreateInfoKHR *in = (const VkPipelineLibraryCreateInfoKHR *)nextInput;
        VkPipelineLibraryCreateInfoKHR *out = (VkPipelineLibraryCreateInfoKHR *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkPipeline *outLibraries = (VkPipeline *)tempMem;
        tempMem += sizeof(VkPipeline) * in->libraryCount;

        *out = *in;

        out->pLibraries = outLibraries;
        for(uint32_t i = 0; i < in->libraryCount; i++)
          outLibraries[i] = Unwrap(in->pLibraries[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO:
      {
        const VkPipelineRenderingCreateInfo *in = (const VkPipelineRenderingCreateInfo *)nextInput;
        VkPipelineRenderingCreateInfo *out = (VkPipelineRenderingCreateInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkFormat *outFormats = (VkFormat *)tempMem;
        tempMem += sizeof(VkFormat) * in->colorAttachmentCount;

        *out = *in;

        out->pColorAttachmentFormats = outFormats;
        if(nextChainFlags.dynRenderingFormatsValid)
        {
          for(uint32_t i = 0; i < in->colorAttachmentCount; i++)
            outFormats[i] = in->pColorAttachmentFormats[i];
        }

        break;
      }
      case VK_STRUCTURE_TYPE_PRESENT_INFO_KHR:
      {
        const VkPresentInfoKHR *in = (const VkPresentInfoKHR *)nextInput;
        VkPresentInfoKHR *out = (VkPresentInfoKHR *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped arrays
        VkSemaphore *outWaitSemaphores = (VkSemaphore *)tempMem;
        tempMem += sizeof(VkSemaphore) * in->waitSemaphoreCount;
        VkSwapchainKHR *outSwapchains = (VkSwapchainKHR *)tempMem;
        tempMem += sizeof(VkSwapchainKHR) * in->swapchainCount;

        *out = *in;
        out->pSwapchains = outSwapchains;
        out->pWaitSemaphores = outWaitSemaphores;

        for(uint32_t i = 0; i < in->swapchainCount; i++)
          outSwapchains[i] = Unwrap(in->pSwapchains[i]);
        for(uint32_t i = 0; i < in->waitSemaphoreCount; i++)
          outWaitSemaphores[i] = Unwrap(in->pWaitSemaphores[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_RENDERING_INFO:
      {
        const VkRenderingInfo *in = (const VkRenderingInfo *)nextInput;
        VkRenderingInfo *out = (VkRenderingInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkRenderingAttachmentInfo *outAttachs = (VkRenderingAttachmentInfo *)tempMem;
        tempMem += sizeof(VkRenderingAttachmentInfo) * in->colorAttachmentCount;

        out->sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
        out->pNext = in->pNext;
        out->flags = in->flags;
        out->renderArea = in->renderArea;
        out->layerCount = in->layerCount;
        out->viewMask = in->viewMask;
        out->colorAttachmentCount = in->colorAttachmentCount;
        out->pColorAttachments = outAttachs;
        for(uint32_t i = 0; i < in->colorAttachmentCount; i++)
        {
          outAttachs[i] = in->pColorAttachments[i];
          UnwrapInPlace(outAttachs[i].imageView);
          UnwrapInPlace(outAttachs[i].resolveImageView);
          UnwrapNextChain(state, "VkRenderingAttachmentInfo", tempMem,
                          (VkBaseInStructure *)&outAttachs[i]);
        }

        if(in->pDepthAttachment)
        {
          VkRenderingAttachmentInfo *depth = (VkRenderingAttachmentInfo *)tempMem;
          out->pDepthAttachment = depth;
          tempMem += sizeof(VkRenderingAttachmentInfo);

          *depth = *in->pDepthAttachment;
          UnwrapInPlace(depth->imageView);
          UnwrapInPlace(depth->resolveImageView);
          UnwrapNextChain(state, "VkRenderingAttachmentInfo", tempMem, (VkBaseInStructure *)depth);
        }
        else
        {
          out->pDepthAttachment = NULL;
        }

        if(in->pStencilAttachment)
        {
          VkRenderingAttachmentInfo *stencil = (VkRenderingAttachmentInfo *)tempMem;
          out->pStencilAttachment = stencil;
          tempMem += sizeof(VkRenderingAttachmentInfo);

          *stencil = *in->pStencilAttachment;
          UnwrapInPlace(stencil->imageView);
          UnwrapInPlace(stencil->resolveImageView);
          UnwrapNextChain(state, "VkRenderingAttachmentInfo", tempMem, (VkBaseInStructure *)stencil);
        }
        else
        {
          out->pStencilAttachment = NULL;
        }

        break;
      }
      case VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO:
      {
        const VkRenderPassAttachmentBeginInfo *in =
            (const VkRenderPassAttachmentBeginInfo *)nextInput;
        VkRenderPassAttachmentBeginInfo *out = (VkRenderPassAttachmentBeginInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkImageView *outAttachments = (VkImageView *)tempMem;
        tempMem += sizeof(VkImageView) * in->attachmentCount;

        *out = *in;

        out->pAttachments = outAttachments;
        for(uint32_t i = 0; i < in->attachmentCount; i++)
          outAttachments[i] = Unwrap(in->pAttachments[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_RESOLVE_IMAGE_INFO_2:
      {
        const VkResolveImageInfo2 *in = (const VkResolveImageInfo2 *)nextInput;
        VkResolveImageInfo2 *out = (VkResolveImageInfo2 *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkImageResolve2 *outRegions = (VkImageResolve2 *)tempMem;
        tempMem += sizeof(VkImageResolve2) * in->regionCount;

        *out = *in;
        UnwrapInPlace(out->srcImage);
        UnwrapInPlace(out->dstImage);

        out->pRegions = outRegions;
        for(uint32_t i = 0; i < in->regionCount; i++)
        {
          outRegions[i] = in->pRegions[i];
          UnwrapNextChain(state, "VkImageResolve2", tempMem, (VkBaseInStructure *)&outRegions[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO:
      {
        const VkSemaphoreWaitInfo *in = (const VkSemaphoreWaitInfo *)nextInput;
        VkSemaphoreWaitInfo *out = (VkSemaphoreWaitInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkSemaphore *outSemaphores = (VkSemaphore *)tempMem;
        tempMem += sizeof(VkSemaphore) * in->semaphoreCount;

        *out = *in;
        out->pSemaphores = outSemaphores;

        for(uint32_t i = 0; i < in->semaphoreCount; i++)
          outSemaphores[i] = Unwrap(in->pSemaphores[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT:
      {
        const VkShaderCreateInfoEXT *in = (const VkShaderCreateInfoEXT *)nextInput;
        VkShaderCreateInfoEXT *out = (VkShaderCreateInfoEXT *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkDescriptorSetLayout *outLayouts = (VkDescriptorSetLayout *)tempMem;
        tempMem += sizeof(VkDescriptorSetLayout) * in->setLayoutCount;

        *out = *in;

        out->pSetLayouts = outLayouts;
        for(uint32_t i = 0; i < in->setLayoutCount; i++)
          outLayouts[i] = Unwrap(in->pSetLayouts[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_SUBMIT_INFO:
      {
        const VkSubmitInfo *in = (const VkSubmitInfo *)nextInput;
        VkSubmitInfo *out = (VkSubmitInfo *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped arrays
        VkSemaphore *outWaitSemaphores = (VkSemaphore *)tempMem;
        tempMem += sizeof(VkSemaphore) * in->waitSemaphoreCount;
        VkCommandBuffer *outCmdBuffers = (VkCommandBuffer *)tempMem;
        tempMem += sizeof(VkCommandBuffer) * in->commandBufferCount;
        VkSemaphore *outSignalSemaphores = (VkSemaphore *)tempMem;
        tempMem += sizeof(VkSemaphore) * in->signalSemaphoreCount;

        *out = *in;
        out->pWaitSemaphores = outWaitSemaphores;
        out->pCommandBuffers = outCmdBuffers;
        out->pSignalSemaphores = outSignalSemaphores;

        for(uint32_t i = 0; i < in->waitSemaphoreCount; i++)
          outWaitSemaphores[i] = Unwrap(in->pWaitSemaphores[i]);
        for(uint32_t i = 0; i < in->commandBufferCount; i++)
          outCmdBuffers[i] = Unwrap(in->pCommandBuffers[i]);
        for(uint32_t i = 0; i < in->signalSemaphoreCount; i++)
          outSignalSemaphores[i] = Unwrap(in->pSignalSemaphores[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_SUBMIT_INFO_2:
      {
        const VkSubmitInfo2 *in = (const VkSubmitInfo2 *)nextInput;
        VkSubmitInfo2 *out = (VkSubmitInfo2 *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped arrays
        VkSemaphoreSubmitInfo *outWaitSemaphores = (VkSemaphoreSubmitInfo *)tempMem;
        tempMem += sizeof(VkSemaphoreSubmitInfo) * in->waitSemaphoreInfoCount;
        VkCommandBufferSubmitInfo *outCmdBuffers = (VkCommandBufferSubmitInfo *)tempMem;
        tempMem += sizeof(VkCommandBufferSubmitInfo) * in->commandBufferInfoCount;
        VkSemaphoreSubmitInfo *outSignalSemaphores = (VkSemaphoreSubmitInfo *)tempMem;
        tempMem += sizeof(VkSemaphoreSubmitInfo) * in->signalSemaphoreInfoCount;

        *out = *in;
        out->pWaitSemaphoreInfos = outWaitSemaphores;
        out->pCommandBufferInfos = outCmdBuffers;
        out->pSignalSemaphoreInfos = outSignalSemaphores;

        for(uint32_t i = 0; i < in->waitSemaphoreInfoCount; i++)
        {
          outWaitSemaphores[i] = in->pWaitSemaphoreInfos[i];
          UnwrapInPlace(outWaitSemaphores[i].semaphore);
          UnwrapNextChain(state, "VkSemaphoreSubmitInfo", tempMem,
                          (VkBaseInStructure *)&outWaitSemaphores[i]);
        }
        for(uint32_t i = 0; i < in->commandBufferInfoCount; i++)
        {
          outCmdBuffers[i] = in->pCommandBufferInfos[i];
          UnwrapInPlace(outCmdBuffers[i].commandBuffer);
          UnwrapNextChain(state, "VkCommandBufferSubmitInfo", tempMem,
                          (VkBaseInStructure *)&outCmdBuffers[i]);
        }
        for(uint32_t i = 0; i < in->signalSemaphoreInfoCount; i++)
        {
          outSignalSemaphores[i] = in->pSignalSemaphoreInfos[i];
          UnwrapInPlace(outSignalSemaphores[i].semaphore);
          UnwrapNextChain(state, "VkSemaphoreSubmitInfo", tempMem,
                          (VkBaseInStructure *)&outSignalSemaphores[i]);
        }

        break;
      }
      case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT:
      {
        const VkSwapchainPresentFenceInfoEXT *in = (const VkSwapchainPresentFenceInfoEXT *)nextInput;
        VkSwapchainPresentFenceInfoEXT *out = (VkSwapchainPresentFenceInfoEXT *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkFence *outFences = (VkFence *)tempMem;
        tempMem += sizeof(VkFence) * in->swapchainCount;

        *out = *in;
        out->pFences = outFences;

        for(uint32_t i = 0; i < in->swapchainCount; i++)
          outFences[i] = Unwrap(in->pFences[i]);

        break;
      }
      case VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET:
      {
        const VkWriteDescriptorSet *in = (const VkWriteDescriptorSet *)nextInput;
        VkWriteDescriptorSet *out = (VkWriteDescriptorSet *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        *out = *in;
        UnwrapInPlace(out->dstSet);

        switch(out->descriptorType)
        {
          case VK_DESCRIPTOR_TYPE_SAMPLER:
          case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
          case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
          case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
          case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
          {
            VkDescriptorImageInfo *outBindings = (VkDescriptorImageInfo *)tempMem;
            tempMem += sizeof(VkDescriptorImageInfo) * in->descriptorCount;

            for(uint32_t d = 0; d < in->descriptorCount; d++)
            {
              outBindings[d] = in->pImageInfo[d];
              UnwrapInPlace(outBindings[d].imageView);
              UnwrapInPlace(outBindings[d].sampler);
            }

            break;
          }
          case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
          case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
          {
            VkBufferView *outBindings = (VkBufferView *)tempMem;
            tempMem += sizeof(VkBufferView) * in->descriptorCount;

            for(uint32_t d = 0; d < in->descriptorCount; d++)
              outBindings[d] = Unwrap(in->pTexelBufferView[d]);

            break;
          }
          case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
          case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
          case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
          case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
          {
            VkDescriptorBufferInfo *outBindings = (VkDescriptorBufferInfo *)tempMem;
            tempMem += sizeof(VkDescriptorBufferInfo) * in->descriptorCount;

            for(uint32_t d = 0; d < in->descriptorCount; d++)
            {
              outBindings[d] = in->pBufferInfo[d];
              UnwrapInPlace(outBindings[d].buffer);
            }

            break;
          }
          case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK:
          case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR:
          {
            // nothing to do/patch
            break;
          }
          default: RDCERR("Unhandled descriptor type unwrapping VkWriteDescriptorSet"); break;
        }

        break;
      }
      case VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR:
      {
        const VkWriteDescriptorSetAccelerationStructureKHR *in =
            (const VkWriteDescriptorSetAccelerationStructureKHR *)nextInput;
        VkWriteDescriptorSetAccelerationStructureKHR *out =
            (VkWriteDescriptorSetAccelerationStructureKHR *)tempMem;

        // append immediately so tempMem is incremented
        AppendModifiedChainedStruct(tempMem, out, nextChainTail);

        // allocate unwrapped array
        VkAccelerationStructureKHR *outAS = (VkAccelerationStructureKHR *)tempMem;
        tempMem += sizeof(VkAccelerationStructureKHR) * in->accelerationStructureCount;

        *out = *in;
        out->pAccelerationStructures = outAS;

        for(uint32_t i = 0; i < in->accelerationStructureCount; i++)
          outAS[i] = Unwrap(in->pAccelerationStructures[i]);

        break;
      }

// Android External Buffer Memory Extension
#if ENABLED(RDOC_ANDROID)
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
                                 VkImportAndroidHardwareBufferInfoANDROID);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID,
                                 VkAndroidHardwareBufferUsageANDROID);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID, VkExternalFormatANDROID);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
                                 VkAndroidHardwareBufferFormatPropertiesANDROID);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
                                 VkAndroidHardwareBufferPropertiesANDROID);
        UNWRAP_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
                                   VkMemoryGetAndroidHardwareBufferInfoANDROID,
                                   UnwrapInPlace(out->memory));
#else
      case VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID:
      case VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID:
      case VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID:
      case VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID:
      case VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID:
      case VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID:
      {
        RDCERR("Support for android external memory buffer extension not compiled in");
        break;
      }
#endif

#if ENABLED(RDOC_GGP)
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_PRESENT_FRAME_TOKEN_GGP, VkPresentFrameTokenGGP);
#else
      case VK_STRUCTURE_TYPE_PRESENT_FRAME_TOKEN_GGP:
      {
        RDCERR("Support for GGP frame token extension not compiled in");
        break;
      }
#endif

// NV win32 external memory extensions
#if ENABLED(RDOC_WIN32)
        // Structs that can be copied into place
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_NV,
                                 VkImportMemoryWin32HandleInfoNV);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_NV,
                                 VkExportMemoryWin32HandleInfoNV);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
                                 VkImportMemoryWin32HandleInfoKHR);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
                                 VkExportMemoryWin32HandleInfoKHR);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_MEMORY_WIN32_HANDLE_PROPERTIES_KHR,
                                 VkMemoryWin32HandlePropertiesKHR);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR,
                                 VkExportSemaphoreWin32HandleInfoKHR);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_D3D12_FENCE_SUBMIT_INFO_KHR,
                                 VkD3D12FenceSubmitInfoKHR);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_EXPORT_FENCE_WIN32_HANDLE_INFO_KHR,
                                 VkExportFenceWin32HandleInfoKHR);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT,
                                 VkSurfaceFullScreenExclusiveWin32InfoEXT);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT,
                                 VkSurfaceCapabilitiesFullScreenExclusiveEXT);
        COPY_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT,
                                 VkSurfaceFullScreenExclusiveInfoEXT);

        UNWRAP_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR,
                                   VkMemoryGetWin32HandleInfoKHR, UnwrapInPlace(out->memory));
        UNWRAP_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR,
                                   VkImportSemaphoreWin32HandleInfoKHR,
                                   UnwrapInPlace(out->semaphore));
        UNWRAP_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_SEMAPHORE_GET_WIN32_HANDLE_INFO_KHR,
                                   VkSemaphoreGetWin32HandleInfoKHR, UnwrapInPlace(out->semaphore));
        UNWRAP_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_IMPORT_FENCE_WIN32_HANDLE_INFO_KHR,
                                   VkImportFenceWin32HandleInfoKHR, UnwrapInPlace(out->fence));
        UNWRAP_STRUCT_CAPTURE_ONLY(VK_STRUCTURE_TYPE_FENCE_GET_WIN32_HANDLE_INFO_KHR,
                                   VkFenceGetWin32HandleInfoKHR, UnwrapInPlace(out->fence));

      case VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_NV:
      case VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_KHR:
      {
        // strip during replay
        if(IsCaptureMode(state))
        {
          // KHR and NV structs are identical
          const VkWin32KeyedMutexAcquireReleaseInfoKHR *in =
              (const VkWin32KeyedMutexAcquireReleaseInfoKHR *)nextInput;
          VkWin32KeyedMutexAcquireReleaseInfoKHR *out =
              (VkWin32KeyedMutexAcquireReleaseInfoKHR *)tempMem;

          // append immediately so tempMem is incremented
          AppendModifiedChainedStruct(tempMem, out, nextChainTail);

          // copy struct across
          *out = *in;

          // allocate unwrapped arrays
          VkDeviceMemory *unwrappedAcquires = (VkDeviceMemory *)tempMem;
          tempMem += sizeof(VkDeviceMemory) * in->acquireCount;
          VkDeviceMemory *unwrappedReleases = (VkDeviceMemory *)tempMem;
          tempMem += sizeof(VkDeviceMemory) * in->releaseCount;

          // unwrap the arrays
          for(uint32_t mem = 0; mem < in->acquireCount; mem++)
            unwrappedAcquires[mem] = Unwrap(in->pAcquireSyncs[mem]);
          for(uint32_t mem = 0; mem < in->releaseCount; mem++)
            unwrappedReleases[mem] = Unwrap(in->pReleaseSyncs[mem]);

          out->pAcquireSyncs = unwrappedAcquires;
          out->pReleaseSyncs = unwrappedReleases;
        }
        break;
      }
#else
      case VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_NV:
      case VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_NV:
      case VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_MEMORY_WIN32_HANDLE_PROPERTIES_KHR:
      case VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_D3D12_FENCE_SUBMIT_INFO_KHR:
      case VK_STRUCTURE_TYPE_SEMAPHORE_GET_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_EXPORT_FENCE_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT:
      case VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT:
      case VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT:
      case VK_STRUCTURE_TYPE_IMPORT_FENCE_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_FENCE_GET_WIN32_HANDLE_INFO_KHR:
      case VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_NV:
      case VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_KHR:
      {
        RDCERR("Support for win32 external memory extensions not compiled in");
        nextChainTail->pNext = nextInput;
        break;
      }
#endif

      case VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT:
      case VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT:
      case VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT:
      case VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT:
      case VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT:
      {
        // could be implemented but would need extra work or doesn't make sense right now
        RDCERR("Struct %s not handled in %s pNext chain", ToStr(nextInput->sType).c_str(),
               structName);
        nextChainTail->pNext = nextInput;
        break;
      }

        UNHANDLED_STRUCTS()
        {
          RDCERR("Unhandled struct %s in %s pNext chain", ToStr(nextInput->sType).c_str(),
                 structName);
          nextChainTail->pNext = nextInput;
          break;
        }

      case VK_STRUCTURE_TYPE_MAX_ENUM:
      {
        RDCERR("Invalid value %x in %s pNext chain", nextInput->sType, structName);
        nextChainTail->pNext = nextInput;
        break;
      }
    }

    nextInput = nextInput->pNext;
  }
}