VkResult WrappedVulkan::vkQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo *pBindInfo, VkFence fence) { if(m_State >= WRITING_CAPFRAME) { CACHE_THREAD_SERIALISER(); for(uint32_t i = 0; i < bindInfoCount; i++) { SCOPED_SERIALISE_CONTEXT(BIND_SPARSE); Serialise_vkQueueBindSparse(localSerialiser, queue, 1, pBindInfo + i, fence); m_FrameCaptureRecord->AddChunk(scope.Get()); GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read); GetResourceManager()->MarkResourceFrameReferenced(GetResID(fence), eFrameRef_Read); // images/buffers aren't marked referenced. If the only ref is a memory bind, we just skip it for(uint32_t w = 0; w < pBindInfo[i].waitSemaphoreCount; w++) GetResourceManager()->MarkResourceFrameReferenced(GetResID(pBindInfo[i].pWaitSemaphores[w]), eFrameRef_Read); for(uint32_t s = 0; s < pBindInfo[i].signalSemaphoreCount; s++) GetResourceManager()->MarkResourceFrameReferenced( GetResID(pBindInfo[i].pSignalSemaphores[s]), eFrameRef_Read); } } // update our internal page tables if(m_State >= WRITING) { for(uint32_t i = 0; i < bindInfoCount; i++) { for(uint32_t buf = 0; buf < pBindInfo[i].bufferBindCount; buf++) { const VkSparseBufferMemoryBindInfo &bind = pBindInfo[i].pBufferBinds[buf]; GetRecord(bind.buffer)->sparseInfo->Update(bind.bindCount, bind.pBinds); } for(uint32_t op = 0; op < pBindInfo[i].imageOpaqueBindCount; op++) { const VkSparseImageOpaqueMemoryBindInfo &bind = pBindInfo[i].pImageOpaqueBinds[op]; GetRecord(bind.image)->sparseInfo->Update(bind.bindCount, bind.pBinds); } for(uint32_t op = 0; op < pBindInfo[i].imageBindCount; op++) { const VkSparseImageMemoryBindInfo &bind = pBindInfo[i].pImageBinds[op]; GetRecord(bind.image)->sparseInfo->Update(bind.bindCount, bind.pBinds); } } } // need to allocate space for each bind batch size_t tempmemSize = sizeof(VkBindSparseInfo) * bindInfoCount; for(uint32_t i = 0; i < bindInfoCount; i++) { // within each batch, need to allocate space for each resource bind tempmemSize += pBindInfo[i].bufferBindCount * sizeof(VkSparseBufferMemoryBindInfo); tempmemSize += pBindInfo[i].imageOpaqueBindCount * sizeof(VkSparseImageOpaqueMemoryBindInfo); tempmemSize += pBindInfo[i].imageBindCount * sizeof(VkSparseImageMemoryBindInfo); tempmemSize += pBindInfo[i].waitSemaphoreCount * sizeof(VkSemaphore); tempmemSize += pBindInfo[i].signalSemaphoreCount * sizeof(VkSparseImageMemoryBindInfo); // within each resource bind, need to save space for each individual bind operation for(uint32_t b = 0; b < pBindInfo[i].bufferBindCount; b++) tempmemSize += pBindInfo[i].pBufferBinds[b].bindCount * sizeof(VkSparseMemoryBind); for(uint32_t b = 0; b < pBindInfo[i].imageOpaqueBindCount; b++) tempmemSize += pBindInfo[i].pImageOpaqueBinds[b].bindCount * sizeof(VkSparseMemoryBind); for(uint32_t b = 0; b < pBindInfo[i].imageBindCount; b++) tempmemSize += pBindInfo[i].pImageBinds[b].bindCount * sizeof(VkSparseImageMemoryBind); } byte *memory = GetTempMemory(tempmemSize); VkBindSparseInfo *unwrapped = (VkBindSparseInfo *)memory; byte *next = (byte *)(unwrapped + bindInfoCount); // now go over each batch.. for(uint32_t i = 0; i < bindInfoCount; i++) { // copy the original so we get all the params we don't need to change RDCASSERT(pBindInfo[i].sType == VK_STRUCTURE_TYPE_BIND_SPARSE_INFO && pBindInfo[i].pNext == NULL); unwrapped[i] = pBindInfo[i]; // unwrap the signal semaphores into a new array VkSemaphore *signal = (VkSemaphore *)next; next += sizeof(VkSemaphore) * unwrapped[i].signalSemaphoreCount; unwrapped[i].pSignalSemaphores = signal; for(uint32_t j = 0; j < unwrapped[i].signalSemaphoreCount; j++) signal[j] = Unwrap(pBindInfo[i].pSignalSemaphores[j]); // and the wait semaphores VkSemaphore *wait = (VkSemaphore *)next; next += sizeof(VkSemaphore) * unwrapped[i].waitSemaphoreCount; unwrapped[i].pWaitSemaphores = wait; for(uint32_t j = 0; j < unwrapped[i].waitSemaphoreCount; j++) wait[j] = Unwrap(pBindInfo[i].pWaitSemaphores[j]); // now copy & unwrap the sparse buffer binds VkSparseBufferMemoryBindInfo *buf = (VkSparseBufferMemoryBindInfo *)next; next += sizeof(VkSparseBufferMemoryBindInfo) * unwrapped[i].bufferBindCount; unwrapped[i].pBufferBinds = buf; for(uint32_t j = 0; j < unwrapped[i].bufferBindCount; j++) { buf[j] = pBindInfo[i].pBufferBinds[j]; buf[j].buffer = Unwrap(buf[j].buffer); // for each buffer bind, copy & unwrap the individual memory binds too VkSparseMemoryBind *binds = (VkSparseMemoryBind *)next; next += sizeof(VkSparseMemoryBind) * buf[j].bindCount; buf[j].pBinds = binds; for(uint32_t k = 0; k < buf[j].bindCount; k++) { binds[k] = pBindInfo[i].pBufferBinds[j].pBinds[k]; binds[k].memory = Unwrap(buf[j].pBinds[k].memory); } } // same as above VkSparseImageOpaqueMemoryBindInfo *opaque = (VkSparseImageOpaqueMemoryBindInfo *)next; next += sizeof(VkSparseImageOpaqueMemoryBindInfo) * unwrapped[i].imageOpaqueBindCount; unwrapped[i].pImageOpaqueBinds = opaque; for(uint32_t j = 0; j < unwrapped[i].imageOpaqueBindCount; j++) { opaque[j] = pBindInfo[i].pImageOpaqueBinds[j]; opaque[j].image = Unwrap(opaque[j].image); VkSparseMemoryBind *binds = (VkSparseMemoryBind *)next; next += sizeof(VkSparseMemoryBind) * opaque[j].bindCount; opaque[j].pBinds = binds; for(uint32_t k = 0; k < opaque[j].bindCount; k++) { binds[k] = pBindInfo[i].pImageOpaqueBinds[j].pBinds[k]; binds[k].memory = Unwrap(opaque[j].pBinds[k].memory); } } // same as above VkSparseImageMemoryBindInfo *im = (VkSparseImageMemoryBindInfo *)next; next += sizeof(VkSparseImageMemoryBindInfo) * unwrapped[i].imageBindCount; unwrapped[i].pImageBinds = im; for(uint32_t j = 0; j < unwrapped[i].imageBindCount; j++) { im[j] = pBindInfo[i].pImageBinds[j]; im[j].image = Unwrap(im[j].image); VkSparseImageMemoryBind *binds = (VkSparseImageMemoryBind *)next; next += sizeof(VkSparseImageMemoryBind) * im[j].bindCount; im[j].pBinds = binds; for(uint32_t k = 0; k < im[j].bindCount; k++) { binds[k] = pBindInfo[i].pImageBinds[j].pBinds[k]; binds[k].memory = Unwrap(im[j].pBinds[k].memory); } } } return ObjDisp(queue)->QueueBindSparse(Unwrap(queue), bindInfoCount, unwrapped, Unwrap(fence)); }
void WrappedVulkan::vkCmdWaitEvents( VkCommandBuffer cmdBuffer, uint32_t eventCount, const VkEvent* pEvents, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers) { { byte *memory = GetTempMemory( sizeof(VkEvent)*eventCount + sizeof(VkBufferMemoryBarrier)*bufferMemoryBarrierCount + sizeof(VkImageMemoryBarrier)*imageMemoryBarrierCount); VkEvent *ev = (VkEvent *)memory; VkImageMemoryBarrier *im = (VkImageMemoryBarrier *)(ev + eventCount); VkBufferMemoryBarrier *buf = (VkBufferMemoryBarrier *)(im + imageMemoryBarrierCount); for(uint32_t i=0; i < eventCount; i++) ev[i] = Unwrap(pEvents[i]); for(uint32_t i=0; i < bufferMemoryBarrierCount; i++) { buf[i] = pBufferMemoryBarriers[i]; buf[i].buffer = Unwrap(buf[i].buffer); } for(uint32_t i=0; i < imageMemoryBarrierCount; i++) { im[i] = pImageMemoryBarriers[i]; im[i].image = Unwrap(im[i].image); } ObjDisp(cmdBuffer)->CmdWaitEvents(Unwrap(cmdBuffer), eventCount, ev, srcStageMask, dstStageMask, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, buf, imageMemoryBarrierCount, im); } if(m_State >= WRITING) { VkResourceRecord *record = GetRecord(cmdBuffer); CACHE_THREAD_SERIALISER(); SCOPED_SERIALISE_CONTEXT(CMD_WAIT_EVENTS); Serialise_vkCmdWaitEvents(localSerialiser, cmdBuffer, eventCount, pEvents, srcStageMask, dstStageMask, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers); if(imageMemoryBarrierCount > 0) { SCOPED_LOCK(m_ImageLayoutsLock); GetResourceManager()->RecordBarriers(GetRecord(cmdBuffer)->cmdInfo->imgbarriers, m_ImageLayouts, imageMemoryBarrierCount, pImageMemoryBarriers); } record->AddChunk(scope.Get()); for(uint32_t i=0; i < eventCount; i++) record->MarkResourceFrameReferenced(GetResID(pEvents[i]), eFrameRef_Read); } }
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; }
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); } } } } }
VkResult WrappedVulkan::vkAllocateDescriptorSets( VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets) { size_t tempmemSize = sizeof(VkDescriptorSetAllocateInfo) + sizeof(VkDescriptorSetLayout)*pAllocateInfo->descriptorSetCount; byte *memory = GetTempMemory(tempmemSize); VkDescriptorSetAllocateInfo *unwrapped = (VkDescriptorSetAllocateInfo *)memory; VkDescriptorSetLayout *layouts = (VkDescriptorSetLayout *)(unwrapped + 1); *unwrapped = *pAllocateInfo; unwrapped->pSetLayouts = layouts; unwrapped->descriptorPool = Unwrap(unwrapped->descriptorPool); for(uint32_t i=0; i < pAllocateInfo->descriptorSetCount; i++) layouts[i] = Unwrap(pAllocateInfo->pSetLayouts[i]); VkResult ret = ObjDisp(device)->AllocateDescriptorSets(Unwrap(device), unwrapped, pDescriptorSets); if(ret != VK_SUCCESS) return ret; for(uint32_t i=0; i < pAllocateInfo->descriptorSetCount; i++) { ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), pDescriptorSets[i]); if(m_State >= WRITING) { Chunk *chunk = NULL; { CACHE_THREAD_SERIALISER(); VkDescriptorSetAllocateInfo info = *pAllocateInfo; info.descriptorSetCount = 1; info.pSetLayouts += i; SCOPED_SERIALISE_CONTEXT(ALLOC_DESC_SET); Serialise_vkAllocateDescriptorSets(localSerialiser, device, &info, &pDescriptorSets[i]); chunk = scope.Get(); } VkResourceRecord *record = GetResourceManager()->AddResourceRecord(pDescriptorSets[i]); record->AddChunk(chunk); ResourceId layoutID = GetResID(pAllocateInfo->pSetLayouts[i]); VkResourceRecord *layoutRecord = GetRecord(pAllocateInfo->pSetLayouts[i]); VkResourceRecord *poolrecord = GetRecord(pAllocateInfo->descriptorPool); { poolrecord->LockChunks(); poolrecord->pooledChildren.push_back(record); poolrecord->UnlockChunks(); } record->pool = poolrecord; record->AddParent(poolrecord); record->AddParent(GetResourceManager()->GetResourceRecord(layoutID)); // just always treat descriptor sets as dirty { SCOPED_LOCK(m_CapTransitionLock); if(m_State != WRITING_CAPFRAME) GetResourceManager()->MarkDirtyResource(id); else GetResourceManager()->MarkPendingDirty(id); } record->descInfo = new DescriptorSetData(); record->descInfo->layout = layoutRecord->descInfo->layout; record->descInfo->layout->CreateBindingsArray(record->descInfo->descBindings); } else { GetResourceManager()->AddLiveResource(id, pDescriptorSets[i]); } } return ret; }
VkResult WrappedVulkan::vkCreateDescriptorSetLayout( VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout) { size_t tempmemSize = sizeof(VkDescriptorSetLayoutBinding)*pCreateInfo->bindingCount; // need to count how many VkSampler arrays to allocate for for(uint32_t i=0; i < pCreateInfo->bindingCount; i++) if(pCreateInfo->pBindings[i].pImmutableSamplers) tempmemSize += pCreateInfo->pBindings[i].descriptorCount*sizeof(VkSampler); byte *memory = GetTempMemory(tempmemSize); VkDescriptorSetLayoutBinding *unwrapped = (VkDescriptorSetLayoutBinding *)memory; VkSampler *nextSampler = (VkSampler *)(unwrapped + pCreateInfo->bindingCount); for(uint32_t i=0; i < pCreateInfo->bindingCount; i++) { unwrapped[i] = pCreateInfo->pBindings[i]; if(unwrapped[i].pImmutableSamplers) { VkSampler *unwrappedSamplers = nextSampler; nextSampler += unwrapped[i].descriptorCount; for(uint32_t j=0; j < unwrapped[i].descriptorCount; j++) unwrappedSamplers[j] = Unwrap(unwrapped[i].pImmutableSamplers[j]); unwrapped[i].pImmutableSamplers = unwrappedSamplers; } } VkDescriptorSetLayoutCreateInfo unwrappedInfo = *pCreateInfo; unwrappedInfo.pBindings = unwrapped; VkResult ret = ObjDisp(device)->CreateDescriptorSetLayout(Unwrap(device), &unwrappedInfo, pAllocator, pSetLayout); if(ret == VK_SUCCESS) { ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), *pSetLayout); if(m_State >= WRITING) { Chunk *chunk = NULL; { CACHE_THREAD_SERIALISER(); SCOPED_SERIALISE_CONTEXT(CREATE_DESCRIPTOR_SET_LAYOUT); Serialise_vkCreateDescriptorSetLayout(localSerialiser, device, pCreateInfo, NULL, pSetLayout); chunk = scope.Get(); } VkResourceRecord *record = GetResourceManager()->AddResourceRecord(*pSetLayout); record->AddChunk(chunk); record->descInfo = new DescriptorSetData(); record->descInfo->layout = new DescSetLayout(); record->descInfo->layout->Init(GetResourceManager(), m_CreationInfo, pCreateInfo); } else { GetResourceManager()->AddLiveResource(id, *pSetLayout); m_CreationInfo.m_DescSetLayout[id].Init(GetResourceManager(), m_CreationInfo, &unwrappedInfo); } } return ret; }
VkResult WrappedVulkan::vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, const VkGraphicsPipelineCreateInfo *pCreateInfos, const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines) { // conservatively request memory for 5 stages on each pipeline // (worst case - can't have compute stage). Avoids needing to count byte *unwrapped = GetTempMemory(sizeof(VkGraphicsPipelineCreateInfo) * count + sizeof(VkPipelineShaderStageCreateInfo) * count * 5); // keep pipelines first in the memory, then the stages VkGraphicsPipelineCreateInfo *unwrappedInfos = (VkGraphicsPipelineCreateInfo *)unwrapped; VkPipelineShaderStageCreateInfo *nextUnwrappedStages = (VkPipelineShaderStageCreateInfo *)(unwrappedInfos + count); for(uint32_t i = 0; i < count; i++) { VkPipelineShaderStageCreateInfo *unwrappedStages = nextUnwrappedStages; nextUnwrappedStages += pCreateInfos[i].stageCount; for(uint32_t j = 0; j < pCreateInfos[i].stageCount; j++) { unwrappedStages[j] = pCreateInfos[i].pStages[j]; unwrappedStages[j].module = Unwrap(unwrappedStages[j].module); } unwrappedInfos[i] = pCreateInfos[i]; unwrappedInfos[i].pStages = unwrappedStages; unwrappedInfos[i].layout = Unwrap(unwrappedInfos[i].layout); unwrappedInfos[i].renderPass = Unwrap(unwrappedInfos[i].renderPass); unwrappedInfos[i].basePipelineHandle = Unwrap(unwrappedInfos[i].basePipelineHandle); } VkResult ret = ObjDisp(device)->CreateGraphicsPipelines( Unwrap(device), Unwrap(pipelineCache), count, unwrappedInfos, pAllocator, pPipelines); if(ret == VK_SUCCESS) { for(uint32_t i = 0; i < count; i++) { ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), pPipelines[i]); if(m_State >= WRITING) { Chunk *chunk = NULL; { CACHE_THREAD_SERIALISER(); VkGraphicsPipelineCreateInfo modifiedCreateInfo; const VkGraphicsPipelineCreateInfo *createInfo = &pCreateInfos[i]; // since we serialise one by one, we need to fixup basePipelineIndex if(createInfo->basePipelineIndex != -1 && createInfo->basePipelineIndex < (int)i) { modifiedCreateInfo = *createInfo; modifiedCreateInfo.basePipelineHandle = pPipelines[modifiedCreateInfo.basePipelineIndex]; modifiedCreateInfo.basePipelineIndex = -1; createInfo = &modifiedCreateInfo; } SCOPED_SERIALISE_CONTEXT(CREATE_GRAPHICS_PIPE); Serialise_vkCreateGraphicsPipelines(localSerialiser, device, pipelineCache, 1, createInfo, NULL, &pPipelines[i]); chunk = scope.Get(); } VkResourceRecord *record = GetResourceManager()->AddResourceRecord(pPipelines[i]); record->AddChunk(chunk); if(pipelineCache != VK_NULL_HANDLE) { VkResourceRecord *cacherecord = GetRecord(pipelineCache); record->AddParent(cacherecord); } VkResourceRecord *rprecord = GetRecord(pCreateInfos[i].renderPass); record->AddParent(rprecord); VkResourceRecord *layoutrecord = GetRecord(pCreateInfos[i].layout); record->AddParent(layoutrecord); for(uint32_t s = 0; s < pCreateInfos[i].stageCount; s++) { VkResourceRecord *modulerecord = GetRecord(pCreateInfos[i].pStages[s].module); record->AddParent(modulerecord); } } else { GetResourceManager()->AddLiveResource(id, pPipelines[i]); m_CreationInfo.m_Pipeline[id].Init(GetResourceManager(), m_CreationInfo, &unwrappedInfos[i]); } } } return ret; }