コード例 #1
0
ファイル: vk_dynamic_funcs.cpp プロジェクト: DrChat/renderdoc
bool WrappedVulkan::Serialise_vkCmdSetDepthBounds(Serialiser *localSerialiser,
                                                  VkCommandBuffer cmdBuffer, float minDepthBounds,
                                                  float maxDepthBounds)
{
  SERIALISE_ELEMENT(ResourceId, cmdid, GetResID(cmdBuffer));
  SERIALISE_ELEMENT(float, mind, minDepthBounds);
  SERIALISE_ELEMENT(float, maxd, maxDepthBounds);

  Serialise_DebugMessages(localSerialiser, false);

  if(m_State < WRITING)
    m_LastCmdBufferID = cmdid;

  if(m_State == EXECUTING)
  {
    if(ShouldRerecordCmd(cmdid) && InRerecordRange(cmdid))
    {
      cmdBuffer = RerecordCmdBuf(cmdid);
      ObjDisp(cmdBuffer)->CmdSetDepthBounds(Unwrap(cmdBuffer), mind, maxd);
      m_RenderState.mindepth = mind;
      m_RenderState.maxdepth = maxd;
    }
  }
  else if(m_State == READING)
  {
    cmdBuffer = GetResourceManager()->GetLiveHandle<VkCommandBuffer>(cmdid);

    ObjDisp(cmdBuffer)->CmdSetDepthBounds(Unwrap(cmdBuffer), mind, maxd);
  }

  return true;
}
コード例 #2
0
ファイル: vk_dynamic_funcs.cpp プロジェクト: DrChat/renderdoc
bool WrappedVulkan::Serialise_vkCmdSetDepthBias(Serialiser *localSerialiser,
                                                VkCommandBuffer cmdBuffer, float depthBias,
                                                float depthBiasClamp, float slopeScaledDepthBias)
{
  SERIALISE_ELEMENT(ResourceId, cmdid, GetResID(cmdBuffer));
  SERIALISE_ELEMENT(float, bias, depthBias);
  SERIALISE_ELEMENT(float, biasclamp, depthBiasClamp);
  SERIALISE_ELEMENT(float, slope, slopeScaledDepthBias);

  Serialise_DebugMessages(localSerialiser, false);

  if(m_State < WRITING)
    m_LastCmdBufferID = cmdid;

  if(m_State == EXECUTING)
  {
    if(ShouldRerecordCmd(cmdid) && InRerecordRange(cmdid))
    {
      cmdBuffer = RerecordCmdBuf(cmdid);
      ObjDisp(cmdBuffer)->CmdSetDepthBias(Unwrap(cmdBuffer), bias, biasclamp, slope);
      m_RenderState.bias.depth = bias;
      m_RenderState.bias.biasclamp = biasclamp;
      m_RenderState.bias.slope = slope;
    }
  }
  else if(m_State == READING)
  {
    cmdBuffer = GetResourceManager()->GetLiveHandle<VkCommandBuffer>(cmdid);

    ObjDisp(cmdBuffer)->CmdSetDepthBias(Unwrap(cmdBuffer), bias, biasclamp, slope);
  }

  return true;
}
コード例 #3
0
bool WrappedVulkan::Serialise_vkResetEvent(
	Serialiser*                                 localSerialiser,
	VkDevice                                    device,
	VkEvent                                     event)
{
	SERIALISE_ELEMENT(ResourceId, id, GetResID(device));
	SERIALISE_ELEMENT(ResourceId, eid, GetResID(event));
		
	Serialise_DebugMessages(localSerialiser, false);
	
	if(m_State < WRITING)
	{
		// see top of this file for current event/fence handling
	}

	return true;
}
コード例 #4
0
bool WrappedVulkan::Serialise_vkGetEventStatus(
			Serialiser*                             localSerialiser,
			VkDevice                                device,
			VkEvent                                 event)
{
	SERIALISE_ELEMENT(ResourceId, id, GetResID(device));
	SERIALISE_ELEMENT(ResourceId, eid, GetResID(event));
		
	Serialise_DebugMessages(localSerialiser, false);
	
	if(m_State < WRITING)
	{
		device = GetResourceManager()->GetLiveHandle<VkDevice>(id);

		ObjDisp(device)->DeviceWaitIdle(Unwrap(device));
	}

	return true;
}
コード例 #5
0
ファイル: vk_dynamic_funcs.cpp プロジェクト: DrChat/renderdoc
bool WrappedVulkan::Serialise_vkCmdSetScissor(Serialiser *localSerialiser,
                                              VkCommandBuffer cmdBuffer, uint32_t firstScissor,
                                              uint32_t scissorCount, const VkRect2D *pScissors)
{
  SERIALISE_ELEMENT(ResourceId, cmdid, GetResID(cmdBuffer));
  SERIALISE_ELEMENT(uint32_t, first, firstScissor);
  SERIALISE_ELEMENT(uint32_t, count, scissorCount);
  SERIALISE_ELEMENT_ARR(VkRect2D, scissors, pScissors, count);

  Serialise_DebugMessages(localSerialiser, false);

  if(m_State < WRITING)
    m_LastCmdBufferID = cmdid;

  if(m_State == EXECUTING)
  {
    if(ShouldRerecordCmd(cmdid) && InRerecordRange(cmdid))
    {
      cmdBuffer = RerecordCmdBuf(cmdid);
      ObjDisp(cmdBuffer)->CmdSetScissor(Unwrap(cmdBuffer), first, count, scissors);

      if(m_RenderState.scissors.size() < first + count)
        m_RenderState.scissors.resize(first + count);

      for(uint32_t i = 0; i < count; i++)
        m_RenderState.scissors[first + i] = scissors[i];
    }
  }
  else if(m_State == READING)
  {
    cmdBuffer = GetResourceManager()->GetLiveHandle<VkCommandBuffer>(cmdid);

    ObjDisp(cmdBuffer)->CmdSetScissor(Unwrap(cmdBuffer), first, count, scissors);
  }

  SAFE_DELETE_ARRAY(scissors);

  return true;
}
コード例 #6
0
ファイル: vk_dynamic_funcs.cpp プロジェクト: DrChat/renderdoc
bool WrappedVulkan::Serialise_vkCmdSetBlendConstants(Serialiser *localSerialiser,
                                                     VkCommandBuffer cmdBuffer,
                                                     const float *blendConst)
{
  SERIALISE_ELEMENT(ResourceId, cmdid, GetResID(cmdBuffer));

  float blendFactor[4];
  if(m_State >= WRITING)
  {
    blendFactor[0] = blendConst[0];
    blendFactor[1] = blendConst[1];
    blendFactor[2] = blendConst[2];
    blendFactor[3] = blendConst[3];
  }
  localSerialiser->SerialisePODArray<4>("blendConst", blendFactor);

  Serialise_DebugMessages(localSerialiser, false);

  if(m_State < WRITING)
    m_LastCmdBufferID = cmdid;

  if(m_State == EXECUTING)
  {
    if(ShouldRerecordCmd(cmdid) && InRerecordRange(cmdid))
    {
      cmdBuffer = RerecordCmdBuf(cmdid);
      ObjDisp(cmdBuffer)->CmdSetBlendConstants(Unwrap(cmdBuffer), blendFactor);
      memcpy(m_RenderState.blendConst, blendFactor, sizeof(blendFactor));
    }
  }
  else if(m_State == READING)
  {
    cmdBuffer = GetResourceManager()->GetLiveHandle<VkCommandBuffer>(cmdid);

    ObjDisp(cmdBuffer)->CmdSetBlendConstants(Unwrap(cmdBuffer), blendFactor);
  }

  return true;
}
コード例 #7
0
bool WrappedVulkan::Serialise_vkWaitForFences(
			Serialiser*                             localSerialiser,
			VkDevice                                device,
			uint32_t                                fenceCount,
			const VkFence*                          pFences,
			VkBool32                                waitAll,
			uint64_t                                timeout)
{
	SERIALISE_ELEMENT(ResourceId, id, GetResID(device));
	SERIALISE_ELEMENT(VkBool32, wait, waitAll);
	SERIALISE_ELEMENT(uint64_t, tmout, timeout);
	SERIALISE_ELEMENT(uint32_t, count, fenceCount);
		
	Serialise_DebugMessages(localSerialiser, false);
	
	vector<VkFence> fences;

	for(uint32_t i=0; i < count; i++)
	{
		ResourceId fence;
		if(m_State >= WRITING)
			fence = GetResID(pFences[i]);

		localSerialiser->Serialise("pFences[]", fence);

		if(m_State < WRITING && GetResourceManager()->HasLiveResource(fence))
			fences.push_back(Unwrap(GetResourceManager()->GetLiveHandle<VkFence>(fence)));
	}

	if(m_State < WRITING)
	{
		device = GetResourceManager()->GetLiveHandle<VkDevice>(id);

		ObjDisp(device)->DeviceWaitIdle(Unwrap(device));
	}

	return true;
}
コード例 #8
0
bool WrappedVulkan::Serialise_vkResetFences(
			Serialiser*                             localSerialiser,
			VkDevice                                device,
			uint32_t                                fenceCount,
			const VkFence*                          pFences)
{
	SERIALISE_ELEMENT(ResourceId, id, GetResID(device));
	SERIALISE_ELEMENT(uint32_t, count, fenceCount);
	
	Serialise_DebugMessages(localSerialiser, false);
	
	vector<VkFence> fences;

	for(uint32_t i=0; i < count; i++)
	{
		ResourceId fence;
		if(m_State >= WRITING)
			fence = GetResID(pFences[i]);

		localSerialiser->Serialise("pFences[]", fence);
		
		if(m_State < WRITING && GetResourceManager()->HasLiveResource(fence))
			fences.push_back(Unwrap(GetResourceManager()->GetLiveHandle<VkFence>(fence)));
	}

	if(m_State < WRITING && !fences.empty())
	{
		// we don't care about fence states ourselves as we cannot record them perfectly and just
		// do full waitidle flushes.
		device = GetResourceManager()->GetLiveHandle<VkDevice>(id);

		// since we don't have anything signalling or waiting on fences, don't bother to reset them
		// either
		//ObjDisp(device)->ResetFences(Unwrap(device), (uint32_t)fences.size(), &fences[0]);
	}

	return true;
}
コード例 #9
0
bool WrappedVulkan::Serialise_vkCmdResetEvent(
		Serialiser*                                 localSerialiser,
		VkCommandBuffer                                 cmdBuffer,
    VkEvent                                     event,
		VkPipelineStageFlags                        stageMask)
{
	SERIALISE_ELEMENT(ResourceId, cmdid, GetResID(cmdBuffer));
	SERIALISE_ELEMENT(ResourceId, eid, GetResID(event));
	SERIALISE_ELEMENT(VkPipelineStageFlagBits, mask, (VkPipelineStageFlagBits)stageMask);
	
	Serialise_DebugMessages(localSerialiser, false);

	if(m_State < WRITING)
		m_LastCmdBufferID = cmdid;
	
	// see top of this file for current event/fence handling

	if(m_State == EXECUTING)
	{
		event = GetResourceManager()->GetLiveHandle<VkEvent>(eid);

		if(ShouldRerecordCmd(cmdid) && InRerecordRange())
		{
			cmdBuffer = RerecordCmdBuf(cmdid);
			//ObjDisp(cmdBuffer)->CmdResetEvent(Unwrap(cmdBuffer), Unwrap(event), mask);
		}
	}
	else if(m_State == READING)
	{
		cmdBuffer = GetResourceManager()->GetLiveHandle<VkCommandBuffer>(cmdid);
		event = GetResourceManager()->GetLiveHandle<VkEvent>(eid);
		
		//ObjDisp(cmdBuffer)->CmdResetEvent(Unwrap(cmdBuffer), Unwrap(event), mask);
	}

	return true;
}
コード例 #10
0
ファイル: vk_dynamic_funcs.cpp プロジェクト: DrChat/renderdoc
bool WrappedVulkan::Serialise_vkCmdSetStencilReference(Serialiser *localSerialiser,
                                                       VkCommandBuffer cmdBuffer,
                                                       VkStencilFaceFlags faceMask,
                                                       uint32_t reference)
{
  SERIALISE_ELEMENT(ResourceId, cmdid, GetResID(cmdBuffer));
  SERIALISE_ELEMENT(VkStencilFaceFlagBits, face, (VkStencilFaceFlagBits)faceMask);
  SERIALISE_ELEMENT(uint32_t, mask, reference);

  Serialise_DebugMessages(localSerialiser, false);

  if(m_State < WRITING)
    m_LastCmdBufferID = cmdid;

  if(m_State == EXECUTING)
  {
    if(ShouldRerecordCmd(cmdid) && InRerecordRange(cmdid))
    {
      cmdBuffer = RerecordCmdBuf(cmdid);
      ObjDisp(cmdBuffer)->CmdSetStencilReference(Unwrap(cmdBuffer), face, mask);

      if(face & VK_STENCIL_FACE_FRONT_BIT)
        m_RenderState.front.ref = mask;
      if(face & VK_STENCIL_FACE_BACK_BIT)
        m_RenderState.back.ref = mask;
    }
  }
  else if(m_State == READING)
  {
    cmdBuffer = GetResourceManager()->GetLiveHandle<VkCommandBuffer>(cmdid);

    ObjDisp(cmdBuffer)->CmdSetStencilReference(Unwrap(cmdBuffer), face, mask);
  }

  return true;
}
コード例 #11
0
ファイル: vk_queue_funcs.cpp プロジェクト: Anteru/renderdoc
bool WrappedVulkan::Serialise_vkQueueSubmit(Serialiser *localSerialiser, VkQueue queue,
                                            uint32_t submitCount, const VkSubmitInfo *pSubmits,
                                            VkFence fence)
{
  SERIALISE_ELEMENT(ResourceId, queueId, GetResID(queue));
  SERIALISE_ELEMENT(ResourceId, fenceId, fence != VK_NULL_HANDLE ? GetResID(fence) : ResourceId());

  SERIALISE_ELEMENT(uint32_t, numCmds, pSubmits->commandBufferCount);

  vector<ResourceId> cmdIds;
  VkCommandBuffer *cmds = m_State >= WRITING ? NULL : new VkCommandBuffer[numCmds];
  for(uint32_t i = 0; i < numCmds; i++)
  {
    ResourceId bakedId;

    if(m_State >= WRITING)
    {
      VkResourceRecord *record = GetRecord(pSubmits->pCommandBuffers[i]);
      RDCASSERT(record->bakedCommands);
      if(record->bakedCommands)
        bakedId = record->bakedCommands->GetResourceID();
    }

    SERIALISE_ELEMENT(ResourceId, id, bakedId);

    if(m_State < WRITING)
    {
      cmdIds.push_back(id);

      cmds[i] = id != ResourceId() ? Unwrap(GetResourceManager()->GetLiveHandle<VkCommandBuffer>(id))
                                   : NULL;
    }
  }

  if(m_State < WRITING)
  {
    queue = GetResourceManager()->GetLiveHandle<VkQueue>(queueId);
    if(fenceId != ResourceId())
      fence = GetResourceManager()->GetLiveHandle<VkFence>(fenceId);
    else
      fence = VK_NULL_HANDLE;
  }

  // we don't serialise semaphores at all, just whether we waited on any.
  // For waiting semaphores, since we don't track state we have to just conservatively
  // wait for queue idle. Since we do that, there's equally no point in signalling semaphores
  SERIALISE_ELEMENT(uint32_t, numWaitSems, pSubmits->waitSemaphoreCount);

  if(m_State < WRITING && numWaitSems > 0)
    ObjDisp(queue)->QueueWaitIdle(Unwrap(queue));

  VkSubmitInfo submitInfo = {
      VK_STRUCTURE_TYPE_SUBMIT_INFO,
      NULL,
      0,
      NULL,
      NULL,    // wait semaphores
      numCmds,
      cmds,    // command buffers
      0,
      NULL,    // signal semaphores
  };

  const string desc = localSerialiser->GetDebugStr();

  Serialise_DebugMessages(localSerialiser, true);

  if(m_State == READING)
  {
    // don't submit the fence, since we have nothing to wait on it being signalled, and we might
    // not have it correctly in the unsignalled state.
    ObjDisp(queue)->QueueSubmit(Unwrap(queue), 1, &submitInfo, VK_NULL_HANDLE);

    for(uint32_t i = 0; i < numCmds; i++)
    {
      ResourceId cmd = GetResourceManager()->GetLiveID(cmdIds[i]);
      GetResourceManager()->ApplyBarriers(m_BakedCmdBufferInfo[cmd].imgbarriers, m_ImageLayouts);
    }

    AddEvent(QUEUE_SUBMIT, desc);

    // we're adding multiple events, need to increment ourselves
    m_RootEventID++;

    string basename = "vkQueueSubmit(" + ToStr::Get(numCmds) + ")";

    for(uint32_t c = 0; c < numCmds; c++)
    {
      string name = StringFormat::Fmt("=> %s[%u]: vkBeginCommandBuffer(%s)", basename.c_str(), c,
                                      ToStr::Get(cmdIds[c]).c_str());

      // add a fake marker
      FetchDrawcall draw;
      draw.name = name;
      draw.flags |= eDraw_SetMarker;
      AddEvent(SET_MARKER, name);
      AddDrawcall(draw, true);
      m_RootEventID++;

      BakedCmdBufferInfo &cmdBufInfo = m_BakedCmdBufferInfo[cmdIds[c]];

      // insert the baked command buffer in-line into this list of notes, assigning new event and
      // drawIDs
      InsertDrawsAndRefreshIDs(cmdBufInfo.draw->children);

      for(size_t e = 0; e < cmdBufInfo.draw->executedCmds.size(); e++)
      {
        vector<uint32_t> &submits =
            m_Partial[Secondary].cmdBufferSubmits[cmdBufInfo.draw->executedCmds[e]];

        for(size_t s = 0; s < submits.size(); s++)
          submits[s] += m_RootEventID;
      }

      for(size_t i = 0; i < cmdBufInfo.debugMessages.size(); i++)
      {
        m_DebugMessages.push_back(cmdBufInfo.debugMessages[i]);
        m_DebugMessages.back().eventID += m_RootEventID;
      }

      // only primary command buffers can be submitted
      m_Partial[Primary].cmdBufferSubmits[cmdIds[c]].push_back(m_RootEventID);

      m_RootEventID += cmdBufInfo.eventCount;
      m_RootDrawcallID += cmdBufInfo.drawCount;

      name = StringFormat::Fmt("=> %s[%u]: vkEndCommandBuffer(%s)", basename.c_str(), c,
                               ToStr::Get(cmdIds[c]).c_str());
      draw.name = name;
      AddEvent(SET_MARKER, name);
      AddDrawcall(draw, true);
      m_RootEventID++;
    }

    // account for the outer loop thinking we've added one event and incrementing,
    // since we've done all the handling ourselves this will be off by one.
    m_RootEventID--;
  }
  else if(m_State == EXECUTING)
  {
    // account for the queue submit event
    m_RootEventID++;

    uint32_t startEID = m_RootEventID;

    // advance m_CurEventID to match the events added when reading
    for(uint32_t c = 0; c < numCmds; c++)
    {
      // 2 extra for the virtual labels around the command buffer
      m_RootEventID += 2 + m_BakedCmdBufferInfo[cmdIds[c]].eventCount;
      m_RootDrawcallID += 2 + m_BakedCmdBufferInfo[cmdIds[c]].drawCount;
    }

    // same accounting for the outer loop as above
    m_RootEventID--;

    if(numCmds == 0)
    {
      // do nothing, don't bother with the logic below
    }
    else if(m_LastEventID <= startEID)
    {
#ifdef VERBOSE_PARTIAL_REPLAY
      RDCDEBUG("Queue Submit no replay %u == %u", m_LastEventID, startEID);
#endif
    }
    else if(m_DrawcallCallback && m_DrawcallCallback->RecordAllCmds())
    {
#ifdef VERBOSE_PARTIAL_REPLAY
      RDCDEBUG("Queue Submit re-recording from %u", m_RootEventID);
#endif

      vector<VkCommandBuffer> rerecordedCmds;

      for(uint32_t c = 0; c < numCmds; c++)
      {
        VkCommandBuffer cmd = RerecordCmdBuf(cmdIds[c]);
        ResourceId rerecord = GetResID(cmd);
#ifdef VERBOSE_PARTIAL_REPLAY
        RDCDEBUG("Queue Submit fully re-recorded replay of %llu, using %llu", cmdIds[c], rerecord);
#endif
        rerecordedCmds.push_back(Unwrap(cmd));

        GetResourceManager()->ApplyBarriers(m_BakedCmdBufferInfo[rerecord].imgbarriers,
                                            m_ImageLayouts);
      }

      submitInfo.commandBufferCount = (uint32_t)rerecordedCmds.size();
      submitInfo.pCommandBuffers = &rerecordedCmds[0];
      // don't submit the fence, since we have nothing to wait on it being signalled, and we might
      // not have it correctly in the unsignalled state.
      ObjDisp(queue)->QueueSubmit(Unwrap(queue), 1, &submitInfo, VK_NULL_HANDLE);
    }
    else if(m_LastEventID > startEID && m_LastEventID < m_RootEventID)
    {
#ifdef VERBOSE_PARTIAL_REPLAY
      RDCDEBUG("Queue Submit partial replay %u < %u", m_LastEventID, m_RootEventID);
#endif

      uint32_t eid = startEID;

      vector<ResourceId> trimmedCmdIds;
      vector<VkCommandBuffer> trimmedCmds;

      for(uint32_t c = 0; c < numCmds; c++)
      {
        // account for the virtual vkBeginCommandBuffer label at the start of the events here
        // so it matches up to baseEvent
        eid++;

        uint32_t end = eid + m_BakedCmdBufferInfo[cmdIds[c]].eventCount;

        if(eid == m_Partial[Primary].baseEvent)
        {
          ResourceId partial = GetResID(RerecordCmdBuf(cmdIds[c], Primary));
#ifdef VERBOSE_PARTIAL_REPLAY
          RDCDEBUG("Queue Submit partial replay of %llu at %u, using %llu", cmdIds[c], eid, partial);
#endif
          trimmedCmdIds.push_back(partial);
          trimmedCmds.push_back(Unwrap(RerecordCmdBuf(cmdIds[c], Primary)));
        }
        else if(m_LastEventID >= end)
        {
#ifdef VERBOSE_PARTIAL_REPLAY
          RDCDEBUG("Queue Submit full replay %llu", cmdIds[c]);
#endif
          trimmedCmdIds.push_back(cmdIds[c]);
          trimmedCmds.push_back(
              Unwrap(GetResourceManager()->GetLiveHandle<VkCommandBuffer>(cmdIds[c])));
        }
        else
        {
#ifdef VERBOSE_PARTIAL_REPLAY
          RDCDEBUG("Queue not submitting %llu", cmdIds[c]);
#endif
        }

        // 1 extra to account for the virtual end command buffer label (begin is accounted for
        // above)
        eid += 1 + m_BakedCmdBufferInfo[cmdIds[c]].eventCount;
      }

      RDCASSERT(trimmedCmds.size() > 0);

      submitInfo.commandBufferCount = (uint32_t)trimmedCmds.size();
      submitInfo.pCommandBuffers = &trimmedCmds[0];
      // don't submit the fence, since we have nothing to wait on it being signalled, and we might
      // not have it correctly in the unsignalled state.
      ObjDisp(queue)->QueueSubmit(Unwrap(queue), 1, &submitInfo, VK_NULL_HANDLE);

      for(uint32_t i = 0; i < trimmedCmdIds.size(); i++)
      {
        ResourceId cmd = trimmedCmdIds[i];
        GetResourceManager()->ApplyBarriers(m_BakedCmdBufferInfo[cmd].imgbarriers, m_ImageLayouts);
      }
    }
    else
    {
#ifdef VERBOSE_PARTIAL_REPLAY
      RDCDEBUG("Queue Submit full replay %u >= %u", m_LastEventID, m_RootEventID);
#endif

      // don't submit the fence, since we have nothing to wait on it being signalled, and we might
      // not have it correctly in the unsignalled state.
      ObjDisp(queue)->QueueSubmit(Unwrap(queue), 1, &submitInfo, VK_NULL_HANDLE);

      for(uint32_t i = 0; i < numCmds; i++)
      {
        ResourceId cmd = GetResourceManager()->GetLiveID(cmdIds[i]);
        GetResourceManager()->ApplyBarriers(m_BakedCmdBufferInfo[cmd].imgbarriers, m_ImageLayouts);
      }
    }
  }

  SAFE_DELETE_ARRAY(cmds);

  return true;
}
コード例 #12
0
bool WrappedVulkan::Serialise_vkUpdateDescriptorSets(Serialiser *localSerialiser, VkDevice device,
                                                     uint32_t writeCount,
                                                     const VkWriteDescriptorSet *pDescriptorWrites,
                                                     uint32_t copyCount,
                                                     const VkCopyDescriptorSet *pDescriptorCopies)
{
  SERIALISE_ELEMENT(ResourceId, devId, GetResID(device));
  SERIALISE_ELEMENT(bool, writes, writeCount == 1);

  VkWriteDescriptorSet writeDesc = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0};
  VkCopyDescriptorSet copyDesc = {VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET, 0};
  if(writes)
  {
    SERIALISE_ELEMENT(VkWriteDescriptorSet, w, *pDescriptorWrites);
    writeDesc = w;
    // take ownership of the arrays (we will delete manually)
    w.pBufferInfo = NULL;
    w.pImageInfo = NULL;
    w.pTexelBufferView = NULL;
  }
  else
  {
    SERIALISE_ELEMENT(VkCopyDescriptorSet, c, *pDescriptorCopies);
    copyDesc = c;
  }

  Serialise_DebugMessages(localSerialiser, false);

  if(m_State < WRITING)
  {
    device = GetResourceManager()->GetLiveHandle<VkDevice>(devId);

    if(writes)
    {
      // check for validity - if a resource wasn't referenced other than in this update
      // (ie. the descriptor set was overwritten or never bound), then the write descriptor
      // will be invalid with some missing handles. It's safe though to just skip this
      // update as we only get here if it's never used.

      // if a set was never bound, it will have been omitted and we just drop any writes to it
      bool valid = (writeDesc.dstSet != VK_NULL_HANDLE);

      if(!valid)
        return true;

      const DescSetLayout &layout =
          m_CreationInfo.m_DescSetLayout
              [m_DescriptorSetState[GetResourceManager()->GetNonDispWrapper(writeDesc.dstSet)->id].layout];

      const DescSetLayout::Binding *layoutBinding = &layout.bindings[writeDesc.dstBinding];
      uint32_t curIdx = writeDesc.dstArrayElement;

      switch(writeDesc.descriptorType)
      {
        case VK_DESCRIPTOR_TYPE_SAMPLER:
        {
          for(uint32_t i = 0; i < writeDesc.descriptorCount; i++)
            valid &= (writeDesc.pImageInfo[i].sampler != VK_NULL_HANDLE);
          break;
        }
        case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
        {
          for(uint32_t i = 0; i < writeDesc.descriptorCount; i++, curIdx++)
          {
            // allow consecutive descriptor bind updates. See vkUpdateDescriptorSets for more
            // explanation
            if(curIdx >= layoutBinding->descriptorCount)
            {
              layoutBinding++;
              curIdx = 0;
            }

            valid &= (writeDesc.pImageInfo[i].sampler != VK_NULL_HANDLE) ||
                     (layoutBinding->immutableSampler &&
                      layoutBinding->immutableSampler[curIdx] != ResourceId());
            valid &= (writeDesc.pImageInfo[i].imageView != VK_NULL_HANDLE);
          }
          break;
        }
        case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
        case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
        case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
        {
          for(uint32_t i = 0; i < writeDesc.descriptorCount; i++)
            valid &= (writeDesc.pImageInfo[i].imageView != VK_NULL_HANDLE);
          break;
        }
        case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
        {
          for(uint32_t i = 0; i < writeDesc.descriptorCount; i++)
            valid &= (writeDesc.pTexelBufferView[i] != VK_NULL_HANDLE);
          break;
        }
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
        {
          for(uint32_t i = 0; i < writeDesc.descriptorCount; i++)
            valid &= (writeDesc.pBufferInfo[i].buffer != VK_NULL_HANDLE);
          break;
        }
        default: RDCERR("Unexpected descriptor type %d", writeDesc.descriptorType);
      }

      if(valid)
      {
        ObjDisp(device)->UpdateDescriptorSets(Unwrap(device), 1, &writeDesc, 0, NULL);

        // update our local tracking
        vector<DescriptorSetSlot *> &bindings =
            m_DescriptorSetState[GetResourceManager()->GetNonDispWrapper(writeDesc.dstSet)->id]
                .currentBindings;

        {
          RDCASSERT(writeDesc.dstBinding < bindings.size());

          DescriptorSetSlot **bind = &bindings[writeDesc.dstBinding];
          layoutBinding = &layout.bindings[writeDesc.dstBinding];
          curIdx = writeDesc.dstArrayElement;

          if(writeDesc.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER ||
             writeDesc.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER)
          {
            for(uint32_t d = 0; d < writeDesc.descriptorCount; d++, curIdx++)
            {
              // allow consecutive descriptor bind updates. See vkUpdateDescriptorSets for more
              // explanation
              if(curIdx >= layoutBinding->descriptorCount)
              {
                layoutBinding++;
                bind++;
                curIdx = 0;
              }

              (*bind)[curIdx].texelBufferView = writeDesc.pTexelBufferView[d];
            }
          }
          else if(writeDesc.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ||
                  writeDesc.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
                  writeDesc.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
                  writeDesc.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
                  writeDesc.descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)
          {
            for(uint32_t d = 0; d < writeDesc.descriptorCount; d++, curIdx++)
            {
              // allow consecutive descriptor bind updates. See vkUpdateDescriptorSets for more
              // explanation
              if(curIdx >= layoutBinding->descriptorCount)
              {
                layoutBinding++;
                bind++;
                curIdx = 0;
              }

              (*bind)[curIdx].imageInfo = writeDesc.pImageInfo[d];
            }
          }
          else
          {
            for(uint32_t d = 0; d < writeDesc.descriptorCount; d++, curIdx++)
            {
              // allow consecutive descriptor bind updates. See vkUpdateDescriptorSets for more
              // explanation
              if(curIdx >= layoutBinding->descriptorCount)
              {
                layoutBinding++;
                bind++;
                curIdx = 0;
              }

              (*bind)[curIdx].bufferInfo = writeDesc.pBufferInfo[d];
            }
          }
        }
      }
    }
    else
    {
      // if a set was never bound, it will have been omitted and we just drop any copies to it
      if(copyDesc.dstSet == VK_NULL_HANDLE || copyDesc.srcSet == VK_NULL_HANDLE)
        return true;

      ObjDisp(device)->UpdateDescriptorSets(Unwrap(device), 0, NULL, 1, &copyDesc);

      ResourceId dstSetId = GetResourceManager()->GetNonDispWrapper(copyDesc.dstSet)->id;
      ResourceId srcSetId = GetResourceManager()->GetNonDispWrapper(copyDesc.srcSet)->id;

      // update our local tracking
      vector<DescriptorSetSlot *> &dstbindings = m_DescriptorSetState[dstSetId].currentBindings;
      vector<DescriptorSetSlot *> &srcbindings = m_DescriptorSetState[srcSetId].currentBindings;

      {
        RDCASSERT(copyDesc.dstBinding < dstbindings.size());
        RDCASSERT(copyDesc.srcBinding < srcbindings.size());

        const DescSetLayout &dstlayout =
            m_CreationInfo.m_DescSetLayout[m_DescriptorSetState[dstSetId].layout];
        const DescSetLayout &srclayout =
            m_CreationInfo.m_DescSetLayout[m_DescriptorSetState[srcSetId].layout];

        const DescSetLayout::Binding *layoutSrcBinding = &srclayout.bindings[copyDesc.srcBinding];
        const DescSetLayout::Binding *layoutDstBinding = &dstlayout.bindings[copyDesc.dstBinding];

        DescriptorSetSlot **dstbind = &dstbindings[copyDesc.dstBinding];
        DescriptorSetSlot **srcbind = &srcbindings[copyDesc.srcBinding];

        uint32_t curDstIdx = copyDesc.dstArrayElement;
        uint32_t curSrcIdx = copyDesc.srcArrayElement;

        for(uint32_t d = 0; d < copyDesc.descriptorCount; d++, curSrcIdx++, curDstIdx++)
        {
          // allow consecutive descriptor bind updates. See vkUpdateDescriptorSets for more
          // explanation
          if(curSrcIdx >= layoutSrcBinding->descriptorCount)
          {
            layoutSrcBinding++;
            srcbind++;
            curSrcIdx = 0;
          }

          // src and dst could wrap independently - think copying from
          // { sampler2D, sampler2D[4], sampler2D } to a { sampler2D[3], sampler2D[3] }
          // or copying from different starting array elements
          if(curDstIdx >= layoutDstBinding->descriptorCount)
          {
            layoutDstBinding++;
            dstbind++;
            curDstIdx = 0;
          }

          (*dstbind)[curDstIdx] = (*srcbind)[curSrcIdx];
        }
      }
    }

    // delete serialised descriptors array
    delete[] writeDesc.pBufferInfo;
    delete[] writeDesc.pImageInfo;
    delete[] writeDesc.pTexelBufferView;
  }

  return true;
}