/******************************************************************************
 * 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_state.h"
#include "vk_common.h"
#include "vk_core.h"
#include "vk_debug.h"
#include "vk_info.h"
#include "vk_resources.h"

namespace
{
struct RenderingInfoStructs
{
  VkRenderingInfo info;

  rdcarray<VkRenderingAttachmentInfo> color;
  VkRenderingAttachmentInfo depth;
  VkRenderingAttachmentInfo stencil;

  VkRenderingFragmentDensityMapAttachmentInfoEXT fragmentDensity;
  VkRenderingFragmentShadingRateAttachmentInfoKHR shadingRate;
  VkMultisampledRenderToSingleSampledInfoEXT tileOnlyMSAA;
};

void setupRenderingInfo(const VulkanRenderState::DynamicRendering &dynamicRendering,
                        RenderingInfoStructs *structs, VkRenderingFlags flags,
                        const VkRect2D &renderArea)
{
  VkRenderingInfo *info = &structs->info;

  *info = {};
  info->sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
  info->flags = flags;
  info->layerCount = dynamicRendering.layerCount;
  info->renderArea = renderArea;
  info->viewMask = dynamicRendering.viewMask;

  structs->depth = dynamicRendering.depth;
  info->pDepthAttachment = &structs->depth;
  if(structs->depth.imageLayout == VK_IMAGE_LAYOUT_UNDEFINED)
    info->pDepthAttachment = NULL;
  structs->stencil = dynamicRendering.stencil;
  info->pStencilAttachment = &structs->stencil;
  if(structs->stencil.imageLayout == VK_IMAGE_LAYOUT_UNDEFINED)
    info->pStencilAttachment = NULL;

  structs->color = dynamicRendering.color;

  info->colorAttachmentCount = (uint32_t)structs->color.size();
  info->pColorAttachments = structs->color.data();

  // patch the load/store actions and unwrap
  for(uint32_t i = 0; i < (uint32_t)structs->color.size() + 2; i++)
  {
    VkRenderingAttachmentInfo *att = (VkRenderingAttachmentInfo *)info->pColorAttachments + i;

    if(i == info->colorAttachmentCount)
      att = (VkRenderingAttachmentInfo *)info->pDepthAttachment;
    else if(i == info->colorAttachmentCount + 1)
      att = (VkRenderingAttachmentInfo *)info->pStencilAttachment;

    if(!att)
      continue;

    if(att->loadOp != VK_ATTACHMENT_LOAD_OP_NONE)
      att->loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;

    if(att->storeOp != VK_ATTACHMENT_STORE_OP_NONE)
      att->storeOp = VK_ATTACHMENT_STORE_OP_STORE;

    att->imageView = Unwrap(att->imageView);
    att->resolveImageView = Unwrap(att->resolveImageView);
  }

  structs->fragmentDensity = {
      VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_DENSITY_MAP_ATTACHMENT_INFO_EXT,
      NULL,
      Unwrap(dynamicRendering.fragmentDensityView),
      dynamicRendering.fragmentDensityLayout,
  };

  if(dynamicRendering.fragmentDensityView != VK_NULL_HANDLE)
  {
    structs->fragmentDensity.pNext = info->pNext;
    info->pNext = &structs->fragmentDensity;
  }

  structs->shadingRate = {
      VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR,
      NULL,
      Unwrap(dynamicRendering.shadingRateView),
      dynamicRendering.shadingRateLayout,
      dynamicRendering.shadingRateTexelSize,
  };

  if(dynamicRendering.shadingRateView != VK_NULL_HANDLE)
  {
    structs->shadingRate.pNext = info->pNext;
    info->pNext = &structs->shadingRate;
  }

  structs->tileOnlyMSAA = {
      VK_STRUCTURE_TYPE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_INFO_EXT,
      NULL,
      dynamicRendering.tileOnlyMSAAEnable,
      dynamicRendering.tileOnlyMSAASampleCount,
  };

  if(dynamicRendering.tileOnlyMSAAEnable)
  {
    structs->tileOnlyMSAA.pNext = info->pNext;
    info->pNext = &structs->tileOnlyMSAA;
  }
}
}    // namespace

void VulkanRenderState::DynamicRendering::CopyAttachmentNext(VkRenderingAttachmentInfo &info)
{
  void *newNext = NULL;

  VkAttachmentFeedbackLoopInfoEXT *feedback = (VkAttachmentFeedbackLoopInfoEXT *)FindNextStruct(
      &info, VK_STRUCTURE_TYPE_ATTACHMENT_FEEDBACK_LOOP_INFO_EXT);
  if(feedback)
  {
    feedbacks.push_back(*feedback);
    feedbacks.back().pNext = newNext;
    newNext = &feedbacks.back();
  }

  // if other structs are added, they can be checked the same here and prepended to newNext in the same way

  info.pNext = newNext;
}

void VulkanRenderState::DynamicRendering::CopyAttachmentNexts()
{
  // ensure we don't invalidate any pointers in the loop below, reserve enough space
  feedbacks.clear();
  feedbacks.reserve(color.size() + 2);

  for(VkRenderingAttachmentInfo &info : color)
    CopyAttachmentNext(info);

  CopyAttachmentNext(depth);
  CopyAttachmentNext(stencil);
}

VulkanRenderState::VulkanRenderState()
{
  RDCEraseEl(ibuffer);
}

void VulkanRenderState::BeginRenderPassAndApplyState(WrappedVulkan *vk, VkCommandBuffer cmd,
                                                     PipelineBinding binding, bool obeySuspending)
{
  if(dynamicRendering.active)
  {
    // this is left in case for future refactors - for action callbacks that want to stop the
    // renderpass, do something, then start it with original state, we need to preserve the
    // suspending flag instead of removing it. For other uses, we remove both flags as we're just
    // doing a manual start/stop and we're not in a suspended pass.
    // Since we do not replay suspend/resume and instead let it defer to load/store, this is not needed
    (void)obeySuspending;

    VkRenderingFlags flags = dynamicRendering.flags;
    flags &= ~(VK_RENDERING_RESUMING_BIT | VK_RENDERING_SUSPENDING_BIT);

    RenderingInfoStructs structs;
    setupRenderingInfo(dynamicRendering, &structs, flags, renderArea);

    ObjDisp(cmd)->CmdBeginRendering(Unwrap(cmd), &structs.info);
  }
  else
  {
    RDCASSERT(renderPass != ResourceId());

    // clear values don't matter as we're using the load renderpass here, that
    // has all load ops set to load (as we're doing a partial replay - can't
    // just clear the targets that are partially written to).

    VkClearValue empty[16] = {};

    RDCASSERT(ARRAY_COUNT(empty) >=
              vk->GetDebugManager()->GetRenderPassInfo(renderPass).attachments.size());

    VulkanCreationInfo::Framebuffer fbinfo = vk->GetDebugManager()->GetFramebufferInfo(framebuffer);

    VkRenderPassBeginInfo rpbegin = {
        VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
        NULL,
        Unwrap(vk->GetDebugManager()->GetRenderPassInfo(renderPass).loadRPs[subpass]),
        Unwrap(fbinfo.loadFBs[subpass]),
        renderArea,
        (uint32_t)vk->GetDebugManager()->GetRenderPassInfo(renderPass).attachments.size(),
        empty,
    };

    VkRenderPassAttachmentBeginInfo imagelessAttachments = {
        VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO,
    };
    rdcarray<VkImageView> imagelessViews;

    if(fbinfo.imageless)
    {
      rpbegin.pNext = &imagelessAttachments;
      imagelessAttachments.attachmentCount = (uint32_t)fbattachments.size();

      for(size_t i = 0; i < fbattachments.size(); i++)
        imagelessViews.push_back(
            Unwrap(vk->GetResourceManager()->GetHandle<VkImageView>(fbattachments[i])));

      imagelessAttachments.pAttachments = imagelessViews.data();
    }

    ObjDisp(cmd)->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, subpassContents);
  }

  if(subpassContents != VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS)
  {
    if(graphics.shaderObject)
      BindShaderObjects(vk, cmd, binding);
    else
      BindPipeline(vk, cmd, binding, true);
  }

  if(IsConditionalRenderingEnabled())
  {
    VkConditionalRenderingBeginInfoEXT beginInfo;
    beginInfo.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
    beginInfo.pNext = VK_NULL_HANDLE;
    beginInfo.buffer =
        Unwrap(vk->GetResourceManager()->GetHandle<VkBuffer>(conditionalRendering.buffer));
    beginInfo.offset = conditionalRendering.offset;
    beginInfo.flags = conditionalRendering.flags;

    ObjDisp(cmd)->CmdBeginConditionalRenderingEXT(Unwrap(cmd), &beginInfo);
  }

  if(dynamicRendering.localRead.AreLocationsNonDefault())
  {
    dynamicRendering.localRead.SetLocations(cmd);
  }

  if(dynamicRendering.localRead.AreInputIndicesNonDefault())
  {
    dynamicRendering.localRead.SetInputIndices(cmd);
  }

  if(dynamicRendering.beginCustomResolve)
  {
    VkBeginCustomResolveInfoEXT beginInfo;
    beginInfo.sType = VK_STRUCTURE_TYPE_BEGIN_CUSTOM_RESOLVE_INFO_EXT;
    beginInfo.pNext = VK_NULL_HANDLE;
    ObjDisp(cmd)->CmdBeginCustomResolveEXT(Unwrap(cmd), &beginInfo);
  }
}

void VulkanRenderState::EndRenderPass(VkCommandBuffer cmd)
{
  VkRenderPassFragmentDensityMapOffsetEndInfoEXT fragmentDensityOffsetStruct = {
      VK_STRUCTURE_TYPE_RENDER_PASS_FRAGMENT_DENSITY_MAP_OFFSET_END_INFO_EXT,
      NULL,
      (uint32_t)fragmentDensityMapOffsets.size(),
      fragmentDensityMapOffsets.data(),
  };

  if(dynamicRendering.active)
  {
    if(!dynamicRendering.suspended)
    {
      if(fragmentDensityMapOffsets.empty())
      {
        ObjDisp(cmd)->CmdEndRendering(Unwrap(cmd));
      }
      else
      {
        VkRenderingEndInfoKHR endInfo = {
            VK_STRUCTURE_TYPE_RENDERING_END_INFO_KHR,
            &fragmentDensityOffsetStruct,
        };

        // the only time we can possibly have fragment offsets and be using dynamic rendering is if
        // this function is available by definition, so we don't have to check for it
        ObjDisp(cmd)->CmdEndRendering2EXT(Unwrap(cmd), &endInfo);
      }
    }
  }
  else
  {
    if(fragmentDensityMapOffsets.empty())
    {
      ObjDisp(cmd)->CmdEndRenderPass(Unwrap(cmd));
    }
    else
    {
      VkSubpassEndInfo endInfo = {
          VK_STRUCTURE_TYPE_SUBPASS_END_INFO,
          &fragmentDensityOffsetStruct,
      };

      // the only time we can possibly have fragment offsets and be using a normal render pass is if
      // this function is available by definition, so we don't have to check for it
      ObjDisp(cmd)->CmdEndRenderPass2(Unwrap(cmd), &endInfo);
    }
  }
}

void VulkanRenderState::FinishSuspendedRenderPass(VkCommandBuffer cmd)
{
  // this is left in case for future refactors - this would resume then fully finish a suspended
  // renderpass. Typically for an action callback that wants to properly stop a renderpass to do
  // other work.
  // Since we do not replay suspend/resume and instead let it defer to load/store, this is not needed.
}

void VulkanRenderState::EndTransformFeedback(WrappedVulkan *vk, VkCommandBuffer cmd)
{
  if(!xfbcounters.empty())
  {
    rdcarray<VkBuffer> buffers;
    rdcarray<VkDeviceSize> offsets;

    for(size_t i = 0; i < xfbcounters.size(); i++)
    {
      buffers.push_back(Unwrap(vk->GetResourceManager()->GetHandle<VkBuffer>(xfbcounters[i].buf)));
      offsets.push_back(xfbcounters[i].offs);
    }

    ObjDisp(cmd)->CmdEndTransformFeedbackEXT(
        Unwrap(cmd), firstxfbcounter, (uint32_t)xfbcounters.size(), buffers.data(), offsets.data());
  }
}

void VulkanRenderState::EndConditionalRendering(VkCommandBuffer cmd)
{
  if(IsConditionalRenderingEnabled())
    ObjDisp(cmd)->CmdEndConditionalRenderingEXT(Unwrap(cmd));
}

bool VulkanRenderState::IsConditionalRenderingEnabled()
{
  return conditionalRendering.buffer != ResourceId() && !conditionalRendering.forceDisable;
}

void VulkanRenderState::BindDescriptorBuffers(WrappedVulkan *vk, VkCommandBuffer cmd)
{
  if(!descBufs.empty())
  {
    VkDescriptorBufferBindingPushDescriptorBufferHandleEXT push;
    rdcarray<VkBufferUsageFlags2CreateInfo> usage2;
    rdcarray<VkDescriptorBufferBindingInfoEXT> bind;

    uint32_t bufferCount = descBufs.count();

    usage2.resize(bufferCount);
    bind.resize(bufferCount);

    for(uint32_t i = 0; i < bufferCount; i++)
    {
      bind[i] = {VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT};
      bind[i].address = descBufs[i].address;

      if(descBufs[i].flags2)
      {
        usage2[i] = {
            VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO,
            NULL,
            descBufs[i].usage,
        };

        bind[i].pNext = &usage2[i];
      }
      else
      {
        bind[i].usage = VkBufferUsageFlags(descBufs[i].usage);
      }

      if(descBufs[i].pushBuffer != ResourceId())
      {
        push = {
            VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_PUSH_DESCRIPTOR_BUFFER_HANDLE_EXT,
            bind[i].pNext,
            Unwrap(vk->GetResourceManager()->GetHandle<VkBuffer>(descBufs[i].pushBuffer)),
        };

        bind[i].pNext = &push;
      }
    }

    ObjDisp(cmd)->CmdBindDescriptorBuffersEXT(Unwrap(cmd), bufferCount, bind.data());
  }
}

void VulkanRenderState::BindPipeline(WrappedVulkan *vk, VkCommandBuffer cmd,
                                     PipelineBinding binding, bool subpass0)
{
  // subpass0 is a patched version of the pipeline created against subpass 0, in case for old style
  // renderpasses we need to use a pipeline that was previously in subpass 1 against our loadrp with
  // only one subpass. It's not needed for dynamic rendering, we can always use the original
  // pipeline
  if(subpass0 && dynamicRendering.active)
    subpass0 = false;

  BindDescriptorBuffers(vk, cmd);

  if(binding == BindGraphics || binding == BindInitial)
  {
    if(graphics.pipeline != ResourceId())
    {
      VkPipeline pipe = vk->GetResourceManager()->GetHandle<VkPipeline>(graphics.pipeline);
      const VulkanCreationInfo::Pipeline pipeinfo =
          vk->GetDebugManager()->GetPipelineInfo(graphics.pipeline);

      if(subpass0 && pipeinfo.subpass0pipe != VK_NULL_HANDLE)
        pipe = pipeinfo.subpass0pipe;

      ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(pipe));

      // don't have to handle separate vert/frag layouts as push constant ranges must be identical
      ResourceId pipeLayoutId = pipeinfo.vertLayout;
      VkPipelineLayout layout = vk->GetResourceManager()->GetHandle<VkPipelineLayout>(pipeLayoutId);

      const rdcarray<VkPushConstantRange> &pushRanges =
          vk->GetDebugManager()->GetPipelineLayoutInfo(pipeLayoutId).pushRanges;

      // only set push constant ranges that the layout uses
      for(size_t i = 0; i < pushRanges.size(); i++)
        ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(layout), pushRanges[i].stageFlags,
                                       pushRanges[i].offset, pushRanges[i].size,
                                       pushconsts + pushRanges[i].offset);
    }
    else if(binding == BindInitial)
    {
      if(vk->GetDriverInfo().NVStaticPipelineRebindStates())
        ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
                                      Unwrap(vk->GetDebugManager()->GetDummyPipeline()));

      BindLastPushConstants(vk, cmd);
    }

    if(graphics.pipeline != ResourceId())
      BindDescriptorSetsForPipeline(vk, cmd, graphics, VK_PIPELINE_BIND_POINT_GRAPHICS);
    else
      BindDescriptorSetsWithoutPipeline(vk, cmd, graphics, VK_PIPELINE_BIND_POINT_GRAPHICS);

    BindDynamicState(vk, cmd);
  }

  if(binding == BindCompute || binding == BindInitial)
  {
    if(compute.pipeline != ResourceId())
    {
      ObjDisp(cmd)->CmdBindPipeline(
          Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
          Unwrap(vk->GetResourceManager()->GetHandle<VkPipeline>(compute.pipeline)));

      ResourceId pipeLayoutId = vk->GetDebugManager()->GetPipelineInfo(compute.pipeline).compLayout;
      VkPipelineLayout layout = vk->GetResourceManager()->GetHandle<VkPipelineLayout>(pipeLayoutId);

      const rdcarray<VkPushConstantRange> &pushRanges =
          vk->GetDebugManager()->GetPipelineLayoutInfo(pipeLayoutId).pushRanges;

      // only set push constant ranges that the layout uses
      for(size_t i = 0; i < pushRanges.size(); i++)
        ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(layout), pushRanges[i].stageFlags,
                                       pushRanges[i].offset, pushRanges[i].size,
                                       pushconsts + pushRanges[i].offset);

      BindDescriptorSetsForPipeline(vk, cmd, compute, VK_PIPELINE_BIND_POINT_COMPUTE);
    }
    else if(binding == BindInitial)
    {
      BindLastPushConstants(vk, cmd);

      BindDescriptorSetsWithoutPipeline(vk, cmd, compute, VK_PIPELINE_BIND_POINT_COMPUTE);
    }
  }

  if(binding == BindRT || binding == BindInitial)
  {
    if(rt.pipeline != ResourceId())
    {
      ObjDisp(cmd)->CmdBindPipeline(
          Unwrap(cmd), VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR,
          Unwrap(vk->GetResourceManager()->GetHandle<VkPipeline>(rt.pipeline)));

      ResourceId pipeLayoutId = vk->GetDebugManager()->GetPipelineInfo(rt.pipeline).compLayout;
      VkPipelineLayout layout = vk->GetResourceManager()->GetHandle<VkPipelineLayout>(pipeLayoutId);

      const rdcarray<VkPushConstantRange> &pushRanges =
          vk->GetDebugManager()->GetPipelineLayoutInfo(pipeLayoutId).pushRanges;

      // only set push constant ranges that the layout uses
      for(size_t i = 0; i < pushRanges.size(); i++)
        ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(layout), pushRanges[i].stageFlags,
                                       pushRanges[i].offset, pushRanges[i].size,
                                       pushconsts + pushRanges[i].offset);

      BindDescriptorSetsForPipeline(vk, cmd, rt, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR);
    }
  }
}

void VulkanRenderState::BindShaderObjects(WrappedVulkan *vk, VkCommandBuffer cmd,
                                          PipelineBinding binding)
{
  BindDescriptorBuffers(vk, cmd);

  if(binding == BindGraphics || binding == BindInitial)
  {
    if(graphics.shaderObject)
    {
      // According to spec, shader objects don't support ray tracing shaders
      for(uint32_t i = 0; i < (uint32_t)ShaderStage::RayGen; i++)
      {
        if(i == (uint32_t)ShaderStage::Compute)
          continue;

        // Workaround for driver bug found in NVIDIA 10 series cards
        if(i == (uint32_t)ShaderStage::Mesh && !vk->MeshShaders())
          continue;

        const VkShaderStageFlagBits stage = (VkShaderStageFlagBits)(1 << (uint32_t)i);
        const VkShaderEXT shader =
            Unwrap(vk->GetResourceManager()->GetHandle<VkShaderEXT>(shaderObjects[i]));

        ObjDisp(cmd)->CmdBindShadersEXT(Unwrap(cmd), 1, &stage, &shader);
      }
    }

    BindLastPushConstants(vk, cmd);

    BindDescriptorSetsForShaders(vk, cmd, graphics, VK_PIPELINE_BIND_POINT_GRAPHICS);

    BindDynamicState(vk, cmd);
  }

  if(binding == BindCompute || binding == BindInitial)
  {
    if(compute.shaderObject && shaderObjects[(uint32_t)ShaderStage::Compute] != ResourceId())
    {
      const VkShaderStageFlagBits stage =
          (VkShaderStageFlagBits)(1 << (uint32_t)ShaderStage::Compute);
      const VkShaderEXT shader = Unwrap(vk->GetResourceManager()->GetHandle<VkShaderEXT>(
          shaderObjects[(uint32_t)ShaderStage::Compute]));
      ObjDisp(cmd)->CmdBindShadersEXT(Unwrap(cmd), 1, &stage, &shader);
    }

    BindLastPushConstants(vk, cmd);

    BindDescriptorSetsForShaders(vk, cmd, compute, VK_PIPELINE_BIND_POINT_COMPUTE);
  }
}

void VulkanRenderState::BindDynamicState(WrappedVulkan *vk, VkCommandBuffer cmd)
{
  if(dynamicStates[VkDynamicRayTracingStackSizeKHR])
    ObjDisp(cmd)->CmdSetRayTracingPipelineStackSizeKHR(Unwrap(cmd), rtStackSize);

  if(!views.empty() && dynamicStates[VkDynamicViewport])
    ObjDisp(cmd)->CmdSetViewport(Unwrap(cmd), 0, (uint32_t)views.size(), &views[0]);
  if(!scissors.empty() && dynamicStates[VkDynamicScissor])
    ObjDisp(cmd)->CmdSetScissor(Unwrap(cmd), 0, (uint32_t)scissors.size(), &scissors[0]);

  if(vk->DynamicColorWrite())
  {
    if(!colorWriteEnable.empty() && dynamicStates[VkDynamicColorWriteEXT])
      ObjDisp(cmd)->CmdSetColorWriteEnableEXT(Unwrap(cmd), (uint32_t)colorWriteEnable.size(),
                                              colorWriteEnable.data());
  }

  if(vk->ExtendedDynamicState() || vk->ShaderObject())
  {
    if(!views.empty() && dynamicStates[VkDynamicViewportCount])
      ObjDisp(cmd)->CmdSetViewportWithCountEXT(Unwrap(cmd), (uint32_t)views.size(), views.data());
    if(!scissors.empty() && dynamicStates[VkDynamicScissorCount])
      ObjDisp(cmd)->CmdSetScissorWithCountEXT(Unwrap(cmd), (uint32_t)scissors.size(),
                                              scissors.data());

    if(dynamicStates[VkDynamicCullMode])
      ObjDisp(cmd)->CmdSetCullModeEXT(Unwrap(cmd), cullMode);
    if(dynamicStates[VkDynamicFrontFace])
      ObjDisp(cmd)->CmdSetFrontFaceEXT(Unwrap(cmd), frontFace);
    if(dynamicStates[VkDynamicPrimitiveTopology])
      ObjDisp(cmd)->CmdSetPrimitiveTopologyEXT(Unwrap(cmd), primitiveTopology);

    if(dynamicStates[VkDynamicDepthBoundsTestEnable])
      ObjDisp(cmd)->CmdSetDepthBoundsTestEnableEXT(Unwrap(cmd), depthBoundsTestEnable);

    if(dynamicStates[VkDynamicDepthTestEnable])
      ObjDisp(cmd)->CmdSetDepthTestEnableEXT(Unwrap(cmd), depthTestEnable);
    if(dynamicStates[VkDynamicDepthWriteEnable])
      ObjDisp(cmd)->CmdSetDepthWriteEnableEXT(Unwrap(cmd), depthWriteEnable);
    if(dynamicStates[VkDynamicDepthCompareOp])
      ObjDisp(cmd)->CmdSetDepthCompareOpEXT(Unwrap(cmd), depthCompareOp);

    if(dynamicStates[VkDynamicStencilTestEnable])
      ObjDisp(cmd)->CmdSetStencilTestEnableEXT(Unwrap(cmd), stencilTestEnable);

    if(dynamicStates[VkDynamicStencilOp])
    {
      ObjDisp(cmd)->CmdSetStencilOpEXT(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT, front.failOp,
                                       front.passOp, front.depthFailOp, front.compareOp);
      ObjDisp(cmd)->CmdSetStencilOpEXT(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT, front.failOp,
                                       front.passOp, front.depthFailOp, front.compareOp);
    }
  }

  if(vk->ExtendedDynamicState2() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicDepthBiasEnable])
      ObjDisp(cmd)->CmdSetDepthBiasEnableEXT(Unwrap(cmd), depthBiasEnable);
    if(dynamicStates[VkDynamicPrimRestart])
      ObjDisp(cmd)->CmdSetPrimitiveRestartEnableEXT(Unwrap(cmd), primRestartEnable);
    if(dynamicStates[VkDynamicRastDiscard])
      ObjDisp(cmd)->CmdSetRasterizerDiscardEnableEXT(Unwrap(cmd), rastDiscardEnable);
  }
  if(vk->ExtendedDynamicState2Logic() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicLogicOpEXT])
      ObjDisp(cmd)->CmdSetLogicOpEXT(Unwrap(cmd), logicOp);
  }
  if(vk->ExtendedDynamicState2CPs() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicControlPointsEXT])
      ObjDisp(cmd)->CmdSetPatchControlPointsEXT(Unwrap(cmd), patchControlPoints);
  }

  if(vk->ExtendedDynamicState3AlphaToCover() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicAlphaToCoverageEXT])
      ObjDisp(cmd)->CmdSetAlphaToCoverageEnableEXT(Unwrap(cmd), alphaToCoverageEnable);
  }
  if(vk->ExtendedDynamicState3AlphaToOne() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicAlphaToOneEXT])
      ObjDisp(cmd)->CmdSetAlphaToOneEnableEXT(Unwrap(cmd), alphaToOneEnable);
  }
  if(vk->ExtendedDynamicState3CBEnable() || vk->ShaderObject())
  {
    if(!colorBlendEnable.empty() && dynamicStates[VkDynamicColorBlendEnableEXT])
      ObjDisp(cmd)->CmdSetColorBlendEnableEXT(Unwrap(cmd), 0, (uint32_t)colorBlendEnable.size(),
                                              colorBlendEnable.data());
  }
  if(vk->ExtendedDynamicState3CBEquation() || vk->ShaderObject())
  {
    if(!colorBlendEquation.empty() && dynamicStates[VkDynamicColorBlendEquationEXT])
      ObjDisp(cmd)->CmdSetColorBlendEquationEXT(Unwrap(cmd), 0, (uint32_t)colorBlendEquation.size(),
                                                colorBlendEquation.data());
  }
  if(vk->ExtendedDynamicState3WriteMask() || vk->ShaderObject())
  {
    if(!colorWriteMask.empty() && dynamicStates[VkDynamicColorWriteMaskEXT])
      ObjDisp(cmd)->CmdSetColorWriteMaskEXT(Unwrap(cmd), 0, (uint32_t)colorWriteMask.size(),
                                            colorWriteMask.data());
  }
  if(vk->ExtendedDynamicState3ConservRast() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicConservativeRastModeEXT])
      ObjDisp(cmd)->CmdSetConservativeRasterizationModeEXT(Unwrap(cmd), conservativeRastMode);
  }
  if(vk->ExtendedDynamicState3DepthClampEnable() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicDepthClampEnableEXT])
      ObjDisp(cmd)->CmdSetDepthClampEnableEXT(Unwrap(cmd), depthClampEnable);
  }
  if(vk->ExtendedDynamicState3DepthClip() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicDepthClipEnableEXT])
      ObjDisp(cmd)->CmdSetDepthClipEnableEXT(Unwrap(cmd), depthClipEnable);
  }
  if(vk->ExtendedDynamicState3DepthClipNegative() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicDepthClipNegativeOneEXT])
      ObjDisp(cmd)->CmdSetDepthClipNegativeOneToOneEXT(Unwrap(cmd), negativeOneToOne);
  }
  if(vk->ExtendedDynamicState3PrimOverest() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicOverstimationSizeEXT])
      ObjDisp(cmd)->CmdSetExtraPrimitiveOverestimationSizeEXT(Unwrap(cmd), primOverestimationSize);
  }
  if(vk->ExtendedDynamicState3LineRast() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicLineRastModeEXT])
      ObjDisp(cmd)->CmdSetLineRasterizationModeEXT(Unwrap(cmd), lineRasterMode);
  }
  if(vk->ExtendedDynamicState3LineStipple() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicLineStippleEnableEXT])
      ObjDisp(cmd)->CmdSetLineStippleEnableEXT(Unwrap(cmd), stippledLineEnable);
  }
  if(vk->ExtendedDynamicState3LogicEnable() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicLogicOpEnableEXT])
      ObjDisp(cmd)->CmdSetLogicOpEnableEXT(Unwrap(cmd), logicOpEnable);
  }
  if(vk->ExtendedDynamicState3PolyMode() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicPolygonModeEXT])
      ObjDisp(cmd)->CmdSetPolygonModeEXT(Unwrap(cmd), polygonMode);
  }
  if(vk->ExtendedDynamicState3ProvokingVert() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicProvokingVertexModeEXT])
      ObjDisp(cmd)->CmdSetProvokingVertexModeEXT(Unwrap(cmd), provokingVertexMode);
  }
  if(vk->ExtendedDynamicState3RastSamples() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicRasterizationSamplesEXT])
      ObjDisp(cmd)->CmdSetRasterizationSamplesEXT(Unwrap(cmd), rastSamples);
  }
  if(vk->ExtendedDynamicState3RastStream() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicRasterizationStreamEXT])
      ObjDisp(cmd)->CmdSetRasterizationStreamEXT(Unwrap(cmd), rasterStream);
  }
  if(vk->ExtendedDynamicState3SampleLoc() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicSampleLocationsEnableEXT])
      ObjDisp(cmd)->CmdSetSampleLocationsEnableEXT(Unwrap(cmd), sampleLocEnable);
  }
  if(vk->ExtendedDynamicState3SampleMask() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicSampleMaskEXT])
      ObjDisp(cmd)->CmdSetSampleMaskEXT(Unwrap(cmd), rastSamples, sampleMask.data());
  }
  if(vk->ExtendedDynamicState3TesselDomain() || vk->ShaderObject())
  {
    if(dynamicStates[VkDynamicTessDomainOriginEXT])
      ObjDisp(cmd)->CmdSetTessellationDomainOriginEXT(Unwrap(cmd), domainOrigin);
  }

  if(dynamicStates[VkDynamicLineWidth] && !vk->GetDriverInfo().QualcommLineWidthDynamicStateCrash())
    ObjDisp(cmd)->CmdSetLineWidth(Unwrap(cmd), lineWidth);

  if(dynamicStates[VkDynamicDepthBias])
    ObjDisp(cmd)->CmdSetDepthBias(Unwrap(cmd), bias.depth, bias.biasclamp, bias.slope);

  if(dynamicStates[VkDynamicBlendConstants])
    ObjDisp(cmd)->CmdSetBlendConstants(Unwrap(cmd), blendConst);

  if(dynamicStates[VkDynamicDepthBounds])
    ObjDisp(cmd)->CmdSetDepthBounds(Unwrap(cmd), mindepth, maxdepth);

  if(dynamicStates[VkDynamicStencilCompareMask])
  {
    ObjDisp(cmd)->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT, back.compare);
    ObjDisp(cmd)->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT, front.compare);
  }

  if(dynamicStates[VkDynamicStencilWriteMask])
  {
    ObjDisp(cmd)->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT, back.write);
    ObjDisp(cmd)->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT, front.write);
  }

  if(dynamicStates[VkDynamicStencilReference])
  {
    ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT, back.ref);
    ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT, front.ref);
  }

  if(!sampleLocations.locations.empty() && dynamicStates[VkDynamicSampleLocationsEXT])
  {
    VkSampleLocationsInfoEXT info = {VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT};
    info.pSampleLocations = sampleLocations.locations.data();
    info.sampleLocationsCount = (uint32_t)sampleLocations.locations.size();
    info.sampleLocationsPerPixel = sampleLocations.sampleCount;
    info.sampleLocationGridSize = sampleLocations.gridSize;
    ObjDisp(cmd)->CmdSetSampleLocationsEXT(Unwrap(cmd), &info);
  }

  if(!discardRectangles.empty() && dynamicStates[VkDynamicDiscardRectangleEXT])
    ObjDisp(cmd)->CmdSetDiscardRectangleEXT(Unwrap(cmd), 0, (uint32_t)discardRectangles.size(),
                                            &discardRectangles[0]);

  if(stippleFactor && dynamicStates[VkDynamicLineStipple])
    ObjDisp(cmd)->CmdSetLineStippleEXT(Unwrap(cmd), stippleFactor, stipplePattern);

  if(vk->FragmentShadingRate())
  {
    if(dynamicStates[VkDynamicShadingRateKHR])
      ObjDisp(cmd)->CmdSetFragmentShadingRateKHR(Unwrap(cmd), &pipelineShadingRate,
                                                 shadingRateCombiners);
  }

  if(vk->DynamicAttachmentLoop())
  {
    if(dynamicStates[VkDynamicAttachmentFeedbackLoopEnableEXT])
      ObjDisp(cmd)->CmdSetAttachmentFeedbackLoopEnableEXT(Unwrap(cmd), feedbackAspects);
  }

  if(ibuffer.buf != ResourceId())
  {
    VkIndexType type = VK_INDEX_TYPE_UINT16;
    if(ibuffer.bytewidth == 4)
      type = VK_INDEX_TYPE_UINT32;
    else if(ibuffer.bytewidth == 1)
      type = VK_INDEX_TYPE_UINT8;

    if(vk->Maintenance5() && ibuffer.size != VK_WHOLE_SIZE)
      ObjDisp(cmd)->CmdBindIndexBuffer2(
          Unwrap(cmd), Unwrap(vk->GetResourceManager()->GetHandle<VkBuffer>(ibuffer.buf)),
          ibuffer.offs, ibuffer.size, type);
    else
      ObjDisp(cmd)->CmdBindIndexBuffer(
          Unwrap(cmd), Unwrap(vk->GetResourceManager()->GetHandle<VkBuffer>(ibuffer.buf)),
          ibuffer.offs, type);
  }
  else if(vk->Maintenance6())
  {
    VkIndexType type = VK_INDEX_TYPE_UINT16;
    if(ibuffer.bytewidth == 4)
      type = VK_INDEX_TYPE_UINT32;
    else if(ibuffer.bytewidth == 1)
      type = VK_INDEX_TYPE_UINT8;

    if(vk->Maintenance5() && ibuffer.size != VK_WHOLE_SIZE)
      ObjDisp(cmd)->CmdBindIndexBuffer2(Unwrap(cmd), VK_NULL_HANDLE, ibuffer.offs, ibuffer.size,
                                        type);
    else
      ObjDisp(cmd)->CmdBindIndexBuffer(Unwrap(cmd), VK_NULL_HANDLE, ibuffer.offs, type);
  }

  if((vk->DynamicVertexInput() || vk->ShaderObject()) && dynamicStates[VkDynamicVertexInputEXT])
  {
    ObjDisp(cmd)->CmdSetVertexInputEXT(Unwrap(cmd), (uint32_t)vertexBindings.size(),
                                       vertexBindings.data(), (uint32_t)vertexAttributes.size(),
                                       vertexAttributes.data());
  }

  bool dynamicStride = dynamicStates[VkDynamicVertexInputBindingStride] &&
                       (vk->ExtendedDynamicState() || vk->ShaderObject());

  for(size_t i = 0; i < vbuffers.size(); i++)
  {
    if(vbuffers[i].buf == ResourceId())
    {
      if(vk->NULLDescriptorsAllowed())
      {
        VkBuffer empty = VK_NULL_HANDLE;

        if(dynamicStride)
          ObjDisp(cmd)->CmdBindVertexBuffers2EXT(
              Unwrap(cmd), (uint32_t)i, 1, &empty, &vbuffers[i].offs,
              vbuffers[i].size == VK_WHOLE_SIZE ? NULL : &vbuffers[i].size, &vbuffers[i].stride);
        else
          ObjDisp(cmd)->CmdBindVertexBuffers(Unwrap(cmd), (uint32_t)i, 1, &empty, &vbuffers[i].offs);
      }

      continue;
    }

    if(dynamicStride)
      ObjDisp(cmd)->CmdBindVertexBuffers2EXT(
          Unwrap(cmd), (uint32_t)i, 1,
          UnwrapPtr(vk->GetResourceManager()->GetHandle<VkBuffer>(vbuffers[i].buf)),
          &vbuffers[i].offs, vbuffers[i].size == VK_WHOLE_SIZE ? NULL : &vbuffers[i].size,
          &vbuffers[i].stride);
    else
      ObjDisp(cmd)->CmdBindVertexBuffers(
          Unwrap(cmd), (uint32_t)i, 1,
          UnwrapPtr(vk->GetResourceManager()->GetHandle<VkBuffer>(vbuffers[i].buf)),
          &vbuffers[i].offs);
  }

  for(size_t i = 0; i < xfbbuffers.size(); i++)
  {
    if(xfbbuffers[i].buf == ResourceId())
      continue;

    ObjDisp(cmd)->CmdBindTransformFeedbackBuffersEXT(
        Unwrap(cmd), (uint32_t)i, 1,
        UnwrapPtr(vk->GetResourceManager()->GetHandle<VkBuffer>(xfbbuffers[i].buf)),
        &xfbbuffers[i].offs, &xfbbuffers[i].size);
  }

  if(!xfbcounters.empty())
  {
    rdcarray<VkBuffer> buffers;
    rdcarray<VkDeviceSize> offsets;

    for(size_t i = 0; i < xfbcounters.size(); i++)
    {
      buffers.push_back(Unwrap(vk->GetResourceManager()->GetHandle<VkBuffer>(xfbcounters[i].buf)));
      offsets.push_back(xfbcounters[i].offs);
    }

    ObjDisp(cmd)->CmdBeginTransformFeedbackEXT(
        Unwrap(cmd), firstxfbcounter, (uint32_t)xfbcounters.size(), buffers.data(), offsets.data());
  }
}

void VulkanRenderState::BindDescriptorSetsForPipeline(WrappedVulkan *vk, VkCommandBuffer cmd,
                                                      VulkanStatePipeline &pipe,
                                                      VkPipelineBindPoint bindPoint)
{
  const rdcarray<ResourceId> &descSetLayouts =
      vk->GetDebugManager()->GetPipelineInfo(pipe.pipeline).descSetLayouts;

  for(size_t i = 0; i < descSetLayouts.size(); i++)
  {
    const DescSetLayout &descLayout = vk->GetDebugManager()->GetDescSetLayout(descSetLayouts[i]);

    if(i < pipe.descSets.size() && pipe.descSets[i].IsBound())
    {
      // if we come to a descriptor set that isn't compatible, stop setting descriptor sets from
      // here on.
      // We can get into this situation if for example we have many sets bound at some point, then
      // there's a pipeline change that causes most or all of them to be invalidated as
      // incompatible, then the program only re-binds some subset that it knows is statically used
      // by the next action. The remaining sets are invalid, but also unused and this is
      // explicitly allowed by the spec. We just have to make sure we don't try to actively bind
      // an incompatible descriptor set.
      ResourceId createdDescSetLayoutId;

      if(pipe.descSets[i].descSet != ResourceId())
      {
        createdDescSetLayoutId = vk->GetDescLayoutForDescSet(pipe.descSets[i].descSet);
      }
      else
      {
        const VulkanCreationInfo::PipelineLayout &refPipeLayout =
            vk->GetDebugManager()->GetPipelineLayoutInfo(pipe.descSets[i].pipeLayout);

        createdDescSetLayoutId = refPipeLayout.descSetLayouts[i];
      }

      if(descSetLayouts[i] != createdDescSetLayoutId)
      {
        const DescSetLayout &createdDescLayout =
            vk->GetDebugManager()->GetDescSetLayout(createdDescSetLayoutId);

        if(!descLayout.isCompatible(createdDescLayout))
        {
          // this set is incompatible, don't rebind it. Assume the application knows the shader
          // doesn't need this set, and the binding is just stale
          continue;
        }
      }

      // if there are dynamic buffers, pass along the offsets

      uint32_t *dynamicOffsets = NULL;

      if(descLayout.dynamicCount > 0)
      {
        dynamicOffsets = &pipe.descSets[i].offsets[0];

        if(pipe.descSets[i].offsets.size() < descLayout.dynamicCount)
        {
          dynamicOffsets = new uint32_t[descLayout.dynamicCount];
          for(uint32_t o = 0; o < descLayout.dynamicCount; o++)
          {
            if(o < pipe.descSets[i].offsets.size())
            {
              dynamicOffsets[o] = pipe.descSets[i].offsets[o];
            }
            else
            {
              dynamicOffsets[o] = 0;
              RDCWARN("Missing dynamic offset for set %u!", (uint32_t)i);
            }
          }
        }
      }

      BindDescriptorSet(vk, descLayout, cmd, bindPoint, (uint32_t)i, dynamicOffsets);

      if(pipe.descSets[i].offsets.size() < descLayout.dynamicCount)
        SAFE_DELETE_ARRAY(dynamicOffsets);
    }
  }
}

void VulkanRenderState::BindDescriptorSetsWithoutPipeline(WrappedVulkan *vk, VkCommandBuffer cmd,
                                                          VulkanStatePipeline &pipe,
                                                          VkPipelineBindPoint bindPoint)
{
  if(pipe.descSets.empty())
    return;

  // we try to bind descriptor sets before a pipeline when we don't have the knowledge that all sets
  // are up to date. This is used when perturbing state at an arbitrary point mid-record rather than
  // just before an action
  //
  // to do this we take the last known bound set as a 'reference' and bind everything that can be
  // compatible with it. Anything not compatible by definition has been invalidated so we don't need
  // to rebind it to be valid.

  uint32_t lastSet = pipe.LastBoundSet();
  ResourceId pipeLayoutId =
      lastSet < pipe.descSets.size() ? pipe.descSets[lastSet].pipeLayout : ResourceId();

  if(pipeLayoutId == ResourceId())
    return;

  const VulkanCreationInfo::PipelineLayout &refPipeLayout =
      vk->GetDebugManager()->GetPipelineLayoutInfo(pipeLayoutId);

  for(size_t i = 0; i < pipe.descSets.size(); i++)
  {
    if(pipe.descSets[i].pipeLayout == ResourceId() || !pipe.descSets[i].IsBound())
      continue;

    const VulkanCreationInfo::PipelineLayout &iPipeLayout =
        vk->GetDebugManager()->GetPipelineLayoutInfo(pipe.descSets[i].pipeLayout);

    if(i != pipe.LastBoundSet())
    {
      // if we come to a descriptor set that isn't compatible with the pipeline layout used in the
      // last bound set, don't bind this descriptor set
      // We can get into this situation if for example we have many sets bound at some point, then a
      // new descriptor set is bound to a different number which is incompatible and only that set
      // (and not the other stale ones) are needed by the next action. The remaining sets are
      // invalid, but also unused and this is explicitly allowed by the spec. We just have to make
      // sure we don't try to actively bind an incompatible descriptor set.

      // quick check, if the pipeline layout is the same as the one used to bind the reference set
      // then its certainly compatible
      if(pipe.descSets[i].pipeLayout != pipe.descSets[pipe.LastBoundSet()].pipeLayout)
      {
        // are we below or above the last bound set
        if(i < pipe.LastBoundSet())
        {
          // we only check if this set is compatible with the pipeline layout on this set.
          // Technically the set might have been perturbed still, or we might invalidate this
          // binding subsequently if there was some other difference between here and the last bound
          // set, but it's fine to bind a compatible set which would be invalid - it is undefined
          // behaviour to use it anyway. If this binding *should* be valid, it will still be
          // valid at the end.

          const DescSetLayout &iDescLayout =
              vk->GetDebugManager()->GetDescSetLayout(iPipeLayout.descSetLayouts[i]);
          const DescSetLayout &refDescLayout =
              vk->GetDebugManager()->GetDescSetLayout(refPipeLayout.descSetLayouts[i]);

          if(iPipeLayout.descSetLayouts[i] != refPipeLayout.descSetLayouts[i] &&
             !iDescLayout.isCompatible(refDescLayout))
          {
            // set is incompatible, don't rebind it
            continue;
          }
        }
        else
        {
          // when binding sets above the last bound set, we need to be careful not to accidentally
          // invalidate it or any previous sets it might have been compatible with.
          // so instead of only checking this set, we check all sets up to this one are compatible
          bool compatible = true;

          for(size_t j = 0; j <= i; j++)
          {
            // if this binding only exists in the current set's pipeline layout (e.g. the reference
            // pipeline layout only had 0..4 and this is 5) then it's automatically considered
            // compatible as everything in the reference layout was compatible up to this point
            if(j >= refPipeLayout.descSetLayouts.size())
              break;

            const DescSetLayout &iDescLayout =
                vk->GetDebugManager()->GetDescSetLayout(iPipeLayout.descSetLayouts[j]);
            const DescSetLayout &refDescLayout =
                vk->GetDebugManager()->GetDescSetLayout(refPipeLayout.descSetLayouts[j]);

            if(iPipeLayout.descSetLayouts[j] != refPipeLayout.descSetLayouts[j] &&
               !iDescLayout.isCompatible(refDescLayout))
            {
              compatible = false;
              break;
            }
          }

          if(!compatible)
            continue;
        }
      }
    }

    if(pipe.descSets[i].IsBound())
    {
      const DescSetLayout &descLayout =
          vk->GetDebugManager()->GetDescSetLayout(iPipeLayout.descSetLayouts[i]);

      // if there are dynamic buffers, pass along the offsets

      uint32_t *dynamicOffsets = NULL;

      if(descLayout.dynamicCount > 0)
      {
        dynamicOffsets = &pipe.descSets[i].offsets[0];

        if(pipe.descSets[i].offsets.size() < descLayout.dynamicCount)
        {
          dynamicOffsets = new uint32_t[descLayout.dynamicCount];
          for(uint32_t o = 0; o < descLayout.dynamicCount; o++)
          {
            if(o < pipe.descSets[i].offsets.size())
            {
              dynamicOffsets[o] = pipe.descSets[i].offsets[o];
            }
            else
            {
              dynamicOffsets[o] = 0;
              RDCWARN("Missing dynamic offset for set %u!", (uint32_t)i);
            }
          }
        }
      }

      BindDescriptorSet(vk, descLayout, cmd, bindPoint, (uint32_t)i, dynamicOffsets);

      if(pipe.descSets[i].offsets.size() < descLayout.dynamicCount)
        SAFE_DELETE_ARRAY(dynamicOffsets);
    }
  }
}

void VulkanRenderState::BindDescriptorSetsForShaders(WrappedVulkan *vk, VkCommandBuffer cmd,
                                                     VulkanStatePipeline &pipe,
                                                     VkPipelineBindPoint bindPoint)
{
  if(pipe.descSets.empty())
    return;

  uint32_t lastSet = pipe.LastBoundSet();
  ResourceId pipeLayoutId =
      lastSet < pipe.descSets.size() ? pipe.descSets[lastSet].pipeLayout : ResourceId();

  if(pipeLayoutId == ResourceId())
    return;

  const rdcarray<ResourceId> &descSetLayouts =
      vk->GetDebugManager()->GetPipelineLayoutInfo(pipeLayoutId).descSetLayouts;

  for(size_t i = 0; i < descSetLayouts.size(); i++)
  {
    const DescSetLayout &descLayout = vk->GetDebugManager()->GetDescSetLayout(descSetLayouts[i]);

    if(i < pipe.descSets.size() && pipe.descSets[i].IsBound())
    {
      // if we come to a descriptor set that isn't compatible, stop setting descriptor sets from
      // here on.
      // We can get into this situation if for example we have many sets bound at some point, then
      // there's a pipeline change that causes most or all of them to be invalidated as
      // incompatible, then the program only re-binds some subset that it knows is statically used
      // by the next action. The remaining sets are invalid, but also unused and this is
      // explicitly allowed by the spec. We just have to make sure we don't try to actively bind
      // an incompatible descriptor set.
      ResourceId createdDescSetLayoutId;

      if(pipe.descSets[i].descSet != ResourceId())
      {
        createdDescSetLayoutId = vk->GetDescLayoutForDescSet(pipe.descSets[i].descSet);
      }
      else
      {
        const VulkanCreationInfo::PipelineLayout &refPipeLayout =
            vk->GetDebugManager()->GetPipelineLayoutInfo(pipe.descSets[i].pipeLayout);

        createdDescSetLayoutId = refPipeLayout.descSetLayouts[i];
      }

      if(descSetLayouts[i] != createdDescSetLayoutId)
      {
        const DescSetLayout &createdDescLayout =
            vk->GetDebugManager()->GetDescSetLayout(createdDescSetLayoutId);

        if(!descLayout.isCompatible(createdDescLayout))
        {
          // this set is incompatible, don't rebind it. Assume the application knows the shader
          // doesn't need this set, and the binding is just stale
          continue;
        }
      }

      // if there are dynamic buffers, pass along the offsets

      uint32_t *dynamicOffsets = NULL;

      if(descLayout.dynamicCount > 0)
      {
        dynamicOffsets = &pipe.descSets[i].offsets[0];

        if(pipe.descSets[i].offsets.size() < descLayout.dynamicCount)
        {
          dynamicOffsets = new uint32_t[descLayout.dynamicCount];
          for(uint32_t o = 0; o < descLayout.dynamicCount; o++)
          {
            if(o < pipe.descSets[i].offsets.size())
            {
              dynamicOffsets[o] = pipe.descSets[i].offsets[o];
            }
            else
            {
              dynamicOffsets[o] = 0;
              RDCWARN("Missing dynamic offset for set %u!", (uint32_t)i);
            }
          }
        }
      }

      BindDescriptorSet(vk, descLayout, cmd, bindPoint, (uint32_t)i, dynamicOffsets);

      if(pipe.descSets[i].offsets.size() < descLayout.dynamicCount)
        SAFE_DELETE_ARRAY(dynamicOffsets);
    }
  }
}

void VulkanRenderState::BindDescriptorSet(WrappedVulkan *vk, const DescSetLayout &descLayout,
                                          VkCommandBuffer cmd, VkPipelineBindPoint bindPoint,
                                          uint32_t setIndex, uint32_t *dynamicOffsets)
{
  const VulkanStatePipeline::DescriptorAndOffsets &desc = GetPipeline(bindPoint).descSets[setIndex];
  ResourceId descSet = desc.descSet;
  ResourceId pipeLayout = desc.pipeLayout;
  VkPipelineLayout layout = vk->GetResourceManager()->GetHandle<VkPipelineLayout>(pipeLayout);

  if((descLayout.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT) == 0)
  {
    if(descSet != ResourceId())
    {
      ObjDisp(cmd)->CmdBindDescriptorSets(
          Unwrap(cmd), bindPoint, Unwrap(layout), setIndex, 1,
          UnwrapPtr(vk->GetResourceManager()->GetHandle<VkDescriptorSet>(descSet)),
          descLayout.dynamicCount, dynamicOffsets);
    }
    else if(desc.descBufferEmbeddedSamplers)
    {
      ObjDisp(cmd)->CmdBindDescriptorBufferEmbeddedSamplersEXT(Unwrap(cmd), bindPoint,
                                                               Unwrap(layout), setIndex);
    }
    else
    {
      ObjDisp(cmd)->CmdSetDescriptorBufferOffsetsEXT(Unwrap(cmd), bindPoint, Unwrap(layout), setIndex,
                                                     1, &desc.descBufferIdx, &desc.descBufferOffset);
    }
  }
  else
  {
    // this isn't a real descriptor set, it's a push descriptor, so we need to push the
    // current state.
    rdcarray<VkWriteDescriptorSet> writes;

    // for descriptor buffer there may be a buffer to bind still
    if(desc.descBufferIdx != ~0U)
    {
      ObjDisp(cmd)->CmdSetDescriptorBufferOffsetsEXT(Unwrap(cmd), bindPoint, Unwrap(layout), setIndex,
                                                     1, &desc.descBufferIdx, &desc.descBufferOffset);
    }

    // any allocated arrays
    rdcarray<VkDescriptorImageInfo *> allocImgWrites;
    rdcarray<VkDescriptorBufferInfo *> allocBufWrites;
    rdcarray<VkBufferView *> allocBufViewWrites;
    rdcarray<VkWriteDescriptorSetInlineUniformBlock *> allocInlineWrites;
    rdcarray<VkWriteDescriptorSetAccelerationStructureKHR *> allocASWrites;

    const WrappedVulkan::DescriptorSetInfo &setInfo = vk->GetDebugManager()->GetDescSetInfo(descSet);

    VkWriteDescriptorSet push = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};

    VulkanResourceManager *rm = vk->GetResourceManager();

    for(size_t b = 0; b < descLayout.bindings.size(); b++)
    {
      const DescSetLayout::Binding &layoutBind = descLayout.bindings[b];

      // skip if this binding isn't used
      if(layoutBind.layoutDescType == VK_DESCRIPTOR_TYPE_MAX_ENUM)
        continue;

      // push.dstSet; // unused for push descriptors
      push.dstBinding = (uint32_t)b;
      push.dstArrayElement = 0;
      // we can use the layout type here, since push descriptors are not allowed to be mutable
      push.descriptorType = layoutBind.layoutDescType;
      push.descriptorCount = layoutBind.descriptorCount;

      const DescriptorSetSlot *slots = setInfo.data.binds[b];

      if(push.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER ||
         push.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER)
      {
        VkBufferView *dst = new VkBufferView[push.descriptorCount];

        for(uint32_t a = 0; a < push.descriptorCount; a++)
          dst[a] = Unwrap(rm->GetHandle<VkBufferView>(slots[a].resource));

        push.pTexelBufferView = dst;
        allocBufViewWrites.push_back(dst);
      }
      else if(push.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ||
              push.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
              push.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
              push.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
              push.descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)
      {
        VkDescriptorImageInfo *dst = new VkDescriptorImageInfo[push.descriptorCount];

        for(uint32_t a = 0; a < push.descriptorCount; a++)
        {
          dst[a].imageLayout = convert(slots[a].imageLayoutOrFormat);
          dst[a].sampler = Unwrap(rm->GetHandle<VkSampler>(slots[a].sampler));
          dst[a].imageView = Unwrap(rm->GetHandle<VkImageView>(slots[a].resource));

          if(layoutBind.immutableSampler && push.descriptorType != VK_DESCRIPTOR_TYPE_SAMPLER)
            dst[a].sampler = Unwrap(rm->GetHandle<VkSampler>(layoutBind.immutableSampler[a]));
        }

        push.pImageInfo = dst;
        allocImgWrites.push_back(dst);
      }
      else if(push.descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK)
      {
        allocInlineWrites.push_back(new VkWriteDescriptorSetInlineUniformBlock);
        VkWriteDescriptorSetInlineUniformBlock *inlineWrite = allocInlineWrites.back();
        inlineWrite->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK;
        inlineWrite->pNext = NULL;
        inlineWrite->dataSize = layoutBind.descriptorCount;
        inlineWrite->pData = setInfo.data.inlineBytes.data() + slots[0].offset;

        push.pNext = inlineWrite;
        push.descriptorCount = layoutBind.descriptorCount;
        writes.push_back(push);

        // skip validity checks
        continue;
      }
      else if(push.descriptorType == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR)
      {
        VkAccelerationStructureKHR *dst = new VkAccelerationStructureKHR[push.descriptorCount];
        for(uint32_t a = 0; a < push.descriptorCount; a++)
          dst[a] = Unwrap(rm->GetHandle<VkAccelerationStructureKHR>(slots[a].resource));

        allocASWrites.push_back(new VkWriteDescriptorSetAccelerationStructureKHR);
        VkWriteDescriptorSetAccelerationStructureKHR *asWrite = allocASWrites.back();

        asWrite->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
        asWrite->pNext = NULL;
        asWrite->accelerationStructureCount = layoutBind.descriptorCount;
        asWrite->pAccelerationStructures = dst;

        push.pNext = asWrite;
        push.descriptorCount = layoutBind.descriptorCount;
        writes.push_back(push);
      }
      else
      {
        VkDescriptorBufferInfo *dst = new VkDescriptorBufferInfo[push.descriptorCount];

        for(uint32_t a = 0; a < push.descriptorCount; a++)
        {
          dst[a].offset = slots[a].offset;
          dst[a].range = slots[a].GetRange();
          dst[a].buffer = Unwrap(rm->GetHandle<VkBuffer>(slots[a].resource));
        }

        push.pBufferInfo = dst;
        allocBufWrites.push_back(dst);
      }

      // start with no descriptors
      push.descriptorCount = 0;

      for(uint32_t w = 0; w < layoutBind.descriptorCount; w++)
      {
        // if this push is valid, we increment the descriptor count and continue
        if(IsValid(vk->NULLDescriptorsAllowed(), push, w - push.dstArrayElement))
        {
          push.descriptorCount++;
        }
        else
        {
          // if this push isn't valid, then we first check to see if we had any previous
          // pending pushs in the array we were going to batch together, if so we add them.
          if(push.descriptorCount > 0)
            writes.push_back(push);

          // skip past any previous descriptors we just wrote, as well as the current invalid
          // one
          if(push.pBufferInfo)
            push.pBufferInfo += push.descriptorCount + 1;
          if(push.pImageInfo)
            push.pImageInfo += push.descriptorCount + 1;
          if(push.pTexelBufferView)
            push.pTexelBufferView += push.descriptorCount + 1;

          // now start again from 0 descriptors, at the next array element
          push.dstArrayElement += push.descriptorCount + 1;
          push.descriptorCount = 0;
        }
      }

      // if there are any left, add them here
      if(push.descriptorCount > 0)
        writes.push_back(push);

      // don't leak the arrays and cause double deletes, NULL them after each time
      push.pImageInfo = NULL;
      push.pBufferInfo = NULL;
      push.pTexelBufferView = NULL;
    }

    ObjDisp(cmd)->CmdPushDescriptorSet(Unwrap(cmd), bindPoint, Unwrap(layout), setIndex,
                                       (uint32_t)writes.size(), writes.data());

    // delete allocated arrays for descriptor writes
    for(VkDescriptorBufferInfo *a : allocBufWrites)
      delete[] a;
    for(VkDescriptorImageInfo *a : allocImgWrites)
      delete[] a;
    for(VkBufferView *a : allocBufViewWrites)
      delete[] a;
    for(VkWriteDescriptorSetInlineUniformBlock *a : allocInlineWrites)
      delete a;
    for(VkWriteDescriptorSetAccelerationStructureKHR *d : allocASWrites)
    {
      delete[] d->pAccelerationStructures;
      delete d;
    }
  }
}

void VulkanRenderState::SetDynamicStatesFromPipeline(WrappedVulkan *vk)
{
  dynamicStates = vk->GetDebugManager()->GetPipelineInfo(graphics.pipeline).dynamicStates;
}

void VulkanRenderState::SetFramebuffer(WrappedVulkan *vk, ResourceId fb,
                                       const VkRenderPassAttachmentBeginInfo *attachmentsInfo)
{
  framebuffer = fb;

  VulkanCreationInfo::Framebuffer fbinfo = vk->GetDebugManager()->GetFramebufferInfo(fb);

  fbattachments.resize(fbinfo.attachments.size());

  if(!fbinfo.imageless)
  {
    for(size_t i = 0; i < fbinfo.attachments.size(); i++)
      fbattachments[i] = fbinfo.attachments[i].createdView;
  }
  else
  {
    for(size_t i = 0; i < fbinfo.attachments.size(); i++)
      fbattachments[i] = GetResID(attachmentsInfo->pAttachments[i]);
  }
}

void VulkanRenderState::BindLastPushConstants(WrappedVulkan *vk, VkCommandBuffer cmd)
{
  if(pushLayout != ResourceId())
  {
    // set push constants with the last layout used
    VkPipelineLayout layout = vk->GetResourceManager()->GetHandle<VkPipelineLayout>(pushLayout);

    const rdcarray<VkPushConstantRange> &pushRanges =
        vk->GetDebugManager()->GetPipelineLayoutInfo(pushLayout).pushRanges;

    // only set push constant ranges that the layout uses
    for(size_t i = 0; i < pushRanges.size(); i++)
      ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(layout), pushRanges[i].stageFlags,
                                     pushRanges[i].offset, pushRanges[i].size,
                                     pushconsts + pushRanges[i].offset);
  }
}
