mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-09 17:17:11 +00:00
2210 lines
76 KiB
C++
2210 lines
76 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-2022 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include <algorithm>
|
|
#include "../vk_core.h"
|
|
#include "../vk_debug.h"
|
|
#include "core/settings.h"
|
|
|
|
RDOC_EXTERN_CONFIG(bool, Vulkan_Debug_VerboseCommandRecording);
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkGetDeviceQueue(SerialiserType &ser, VkDevice device,
|
|
uint32_t queueFamilyIndex, uint32_t queueIndex,
|
|
VkQueue *pQueue)
|
|
{
|
|
SERIALISE_ELEMENT(device);
|
|
SERIALISE_ELEMENT(queueFamilyIndex).Important();
|
|
SERIALISE_ELEMENT(queueIndex).Important();
|
|
SERIALISE_ELEMENT_LOCAL(Queue, GetResID(*pQueue)).TypedAs("VkQueue"_lit);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
VkQueue queue;
|
|
|
|
uint32_t remapFamily = m_QueueRemapping[queueFamilyIndex][queueIndex].family;
|
|
uint32_t remapIndex = m_QueueRemapping[queueFamilyIndex][queueIndex].index;
|
|
|
|
if(remapFamily != queueFamilyIndex || remapIndex != queueIndex)
|
|
RDCLOG("Remapped Queue %u/%u from capture to %u/%u on replay", queueFamilyIndex, queueIndex,
|
|
remapFamily, remapIndex);
|
|
|
|
ObjDisp(device)->GetDeviceQueue(Unwrap(device), remapFamily, remapIndex, &queue);
|
|
|
|
if(GetResourceManager()->HasWrapper(ToTypedHandle(queue)))
|
|
{
|
|
ResourceId live = GetResourceManager()->GetDispWrapper(queue)->id;
|
|
|
|
// whenever the new ID is requested, return the old ID, via replacements.
|
|
GetResourceManager()->ReplaceResource(Queue, GetResourceManager()->GetOriginalID(live));
|
|
}
|
|
else
|
|
{
|
|
GetResourceManager()->WrapResource(Unwrap(device), queue);
|
|
GetResourceManager()->AddLiveResource(Queue, queue);
|
|
}
|
|
|
|
if(remapFamily == m_QueueFamilyIdx && m_Queue == VK_NULL_HANDLE)
|
|
{
|
|
m_Queue = queue;
|
|
|
|
// we can now submit any cmds that were queued (e.g. from creating debug
|
|
// manager on vkCreateDevice)
|
|
SubmitCmds();
|
|
}
|
|
|
|
if(remapFamily < m_ExternalQueues.size())
|
|
{
|
|
if(m_ExternalQueues[remapFamily].queue == VK_NULL_HANDLE)
|
|
m_ExternalQueues[remapFamily].queue = queue;
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected queue family index %u", remapFamily);
|
|
}
|
|
|
|
m_CreationInfo.m_Queue[GetResID(queue)] = remapFamily;
|
|
|
|
AddResource(Queue, ResourceType::Queue, "Queue");
|
|
DerivedResource(device, Queue);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedVulkan::vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex,
|
|
uint32_t queueIndex, VkQueue *pQueue)
|
|
{
|
|
SERIALISE_TIME_CALL(
|
|
ObjDisp(device)->GetDeviceQueue(Unwrap(device), queueFamilyIndex, queueIndex, pQueue));
|
|
|
|
if(m_SetDeviceLoaderData)
|
|
m_SetDeviceLoaderData(m_Device, *pQueue);
|
|
else
|
|
SetDispatchTableOverMagicNumber(device, *pQueue);
|
|
|
|
RDCASSERT(IsCaptureMode(m_State));
|
|
|
|
{
|
|
// it's perfectly valid for enumerate type functions to return the same handle
|
|
// each time. If that happens, we will already have a wrapper created so just
|
|
// return the wrapped object to the user and do nothing else
|
|
if(m_QueueFamilies[queueFamilyIndex][queueIndex] != VK_NULL_HANDLE)
|
|
{
|
|
*pQueue = m_QueueFamilies[queueFamilyIndex][queueIndex];
|
|
}
|
|
else
|
|
{
|
|
ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), *pQueue);
|
|
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkGetDeviceQueue);
|
|
Serialise_vkGetDeviceQueue(ser, device, queueFamilyIndex, queueIndex, pQueue);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
VkResourceRecord *record = GetResourceManager()->AddResourceRecord(*pQueue);
|
|
RDCASSERT(record);
|
|
|
|
record->queueFamilyIndex = queueFamilyIndex;
|
|
|
|
VkResourceRecord *instrecord = GetRecord(m_Instance);
|
|
|
|
// treat queues as pool members of the instance (ie. freed when the instance dies)
|
|
{
|
|
instrecord->LockChunks();
|
|
instrecord->pooledChildren.push_back(record);
|
|
instrecord->UnlockChunks();
|
|
}
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
|
|
m_QueueFamilies[queueFamilyIndex][queueIndex] = *pQueue;
|
|
|
|
if(queueFamilyIndex < m_ExternalQueues.size())
|
|
{
|
|
if(m_ExternalQueues[queueFamilyIndex].queue == VK_NULL_HANDLE)
|
|
m_ExternalQueues[queueFamilyIndex].queue = *pQueue;
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected queue family index %u", queueFamilyIndex);
|
|
}
|
|
|
|
if(queueFamilyIndex == m_QueueFamilyIdx)
|
|
{
|
|
m_Queue = *pQueue;
|
|
|
|
// we can now submit any cmds that were queued (e.g. from creating debug
|
|
// manager on vkCreateDevice)
|
|
SubmitCmds();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedVulkan::DoSubmit(VkQueue queue, VkSubmitInfo2 submitInfo)
|
|
{
|
|
// don't submit any semaphores
|
|
submitInfo.waitSemaphoreInfoCount = 0;
|
|
submitInfo.pWaitSemaphoreInfos = NULL;
|
|
submitInfo.signalSemaphoreInfoCount = 0;
|
|
submitInfo.pSignalSemaphoreInfos = NULL;
|
|
|
|
if(GetExtensions(NULL).ext_KHR_synchronization2)
|
|
{
|
|
// if we have KHR_sync2 this is easy! unwrap, add our submit chain, and do it
|
|
|
|
byte *tempMem = GetTempMemory(GetNextPatchSize(&submitInfo));
|
|
VkSubmitInfo2 *unwrapped = UnwrapStructAndChain(m_State, tempMem, &submitInfo);
|
|
AppendNextStruct(*unwrapped, m_SubmitChain);
|
|
|
|
// 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)->QueueSubmit2(Unwrap(queue), 1, unwrapped, VK_NULL_HANDLE);
|
|
}
|
|
else
|
|
{
|
|
// otherwise we need to decompose into an original submit
|
|
|
|
VkSubmitInfo info = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
rdcarray<VkCommandBuffer> commandBuffers;
|
|
rdcarray<uint32_t> groupMasks;
|
|
|
|
VkProtectedSubmitInfo prot = {VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO, NULL, VK_TRUE};
|
|
VkDeviceGroupSubmitInfo group = {VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO};
|
|
|
|
// we expect the pNext chain to be NULL, as there's nothing we should be replaying that we can
|
|
// represent in this decomposed version
|
|
RDCASSERTEQUAL((void *)submitInfo.pNext, (void *)NULL);
|
|
|
|
if(submitInfo.flags & VK_SUBMIT_PROTECTED_BIT)
|
|
{
|
|
// we created the protected structure with the flag as TRUE since otherwise we just don't
|
|
// chain it on at all
|
|
AppendNextStruct(info, &prot);
|
|
}
|
|
|
|
commandBuffers.resize(submitInfo.commandBufferInfoCount);
|
|
for(uint32_t i = 0; i < submitInfo.commandBufferInfoCount; i++)
|
|
{
|
|
commandBuffers[i] = submitInfo.pCommandBufferInfos[i].commandBuffer;
|
|
|
|
if(submitInfo.pCommandBufferInfos[i].deviceMask != 0)
|
|
{
|
|
groupMasks.resize(submitInfo.commandBufferInfoCount);
|
|
groupMasks[i] = submitInfo.pCommandBufferInfos[i].deviceMask;
|
|
}
|
|
}
|
|
|
|
info.commandBufferCount = submitInfo.commandBufferInfoCount;
|
|
info.pCommandBuffers = commandBuffers.data();
|
|
|
|
if(!groupMasks.empty())
|
|
{
|
|
group.commandBufferCount = info.commandBufferCount;
|
|
group.pCommandBufferDeviceMasks = groupMasks.data();
|
|
// if we set up group masks, chain on the struct
|
|
AppendNextStruct(info, &group);
|
|
}
|
|
|
|
byte *tempMem = GetTempMemory(GetNextPatchSize(&info));
|
|
VkSubmitInfo *unwrapped = UnwrapStructAndChain(m_State, tempMem, &info);
|
|
AppendNextStruct(*unwrapped, m_SubmitChain);
|
|
|
|
// 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, unwrapped, VK_NULL_HANDLE);
|
|
}
|
|
}
|
|
|
|
void WrappedVulkan::ReplayQueueSubmit(VkQueue queue, VkSubmitInfo2 submitInfo, rdcstr basename)
|
|
{
|
|
if(IsLoading(m_State))
|
|
{
|
|
DoSubmit(queue, submitInfo);
|
|
|
|
AddEvent();
|
|
|
|
// we're adding multiple events, need to increment ourselves
|
|
m_RootEventID++;
|
|
|
|
for(uint32_t c = 0; c < submitInfo.commandBufferInfoCount; c++)
|
|
{
|
|
ResourceId cmd = GetResourceManager()->GetOriginalID(
|
|
GetResID(submitInfo.pCommandBufferInfos[c].commandBuffer));
|
|
|
|
BakedCmdBufferInfo &cmdBufInfo = m_BakedCmdBufferInfo[cmd];
|
|
|
|
UpdateImageStates(m_BakedCmdBufferInfo[cmd].imageStates);
|
|
|
|
rdcstr name = StringFormat::Fmt("=> %s[%u]: vkBeginCommandBuffer(%s)", basename.c_str(), c,
|
|
ToStr(cmd).c_str());
|
|
|
|
ActionDescription action;
|
|
{
|
|
// add a fake marker
|
|
action.customName = name;
|
|
action.flags |=
|
|
ActionFlags::CommandBufferBoundary | ActionFlags::PassBoundary | ActionFlags::BeginPass;
|
|
AddEvent();
|
|
|
|
m_RootEvents.back().chunkIndex = cmdBufInfo.beginChunk;
|
|
m_Events.back().chunkIndex = cmdBufInfo.beginChunk;
|
|
|
|
AddAction(action);
|
|
m_RootEventID++;
|
|
}
|
|
|
|
// insert the baked command buffer in-line into this list of notes, assigning new event
|
|
// and drawIDs
|
|
InsertActionsAndRefreshIDs(cmdBufInfo);
|
|
|
|
for(size_t e = 0; e < cmdBufInfo.action->executedCmds.size(); e++)
|
|
{
|
|
rdcarray<Submission> &submits =
|
|
m_Partial[Secondary].cmdBufferSubmits[cmdBufInfo.action->executedCmds[e]];
|
|
|
|
for(size_t s = 0; s < submits.size(); s++)
|
|
{
|
|
if(!submits[s].rebased)
|
|
{
|
|
submits[s].baseEvent += m_RootEventID;
|
|
submits[s].rebased = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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[cmd].push_back(Submission(m_RootEventID));
|
|
|
|
m_RootEventID += cmdBufInfo.eventCount;
|
|
m_RootActionID += cmdBufInfo.actionCount;
|
|
|
|
{
|
|
// pull in any remaining events on the command buffer that weren't added to an action
|
|
uint32_t i = 0;
|
|
for(APIEvent &apievent : cmdBufInfo.curEvents)
|
|
{
|
|
apievent.eventId = m_RootEventID - cmdBufInfo.curEvents.count() + i;
|
|
|
|
m_RootEvents.push_back(apievent);
|
|
m_Events.resize(apievent.eventId + 1);
|
|
m_Events[apievent.eventId] = apievent;
|
|
|
|
i++;
|
|
}
|
|
|
|
for(auto it = cmdBufInfo.resourceUsage.begin(); it != cmdBufInfo.resourceUsage.end(); ++it)
|
|
{
|
|
EventUsage u = it->second;
|
|
u.eventId += m_RootEventID - cmdBufInfo.curEvents.count();
|
|
m_ResourceUses[it->first].push_back(u);
|
|
m_EventFlags[u.eventId] |= PipeRWUsageEventFlags(u.usage);
|
|
}
|
|
|
|
name = StringFormat::Fmt("=> %s[%u]: vkEndCommandBuffer(%s)", basename.c_str(), c,
|
|
ToStr(cmd).c_str());
|
|
action.customName = name;
|
|
action.flags =
|
|
ActionFlags::CommandBufferBoundary | ActionFlags::PassBoundary | ActionFlags::EndPass;
|
|
AddEvent();
|
|
|
|
m_RootEvents.back().chunkIndex = cmdBufInfo.endChunk;
|
|
m_Events.back().chunkIndex = cmdBufInfo.endChunk;
|
|
|
|
AddAction(action);
|
|
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
|
|
{
|
|
// 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 < submitInfo.commandBufferInfoCount; c++)
|
|
{
|
|
ResourceId cmd = GetResourceManager()->GetOriginalID(
|
|
GetResID(submitInfo.pCommandBufferInfos[c].commandBuffer));
|
|
|
|
m_RootEventID += m_BakedCmdBufferInfo[cmd].eventCount;
|
|
m_RootActionID += m_BakedCmdBufferInfo[cmd].actionCount;
|
|
|
|
// 2 extra for the virtual labels around the command buffer
|
|
{
|
|
m_RootEventID += 2;
|
|
m_RootActionID += 2;
|
|
}
|
|
}
|
|
|
|
// same accounting for the outer loop as above
|
|
m_RootEventID--;
|
|
|
|
if(submitInfo.commandBufferInfoCount == 0)
|
|
{
|
|
// do nothing, don't bother with the logic below
|
|
}
|
|
else if(m_LastEventID <= startEID)
|
|
{
|
|
#if ENABLED(VERBOSE_PARTIAL_REPLAY)
|
|
RDCDEBUG("Queue Submit no replay %u == %u", m_LastEventID, startEID);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if ENABLED(VERBOSE_PARTIAL_REPLAY)
|
|
RDCDEBUG("Queue Submit from re-recorded commands, root EID %u last EID", m_RootEventID,
|
|
m_LastEventID);
|
|
#endif
|
|
|
|
uint32_t eid = startEID;
|
|
|
|
rdcarray<VkCommandBufferSubmitInfo> rerecordedCmds;
|
|
|
|
for(uint32_t c = 0; c < submitInfo.commandBufferInfoCount; c++)
|
|
{
|
|
VkCommandBufferSubmitInfo info = submitInfo.pCommandBufferInfos[c];
|
|
ResourceId cmdId = GetResourceManager()->GetOriginalID(GetResID(info.commandBuffer));
|
|
|
|
// account for the virtual vkBeginCommandBuffer label at the start of the events here
|
|
// so it matches up to baseEvent
|
|
eid++;
|
|
|
|
#if ENABLED(VERBOSE_PARTIAL_REPLAY)
|
|
uint32_t end = eid + m_BakedCmdBufferInfo[cmdId].eventCount;
|
|
#endif
|
|
|
|
if(eid <= m_LastEventID)
|
|
{
|
|
VkCommandBuffer cmd = RerecordCmdBuf(cmdId);
|
|
#if ENABLED(VERBOSE_PARTIAL_REPLAY)
|
|
ResourceId rerecord = GetResID(cmd);
|
|
RDCDEBUG("Queue Submit re-recorded replay of %s, using %s (%u -> %u <= %u)",
|
|
ToStr(cmdId).c_str(), ToStr(rerecord).c_str(), eid, end, m_LastEventID);
|
|
#endif
|
|
info.commandBuffer = cmd;
|
|
rerecordedCmds.push_back(info);
|
|
|
|
UpdateImageStates(m_BakedCmdBufferInfo[cmdId].imageStates);
|
|
}
|
|
else
|
|
{
|
|
#if ENABLED(VERBOSE_PARTIAL_REPLAY)
|
|
RDCDEBUG("Queue not submitting %s", ToStr(cmdId).c_str());
|
|
#endif
|
|
}
|
|
|
|
eid += m_BakedCmdBufferInfo[cmdId].eventCount;
|
|
|
|
// 1 extra to account for the virtual end command buffer label (begin is accounted for
|
|
// above)
|
|
eid++;
|
|
}
|
|
|
|
submitInfo.pCommandBufferInfos = rerecordedCmds.data();
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
submitInfo.commandBufferInfoCount = 1;
|
|
for(size_t i = 0; i < rerecordedCmds.size(); i++)
|
|
{
|
|
DoSubmit(queue, submitInfo);
|
|
submitInfo.pCommandBufferInfos++;
|
|
|
|
FlushQ();
|
|
}
|
|
#else
|
|
submitInfo.commandBufferInfoCount = (uint32_t)rerecordedCmds.size();
|
|
|
|
DoSubmit(queue, submitInfo);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
FlushQ();
|
|
#endif
|
|
}
|
|
|
|
bool WrappedVulkan::PatchIndirectDraw(size_t drawIndex, uint32_t paramStride,
|
|
VkIndirectPatchType type, ActionDescription &action,
|
|
byte *&argptr, byte *argend)
|
|
{
|
|
bool valid = false;
|
|
|
|
action.drawIndex = (uint32_t)drawIndex;
|
|
|
|
if(type == VkIndirectPatchType::DrawIndirect || type == VkIndirectPatchType::DrawIndirectCount)
|
|
{
|
|
if(argptr && argptr + sizeof(VkDrawIndirectCommand) <= argend)
|
|
{
|
|
VkDrawIndirectCommand *arg = (VkDrawIndirectCommand *)argptr;
|
|
|
|
action.numIndices = arg->vertexCount;
|
|
action.numInstances = arg->instanceCount;
|
|
action.vertexOffset = arg->firstVertex;
|
|
action.instanceOffset = arg->firstInstance;
|
|
|
|
valid = true;
|
|
}
|
|
}
|
|
else if(type == VkIndirectPatchType::DrawIndirectByteCount)
|
|
{
|
|
if(argptr && argptr + 4 <= argend)
|
|
{
|
|
uint32_t *arg = (uint32_t *)argptr;
|
|
|
|
action.numIndices = *arg;
|
|
|
|
valid = true;
|
|
}
|
|
}
|
|
else if(type == VkIndirectPatchType::DrawIndexedIndirect ||
|
|
type == VkIndirectPatchType::DrawIndexedIndirectCount)
|
|
{
|
|
if(argptr && argptr + sizeof(VkDrawIndexedIndirectCommand) <= argend)
|
|
{
|
|
VkDrawIndexedIndirectCommand *arg = (VkDrawIndexedIndirectCommand *)argptr;
|
|
|
|
action.numIndices = arg->indexCount;
|
|
action.numInstances = arg->instanceCount;
|
|
action.baseVertex = arg->vertexOffset;
|
|
action.indexOffset = arg->firstIndex;
|
|
action.instanceOffset = arg->firstInstance;
|
|
|
|
valid = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected indirect action type");
|
|
}
|
|
|
|
if(valid && !action.events.empty())
|
|
{
|
|
SDChunk *chunk = m_StructuredFile->chunks[action.events.back().chunkIndex];
|
|
|
|
if(chunk->metadata.chunkID != (uint32_t)VulkanChunk::vkCmdIndirectSubCommand)
|
|
chunk = m_StructuredFile->chunks[action.events.back().chunkIndex - 1];
|
|
|
|
SDObject *drawIdx = chunk->FindChild("drawIndex");
|
|
|
|
if(drawIdx)
|
|
drawIdx->data.basic.u = drawIndex;
|
|
|
|
SDObject *offset = chunk->FindChild("offset");
|
|
|
|
if(offset)
|
|
offset->data.basic.u += drawIndex * paramStride;
|
|
|
|
SDObject *command = chunk->FindChild("command");
|
|
|
|
// single action indirect draws don't have a command child since it can't be added without
|
|
// breaking serialising the chunk.
|
|
if(command)
|
|
{
|
|
// patch up structured data contents
|
|
if(SDObject *sub = command->FindChild("vertexCount"))
|
|
sub->data.basic.u = action.numIndices;
|
|
if(SDObject *sub = command->FindChild("indexCount"))
|
|
sub->data.basic.u = action.numIndices;
|
|
if(SDObject *sub = command->FindChild("instanceCount"))
|
|
sub->data.basic.u = action.numInstances;
|
|
if(SDObject *sub = command->FindChild("firstVertex"))
|
|
sub->data.basic.u = action.vertexOffset;
|
|
if(SDObject *sub = command->FindChild("vertexOffset"))
|
|
sub->data.basic.u = action.baseVertex;
|
|
if(SDObject *sub = command->FindChild("firstIndex"))
|
|
sub->data.basic.u = action.indexOffset;
|
|
if(SDObject *sub = command->FindChild("firstInstance"))
|
|
sub->data.basic.u = action.instanceOffset;
|
|
}
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
void WrappedVulkan::InsertActionsAndRefreshIDs(BakedCmdBufferInfo &cmdBufInfo)
|
|
{
|
|
rdcarray<VulkanActionTreeNode> &cmdBufNodes = cmdBufInfo.action->children;
|
|
|
|
// assign new action IDs
|
|
for(size_t i = 0; i < cmdBufNodes.size(); i++)
|
|
{
|
|
VulkanActionTreeNode n = cmdBufNodes[i];
|
|
n.action.eventId += m_RootEventID;
|
|
n.action.actionId += m_RootActionID;
|
|
|
|
if(n.indirectPatch.type == VkIndirectPatchType::DispatchIndirect)
|
|
{
|
|
VkDispatchIndirectCommand unknown = {0};
|
|
bytebuf argbuf;
|
|
GetDebugManager()->GetBufferData(GetResID(n.indirectPatch.buf), 0, 0, argbuf);
|
|
VkDispatchIndirectCommand *args = (VkDispatchIndirectCommand *)&argbuf[0];
|
|
|
|
if(argbuf.size() < sizeof(VkDispatchIndirectCommand))
|
|
{
|
|
RDCERR("Couldn't fetch arguments buffer for vkCmdDispatchIndirect");
|
|
args = &unknown;
|
|
}
|
|
|
|
n.action.customName =
|
|
StringFormat::Fmt("vkCmdDispatchIndirect(<%u, %u, %u>)", args->x, args->y, args->z);
|
|
n.action.dispatchDimension[0] = args->x;
|
|
n.action.dispatchDimension[1] = args->y;
|
|
n.action.dispatchDimension[2] = args->z;
|
|
}
|
|
else if(n.indirectPatch.type == VkIndirectPatchType::DrawIndirectByteCount ||
|
|
n.indirectPatch.type == VkIndirectPatchType::DrawIndirect ||
|
|
n.indirectPatch.type == VkIndirectPatchType::DrawIndexedIndirect ||
|
|
n.indirectPatch.type == VkIndirectPatchType::DrawIndirectCount ||
|
|
n.indirectPatch.type == VkIndirectPatchType::DrawIndexedIndirectCount)
|
|
{
|
|
bool hasCount = (n.indirectPatch.type == VkIndirectPatchType::DrawIndirectCount ||
|
|
n.indirectPatch.type == VkIndirectPatchType::DrawIndexedIndirectCount);
|
|
bytebuf argbuf;
|
|
GetDebugManager()->GetBufferData(GetResID(n.indirectPatch.buf), 0, 0, argbuf);
|
|
|
|
byte *ptr = argbuf.begin(), *end = argbuf.end();
|
|
|
|
uint32_t indirectCount = n.indirectPatch.count;
|
|
if(hasCount)
|
|
{
|
|
if(argbuf.size() >= 16)
|
|
{
|
|
uint32_t *count = (uint32_t *)end;
|
|
count -= 4;
|
|
indirectCount = *count;
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Couldn't get indirect action count");
|
|
}
|
|
|
|
if(indirectCount > n.indirectPatch.count)
|
|
{
|
|
RDCERR("Indirect count higher than maxCount, clamping");
|
|
indirectCount = n.indirectPatch.count;
|
|
}
|
|
|
|
// this can be negative if indirectCount is 0
|
|
int32_t eidShift = indirectCount - 1;
|
|
|
|
// we reserved one event and action for the indirect count based action.
|
|
// if we ended up with a different number eidShift will be non-zero, so we need to adjust
|
|
// all subsequent EIDs and action IDs and either remove the subdraw we allocated (if no
|
|
// draws
|
|
// happened) or clone the subdraw to create more that we can then patch.
|
|
if(eidShift != 0)
|
|
{
|
|
// i is the pushmarker, so i + 1 is the sub draws, and i + 2 is the pop marker.
|
|
// adjust all EIDs and action IDs after that point
|
|
for(size_t j = i + 2; j < cmdBufNodes.size(); j++)
|
|
{
|
|
cmdBufNodes[j].action.eventId += eidShift;
|
|
cmdBufNodes[j].action.actionId += eidShift;
|
|
|
|
for(APIEvent &ev : cmdBufNodes[j].action.events)
|
|
ev.eventId += eidShift;
|
|
|
|
for(rdcpair<ResourceId, EventUsage> &use : cmdBufNodes[j].resourceUsage)
|
|
use.second.eventId += eidShift;
|
|
}
|
|
|
|
for(size_t j = 0; j < cmdBufInfo.debugMessages.size(); j++)
|
|
{
|
|
if(cmdBufInfo.debugMessages[j].eventId >= cmdBufNodes[i].action.eventId + 2)
|
|
cmdBufInfo.debugMessages[j].eventId += eidShift;
|
|
}
|
|
|
|
cmdBufInfo.eventCount += eidShift;
|
|
cmdBufInfo.actionCount += eidShift;
|
|
|
|
// we also need to patch the original secondary command buffer here, if the indirect call
|
|
// was on a secondary, so that vkCmdExecuteCommands knows accurately how many events are
|
|
// in the command buffer.
|
|
if(n.indirectPatch.commandBuffer != ResourceId())
|
|
{
|
|
m_BakedCmdBufferInfo[n.indirectPatch.commandBuffer].eventCount += eidShift;
|
|
m_BakedCmdBufferInfo[n.indirectPatch.commandBuffer].actionCount += eidShift;
|
|
}
|
|
|
|
for(size_t e = 0; e < cmdBufInfo.action->executedCmds.size(); e++)
|
|
{
|
|
rdcarray<Submission> &submits =
|
|
m_Partial[Secondary].cmdBufferSubmits[cmdBufInfo.action->executedCmds[e]];
|
|
|
|
for(size_t s = 0; s < submits.size(); s++)
|
|
{
|
|
if(submits[s].baseEvent >= cmdBufNodes[i].action.eventId + 2)
|
|
submits[s].baseEvent += eidShift;
|
|
}
|
|
}
|
|
|
|
RDCASSERT(cmdBufNodes[i + 1].action.events.size() == 1);
|
|
uint32_t chunkIndex = cmdBufNodes[i + 1].action.events[0].chunkIndex;
|
|
|
|
// everything afterwards is adjusted. Now see if we need to remove the subdraw or clone it
|
|
if(indirectCount == 0)
|
|
{
|
|
// i is the pushmarker, which we leave. i+1 is the subdraw
|
|
cmdBufNodes.erase(i + 1);
|
|
}
|
|
else
|
|
{
|
|
// duplicate the fake structured data chunk N times
|
|
SDChunk *chunk = m_StructuredFile->chunks[chunkIndex];
|
|
|
|
uint32_t baseAddedChunk = (uint32_t)m_StructuredFile->chunks.size();
|
|
m_StructuredFile->chunks.reserve(m_StructuredFile->chunks.size() + eidShift);
|
|
for(int32_t e = 0; e < eidShift; e++)
|
|
m_StructuredFile->chunks.push_back(chunk->Duplicate());
|
|
|
|
// now copy the subdraw so we're not inserting into the array from itself
|
|
VulkanActionTreeNode node = cmdBufNodes[i + 1];
|
|
|
|
cmdBufNodes.resize(cmdBufNodes.size() + eidShift);
|
|
for(size_t e = cmdBufNodes.size() - 1; e > i + 1 + eidShift; e--)
|
|
cmdBufNodes[e] = std::move(cmdBufNodes[e - eidShift]);
|
|
|
|
// then insert enough duplicates
|
|
for(int32_t e = 0; e < eidShift; e++)
|
|
{
|
|
node.action.eventId++;
|
|
node.action.actionId++;
|
|
|
|
for(APIEvent &ev : node.action.events)
|
|
{
|
|
ev.eventId++;
|
|
ev.chunkIndex = baseAddedChunk + e;
|
|
}
|
|
|
|
for(rdcpair<ResourceId, EventUsage> &use : node.resourceUsage)
|
|
use.second.eventId++;
|
|
|
|
cmdBufNodes[i + 2 + e] = node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// indirect count versions always have a multidraw marker regions, but static count of 1 would
|
|
// be in-lined as a single action, so we patch in-place
|
|
if(!hasCount && indirectCount == 1)
|
|
{
|
|
rdcstr name = GetStructuredFile()->chunks[n.action.events.back().chunkIndex]->name;
|
|
|
|
bool valid =
|
|
PatchIndirectDraw(0, n.indirectPatch.stride, n.indirectPatch.type, n.action, ptr, end);
|
|
|
|
if(n.indirectPatch.type == VkIndirectPatchType::DrawIndirectByteCount)
|
|
{
|
|
if(n.action.numIndices > n.indirectPatch.vertexoffset)
|
|
n.action.numIndices -= n.indirectPatch.vertexoffset;
|
|
else
|
|
n.action.numIndices = 0;
|
|
|
|
n.action.numIndices /= n.indirectPatch.stride;
|
|
}
|
|
|
|
// if the actual action count was greater than 1, display this as an indirect count
|
|
const char *countString = (n.indirectPatch.count > 1 ? "<1>" : "1");
|
|
|
|
if(valid)
|
|
n.action.customName = StringFormat::Fmt("%s(%s) => <%u, %u>", name.c_str(), countString,
|
|
n.action.numIndices, n.action.numInstances);
|
|
else
|
|
n.action.customName = StringFormat::Fmt("%s(%s) => <?, ?>", name.c_str(), countString);
|
|
}
|
|
else
|
|
{
|
|
// we should have N draws immediately following this one, check that that's the case
|
|
RDCASSERT(i + indirectCount < cmdBufNodes.size(), i, indirectCount, n.indirectPatch.count,
|
|
cmdBufNodes.size());
|
|
|
|
rdcstr name = GetStructuredFile()->chunks[n.action.events.back().chunkIndex]->name;
|
|
|
|
// patch the count onto the root action name. The root is otherwise un-suffixed to allow
|
|
// for collapsing non-multidraws and making everything generally simpler
|
|
if(hasCount)
|
|
n.action.customName = StringFormat::Fmt("%s(<%u>)", name.c_str(), indirectCount);
|
|
else
|
|
n.action.customName = StringFormat::Fmt("%s(%u)", name.c_str(), n.indirectPatch.count);
|
|
|
|
for(size_t j = 0; j < (size_t)indirectCount && i + j + 1 < cmdBufNodes.size(); j++)
|
|
{
|
|
VulkanActionTreeNode &n2 = cmdBufNodes[i + j + 1];
|
|
|
|
bool valid = PatchIndirectDraw(j, n.indirectPatch.stride, n.indirectPatch.type, n2.action,
|
|
ptr, end);
|
|
|
|
name = GetStructuredFile()->chunks[n2.action.events.back().chunkIndex]->name;
|
|
|
|
if(valid)
|
|
n2.action.customName = StringFormat::Fmt("%s[%zu](<%u, %u>)", name.c_str(), j,
|
|
n2.action.numIndices, n2.action.numInstances);
|
|
else
|
|
n2.action.customName = StringFormat::Fmt("%s[%zu](<?, ?>)", name.c_str(), j);
|
|
|
|
if(ptr)
|
|
ptr += n.indirectPatch.stride;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(APIEvent &ev : n.action.events)
|
|
{
|
|
ev.eventId += m_RootEventID;
|
|
m_Events.resize(ev.eventId + 1);
|
|
m_Events[ev.eventId] = ev;
|
|
}
|
|
|
|
if(!n.action.events.empty())
|
|
{
|
|
ActionUse use(n.action.events.back().fileOffset, n.action.eventId);
|
|
|
|
// insert in sorted location
|
|
auto drawit = std::lower_bound(m_ActionUses.begin(), m_ActionUses.end(), use);
|
|
m_ActionUses.insert(drawit - m_ActionUses.begin(), use);
|
|
}
|
|
|
|
RDCASSERT(n.children.empty());
|
|
|
|
for(auto it = n.resourceUsage.begin(); it != n.resourceUsage.end(); ++it)
|
|
{
|
|
EventUsage u = it->second;
|
|
u.eventId += m_RootEventID;
|
|
m_ResourceUses[it->first].push_back(u);
|
|
m_EventFlags[u.eventId] |= PipeRWUsageEventFlags(u.usage);
|
|
}
|
|
|
|
GetActionStack().back()->children.push_back(n);
|
|
|
|
// if this is a push marker too, step down the action stack
|
|
if(cmdBufNodes[i].action.flags & ActionFlags::PushMarker)
|
|
GetActionStack().push_back(&GetActionStack().back()->children.back());
|
|
|
|
// similarly for a pop, but don't pop off the root
|
|
if((cmdBufNodes[i].action.flags & ActionFlags::PopMarker) && GetActionStack().size() > 1)
|
|
GetActionStack().pop_back();
|
|
}
|
|
}
|
|
|
|
void WrappedVulkan::CaptureQueueSubmit(VkQueue queue,
|
|
const rdcarray<VkCommandBuffer> &commandBuffers, VkFence fence)
|
|
{
|
|
bool capframe = IsActiveCapturing(m_State);
|
|
bool backframe = IsBackgroundCapturing(m_State);
|
|
|
|
std::unordered_set<ResourceId> refdIDs;
|
|
|
|
std::set<rdcpair<ResourceId, VkResourceRecord *>> capDescriptors;
|
|
std::set<rdcpair<ResourceId, VkResourceRecord *>> descriptorSets;
|
|
|
|
// pull in any copy sources, conservatively
|
|
if(capframe)
|
|
{
|
|
SCOPED_LOCK(m_CapDescriptorsLock);
|
|
capDescriptors.swap(m_CapDescriptors);
|
|
}
|
|
|
|
descriptorSets = capDescriptors;
|
|
|
|
for(size_t i = 0; i < commandBuffers.size(); i++)
|
|
{
|
|
ResourceId cmd = GetResID(commandBuffers[i]);
|
|
|
|
VkResourceRecord *record = GetRecord(commandBuffers[i]);
|
|
|
|
UpdateImageStates(record->bakedCommands->cmdInfo->imageStates);
|
|
|
|
if(Vulkan_Debug_VerboseCommandRecording())
|
|
{
|
|
RDCLOG("vkQueueSubmit() to queue %s, cmd %zu of %zu: %s baked to %s",
|
|
ToStr(GetResID(queue)).c_str(), i, commandBuffers.size(),
|
|
ToStr(record->GetResourceID()).c_str(),
|
|
ToStr(record->bakedCommands->GetResourceID()).c_str());
|
|
}
|
|
|
|
if(capframe)
|
|
{
|
|
// add the bound descriptor sets
|
|
for(auto it = record->bakedCommands->cmdInfo->boundDescSets.begin();
|
|
it != record->bakedCommands->cmdInfo->boundDescSets.end(); ++it)
|
|
{
|
|
descriptorSets.insert(*it);
|
|
}
|
|
|
|
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);
|
|
|
|
GetResourceManager()->MergeReferencedMemory(record->bakedCommands->cmdInfo->memFrameRefs);
|
|
|
|
// ref the parent command buffer's alloc record, this will pull in the cmd buffer pool
|
|
GetResourceManager()->MarkResourceFrameReferenced(
|
|
record->cmdInfo->allocRecord->GetResourceID(), eFrameRef_Read);
|
|
|
|
const rdcarray<VkResourceRecord *> &subcmds = record->bakedCommands->cmdInfo->subcmds;
|
|
|
|
for(size_t sub = 0; sub < subcmds.size(); sub++)
|
|
{
|
|
VkResourceRecord *bakedSubcmds = subcmds[sub]->bakedCommands;
|
|
bakedSubcmds->AddResourceReferences(GetResourceManager());
|
|
bakedSubcmds->AddReferencedIDs(refdIDs);
|
|
UpdateImageStates(bakedSubcmds->cmdInfo->imageStates);
|
|
GetResourceManager()->MergeReferencedMemory(bakedSubcmds->cmdInfo->memFrameRefs);
|
|
GetResourceManager()->MarkResourceFrameReferenced(
|
|
subcmds[sub]->cmdInfo->allocRecord->GetResourceID(), eFrameRef_Read);
|
|
|
|
bakedSubcmds->AddRef();
|
|
}
|
|
|
|
{
|
|
SCOPED_LOCK(m_CmdBufferRecordsLock);
|
|
m_CmdBufferRecords.push_back(record->bakedCommands);
|
|
for(size_t sub = 0; sub < subcmds.size(); sub++)
|
|
m_CmdBufferRecords.push_back(subcmds[sub]->bakedCommands);
|
|
}
|
|
|
|
record->bakedCommands->AddRef();
|
|
}
|
|
}
|
|
|
|
if(backframe)
|
|
{
|
|
rdcarray<VkResourceRecord *> maps;
|
|
{
|
|
SCOPED_LOCK(m_CoherentMapsLock);
|
|
maps = m_CoherentMaps;
|
|
}
|
|
|
|
for(auto it = maps.begin(); it != maps.end(); ++it)
|
|
{
|
|
VkResourceRecord *record = *it;
|
|
GetResourceManager()->MarkResourceFrameReferenced(record->GetResourceID(),
|
|
eFrameRef_ReadBeforeWrite);
|
|
}
|
|
|
|
// pull in frame refs while background capturing too
|
|
for(size_t i = 0; i < commandBuffers.size(); i++)
|
|
{
|
|
VkResourceRecord *record = GetRecord(commandBuffers[i]);
|
|
|
|
record->bakedCommands->AddResourceReferences(GetResourceManager());
|
|
|
|
for(VkResourceRecord *sub : record->bakedCommands->cmdInfo->subcmds)
|
|
sub->bakedCommands->AddResourceReferences(GetResourceManager());
|
|
}
|
|
|
|
// every 20 submits clean background references, in case the application isn't presenting.
|
|
if((Atomic::Inc64(&m_QueueCounter) % 20) == 0)
|
|
{
|
|
GetResourceManager()->CleanBackgroundFrameReferences();
|
|
}
|
|
}
|
|
|
|
if(capframe)
|
|
{
|
|
VulkanResourceManager *rm = GetResourceManager();
|
|
|
|
// for each descriptor set, mark it referenced as well as all resources currently bound to it
|
|
for(auto it = descriptorSets.begin(); it != descriptorSets.end(); ++it)
|
|
{
|
|
rm->MarkResourceFrameReferenced(it->first, eFrameRef_Read);
|
|
|
|
VkResourceRecord *setrecord = it->second;
|
|
|
|
DescriptorBindRefs refs;
|
|
|
|
DescSetLayout *layout = setrecord->descInfo->layout;
|
|
|
|
for(size_t b = 0, num = layout->bindings.size(); b < num; b++)
|
|
{
|
|
const DescSetLayout::Binding &bind = layout->bindings[b];
|
|
|
|
// skip empty bindings
|
|
if(bind.descriptorType == VK_DESCRIPTOR_TYPE_MAX_ENUM ||
|
|
bind.descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK)
|
|
continue;
|
|
|
|
uint32_t count = bind.descriptorCount;
|
|
if(bind.variableSize)
|
|
count = setrecord->descInfo->data.variableDescriptorCount;
|
|
|
|
FrameRefType ref = GetRefType(bind.descriptorType);
|
|
|
|
for(uint32_t a = 0; a < count; a++)
|
|
setrecord->descInfo->data.binds[b][a].AccumulateBindRefs(refs, rm, ref);
|
|
}
|
|
|
|
for(auto refit = refs.bindFrameRefs.begin(); refit != refs.bindFrameRefs.end(); ++refit)
|
|
{
|
|
refdIDs.insert(refit->first);
|
|
GetResourceManager()->MarkResourceFrameReferenced(refit->first, refit->second);
|
|
}
|
|
|
|
for(auto refit = refs.sparseRefs.begin(); refit != refs.sparseRefs.end(); ++refit)
|
|
{
|
|
GetResourceManager()->MarkSparseMapReferenced((*refit)->resInfo);
|
|
}
|
|
|
|
UpdateImageStates(refs.bindImageStates);
|
|
GetResourceManager()->MergeReferencedMemory(refs.bindMemRefs);
|
|
|
|
// for storage buffers we have to pessimise memory references because the order matters - if
|
|
// the first recorded reference is a complete write then a later readbeforewrite won't
|
|
// properly mark it as needing initial states preserved. So we do that here. Images are
|
|
// handled separately
|
|
for(auto refit = refs.storableRefs.begin(); refit != refs.storableRefs.end(); ++refit)
|
|
{
|
|
GetResourceManager()->FixupStorageBufferMemory(refs.storableRefs);
|
|
}
|
|
}
|
|
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read);
|
|
|
|
if(fence != VK_NULL_HANDLE)
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResID(fence), eFrameRef_Read);
|
|
|
|
rdcarray<VkResourceRecord *> maps;
|
|
|
|
// don't flush maps when there are no command buffers
|
|
if(!commandBuffers.empty())
|
|
{
|
|
SCOPED_LOCK(m_CoherentMapsLock);
|
|
maps = m_CoherentMaps;
|
|
}
|
|
|
|
for(auto it = maps.begin(); it != maps.end(); ++it)
|
|
{
|
|
VkResourceRecord *record = *it;
|
|
MemMapState &state = *record->memMapState;
|
|
|
|
SCOPED_LOCK(state.mrLock);
|
|
|
|
// potential persistent map
|
|
if(state.mapCoherent && state.mappedPtr)
|
|
{
|
|
// only need to flush memory that could affect this submitted batch of work, or if there are
|
|
// BDA buffers bound (as we can't track those!)
|
|
if(!record->hasBDA && refdIDs.find(record->GetResourceID()) == refdIDs.end())
|
|
{
|
|
RDCDEBUG("Map of memory %s not referenced in this queue - not flushing",
|
|
ToStr(record->GetResourceID()).c_str());
|
|
continue;
|
|
}
|
|
|
|
size_t diffStart = 0, diffEnd = 0;
|
|
bool found = true;
|
|
|
|
// 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(state.readbackOnGPU)
|
|
{
|
|
RDCDEBUG("Reading back %s with GPU for comparison", ToStr(record->GetResourceID()).c_str());
|
|
|
|
GetDebugManager()->InitReadbackBuffer(state.mapOffset + state.mapSize);
|
|
|
|
// immediately issue a command buffer to copy back the data. We do that on this queue to
|
|
// avoid complexity with synchronising with another queue, but the transfer queue if
|
|
// available would be better for this purpose.
|
|
VkCommandBuffer copycmd;
|
|
|
|
const uint32_t queueFamilyIndex = GetRecord(queue)->queueFamilyIndex;
|
|
|
|
if(m_QueueFamilyIdx == queueFamilyIndex)
|
|
copycmd = GetNextCmd();
|
|
else
|
|
copycmd = GetExtQueueCmd(queueFamilyIndex);
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
ObjDisp(copycmd)->BeginCommandBuffer(Unwrap(copycmd), &beginInfo);
|
|
|
|
VkBufferCopy region = {state.mapOffset, state.mapOffset, state.mapSize};
|
|
|
|
ObjDisp(copycmd)->CmdCopyBuffer(Unwrap(copycmd), Unwrap(state.wholeMemBuf),
|
|
Unwrap(GetDebugManager()->GetReadbackBuffer()), 1, ®ion);
|
|
|
|
// wait for transfer to finish before reading on CPU
|
|
VkBufferMemoryBarrier bufBarrier = {
|
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_ACCESS_HOST_READ_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(GetDebugManager()->GetReadbackBuffer()),
|
|
0,
|
|
VK_WHOLE_SIZE,
|
|
};
|
|
|
|
DoPipelineBarrier(copycmd, 1, &bufBarrier);
|
|
|
|
ObjDisp(copycmd)->EndCommandBuffer(Unwrap(copycmd));
|
|
|
|
if(m_QueueFamilyIdx == queueFamilyIndex)
|
|
{
|
|
VkSubmitInfo submit = {
|
|
VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL, 0, NULL, NULL, 1, UnwrapPtr(copycmd),
|
|
};
|
|
VkResult copyret = ObjDisp(queue)->QueueSubmit(Unwrap(queue), 1, &submit, VK_NULL_HANDLE);
|
|
RDCASSERTEQUAL(copyret, VK_SUCCESS);
|
|
|
|
ObjDisp(queue)->QueueWaitIdle(Unwrap(queue));
|
|
|
|
RemovePendingCommandBuffer(copycmd);
|
|
AddFreeCommandBuffer(copycmd);
|
|
}
|
|
else
|
|
{
|
|
SubmitAndFlushExtQueue(queueFamilyIndex);
|
|
}
|
|
|
|
VkMappedMemoryRange range = {
|
|
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
NULL,
|
|
Unwrap(GetDebugManager()->GetReadbackMemory()),
|
|
0,
|
|
VK_WHOLE_SIZE,
|
|
};
|
|
|
|
VkResult copyret =
|
|
ObjDisp(queue)->InvalidateMappedMemoryRanges(Unwrap(m_Device), 1, &range);
|
|
RDCASSERTEQUAL(copyret, VK_SUCCESS);
|
|
|
|
state.cpuReadPtr = GetDebugManager()->GetReadbackPtr();
|
|
}
|
|
else
|
|
{
|
|
state.cpuReadPtr = state.mappedPtr;
|
|
}
|
|
|
|
// if we have a previous set of data, compare.
|
|
// otherwise just serialise it all
|
|
if(state.refData)
|
|
found = FindDiffRange(((byte *)state.cpuReadPtr) + state.mapOffset, state.refData,
|
|
(size_t)state.mapSize, diffStart, diffEnd);
|
|
else
|
|
diffEnd = (size_t)state.mapSize;
|
|
|
|
// sanitise diff start/end. Since the mapped pointer might be written on another thread
|
|
// (or even the GPU) this could cause a difference to appear and disappear transiently. In
|
|
// this case FindDiffRange could find the difference when locating the start but not find
|
|
// it when locating the end. In this case we don't need to write the difference (the
|
|
// application is responsible for ensuring it's not writing to memory the GPU might need)
|
|
if(diffEnd <= diffStart)
|
|
found = false;
|
|
|
|
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 %s (%llu -> %llu)",
|
|
ToStr(record->GetResourceID()).c_str(), (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,
|
|
};
|
|
InternalFlushMemoryRange(dev, range, true, capframe);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RDCDEBUG("Persistent map flush not needed for %s", ToStr(record->GetResourceID()).c_str());
|
|
}
|
|
|
|
// restore this just in case
|
|
state.cpuReadPtr = state.mappedPtr;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(const rdcpair<ResourceId, VkResourceRecord *> &it : capDescriptors)
|
|
it.second->Delete(GetResourceManager());
|
|
capDescriptors.clear();
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkQueueSubmit(SerialiserType &ser, VkQueue queue, uint32_t submitCount,
|
|
const VkSubmitInfo *pSubmits, VkFence fence)
|
|
{
|
|
SERIALISE_ELEMENT(queue);
|
|
SERIALISE_ELEMENT(submitCount);
|
|
SERIALISE_ELEMENT_ARRAY(pSubmits, submitCount).Important();
|
|
SERIALISE_ELEMENT(fence);
|
|
|
|
Serialise_DebugMessages(ser);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
// if there are multiple queue submissions in flight, wait for the previous queue to finish
|
|
// before executing this, as we don't have the sync information to properly sync.
|
|
if(m_PrevQueue != queue)
|
|
{
|
|
RDCDEBUG("Previous queue execution was on queue %s, now executing %s, syncing GPU",
|
|
ToStr(GetResID(m_PrevQueue)).c_str(), ToStr(GetResID(queue)).c_str());
|
|
if(m_PrevQueue != VK_NULL_HANDLE)
|
|
ObjDisp(m_PrevQueue)->QueueWaitIdle(Unwrap(m_PrevQueue));
|
|
|
|
m_PrevQueue = queue;
|
|
}
|
|
|
|
// if we ever waited on any semaphores, wait for idle here.
|
|
bool doWait = false;
|
|
for(uint32_t i = 0; i < submitCount; i++)
|
|
if(pSubmits[i].waitSemaphoreCount > 0)
|
|
doWait = true;
|
|
|
|
if(doWait)
|
|
ObjDisp(queue)->QueueWaitIdle(Unwrap(queue));
|
|
|
|
// add an action use for this submission, to tally up with any debug messages that come from it
|
|
if(IsLoading(m_State))
|
|
{
|
|
ActionUse use(m_CurChunkOffset, m_RootEventID);
|
|
|
|
// insert in sorted location
|
|
auto drawit = std::lower_bound(m_ActionUses.begin(), m_ActionUses.end(), use);
|
|
m_ActionUses.insert(drawit - m_ActionUses.begin(), use);
|
|
}
|
|
|
|
rdcarray<VkCommandBufferSubmitInfo> cmds;
|
|
|
|
for(uint32_t sub = 0; sub < submitCount; sub++)
|
|
{
|
|
// make a fake VkSubmitInfo2. If KHR_synchronization2 isn't supported this may then decay
|
|
// back down into separate structs but it keeps a lot of the processing the same in both paths
|
|
// and it's easier to promote this then decay if necessary (knowing no unsupported features
|
|
// will be used)
|
|
VkSubmitInfo2 submitInfo = {};
|
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
|
|
|
|
const VkProtectedSubmitInfo *prot = (const VkProtectedSubmitInfo *)FindNextStruct(
|
|
&pSubmits[sub], VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO);
|
|
const VkDeviceGroupSubmitInfo *group = (const VkDeviceGroupSubmitInfo *)FindNextStruct(
|
|
&pSubmits[sub], VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO);
|
|
|
|
cmds.resize(pSubmits[sub].commandBufferCount);
|
|
for(uint32_t c = 0; c < pSubmits[sub].commandBufferCount; c++)
|
|
{
|
|
VkCommandBufferSubmitInfo &cmd = cmds[c];
|
|
cmd.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
|
cmd.commandBuffer = pSubmits[sub].pCommandBuffers[c];
|
|
|
|
if(group && c < group->commandBufferCount)
|
|
cmd.deviceMask = group->pCommandBufferDeviceMasks[c];
|
|
}
|
|
|
|
submitInfo.commandBufferInfoCount = (uint32_t)cmds.size();
|
|
submitInfo.pCommandBufferInfos = cmds.data();
|
|
|
|
if(prot && prot->protectedSubmit)
|
|
submitInfo.flags |= VK_SUBMIT_PROTECTED_BIT;
|
|
|
|
// don't replay any semaphores, this means we don't have to care about
|
|
// VkD3D12FenceSubmitInfoKHR or VkTimelineSemaphoreSubmitInfo.
|
|
// we unwrap VkProtectedSubmitInfo and VkDeviceGroupSubmitInfo above.
|
|
// VkWin32KeyedMutexAcquireReleaseInfoKHR and VkWin32KeyedMutexAcquireReleaseInfoNV we
|
|
// deliberately don't replay
|
|
// VkPerformanceQuerySubmitInfoKHR we don't replay since we don't replay perf counter work
|
|
|
|
rdcstr basename = StringFormat::Fmt("vkQueueSubmit(%u)", submitInfo.commandBufferInfoCount);
|
|
|
|
ReplayQueueSubmit(queue, submitInfo, basename);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VkResult WrappedVulkan::vkQueueSubmit(VkQueue queue, uint32_t submitCount,
|
|
const VkSubmitInfo *pSubmits, VkFence fence)
|
|
{
|
|
SCOPED_DBG_SINK();
|
|
|
|
if(HasFatalError())
|
|
return VK_ERROR_DEVICE_LOST;
|
|
|
|
if(!m_MarkedActive)
|
|
{
|
|
m_MarkedActive = true;
|
|
RenderDoc::Inst().AddActiveDriver(RDCDriver::Vulkan, false);
|
|
}
|
|
|
|
if(IsActiveCapturing(m_State))
|
|
{
|
|
// 15 is quite a lot of submissions.
|
|
const int expectedMaxSubmissions = 15;
|
|
|
|
RenderDoc::Inst().SetProgress(CaptureProgress::FrameCapture,
|
|
FakeProgress(m_SubmitCounter, expectedMaxSubmissions));
|
|
m_SubmitCounter++;
|
|
}
|
|
|
|
VkResult ret = VK_SUCCESS;
|
|
bool present = false;
|
|
bool beginCapture = false;
|
|
bool endCapture = false;
|
|
rdcarray<VkCommandBuffer> commandBuffers;
|
|
|
|
for(uint32_t s = 0; s < submitCount; s++)
|
|
{
|
|
for(uint32_t i = 0; i < pSubmits[s].commandBufferCount; i++)
|
|
{
|
|
VkResourceRecord *record = GetRecord(pSubmits[s].pCommandBuffers[i]);
|
|
present |= record->bakedCommands->cmdInfo->present;
|
|
beginCapture |= record->bakedCommands->cmdInfo->beginCapture;
|
|
endCapture |= record->bakedCommands->cmdInfo->endCapture;
|
|
|
|
commandBuffers.push_back(pSubmits[s].pCommandBuffers[i]);
|
|
}
|
|
}
|
|
|
|
if(beginCapture)
|
|
{
|
|
RenderDoc::Inst().StartFrameCapture(LayerDisp(m_Instance), NULL);
|
|
}
|
|
|
|
{
|
|
SCOPED_READLOCK(m_CapTransitionLock);
|
|
|
|
bool capframe = IsActiveCapturing(m_State);
|
|
|
|
CaptureQueueSubmit(queue, commandBuffers, fence);
|
|
|
|
size_t tempmemSize = sizeof(VkSubmitInfo) * submitCount;
|
|
|
|
// because we pass the base struct this will calculate the patch size including it
|
|
for(uint32_t i = 0; i < submitCount; i++)
|
|
tempmemSize += GetNextPatchSize(&pSubmits[i]);
|
|
|
|
byte *memory = GetTempMemory(tempmemSize);
|
|
|
|
VkSubmitInfo *unwrappedSubmits = (VkSubmitInfo *)memory;
|
|
memory += sizeof(VkSubmitInfo) * submitCount;
|
|
|
|
for(uint32_t i = 0; i < submitCount; i++)
|
|
unwrappedSubmits[i] = *UnwrapStructAndChain(m_State, memory, &pSubmits[i]);
|
|
|
|
SERIALISE_TIME_CALL(ret = ObjDisp(queue)->QueueSubmit(Unwrap(queue), submitCount,
|
|
unwrappedSubmits, Unwrap(fence)));
|
|
|
|
if(capframe)
|
|
{
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
|
|
ser.SetActionChunk();
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueueSubmit);
|
|
Serialise_vkQueueSubmit(ser, queue, submitCount, pSubmits, fence);
|
|
|
|
m_FrameCaptureRecord->AddChunk(scope.Get());
|
|
}
|
|
|
|
for(uint32_t s = 0; s < submitCount; s++)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(endCapture)
|
|
{
|
|
RenderDoc::Inst().EndFrameCapture(LayerDisp(m_Instance), NULL);
|
|
}
|
|
|
|
if(present)
|
|
{
|
|
AdvanceFrame();
|
|
Present(LayerDisp(m_Instance), NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkQueueSubmit2(SerialiserType &ser, VkQueue queue, uint32_t submitCount,
|
|
const VkSubmitInfo2 *pSubmits, VkFence fence)
|
|
{
|
|
SERIALISE_ELEMENT(queue);
|
|
SERIALISE_ELEMENT(submitCount);
|
|
SERIALISE_ELEMENT_ARRAY(pSubmits, submitCount).Important();
|
|
SERIALISE_ELEMENT(fence);
|
|
|
|
Serialise_DebugMessages(ser);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
// if there are multiple queue submissions in flight, wait for the previous queue to finish
|
|
// before executing this, as we don't have the sync information to properly sync.
|
|
if(m_PrevQueue != queue)
|
|
{
|
|
RDCDEBUG("Previous queue execution was on queue %s, now executing %s, syncing GPU",
|
|
ToStr(GetResID(m_PrevQueue)).c_str(), ToStr(GetResID(queue)).c_str());
|
|
if(m_PrevQueue != VK_NULL_HANDLE)
|
|
ObjDisp(m_PrevQueue)->QueueWaitIdle(Unwrap(m_PrevQueue));
|
|
|
|
m_PrevQueue = queue;
|
|
}
|
|
|
|
// if we ever waited on any semaphores, wait for idle here.
|
|
bool doWait = false;
|
|
for(uint32_t i = 0; i < submitCount; i++)
|
|
if(pSubmits[i].waitSemaphoreInfoCount > 0)
|
|
doWait = true;
|
|
|
|
if(doWait)
|
|
ObjDisp(queue)->QueueWaitIdle(Unwrap(queue));
|
|
|
|
// add an action use for this submission, to tally up with any debug messages that come from it
|
|
if(IsLoading(m_State))
|
|
{
|
|
ActionUse use(m_CurChunkOffset, m_RootEventID);
|
|
|
|
// insert in sorted location
|
|
auto drawit = std::lower_bound(m_ActionUses.begin(), m_ActionUses.end(), use);
|
|
m_ActionUses.insert(drawit - m_ActionUses.begin(), use);
|
|
}
|
|
|
|
for(uint32_t sub = 0; sub < submitCount; sub++)
|
|
{
|
|
rdcstr basename = StringFormat::Fmt("vkQueueSubmit2(%u)", pSubmits[sub].commandBufferInfoCount);
|
|
|
|
ReplayQueueSubmit(queue, pSubmits[sub], basename);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VkResult WrappedVulkan::vkQueueSubmit2(VkQueue queue, uint32_t submitCount,
|
|
const VkSubmitInfo2 *pSubmits, VkFence fence)
|
|
{
|
|
SCOPED_DBG_SINK();
|
|
|
|
if(HasFatalError())
|
|
return VK_ERROR_DEVICE_LOST;
|
|
|
|
if(!m_MarkedActive)
|
|
{
|
|
m_MarkedActive = true;
|
|
RenderDoc::Inst().AddActiveDriver(RDCDriver::Vulkan, false);
|
|
}
|
|
|
|
if(IsActiveCapturing(m_State))
|
|
{
|
|
// 15 is quite a lot of submissions.
|
|
const int expectedMaxSubmissions = 15;
|
|
|
|
RenderDoc::Inst().SetProgress(CaptureProgress::FrameCapture,
|
|
FakeProgress(m_SubmitCounter, expectedMaxSubmissions));
|
|
m_SubmitCounter++;
|
|
}
|
|
|
|
VkResult ret = VK_SUCCESS;
|
|
bool present = false;
|
|
bool beginCapture = false;
|
|
bool endCapture = false;
|
|
rdcarray<VkCommandBuffer> commandBuffers;
|
|
|
|
for(uint32_t s = 0; s < submitCount; s++)
|
|
{
|
|
for(uint32_t i = 0; i < pSubmits[s].commandBufferInfoCount; i++)
|
|
{
|
|
VkResourceRecord *record = GetRecord(pSubmits[s].pCommandBufferInfos[i].commandBuffer);
|
|
present |= record->bakedCommands->cmdInfo->present;
|
|
beginCapture |= record->bakedCommands->cmdInfo->beginCapture;
|
|
endCapture |= record->bakedCommands->cmdInfo->endCapture;
|
|
|
|
commandBuffers.push_back(pSubmits[s].pCommandBufferInfos[i].commandBuffer);
|
|
}
|
|
}
|
|
|
|
if(beginCapture)
|
|
{
|
|
RenderDoc::Inst().StartFrameCapture(LayerDisp(m_Instance), NULL);
|
|
}
|
|
|
|
{
|
|
SCOPED_READLOCK(m_CapTransitionLock);
|
|
|
|
bool capframe = IsActiveCapturing(m_State);
|
|
|
|
CaptureQueueSubmit(queue, commandBuffers, fence);
|
|
|
|
size_t tempmemSize = sizeof(VkSubmitInfo2) * submitCount;
|
|
|
|
// because we pass the base struct this will calculate the patch size including it
|
|
for(uint32_t i = 0; i < submitCount; i++)
|
|
tempmemSize += GetNextPatchSize(&pSubmits[i]);
|
|
|
|
byte *memory = GetTempMemory(tempmemSize);
|
|
|
|
VkSubmitInfo2 *unwrappedSubmits = (VkSubmitInfo2 *)memory;
|
|
memory += sizeof(VkSubmitInfo2) * submitCount;
|
|
|
|
for(uint32_t i = 0; i < submitCount; i++)
|
|
unwrappedSubmits[i] = *UnwrapStructAndChain(m_State, memory, &pSubmits[i]);
|
|
|
|
SERIALISE_TIME_CALL(ret = ObjDisp(queue)->QueueSubmit2(Unwrap(queue), submitCount,
|
|
unwrappedSubmits, Unwrap(fence)));
|
|
|
|
if(capframe)
|
|
{
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
|
|
ser.SetActionChunk();
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueueSubmit2);
|
|
Serialise_vkQueueSubmit2(ser, queue, submitCount, pSubmits, fence);
|
|
|
|
m_FrameCaptureRecord->AddChunk(scope.Get());
|
|
}
|
|
|
|
for(uint32_t s = 0; s < submitCount; s++)
|
|
{
|
|
for(uint32_t sem = 0; sem < pSubmits[s].waitSemaphoreInfoCount; sem++)
|
|
GetResourceManager()->MarkResourceFrameReferenced(
|
|
GetResID(pSubmits[s].pWaitSemaphoreInfos[sem].semaphore), eFrameRef_Read);
|
|
|
|
for(uint32_t sem = 0; sem < pSubmits[s].signalSemaphoreInfoCount; sem++)
|
|
GetResourceManager()->MarkResourceFrameReferenced(
|
|
GetResID(pSubmits[s].pSignalSemaphoreInfos[sem].semaphore), eFrameRef_Read);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(endCapture)
|
|
{
|
|
RenderDoc::Inst().EndFrameCapture(LayerDisp(m_Instance), NULL);
|
|
}
|
|
|
|
if(present)
|
|
{
|
|
AdvanceFrame();
|
|
Present(LayerDisp(m_Instance), NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkQueueBindSparse(SerialiserType &ser, VkQueue queue,
|
|
uint32_t bindInfoCount,
|
|
const VkBindSparseInfo *pBindInfo, VkFence fence)
|
|
{
|
|
SERIALISE_ELEMENT(queue);
|
|
SERIALISE_ELEMENT(bindInfoCount);
|
|
SERIALISE_ELEMENT_ARRAY(pBindInfo, bindInfoCount).Important();
|
|
SERIALISE_ELEMENT(fence);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
// similar to vkQueueSubmit we don't need 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
|
|
bool doWait = false;
|
|
for(uint32_t i = 0; i < bindInfoCount; i++)
|
|
if(pBindInfo[i].waitSemaphoreCount > 0)
|
|
doWait = true;
|
|
|
|
if(doWait)
|
|
ObjDisp(queue)->QueueWaitIdle(Unwrap(queue));
|
|
|
|
for(uint32_t bind = 0; bind < bindInfoCount; bind++)
|
|
{
|
|
// we can freely mutate the info as it's locally allocated
|
|
VkBindSparseInfo &bindInfo = ((VkBindSparseInfo *)pBindInfo)[bind];
|
|
bindInfo.pWaitSemaphores = NULL;
|
|
bindInfo.waitSemaphoreCount = 0;
|
|
bindInfo.pSignalSemaphores = NULL;
|
|
bindInfo.signalSemaphoreCount = 0;
|
|
|
|
// remove any binds for resources that aren't present, since this
|
|
// is totally valid (if the resource wasn't referenced in anything
|
|
// else, it will be omitted from the capture)
|
|
VkSparseBufferMemoryBindInfo *buf = (VkSparseBufferMemoryBindInfo *)bindInfo.pBufferBinds;
|
|
for(uint32_t i = 0; i < bindInfo.bufferBindCount; i++)
|
|
{
|
|
if(buf[i].buffer == VK_NULL_HANDLE)
|
|
{
|
|
bindInfo.bufferBindCount--;
|
|
std::swap(buf[i], buf[bindInfo.bufferBindCount]);
|
|
}
|
|
else
|
|
{
|
|
if(IsLoading(m_State))
|
|
m_SparseBindResources.insert(GetResID(buf[i].buffer));
|
|
|
|
buf[i].buffer = Unwrap(buf[i].buffer);
|
|
|
|
VkSparseMemoryBind *binds = (VkSparseMemoryBind *)buf[i].pBinds;
|
|
for(uint32_t b = 0; b < buf[i].bindCount; b++)
|
|
binds[b].memory = Unwrap(binds[b].memory);
|
|
}
|
|
}
|
|
|
|
VkSparseImageOpaqueMemoryBindInfo *imopaque =
|
|
(VkSparseImageOpaqueMemoryBindInfo *)bindInfo.pImageOpaqueBinds;
|
|
for(uint32_t i = 0; i < bindInfo.imageOpaqueBindCount; i++)
|
|
{
|
|
if(imopaque[i].image == VK_NULL_HANDLE)
|
|
{
|
|
bindInfo.imageOpaqueBindCount--;
|
|
std::swap(imopaque[i], imopaque[bindInfo.imageOpaqueBindCount]);
|
|
}
|
|
else
|
|
{
|
|
if(IsLoading(m_State))
|
|
m_SparseBindResources.insert(GetResID(imopaque[i].image));
|
|
|
|
imopaque[i].image = Unwrap(imopaque[i].image);
|
|
|
|
VkSparseMemoryBind *binds = (VkSparseMemoryBind *)imopaque[i].pBinds;
|
|
for(uint32_t b = 0; b < imopaque[i].bindCount; b++)
|
|
binds[b].memory = Unwrap(binds[b].memory);
|
|
}
|
|
}
|
|
|
|
VkSparseImageMemoryBindInfo *im = (VkSparseImageMemoryBindInfo *)bindInfo.pImageBinds;
|
|
for(uint32_t i = 0; i < bindInfo.imageBindCount; i++)
|
|
{
|
|
if(im[i].image == VK_NULL_HANDLE)
|
|
{
|
|
bindInfo.imageBindCount--;
|
|
std::swap(im[i], im[bindInfo.imageBindCount]);
|
|
}
|
|
else
|
|
{
|
|
if(IsLoading(m_State))
|
|
m_SparseBindResources.insert(GetResID(im[i].image));
|
|
|
|
im[i].image = Unwrap(im[i].image);
|
|
|
|
VkSparseImageMemoryBind *binds = (VkSparseImageMemoryBind *)im[i].pBinds;
|
|
for(uint32_t b = 0; b < im[i].bindCount; b++)
|
|
binds[b].memory = Unwrap(binds[b].memory);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)->QueueBindSparse(Unwrap(queue), bindInfoCount, pBindInfo, VK_NULL_HANDLE);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VkResult WrappedVulkan::vkQueueBindSparse(VkQueue queue, uint32_t bindInfoCount,
|
|
const VkBindSparseInfo *pBindInfo, VkFence fence)
|
|
{
|
|
if(HasFatalError())
|
|
return VK_ERROR_DEVICE_LOST;
|
|
|
|
// need to allocate space for each bind batch
|
|
size_t tempmemSize = sizeof(VkBindSparseInfo) * bindInfoCount;
|
|
|
|
for(uint32_t i = 0; i < bindInfoCount; i++)
|
|
{
|
|
tempmemSize += GetNextPatchSize(pBindInfo[i].pNext);
|
|
|
|
// 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];
|
|
|
|
UnwrapNextChain(m_State, "VkBindSparseInfo", next, (VkBaseInStructure *)&unwrapped[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);
|
|
}
|
|
}
|
|
}
|
|
|
|
VkResult ret;
|
|
SERIALISE_TIME_CALL(ret = ObjDisp(queue)->QueueBindSparse(Unwrap(queue), bindInfoCount, unwrapped,
|
|
Unwrap(fence)));
|
|
|
|
if(IsActiveCapturing(m_State))
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
|
|
{
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueueBindSparse);
|
|
ser.SetActionChunk();
|
|
Serialise_vkQueueBindSparse(ser, queue, bindInfoCount, pBindInfo, fence);
|
|
|
|
m_FrameCaptureRecord->AddChunk(scope.Get());
|
|
}
|
|
|
|
for(uint32_t i = 0; i < bindInfoCount; i++)
|
|
{
|
|
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(IsCaptureMode(m_State))
|
|
{
|
|
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)->resInfo->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)->resInfo->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)->resInfo->Update(bind.bindCount, bind.pBinds);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkQueueWaitIdle(SerialiserType &ser, VkQueue queue)
|
|
{
|
|
SERIALISE_ELEMENT(queue).Important();
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
ObjDisp(queue)->QueueWaitIdle(Unwrap(queue));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VkResult WrappedVulkan::vkQueueWaitIdle(VkQueue queue)
|
|
{
|
|
VkResult ret;
|
|
SERIALISE_TIME_CALL(ret = ObjDisp(queue)->QueueWaitIdle(Unwrap(queue)));
|
|
|
|
if(IsActiveCapturing(m_State))
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueueWaitIdle);
|
|
Serialise_vkQueueWaitIdle(ser, queue);
|
|
|
|
m_FrameCaptureRecord->AddChunk(scope.Get());
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkQueueBeginDebugUtilsLabelEXT(SerialiserType &ser, VkQueue queue,
|
|
const VkDebugUtilsLabelEXT *pLabelInfo)
|
|
{
|
|
SERIALISE_ELEMENT(queue);
|
|
SERIALISE_ELEMENT_LOCAL(Label, *pLabelInfo).Important();
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
if(ObjDisp(queue)->QueueBeginDebugUtilsLabelEXT)
|
|
ObjDisp(queue)->QueueBeginDebugUtilsLabelEXT(Unwrap(queue), &Label);
|
|
|
|
if(IsLoading(m_State))
|
|
{
|
|
ActionDescription action;
|
|
action.customName = Label.pLabelName ? Label.pLabelName : "";
|
|
action.flags |= ActionFlags::PushMarker;
|
|
|
|
action.markerColor.x = RDCCLAMP(Label.color[0], 0.0f, 1.0f);
|
|
action.markerColor.y = RDCCLAMP(Label.color[1], 0.0f, 1.0f);
|
|
action.markerColor.z = RDCCLAMP(Label.color[2], 0.0f, 1.0f);
|
|
action.markerColor.w = RDCCLAMP(Label.color[3], 0.0f, 1.0f);
|
|
|
|
AddEvent();
|
|
AddAction(action);
|
|
|
|
// now push the action stack
|
|
GetActionStack().push_back(&GetActionStack().back()->children.back());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedVulkan::vkQueueBeginDebugUtilsLabelEXT(VkQueue queue,
|
|
const VkDebugUtilsLabelEXT *pLabelInfo)
|
|
{
|
|
if(ObjDisp(queue)->QueueBeginDebugUtilsLabelEXT)
|
|
{
|
|
SERIALISE_TIME_CALL(ObjDisp(queue)->QueueBeginDebugUtilsLabelEXT(Unwrap(queue), pLabelInfo));
|
|
}
|
|
|
|
if(IsActiveCapturing(m_State))
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
ser.SetActionChunk();
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueueBeginDebugUtilsLabelEXT);
|
|
Serialise_vkQueueBeginDebugUtilsLabelEXT(ser, queue, pLabelInfo);
|
|
|
|
m_FrameCaptureRecord->AddChunk(scope.Get());
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read);
|
|
}
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkQueueEndDebugUtilsLabelEXT(SerialiserType &ser, VkQueue queue)
|
|
{
|
|
SERIALISE_ELEMENT(queue).Unimportant();
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
if(ObjDisp(queue)->QueueEndDebugUtilsLabelEXT)
|
|
ObjDisp(queue)->QueueEndDebugUtilsLabelEXT(Unwrap(queue));
|
|
|
|
if(IsLoading(m_State))
|
|
{
|
|
ActionDescription action;
|
|
action.flags = ActionFlags::PopMarker;
|
|
|
|
AddEvent();
|
|
AddAction(action);
|
|
|
|
if(GetActionStack().size() > 1)
|
|
GetActionStack().pop_back();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedVulkan::vkQueueEndDebugUtilsLabelEXT(VkQueue queue)
|
|
{
|
|
if(ObjDisp(queue)->QueueEndDebugUtilsLabelEXT)
|
|
{
|
|
SERIALISE_TIME_CALL(ObjDisp(queue)->QueueEndDebugUtilsLabelEXT(Unwrap(queue)));
|
|
}
|
|
|
|
if(IsActiveCapturing(m_State))
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
ser.SetActionChunk();
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueueEndDebugUtilsLabelEXT);
|
|
Serialise_vkQueueEndDebugUtilsLabelEXT(ser, queue);
|
|
|
|
m_FrameCaptureRecord->AddChunk(scope.Get());
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read);
|
|
}
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkQueueInsertDebugUtilsLabelEXT(SerialiserType &ser, VkQueue queue,
|
|
const VkDebugUtilsLabelEXT *pLabelInfo)
|
|
{
|
|
SERIALISE_ELEMENT(queue);
|
|
SERIALISE_ELEMENT_LOCAL(Label, *pLabelInfo).Important();
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
if(ObjDisp(queue)->QueueInsertDebugUtilsLabelEXT)
|
|
ObjDisp(queue)->QueueInsertDebugUtilsLabelEXT(Unwrap(queue), &Label);
|
|
|
|
if(IsLoading(m_State))
|
|
{
|
|
ActionDescription action;
|
|
action.customName = Label.pLabelName ? Label.pLabelName : "";
|
|
action.flags |= ActionFlags::SetMarker;
|
|
|
|
action.markerColor.x = RDCCLAMP(Label.color[0], 0.0f, 1.0f);
|
|
action.markerColor.y = RDCCLAMP(Label.color[1], 0.0f, 1.0f);
|
|
action.markerColor.z = RDCCLAMP(Label.color[2], 0.0f, 1.0f);
|
|
action.markerColor.w = RDCCLAMP(Label.color[3], 0.0f, 1.0f);
|
|
|
|
AddEvent();
|
|
AddAction(action);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedVulkan::vkQueueInsertDebugUtilsLabelEXT(VkQueue queue,
|
|
const VkDebugUtilsLabelEXT *pLabelInfo)
|
|
{
|
|
if(ObjDisp(queue)->QueueInsertDebugUtilsLabelEXT)
|
|
{
|
|
SERIALISE_TIME_CALL(ObjDisp(queue)->QueueInsertDebugUtilsLabelEXT(Unwrap(queue), pLabelInfo));
|
|
}
|
|
|
|
if(pLabelInfo)
|
|
HandleFrameMarkers(pLabelInfo->pLabelName, queue);
|
|
if(IsActiveCapturing(m_State))
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
ser.SetActionChunk();
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueueInsertDebugUtilsLabelEXT);
|
|
Serialise_vkQueueInsertDebugUtilsLabelEXT(ser, queue, pLabelInfo);
|
|
|
|
m_FrameCaptureRecord->AddChunk(scope.Get());
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read);
|
|
}
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool WrappedVulkan::Serialise_vkGetDeviceQueue2(SerialiserType &ser, VkDevice device,
|
|
const VkDeviceQueueInfo2 *pQueueInfo, VkQueue *pQueue)
|
|
{
|
|
SERIALISE_ELEMENT(device);
|
|
SERIALISE_ELEMENT_LOCAL(QueueInfo, *pQueueInfo).Important();
|
|
SERIALISE_ELEMENT_LOCAL(Queue, GetResID(*pQueue)).TypedAs("VkQueue"_lit);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
uint32_t queueFamilyIndex = QueueInfo.queueFamilyIndex;
|
|
uint32_t queueIndex = QueueInfo.queueIndex;
|
|
|
|
uint32_t remapFamily = m_QueueRemapping[queueFamilyIndex][queueIndex].family;
|
|
uint32_t remapIndex = m_QueueRemapping[queueFamilyIndex][queueIndex].index;
|
|
|
|
if(remapFamily != queueFamilyIndex || remapIndex != queueIndex)
|
|
RDCLOG("Remapped Queue %u/%u from capture to %u/%u on replay", queueFamilyIndex, queueIndex,
|
|
remapFamily, remapIndex);
|
|
|
|
VkQueue queue;
|
|
QueueInfo.queueFamilyIndex = remapFamily;
|
|
QueueInfo.queueIndex = remapIndex;
|
|
ObjDisp(device)->GetDeviceQueue2(Unwrap(device), &QueueInfo, &queue);
|
|
|
|
GetResourceManager()->WrapResource(Unwrap(device), queue);
|
|
GetResourceManager()->AddLiveResource(Queue, queue);
|
|
|
|
if(remapFamily == m_QueueFamilyIdx && m_Queue == VK_NULL_HANDLE)
|
|
{
|
|
m_Queue = queue;
|
|
|
|
// we can now submit any cmds that were queued (e.g. from creating debug
|
|
// manager on vkCreateDevice)
|
|
SubmitCmds();
|
|
}
|
|
|
|
if(remapFamily < m_ExternalQueues.size())
|
|
{
|
|
if(m_ExternalQueues[remapFamily].queue == VK_NULL_HANDLE)
|
|
m_ExternalQueues[remapFamily].queue = queue;
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected queue family index %u", remapFamily);
|
|
}
|
|
|
|
m_CreationInfo.m_Queue[GetResID(queue)] = remapFamily;
|
|
|
|
AddResource(Queue, ResourceType::Queue, "Queue");
|
|
DerivedResource(device, Queue);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedVulkan::vkGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 *pQueueInfo,
|
|
VkQueue *pQueue)
|
|
{
|
|
SERIALISE_TIME_CALL(ObjDisp(device)->GetDeviceQueue2(Unwrap(device), pQueueInfo, pQueue));
|
|
|
|
if(m_SetDeviceLoaderData)
|
|
m_SetDeviceLoaderData(m_Device, *pQueue);
|
|
else
|
|
SetDispatchTableOverMagicNumber(device, *pQueue);
|
|
|
|
RDCASSERT(IsCaptureMode(m_State));
|
|
|
|
{
|
|
// it's perfectly valid for enumerate type functions to return the same handle
|
|
// each time. If that happens, we will already have a wrapper created so just
|
|
// return the wrapped object to the user and do nothing else
|
|
if(m_QueueFamilies[pQueueInfo->queueFamilyIndex][pQueueInfo->queueIndex] != VK_NULL_HANDLE)
|
|
{
|
|
*pQueue = m_QueueFamilies[pQueueInfo->queueFamilyIndex][pQueueInfo->queueIndex];
|
|
}
|
|
else
|
|
{
|
|
ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), *pQueue);
|
|
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
CACHE_THREAD_SERIALISER();
|
|
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkGetDeviceQueue2);
|
|
Serialise_vkGetDeviceQueue2(ser, device, pQueueInfo, pQueue);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
VkResourceRecord *record = GetResourceManager()->AddResourceRecord(*pQueue);
|
|
RDCASSERT(record);
|
|
|
|
record->queueFamilyIndex = pQueueInfo->queueFamilyIndex;
|
|
|
|
VkResourceRecord *instrecord = GetRecord(m_Instance);
|
|
|
|
// treat queues as pool members of the instance (ie. freed when the instance dies)
|
|
{
|
|
instrecord->LockChunks();
|
|
instrecord->pooledChildren.push_back(record);
|
|
instrecord->UnlockChunks();
|
|
}
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
|
|
m_QueueFamilies[pQueueInfo->queueFamilyIndex][pQueueInfo->queueIndex] = *pQueue;
|
|
|
|
if(pQueueInfo->queueFamilyIndex < m_ExternalQueues.size())
|
|
{
|
|
if(m_ExternalQueues[pQueueInfo->queueFamilyIndex].queue == VK_NULL_HANDLE)
|
|
m_ExternalQueues[pQueueInfo->queueFamilyIndex].queue = *pQueue;
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected queue family index %u", pQueueInfo->queueFamilyIndex);
|
|
}
|
|
|
|
if(pQueueInfo->queueFamilyIndex == m_QueueFamilyIdx)
|
|
{
|
|
m_Queue = *pQueue;
|
|
|
|
// we can now submit any cmds that were queued (e.g. from creating debug
|
|
// manager on vkCreateDevice)
|
|
SubmitCmds();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(void, vkGetDeviceQueue, VkDevice device, uint32_t queueFamilyIndex,
|
|
uint32_t queueIndex, VkQueue *pQueue);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(VkResult, vkQueueSubmit, VkQueue queue, uint32_t submitCount,
|
|
const VkSubmitInfo *pSubmits, VkFence fence);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(VkResult, vkQueueBindSparse, VkQueue queue, uint32_t bindInfoCount,
|
|
const VkBindSparseInfo *pBindInfo, VkFence fence);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(VkResult, vkQueueWaitIdle, VkQueue queue);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(void, vkQueueBeginDebugUtilsLabelEXT, VkQueue queue,
|
|
const VkDebugUtilsLabelEXT *pLabelInfo);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(void, vkQueueEndDebugUtilsLabelEXT, VkQueue queue);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(void, vkQueueInsertDebugUtilsLabelEXT, VkQueue queue,
|
|
const VkDebugUtilsLabelEXT *pLabelInfo);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(void, vkGetDeviceQueue2, VkDevice device,
|
|
const VkDeviceQueueInfo2 *pQueueInfo, VkQueue *pQueue);
|
|
|
|
INSTANTIATE_FUNCTION_SERIALISED(VkResult, vkQueueSubmit2, VkQueue queue, uint32_t submitCount,
|
|
const VkSubmitInfo2 *pSubmits, VkFence fence);
|