Exemplo n.º 1
0
VkResult WrappedVulkan::vkWaitForFences(
			VkDevice                                device,
			uint32_t                                fenceCount,
			const VkFence*                          pFences,
			VkBool32                                waitAll,
			uint64_t                                timeout)
{
	SCOPED_DBG_SINK();

	VkFence *unwrapped = GetTempArray<VkFence>(fenceCount);
	for (uint32_t i = 0; i < fenceCount; i++) unwrapped[i] = Unwrap(pFences[i]);
	VkResult ret = ObjDisp(device)->WaitForFences(Unwrap(device), fenceCount, unwrapped, waitAll, timeout);
	
	if(m_State >= WRITING_CAPFRAME)
	{
		CACHE_THREAD_SERIALISER();

		SCOPED_SERIALISE_CONTEXT(WAIT_FENCES);
		Serialise_vkWaitForFences(localSerialiser, device, fenceCount, pFences, waitAll, timeout);

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

	return ret;
}
Exemplo n.º 2
0
void WrappedVulkan::vkCmdSetBlendConstants(VkCommandBuffer cmdBuffer, const float *blendConst)
{
  SCOPED_DBG_SINK();

  ObjDisp(cmdBuffer)->CmdSetBlendConstants(Unwrap(cmdBuffer), blendConst);

  if(m_State >= WRITING)
  {
    VkResourceRecord *record = GetRecord(cmdBuffer);

    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CONTEXT(SET_BLEND_CONST);
    Serialise_vkCmdSetBlendConstants(localSerialiser, cmdBuffer, blendConst);

    record->AddChunk(scope.Get());
  }
}
Exemplo n.º 3
0
void WrappedVulkan::vkCmdSetLineWidth(VkCommandBuffer cmdBuffer, float lineWidth)
{
  SCOPED_DBG_SINK();

  ObjDisp(cmdBuffer)->CmdSetLineWidth(Unwrap(cmdBuffer), lineWidth);

  if(m_State >= WRITING)
  {
    VkResourceRecord *record = GetRecord(cmdBuffer);

    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CONTEXT(SET_LINE_WIDTH);
    Serialise_vkCmdSetLineWidth(localSerialiser, cmdBuffer, lineWidth);

    record->AddChunk(scope.Get());
  }
}
Exemplo n.º 4
0
void WrappedVulkan::vkCmdSetViewport(VkCommandBuffer cmdBuffer, uint32_t firstViewport,
                                     uint32_t viewportCount, const VkViewport *pViewports)
{
  SCOPED_DBG_SINK();

  ObjDisp(cmdBuffer)->CmdSetViewport(Unwrap(cmdBuffer), firstViewport, viewportCount, pViewports);

  if(m_State >= WRITING)
  {
    VkResourceRecord *record = GetRecord(cmdBuffer);

    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CONTEXT(SET_VP);
    Serialise_vkCmdSetViewport(localSerialiser, cmdBuffer, firstViewport, viewportCount, pViewports);

    record->AddChunk(scope.Get());
  }
}
Exemplo n.º 5
0
void WrappedVulkan::vkCmdSetStencilReference(VkCommandBuffer cmdBuffer, VkStencilFaceFlags faceMask,
                                             uint32_t reference)
{
  SCOPED_DBG_SINK();

  ObjDisp(cmdBuffer)->CmdSetStencilReference(Unwrap(cmdBuffer), faceMask, reference);

  if(m_State >= WRITING)
  {
    VkResourceRecord *record = GetRecord(cmdBuffer);

    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CONTEXT(SET_STENCIL_REF);
    Serialise_vkCmdSetStencilReference(localSerialiser, cmdBuffer, faceMask, reference);

    record->AddChunk(scope.Get());
  }
}
Exemplo n.º 6
0
void WrappedVulkan::vkCmdSetDepthBounds(VkCommandBuffer cmdBuffer, float minDepthBounds,
                                        float maxDepthBounds)
{
  SCOPED_DBG_SINK();

  ObjDisp(cmdBuffer)->CmdSetDepthBounds(Unwrap(cmdBuffer), minDepthBounds, maxDepthBounds);

  if(m_State >= WRITING)
  {
    VkResourceRecord *record = GetRecord(cmdBuffer);

    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CONTEXT(SET_DEPTH_BOUNDS);
    Serialise_vkCmdSetDepthBounds(localSerialiser, cmdBuffer, minDepthBounds, maxDepthBounds);

    record->AddChunk(scope.Get());
  }
}
Exemplo n.º 7
0
void WrappedVulkan::vkCmdSetScissor(VkCommandBuffer cmdBuffer, uint32_t firstScissor,
                                    uint32_t scissorCount, const VkRect2D *pScissors)
{
  SCOPED_DBG_SINK();

  ObjDisp(cmdBuffer)->CmdSetScissor(Unwrap(cmdBuffer), firstScissor, scissorCount, pScissors);

  if(m_State >= WRITING)
  {
    VkResourceRecord *record = GetRecord(cmdBuffer);

    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CONTEXT(SET_SCISSOR);
    Serialise_vkCmdSetScissor(localSerialiser, cmdBuffer, firstScissor, scissorCount, pScissors);

    record->AddChunk(scope.Get());
  }
}
Exemplo n.º 8
0
VkResult WrappedVulkan::vkGetEventStatus(
			VkDevice                                device,
			VkEvent                                 event)
{
	SCOPED_DBG_SINK();

	VkResult ret = ObjDisp(device)->GetEventStatus(Unwrap(device), Unwrap(event));
	
	if(m_State >= WRITING_CAPFRAME)
	{
		CACHE_THREAD_SERIALISER();

		SCOPED_SERIALISE_CONTEXT(GET_EVENT_STATUS);
		Serialise_vkGetEventStatus(localSerialiser, device, event);

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

	return ret;
}
Exemplo n.º 9
0
void WrappedVulkan::vkCmdSetDepthBias(VkCommandBuffer cmdBuffer, float depthBias,
                                      float depthBiasClamp, float slopeScaledDepthBias)
{
  SCOPED_DBG_SINK();

  ObjDisp(cmdBuffer)->CmdSetDepthBias(Unwrap(cmdBuffer), depthBias, depthBiasClamp,
                                      slopeScaledDepthBias);

  if(m_State >= WRITING)
  {
    VkResourceRecord *record = GetRecord(cmdBuffer);

    CACHE_THREAD_SERIALISER();

    SCOPED_SERIALISE_CONTEXT(SET_DEPTH_BIAS);
    Serialise_vkCmdSetDepthBias(localSerialiser, cmdBuffer, depthBias, depthBiasClamp,
                                slopeScaledDepthBias);

    record->AddChunk(scope.Get());
  }
}
Exemplo n.º 10
0
void WrappedVulkan::vkCmdResetEvent(
    VkCommandBuffer                                 cmdBuffer,
    VkEvent                                     event,
		VkPipelineStageFlags                        stageMask)
{
	SCOPED_DBG_SINK();

	ObjDisp(cmdBuffer)->CmdResetEvent(Unwrap(cmdBuffer), Unwrap(event), stageMask);

	if(m_State >= WRITING)
	{
		VkResourceRecord *record = GetRecord(cmdBuffer);

		CACHE_THREAD_SERIALISER();

		SCOPED_SERIALISE_CONTEXT(CMD_RESET_EVENT);
		Serialise_vkCmdResetEvent(localSerialiser, cmdBuffer, event, stageMask);

		record->AddChunk(scope.Get());
		record->MarkResourceFrameReferenced(GetResID(event), eFrameRef_Read);
	}
}
Exemplo n.º 11
0
VkResult WrappedVulkan::vkQueueSubmit(VkQueue queue, uint32_t submitCount,
                                      const VkSubmitInfo *pSubmits, VkFence fence)
{
  SCOPED_DBG_SINK();

  size_t tempmemSize = sizeof(VkSubmitInfo) * submitCount;

  // need to count how many semaphore and command buffer arrays to allocate for
  for(uint32_t i = 0; i < submitCount; i++)
  {
    tempmemSize += pSubmits[i].commandBufferCount * sizeof(VkCommandBuffer);
    tempmemSize += pSubmits[i].signalSemaphoreCount * sizeof(VkSemaphore);
    tempmemSize += pSubmits[i].waitSemaphoreCount * sizeof(VkSemaphore);
  }

  byte *memory = GetTempMemory(tempmemSize);

  VkSubmitInfo *unwrappedSubmits = (VkSubmitInfo *)memory;
  VkSemaphore *unwrappedWaitSems = (VkSemaphore *)(unwrappedSubmits + submitCount);

  for(uint32_t i = 0; i < submitCount; i++)
  {
    RDCASSERT(pSubmits[i].sType == VK_STRUCTURE_TYPE_SUBMIT_INFO && pSubmits[i].pNext == NULL);
    unwrappedSubmits[i] = pSubmits[i];

    unwrappedSubmits[i].pWaitSemaphores =
        unwrappedSubmits[i].waitSemaphoreCount ? unwrappedWaitSems : NULL;
    for(uint32_t o = 0; o < unwrappedSubmits[i].waitSemaphoreCount; o++)
      unwrappedWaitSems[o] = Unwrap(pSubmits[i].pWaitSemaphores[o]);
    unwrappedWaitSems += unwrappedSubmits[i].waitSemaphoreCount;

    VkCommandBuffer *unwrappedCommandBuffers = (VkCommandBuffer *)unwrappedWaitSems;

    unwrappedSubmits[i].pCommandBuffers =
        unwrappedSubmits[i].commandBufferCount ? unwrappedCommandBuffers : NULL;
    for(uint32_t o = 0; o < unwrappedSubmits[i].commandBufferCount; o++)
      unwrappedCommandBuffers[o] = Unwrap(pSubmits[i].pCommandBuffers[o]);
    unwrappedCommandBuffers += unwrappedSubmits[i].commandBufferCount;

    VkSemaphore *unwrappedSignalSems = (VkSemaphore *)unwrappedCommandBuffers;

    unwrappedSubmits[i].pSignalSemaphores =
        unwrappedSubmits[i].signalSemaphoreCount ? unwrappedSignalSems : NULL;
    for(uint32_t o = 0; o < unwrappedSubmits[i].signalSemaphoreCount; o++)
      unwrappedSignalSems[o] = Unwrap(pSubmits[i].pSignalSemaphores[o]);
  }

  VkResult ret =
      ObjDisp(queue)->QueueSubmit(Unwrap(queue), submitCount, unwrappedSubmits, Unwrap(fence));

  bool capframe = false;
  set<ResourceId> refdIDs;

  for(uint32_t s = 0; s < submitCount; s++)
  {
    for(uint32_t i = 0; i < pSubmits[s].commandBufferCount; i++)
    {
      ResourceId cmd = GetResID(pSubmits[s].pCommandBuffers[i]);

      VkResourceRecord *record = GetRecord(pSubmits[s].pCommandBuffers[i]);

      {
        SCOPED_LOCK(m_ImageLayoutsLock);
        GetResourceManager()->ApplyBarriers(record->bakedCommands->cmdInfo->imgbarriers,
                                            m_ImageLayouts);
      }

      // need to lock the whole section of code, not just the check on
      // m_State, as we also need to make sure we don't check the state,
      // start marking dirty resources then while we're doing so the
      // state becomes capframe.
      // the next sections where we mark resources referenced and add
      // the submit chunk to the frame record don't have to be protected.
      // Only the decision of whether we're inframe or not, and marking
      // dirty.
      {
        SCOPED_LOCK(m_CapTransitionLock);
        if(m_State == WRITING_CAPFRAME)
        {
          for(auto it = record->bakedCommands->cmdInfo->dirtied.begin();
              it != record->bakedCommands->cmdInfo->dirtied.end(); ++it)
            GetResourceManager()->MarkPendingDirty(*it);

          capframe = true;
        }
        else
        {
          for(auto it = record->bakedCommands->cmdInfo->dirtied.begin();
              it != record->bakedCommands->cmdInfo->dirtied.end(); ++it)
            GetResourceManager()->MarkDirtyResource(*it);
        }
      }

      if(capframe)
      {
        // for each bound descriptor set, mark it referenced as well as all resources currently
        // bound to it
        for(auto it = record->bakedCommands->cmdInfo->boundDescSets.begin();
            it != record->bakedCommands->cmdInfo->boundDescSets.end(); ++it)
        {
          GetResourceManager()->MarkResourceFrameReferenced(GetResID(*it), eFrameRef_Read);

          VkResourceRecord *setrecord = GetRecord(*it);

          for(auto refit = setrecord->descInfo->bindFrameRefs.begin();
              refit != setrecord->descInfo->bindFrameRefs.end(); ++refit)
          {
            refdIDs.insert(refit->first);
            GetResourceManager()->MarkResourceFrameReferenced(refit->first, refit->second.second);

            if(refit->second.first & DescriptorSetData::SPARSE_REF_BIT)
            {
              VkResourceRecord *sparserecord = GetResourceManager()->GetResourceRecord(refit->first);

              GetResourceManager()->MarkSparseMapReferenced(sparserecord->sparseInfo);
            }
          }
        }

        for(auto it = record->bakedCommands->cmdInfo->sparse.begin();
            it != record->bakedCommands->cmdInfo->sparse.end(); ++it)
          GetResourceManager()->MarkSparseMapReferenced(*it);

        // pull in frame refs from this baked command buffer
        record->bakedCommands->AddResourceReferences(GetResourceManager());
        record->bakedCommands->AddReferencedIDs(refdIDs);

        // ref the parent command buffer by itself, this will pull in the cmd buffer pool
        GetResourceManager()->MarkResourceFrameReferenced(record->GetResourceID(), eFrameRef_Read);

        for(size_t sub = 0; sub < record->bakedCommands->cmdInfo->subcmds.size(); sub++)
        {
          record->bakedCommands->cmdInfo->subcmds[sub]->bakedCommands->AddResourceReferences(
              GetResourceManager());
          record->bakedCommands->cmdInfo->subcmds[sub]->bakedCommands->AddReferencedIDs(refdIDs);
          GetResourceManager()->MarkResourceFrameReferenced(
              record->bakedCommands->cmdInfo->subcmds[sub]->GetResourceID(), eFrameRef_Read);

          record->bakedCommands->cmdInfo->subcmds[sub]->bakedCommands->AddRef();
        }

        GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read);

        if(fence != VK_NULL_HANDLE)
          GetResourceManager()->MarkResourceFrameReferenced(GetResID(fence), eFrameRef_Read);

        {
          SCOPED_LOCK(m_CmdBufferRecordsLock);
          m_CmdBufferRecords.push_back(record->bakedCommands);
          for(size_t sub = 0; sub < record->bakedCommands->cmdInfo->subcmds.size(); sub++)
            m_CmdBufferRecords.push_back(record->bakedCommands->cmdInfo->subcmds[sub]->bakedCommands);
        }

        record->bakedCommands->AddRef();
      }

      record->cmdInfo->dirtied.clear();
    }
  }

  if(capframe)
  {
    vector<VkResourceRecord *> maps;
    {
      SCOPED_LOCK(m_CoherentMapsLock);
      maps = m_CoherentMaps;
    }

    for(auto it = maps.begin(); it != maps.end(); ++it)
    {
      VkResourceRecord *record = *it;
      MemMapState &state = *record->memMapState;

      // potential persistent map
      if(state.mapCoherent && state.mappedPtr && !state.mapFlushed)
      {
        // only need to flush memory that could affect this submitted batch of work
        if(refdIDs.find(record->GetResourceID()) == refdIDs.end())
        {
          RDCDEBUG("Map of memory %llu not referenced in this queue - not flushing",
                   record->GetResourceID());
          continue;
        }

        size_t diffStart = 0, diffEnd = 0;
        bool found = true;

// enabled as this is necessary for programs with very large coherent mappings
// (> 1GB) as otherwise more than a couple of vkQueueSubmit calls leads to vast
// memory allocation. There might still be bugs lurking in here though
#if 1
        // this causes vkFlushMappedMemoryRanges call to allocate and copy to refData
        // from serialised buffer. We want to copy *precisely* the serialised data,
        // otherwise there is a gap in time between serialising out a snapshot of
        // the buffer and whenever we then copy into the ref data, e.g. below.
        // during this time, data could be written to the buffer and it won't have
        // been caught in the serialised snapshot, and if it doesn't change then
        // it *also* won't be caught in any future FindDiffRange() calls.
        //
        // Likewise once refData is allocated, the call below will also update it
        // with the data serialised out for the same reason.
        //
        // Note: it's still possible that data is being written to by the
        // application while it's being serialised out in the snapshot below. That
        // is OK, since the application is responsible for ensuring it's not writing
        // data that would be needed by the GPU in this submit. As long as the
        // refdata we use for future use is identical to what was serialised, we
        // shouldn't miss anything
        state.needRefData = true;

        // if we have a previous set of data, compare.
        // otherwise just serialise it all
        if(state.refData)
          found = FindDiffRange((byte *)state.mappedPtr, state.refData, (size_t)state.mapSize,
                                diffStart, diffEnd);
        else
#endif
          diffEnd = (size_t)state.mapSize;

        if(found)
        {
          // MULTIDEVICE should find the device for this queue.
          // MULTIDEVICE only want to flush maps associated with this queue
          VkDevice dev = GetDev();

          {
            RDCLOG("Persistent map flush forced for %llu (%llu -> %llu)", record->GetResourceID(),
                   (uint64_t)diffStart, (uint64_t)diffEnd);
            VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, NULL,
                                         (VkDeviceMemory)(uint64_t)record->Resource,
                                         state.mapOffset + diffStart, diffEnd - diffStart};
            vkFlushMappedMemoryRanges(dev, 1, &range);
            state.mapFlushed = false;
          }

          GetResourceManager()->MarkPendingDirty(record->GetResourceID());
        }
        else
        {
          RDCDEBUG("Persistent map flush not needed for %llu", record->GetResourceID());
        }
      }
    }

    {
      CACHE_THREAD_SERIALISER();

      for(uint32_t s = 0; s < submitCount; s++)
      {
        SCOPED_SERIALISE_CONTEXT(QUEUE_SUBMIT);
        Serialise_vkQueueSubmit(localSerialiser, queue, 1, &pSubmits[s], fence);

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

        for(uint32_t sem = 0; sem < pSubmits[s].waitSemaphoreCount; sem++)
          GetResourceManager()->MarkResourceFrameReferenced(
              GetResID(pSubmits[s].pWaitSemaphores[sem]), eFrameRef_Read);

        for(uint32_t sem = 0; sem < pSubmits[s].signalSemaphoreCount; sem++)
          GetResourceManager()->MarkResourceFrameReferenced(
              GetResID(pSubmits[s].pSignalSemaphores[sem]), eFrameRef_Read);
      }
    }
  }

  return ret;
}
Exemplo n.º 12
0
void WrappedVulkan::vkUpdateDescriptorSets(VkDevice device, uint32_t writeCount,
                                           const VkWriteDescriptorSet *pDescriptorWrites,
                                           uint32_t copyCount,
                                           const VkCopyDescriptorSet *pDescriptorCopies)
{
  SCOPED_DBG_SINK();

  {
    // need to count up number of descriptor infos, to be able to alloc enough space
    uint32_t numInfos = 0;
    for(uint32_t i = 0; i < writeCount; i++)
      numInfos += pDescriptorWrites[i].descriptorCount;

    byte *memory = GetTempMemory(sizeof(VkDescriptorBufferInfo) * numInfos +
                                 sizeof(VkWriteDescriptorSet) * writeCount +
                                 sizeof(VkCopyDescriptorSet) * copyCount);

    RDCCOMPILE_ASSERT(sizeof(VkDescriptorBufferInfo) >= sizeof(VkDescriptorImageInfo),
                      "Descriptor structs sizes are unexpected, ensure largest size is used");

    VkWriteDescriptorSet *unwrappedWrites = (VkWriteDescriptorSet *)memory;
    VkCopyDescriptorSet *unwrappedCopies = (VkCopyDescriptorSet *)(unwrappedWrites + writeCount);
    VkDescriptorBufferInfo *nextDescriptors = (VkDescriptorBufferInfo *)(unwrappedCopies + copyCount);

    for(uint32_t i = 0; i < writeCount; i++)
    {
      unwrappedWrites[i] = pDescriptorWrites[i];
      unwrappedWrites[i].dstSet = Unwrap(unwrappedWrites[i].dstSet);

      VkDescriptorBufferInfo *bufInfos = nextDescriptors;
      VkDescriptorImageInfo *imInfos = (VkDescriptorImageInfo *)bufInfos;
      VkBufferView *bufViews = (VkBufferView *)bufInfos;
      nextDescriptors += pDescriptorWrites[i].descriptorCount;

      RDCCOMPILE_ASSERT(sizeof(VkDescriptorBufferInfo) >= sizeof(VkDescriptorImageInfo),
                        "Structure sizes mean not enough space is allocated for write data");
      RDCCOMPILE_ASSERT(sizeof(VkDescriptorBufferInfo) >= sizeof(VkBufferView),
                        "Structure sizes mean not enough space is allocated for write data");

      // unwrap and assign the appropriate array
      if(pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER ||
         pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER)
      {
        unwrappedWrites[i].pTexelBufferView = (VkBufferView *)bufInfos;
        for(uint32_t j = 0; j < pDescriptorWrites[i].descriptorCount; j++)
          bufViews[j] = Unwrap(pDescriptorWrites[i].pTexelBufferView[j]);
      }
      else if(pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ||
              pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
              pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
              pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
              pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)
      {
        bool hasSampler =
            (pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ||
             pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
        bool hasImage =
            (pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
             pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
             pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
             pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT);

        unwrappedWrites[i].pImageInfo = (VkDescriptorImageInfo *)bufInfos;
        for(uint32_t j = 0; j < pDescriptorWrites[i].descriptorCount; j++)
        {
          if(hasImage)
            imInfos[j].imageView = Unwrap(pDescriptorWrites[i].pImageInfo[j].imageView);
          if(hasSampler)
            imInfos[j].sampler = Unwrap(pDescriptorWrites[i].pImageInfo[j].sampler);
          imInfos[j].imageLayout = pDescriptorWrites[i].pImageInfo[j].imageLayout;
        }
      }
      else
      {
        unwrappedWrites[i].pBufferInfo = bufInfos;
        for(uint32_t j = 0; j < pDescriptorWrites[i].descriptorCount; j++)
        {
          bufInfos[j].buffer = Unwrap(pDescriptorWrites[i].pBufferInfo[j].buffer);
          bufInfos[j].offset = pDescriptorWrites[i].pBufferInfo[j].offset;
          bufInfos[j].range = pDescriptorWrites[i].pBufferInfo[j].range;
        }
      }
    }

    for(uint32_t i = 0; i < copyCount; i++)
    {
      unwrappedCopies[i] = pDescriptorCopies[i];
      unwrappedCopies[i].dstSet = Unwrap(unwrappedCopies[i].dstSet);
      unwrappedCopies[i].srcSet = Unwrap(unwrappedCopies[i].srcSet);
    }

    ObjDisp(device)->UpdateDescriptorSets(Unwrap(device), writeCount, unwrappedWrites, copyCount,
                                          unwrappedCopies);
  }

  bool capframe = false;
  {
    SCOPED_LOCK(m_CapTransitionLock);
    capframe = (m_State == WRITING_CAPFRAME);
  }

  if(capframe)
  {
    // don't have to mark referenced any of the resources pointed to by the descriptor set - that's
    // handled
    // on queue submission by marking ref'd all the current bindings of the sets referenced by the
    // cmd buffer

    for(uint32_t i = 0; i < writeCount; i++)
    {
      {
        CACHE_THREAD_SERIALISER();

        SCOPED_SERIALISE_CONTEXT(UPDATE_DESC_SET);
        Serialise_vkUpdateDescriptorSets(localSerialiser, device, 1, &pDescriptorWrites[i], 0, NULL);

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

      // as long as descriptor sets are forced to have initial states, we don't have to mark them
      // ref'd for
      // write here. The reason being that as long as we only mark them as ref'd when they're
      // actually bound,
      // we can safely skip the ref here and it means any descriptor set updates of descriptor sets
      // that are
      // never used in the frame can be ignored.
      // GetResourceManager()->MarkResourceFrameReferenced(GetResID(pDescriptorWrites[i].destSet),
      // eFrameRef_Write);
    }

    for(uint32_t i = 0; i < copyCount; i++)
    {
      {
        CACHE_THREAD_SERIALISER();

        SCOPED_SERIALISE_CONTEXT(UPDATE_DESC_SET);
        Serialise_vkUpdateDescriptorSets(localSerialiser, device, 0, NULL, 1, &pDescriptorCopies[i]);

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

      // Like writes we don't have to mark the written descriptor set as used because unless it's
      // bound somewhere
      // we don't need it anyway. However we DO have to mark the source set as used because it
      // doesn't have to
      // be bound to still be needed (think about if the dest set is bound somewhere after this copy
      // - what refs
      // the source set?).
      // At the same time as ref'ing the source set, we must ref all of its resources (via the
      // bindFrameRefs).
      // We just ref all rather than looking at only the copied sets to keep things simple.
      // This does mean a slightly conservative ref'ing if the dest set doesn't end up getting
      // bound, but we only
      // do this during frame capture so it's not too bad.
      // GetResourceManager()->MarkResourceFrameReferenced(GetResID(pDescriptorCopies[i].destSet),
      // eFrameRef_Write);

      {
        GetResourceManager()->MarkResourceFrameReferenced(GetResID(pDescriptorCopies[i].srcSet),
                                                          eFrameRef_Read);

        VkResourceRecord *setrecord = GetRecord(pDescriptorCopies[i].srcSet);

        for(auto refit = setrecord->descInfo->bindFrameRefs.begin();
            refit != setrecord->descInfo->bindFrameRefs.end(); ++refit)
        {
          GetResourceManager()->MarkResourceFrameReferenced(refit->first, refit->second.second);

          if(refit->second.first & DescriptorSetData::SPARSE_REF_BIT)
          {
            VkResourceRecord *record = GetResourceManager()->GetResourceRecord(refit->first);

            GetResourceManager()->MarkSparseMapReferenced(record->sparseInfo);
          }
        }
      }
    }
  }

  // need to track descriptor set contents whether capframing or idle
  if(m_State >= WRITING)
  {
    for(uint32_t i = 0; i < writeCount; i++)
    {
      VkResourceRecord *record = GetRecord(pDescriptorWrites[i].dstSet);
      RDCASSERT(record->descInfo && record->descInfo->layout);
      const DescSetLayout &layout = *record->descInfo->layout;

      RDCASSERT(pDescriptorWrites[i].dstBinding < record->descInfo->descBindings.size());

      DescriptorSetSlot **binding = &record->descInfo->descBindings[pDescriptorWrites[i].dstBinding];

      const DescSetLayout::Binding *layoutBinding = &layout.bindings[pDescriptorWrites[i].dstBinding];

      FrameRefType ref = eFrameRef_Write;

      switch(layoutBinding->descriptorType)
      {
        case VK_DESCRIPTOR_TYPE_SAMPLER:
        case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
        case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
        case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
        case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: ref = eFrameRef_Read; break;
        case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
        case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: ref = eFrameRef_Write; break;
        default: RDCERR("Unexpected descriptor type");
      }

      // We need to handle the cases where these bindings are stale:
      // ie. image handle 0xf00baa is allocated
      // bound into a descriptor set
      // image is released
      // descriptor set is bound but this image is never used by shader etc.
      //
      // worst case, a new image or something has been added with this handle -
      // in this case we end up ref'ing an image that isn't actually used.
      // Worst worst case, we ref an image as write when actually it's not, but
      // this is likewise not a serious problem, and rather difficult to solve
      // (would need to version handles somehow, but don't have enough bits
      // to do that reliably).
      //
      // This is handled by RemoveBindFrameRef silently dropping id == ResourceId()

      // start at the dstArrayElement
      uint32_t curIdx = pDescriptorWrites[i].dstArrayElement;

      for(uint32_t d = 0; d < pDescriptorWrites[i].descriptorCount; d++, curIdx++)
      {
        // roll over onto the next binding, on the assumption that it is the same
        // type and there is indeed a next binding at all. See spec language:
        //
        // If the dstBinding has fewer than descriptorCount array elements remaining starting from
        // dstArrayElement, then the remainder will be used to update the subsequent binding -
        // dstBinding+1 starting at array element zero. This behavior applies recursively, with the
        // update affecting consecutive bindings as needed to update all descriptorCount
        // descriptors. All consecutive bindings updated via a single VkWriteDescriptorSet structure
        // must have identical descriptorType and stageFlags, and must all either use immutable
        // samplers or must all not use immutable samplers.

        if(curIdx >= layoutBinding->descriptorCount)
        {
          layoutBinding++;
          binding++;
          curIdx = 0;
        }

        DescriptorSetSlot &bind = (*binding)[curIdx];

        if(bind.texelBufferView != VK_NULL_HANDLE)
        {
          record->RemoveBindFrameRef(GetResID(bind.texelBufferView));

          VkResourceRecord *viewRecord = GetRecord(bind.texelBufferView);
          if(viewRecord && viewRecord->baseResource != ResourceId())
            record->RemoveBindFrameRef(viewRecord->baseResource);
        }
        if(bind.imageInfo.imageView != VK_NULL_HANDLE)
        {
          record->RemoveBindFrameRef(GetResID(bind.imageInfo.imageView));

          VkResourceRecord *viewRecord = GetRecord(bind.imageInfo.imageView);
          if(viewRecord)
          {
            record->RemoveBindFrameRef(viewRecord->baseResource);
            if(viewRecord->baseResourceMem != ResourceId())
              record->RemoveBindFrameRef(viewRecord->baseResourceMem);
          }
        }
        if(bind.imageInfo.sampler != VK_NULL_HANDLE)
        {
          record->RemoveBindFrameRef(GetResID(bind.imageInfo.sampler));
        }
        if(bind.bufferInfo.buffer != VK_NULL_HANDLE)
        {
          record->RemoveBindFrameRef(GetResID(bind.bufferInfo.buffer));

          VkResourceRecord *bufRecord = GetRecord(bind.bufferInfo.buffer);
          if(bufRecord && bufRecord->baseResource != ResourceId())
            record->RemoveBindFrameRef(bufRecord->baseResource);
        }

        // NULL everything out now so that we don't accidentally reference an object
        // that was removed already
        bind.texelBufferView = VK_NULL_HANDLE;
        bind.bufferInfo.buffer = VK_NULL_HANDLE;
        bind.imageInfo.imageView = VK_NULL_HANDLE;
        bind.imageInfo.sampler = VK_NULL_HANDLE;

        if(pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER ||
           pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER)
        {
          bind.texelBufferView = pDescriptorWrites[i].pTexelBufferView[d];
        }
        else if(pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ||
                pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
                pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
                pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
                pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)
        {
          bind.imageInfo = pDescriptorWrites[i].pImageInfo[d];

          // ignore descriptors not part of the write, by NULL'ing out those members
          // as they might not even point to a valid object
          if(pDescriptorWrites[i].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER)
            bind.imageInfo.imageView = VK_NULL_HANDLE;
          else if(pDescriptorWrites[i].descriptorType != VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
            bind.imageInfo.sampler = VK_NULL_HANDLE;
        }
        else
        {
          bind.bufferInfo = pDescriptorWrites[i].pBufferInfo[d];
        }

        if(bind.texelBufferView != VK_NULL_HANDLE)
        {
          record->AddBindFrameRef(GetResID(bind.texelBufferView), eFrameRef_Read,
                                  GetRecord(bind.texelBufferView)->sparseInfo != NULL);
          if(GetRecord(bind.texelBufferView)->baseResource != ResourceId())
            record->AddBindFrameRef(GetRecord(bind.texelBufferView)->baseResource, ref);
        }
        if(bind.imageInfo.imageView != VK_NULL_HANDLE)
        {
          record->AddBindFrameRef(GetResID(bind.imageInfo.imageView), eFrameRef_Read,
                                  GetRecord(bind.imageInfo.imageView)->sparseInfo != NULL);
          record->AddBindFrameRef(GetRecord(bind.imageInfo.imageView)->baseResource, ref);
          if(GetRecord(bind.imageInfo.imageView)->baseResourceMem != ResourceId())
            record->AddBindFrameRef(GetRecord(bind.imageInfo.imageView)->baseResourceMem,
                                    eFrameRef_Read);
        }
        if(bind.imageInfo.sampler != VK_NULL_HANDLE)
        {
          record->AddBindFrameRef(GetResID(bind.imageInfo.sampler), eFrameRef_Read);
        }
        if(bind.bufferInfo.buffer != VK_NULL_HANDLE)
        {
          record->AddBindFrameRef(GetResID(bind.bufferInfo.buffer), eFrameRef_Read,
                                  GetRecord(bind.bufferInfo.buffer)->sparseInfo != NULL);
          if(GetRecord(bind.bufferInfo.buffer)->baseResource != ResourceId())
            record->AddBindFrameRef(GetRecord(bind.bufferInfo.buffer)->baseResource, ref);
        }
      }
    }

    // this is almost identical to the above loop, except that instead of sourcing the descriptors
    // from the writedescriptor struct, we source it from our stored bindings on the source
    // descrpitor set

    for(uint32_t i = 0; i < copyCount; i++)
    {
      VkResourceRecord *dstrecord = GetRecord(pDescriptorCopies[i].dstSet);
      RDCASSERT(dstrecord->descInfo && dstrecord->descInfo->layout);
      const DescSetLayout &dstlayout = *dstrecord->descInfo->layout;

      VkResourceRecord *srcrecord = GetRecord(pDescriptorCopies[i].srcSet);
      RDCASSERT(srcrecord->descInfo && srcrecord->descInfo->layout);
      const DescSetLayout &srclayout = *srcrecord->descInfo->layout;

      RDCASSERT(pDescriptorCopies[i].dstBinding < dstrecord->descInfo->descBindings.size());
      RDCASSERT(pDescriptorCopies[i].srcBinding < srcrecord->descInfo->descBindings.size());

      DescriptorSetSlot **dstbinding =
          &dstrecord->descInfo->descBindings[pDescriptorCopies[i].dstBinding];
      DescriptorSetSlot **srcbinding =
          &srcrecord->descInfo->descBindings[pDescriptorCopies[i].srcBinding];

      const DescSetLayout::Binding *dstlayoutBinding =
          &dstlayout.bindings[pDescriptorCopies[i].dstBinding];
      const DescSetLayout::Binding *srclayoutBinding =
          &srclayout.bindings[pDescriptorCopies[i].srcBinding];

      FrameRefType ref = eFrameRef_Write;

      switch(dstlayoutBinding->descriptorType)
      {
        case VK_DESCRIPTOR_TYPE_SAMPLER:
        case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
        case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
        case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
        case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: ref = eFrameRef_Read; break;
        case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
        case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: ref = eFrameRef_Write; break;
        default: RDCERR("Unexpected descriptor type");
      }

      // allow roll-over between consecutive bindings. See above in the plain write case for more
      // explanation
      uint32_t curSrcIdx = pDescriptorCopies[i].srcArrayElement;
      uint32_t curDstIdx = pDescriptorCopies[i].dstArrayElement;

      for(uint32_t d = 0; d < pDescriptorCopies[i].descriptorCount; d++, curSrcIdx++, curDstIdx++)
      {
        if(curDstIdx >= dstlayoutBinding->descriptorCount)
        {
          dstlayoutBinding++;
          dstbinding++;
          curDstIdx = 0;
        }

        // dst and src indices must roll-over independently
        if(curSrcIdx >= srclayoutBinding->descriptorCount)
        {
          srclayoutBinding++;
          srcbinding++;
          curSrcIdx = 0;
        }

        DescriptorSetSlot &bind = (*dstbinding)[curDstIdx];

        if(bind.texelBufferView != VK_NULL_HANDLE)
        {
          dstrecord->RemoveBindFrameRef(GetResID(bind.texelBufferView));
          if(GetRecord(bind.texelBufferView)->baseResource != ResourceId())
            dstrecord->RemoveBindFrameRef(GetRecord(bind.texelBufferView)->baseResource);
        }
        if(bind.imageInfo.imageView != VK_NULL_HANDLE)
        {
          dstrecord->RemoveBindFrameRef(GetResID(bind.imageInfo.imageView));
          dstrecord->RemoveBindFrameRef(GetRecord(bind.imageInfo.imageView)->baseResource);
          if(GetRecord(bind.imageInfo.imageView)->baseResourceMem != ResourceId())
            dstrecord->RemoveBindFrameRef(GetRecord(bind.imageInfo.imageView)->baseResourceMem);
        }
        if(bind.imageInfo.sampler != VK_NULL_HANDLE)
        {
          dstrecord->RemoveBindFrameRef(GetResID(bind.imageInfo.sampler));
        }
        if(bind.bufferInfo.buffer != VK_NULL_HANDLE)
        {
          dstrecord->RemoveBindFrameRef(GetResID(bind.bufferInfo.buffer));
          if(GetRecord(bind.bufferInfo.buffer)->baseResource != ResourceId())
            dstrecord->RemoveBindFrameRef(GetRecord(bind.bufferInfo.buffer)->baseResource);
        }

        bind = (*srcbinding)[curSrcIdx];

        if(bind.texelBufferView != VK_NULL_HANDLE)
        {
          dstrecord->AddBindFrameRef(GetResID(bind.texelBufferView), eFrameRef_Read,
                                     GetRecord(bind.texelBufferView)->sparseInfo != NULL);
          if(GetRecord(bind.texelBufferView)->baseResource != ResourceId())
            dstrecord->AddBindFrameRef(GetRecord(bind.texelBufferView)->baseResource, ref);
        }
        if(bind.imageInfo.imageView != VK_NULL_HANDLE)
        {
          dstrecord->AddBindFrameRef(GetResID(bind.imageInfo.imageView), eFrameRef_Read,
                                     GetRecord(bind.imageInfo.imageView)->sparseInfo != NULL);
          dstrecord->AddBindFrameRef(GetRecord(bind.imageInfo.imageView)->baseResource, ref);
          if(GetRecord(bind.imageInfo.imageView)->baseResourceMem != ResourceId())
            dstrecord->AddBindFrameRef(GetRecord(bind.imageInfo.imageView)->baseResourceMem,
                                       eFrameRef_Read);
        }
        if(bind.imageInfo.sampler != VK_NULL_HANDLE)
        {
          dstrecord->AddBindFrameRef(GetResID(bind.imageInfo.sampler), ref);
        }
        if(bind.bufferInfo.buffer != VK_NULL_HANDLE)
        {
          dstrecord->AddBindFrameRef(GetResID(bind.bufferInfo.buffer), eFrameRef_Read,
                                     GetRecord(bind.bufferInfo.buffer)->sparseInfo != NULL);
          if(GetRecord(bind.bufferInfo.buffer)->baseResource != ResourceId())
            dstrecord->AddBindFrameRef(GetRecord(bind.bufferInfo.buffer)->baseResource, ref);
        }
      }
    }
  }
}