void WrappedVulkan::vkUpdateDescriptorSets(
    VkDevice                                    device,
    uint32_t                                    writeCount,
    const VkWriteDescriptorSet*                 pDescriptorWrites,
    uint32_t                                    copyCount,
    const VkCopyDescriptorSet*                  pDescriptorCopies)
{
    {
        // 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;

            // 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)
            {
                unwrappedWrites[i].pImageInfo = (VkDescriptorImageInfo *)bufInfos;
                for(uint32_t j=0; j < pDescriptorWrites[i].descriptorCount; j++)
                {
                    imInfos[j].imageView = Unwrap(pDescriptorWrites[i].pImageInfo[j].imageView);
                    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];

            FrameRefType ref = eFrameRef_Write;

            switch(layout.bindings[pDescriptorWrites[i].dstBinding].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()

            for(uint32_t d=0; d < pDescriptorWrites[i].descriptorCount; d++)
            {
                DescriptorSetSlot &bind = binding[pDescriptorWrites[i].dstArrayElement + d];

                if(bind.texelBufferView != VK_NULL_HANDLE)
                {
                    record->RemoveBindFrameRef(GetResID(bind.texelBufferView));
                    if(GetRecord(bind.texelBufferView)->baseResource != ResourceId())
                        record->RemoveBindFrameRef(GetRecord(bind.texelBufferView)->baseResource);
                }
                if(bind.imageInfo.imageView != VK_NULL_HANDLE)
                {
                    record->RemoveBindFrameRef(GetResID(bind.imageInfo.imageView));
                    record->RemoveBindFrameRef(GetRecord(bind.imageInfo.imageView)->baseResource);
                    if(GetRecord(bind.imageInfo.imageView)->baseResourceMem != ResourceId())
                        record->RemoveBindFrameRef(GetRecord(bind.imageInfo.imageView)->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));
                    if(GetRecord(bind.bufferInfo.buffer)->baseResource != ResourceId())
                        record->RemoveBindFrameRef(GetRecord(bind.bufferInfo.buffer)->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 &layout = *dstrecord->descInfo->layout;

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

            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];

            FrameRefType ref = eFrameRef_Write;

            switch(layout.bindings[pDescriptorCopies[i].dstBinding].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");
            }

            for(uint32_t d=0; d < pDescriptorCopies[i].descriptorCount; d++)
            {
                DescriptorSetSlot &bind = dstbinding[pDescriptorCopies[i].dstArrayElement + d];

                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[pDescriptorCopies[i].srcArrayElement + d];

                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);
                }
            }
        }

    }
}