From ea3e2c8610f888f9da17a05555555bf094c349e5 Mon Sep 17 00:00:00 2001 From: baldurk Date: Tue, 12 Jun 2018 14:54:54 +0100 Subject: [PATCH] Add multiqueue support for Vulkan. Closes #373 --- .../driver/d3d12/d3d12_command_list_wrap.cpp | 12 +- renderdoc/driver/vulkan/vk_common.cpp | 15 +- renderdoc/driver/vulkan/vk_common.h | 3 - renderdoc/driver/vulkan/vk_core.cpp | 92 +++- renderdoc/driver/vulkan/vk_core.h | 80 ++- renderdoc/driver/vulkan/vk_info.h | 3 + renderdoc/driver/vulkan/vk_initstate.cpp | 88 +++- renderdoc/driver/vulkan/vk_manager.cpp | 24 +- renderdoc/driver/vulkan/vk_manager.h | 2 +- renderdoc/driver/vulkan/vk_resources.h | 12 +- renderdoc/driver/vulkan/vk_serialise.cpp | 28 +- .../driver/vulkan/wrappers/vk_cmd_funcs.cpp | 30 +- .../vulkan/wrappers/vk_device_funcs.cpp | 454 +++++++++++++----- .../driver/vulkan/wrappers/vk_get_funcs.cpp | 17 +- .../driver/vulkan/wrappers/vk_queue_funcs.cpp | 79 ++- .../vulkan/wrappers/vk_resource_funcs.cpp | 28 +- .../driver/vulkan/wrappers/vk_sync_funcs.cpp | 31 +- .../driver/vulkan/wrappers/vk_wsi_funcs.cpp | 65 ++- renderdoc/serialise/serialiser.h | 3 +- 19 files changed, 817 insertions(+), 249 deletions(-) diff --git a/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp b/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp index c3663f40f..c412aa72f 100644 --- a/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp @@ -1250,7 +1250,7 @@ bool WrappedID3D12GraphicsCommandList2::Serialise_OMSetRenderTargets( std::vector RTVs; - if(ser.VersionCheck(0x5)) + if(ser.VersionAtLeast(0x5)) { if(ser.IsWriting()) { @@ -1286,7 +1286,7 @@ bool WrappedID3D12GraphicsCommandList2::Serialise_OMSetRenderTargets( D3D12Descriptor DSV = {}; - if(ser.VersionCheck(0x5)) + if(ser.VersionAtLeast(0x5)) { // read and serialise the D3D12Descriptor contents directly, as the call has semantics of // consuming the descriptor immediately. @@ -4321,7 +4321,7 @@ bool WrappedID3D12GraphicsCommandList2::Serialise_ClearDepthStencilView( { ID3D12GraphicsCommandList *pCommandList = this; SERIALISE_ELEMENT(pCommandList); - if(ser.VersionCheck(0x5)) + if(ser.VersionAtLeast(0x5)) { // read and serialise the D3D12Descriptor contents directly, as the call has semantics of // consuming the descriptor immediately @@ -4420,7 +4420,7 @@ bool WrappedID3D12GraphicsCommandList2::Serialise_ClearRenderTargetView( { ID3D12GraphicsCommandList *pCommandList = this; SERIALISE_ELEMENT(pCommandList); - if(ser.VersionCheck(0x5)) + if(ser.VersionAtLeast(0x5)) { // read and serialise the D3D12Descriptor contents directly, as the call has semantics of // consuming the descriptor immediately @@ -4515,7 +4515,7 @@ bool WrappedID3D12GraphicsCommandList2::Serialise_ClearUnorderedAccessViewUint( ID3D12GraphicsCommandList *pCommandList = this; SERIALISE_ELEMENT(pCommandList); SERIALISE_ELEMENT(ViewGPUHandleInCurrentHeap); - if(ser.VersionCheck(0x5)) + if(ser.VersionAtLeast(0x5)) { // read and serialise the D3D12Descriptor contents directly, as the call has semantics of // consuming the descriptor immediately. This is only true for the CPU-side handle @@ -4620,7 +4620,7 @@ bool WrappedID3D12GraphicsCommandList2::Serialise_ClearUnorderedAccessViewFloat( ID3D12GraphicsCommandList *pCommandList = this; SERIALISE_ELEMENT(pCommandList); SERIALISE_ELEMENT(ViewGPUHandleInCurrentHeap); - if(ser.VersionCheck(0x5)) + if(ser.VersionAtLeast(0x5)) { // read and serialise the D3D12Descriptor contents directly, as the call has semantics of // consuming the descriptor immediately. This is only true for the CPU-side handle diff --git a/renderdoc/driver/vulkan/vk_common.cpp b/renderdoc/driver/vulkan/vk_common.cpp index 33ad6642f..1e1c4fd9d 100644 --- a/renderdoc/driver/vulkan/vk_common.cpp +++ b/renderdoc/driver/vulkan/vk_common.cpp @@ -237,6 +237,11 @@ bool VkInitParams::IsSupportedVersion(uint64_t ver) if(ver == CurrentVersion) return true; + // 0xC -> 0xD - supported multiple queues. This didn't cause a large change to the serialisation + // but there were some slight inconsistencies that required a version bump + if(ver == 0xC) + return true; + // 0xB -> 0xC - generally this is when we started serialising pNext chains that older RenderDoc // couldn't support. But we don't need any special backwards compatibiltiy code as it's just added // serialisation. @@ -755,16 +760,6 @@ void ReplacePresentableImageLayout(VkImageLayout &layout) layout = VK_IMAGE_LAYOUT_GENERAL; } -void ReplaceExternalQueueFamily(uint32_t &srcQueueFamily, uint32_t &dstQueueFamily) -{ - if(srcQueueFamily == VK_QUEUE_FAMILY_EXTERNAL || dstQueueFamily == VK_QUEUE_FAMILY_EXTERNAL) - { - // we should ignore this family transition since we're not synchronising with an - // external access. - srcQueueFamily = dstQueueFamily = VK_QUEUE_FAMILY_IGNORED; - } -} - int SampleCount(VkSampleCountFlagBits countFlag) { switch(countFlag) diff --git a/renderdoc/driver/vulkan/vk_common.h b/renderdoc/driver/vulkan/vk_common.h index a77f4c112..3279224b8 100644 --- a/renderdoc/driver/vulkan/vk_common.h +++ b/renderdoc/driver/vulkan/vk_common.h @@ -30,8 +30,6 @@ // MULTIDEVICE - parts of the code that will need to be updated to support // multiple devices or queues. -// SHARING - as above, for handling resource sharing between queues - #include "common/common.h" #define VK_NO_PROTOTYPES @@ -104,7 +102,6 @@ StencilOperation MakeStencilOp(VkStencilOp op); VkAccessFlags MakeAccessMask(VkImageLayout layout); void ReplacePresentableImageLayout(VkImageLayout &layout); -void ReplaceExternalQueueFamily(uint32_t &srcQueueFamily, uint32_t &dstQueueFamily); void DoPipelineBarrier(VkCommandBuffer cmd, uint32_t count, VkImageMemoryBarrier *barriers); void DoPipelineBarrier(VkCommandBuffer cmd, uint32_t count, VkBufferMemoryBarrier *barriers); diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index 7432866aa..5250d5dce 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -141,7 +141,6 @@ WrappedVulkan::WrappedVulkan() : m_RenderState(this, &m_CreationInfo) m_Device = VK_NULL_HANDLE; m_Queue = VK_NULL_HANDLE; m_QueueFamilyIdx = 0; - m_SupportedQueueFamily = 0; m_DbgMsgCallback = VK_NULL_HANDLE; m_HeaderChunk = NULL; @@ -208,9 +207,13 @@ VkCommandBuffer WrappedVulkan::GetNextCmd() } else { - VkCommandBufferAllocateInfo cmdInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, - Unwrap(m_InternalCmds.cmdpool), - VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1}; + VkCommandBufferAllocateInfo cmdInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + NULL, + Unwrap(m_InternalCmds.cmdpool), + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1, + }; VkResult vkr = ObjDisp(m_Device)->AllocateCommandBuffers(Unwrap(m_Device), &cmdInfo, &ret); if(m_SetDeviceLoaderData) @@ -339,6 +342,51 @@ void WrappedVulkan::FlushQ() } } +VkCommandBuffer WrappedVulkan::GetExtQueueCmd(uint32_t queueFamilyIdx) +{ + if(queueFamilyIdx >= m_ExternalQueues.size()) + { + RDCERR("Unsupported queue family %u", queueFamilyIdx); + return VK_NULL_HANDLE; + } + + VkCommandBuffer buf = m_ExternalQueues[queueFamilyIdx].buffer; + + ObjDisp(buf)->ResetCommandBuffer(Unwrap(buf), 0); + + return buf; +} + +void WrappedVulkan::SubmitAndFlushExtQueue(uint32_t queueFamilyIdx) +{ + if(queueFamilyIdx >= m_ExternalQueues.size()) + { + RDCERR("Unsupported queue family %u", queueFamilyIdx); + return; + } + + VkCommandBuffer buf = Unwrap(m_ExternalQueues[queueFamilyIdx].buffer); + + VkSubmitInfo submitInfo = { + VK_STRUCTURE_TYPE_SUBMIT_INFO, + NULL, + 0, + NULL, + NULL, // wait semaphores + 1, + &buf, // command buffers + 0, + NULL, // signal semaphores + }; + + VkQueue q = m_ExternalQueues[queueFamilyIdx].queue; + + VkResult vkr = ObjDisp(q)->QueueSubmit(Unwrap(q), 1, &submitInfo, VK_NULL_HANDLE); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + ObjDisp(q)->QueueWaitIdle(Unwrap(q)); +} + uint32_t WrappedVulkan::HandlePreCallback(VkCommandBuffer commandBuffer, DrawFlags type, uint32_t multiDrawOffset) { @@ -1032,6 +1080,38 @@ void WrappedVulkan::StartFrameCapture(void *dev, void *wnd) // and go into the frame record. { SCOPED_LOCK(m_CapTransitionLock); + + // wait for all work to finish and apply a memory barrier to ensure all memory is visible + for(size_t i = 0; i < m_QueueFamilies.size(); i++) + { + for(uint32_t q = 0; q < m_QueueFamilyCounts[i]; q++) + { + if(m_QueueFamilies[i][q] != VK_NULL_HANDLE) + ObjDisp(m_QueueFamilies[i][q])->QueueWaitIdle(Unwrap(m_QueueFamilies[i][q])); + } + } + + { + VkMemoryBarrier memBarrier = { + VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_ALL_WRITE_BITS, VK_ACCESS_ALL_READ_BITS, + }; + + VkCommandBuffer cmd = GetNextCmd(); + + VkResult vkr = VK_SUCCESS; + + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + + vkr = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + DoPipelineBarrier(cmd, 1, &memBarrier); + + vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd)); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + } + GetResourceManager()->PrepareInitialContents(); RDCDEBUG("Attempting capture"); @@ -1886,8 +1966,8 @@ ReplayStatus WrappedVulkan::ContextReplayLog(CaptureState readType, uint32_t sta for(size_t i = 0; i < m_CleanupEvents.size(); i++) ObjDisp(GetDev())->DestroyEvent(Unwrap(GetDev()), m_CleanupEvents[i], NULL); - vkFreeCommandBuffers(GetDev(), m_InternalCmds.cmdpool, (uint32_t)m_RerecordCmdList.size(), - m_RerecordCmdList.data()); + for(const std::pair &rerecord : m_RerecordCmdList) + vkFreeCommandBuffers(GetDev(), rerecord.first, 1, &rerecord.second); } m_CleanupEvents.clear(); diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index 5bd32becb..cf400d668 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -55,7 +55,7 @@ struct VkInitParams uint32_t GetSerialiseSize(); // check if a frame capture section version is supported - static const uint64_t CurrentVersion = 0xC; + static const uint64_t CurrentVersion = 0xD; static bool IsSupportedVersion(uint64_t ver); }; @@ -68,7 +68,7 @@ struct VulkanDrawcallTreeNode DrawcallDescription draw; vector children; - vector > resourceUsage; + vector> resourceUsage; vector executedCmds; @@ -305,6 +305,7 @@ private: RDCEraseEl(props); RDCEraseEl(memProps); RDCEraseEl(fmtprops); + RDCEraseEl(queueProps); } uint32_t GetMemoryIndex(uint32_t resourceRequiredBitmask, uint32_t allocRequiredProps, @@ -325,19 +326,31 @@ private: VkPhysicalDeviceProperties props; VkPhysicalDeviceMemoryProperties memProps; VkFormatProperties fmtprops[VK_FORMAT_RANGE_SIZE]; + + uint32_t queueCount = 0; + VkQueueFamilyProperties queueProps[16]; }; PFN_vkSetDeviceLoaderData m_SetDeviceLoaderData; - VkInstance m_Instance; // the instance corresponding to this WrappedVulkan - VkDebugReportCallbackEXT m_DbgMsgCallback; // the instance's dbg msg callback handle - VkPhysicalDevice m_PhysicalDevice; // the physical device we created m_Device with - VkDevice m_Device; // the device used for our own command buffer work - PhysicalDeviceData - m_PhysicalDeviceData; // the data about the physical device used for the above device; - uint32_t - m_QueueFamilyIdx; // the family index that we've selected in CreateDevice for our queue - VkQueue m_Queue; // the queue used for our own command buffer work + // the instance corresponding to this WrappedVulkan + VkInstance m_Instance; + // the instance's dbg msg callback handle + VkDebugReportCallbackEXT m_DbgMsgCallback; + // the physical device we created m_Device with + VkPhysicalDevice m_PhysicalDevice; + // the device used for our own command buffer work + VkDevice m_Device; + // the data about the physical device used for the above device + PhysicalDeviceData m_PhysicalDeviceData; + // the family index that we've selected in CreateDevice for our queue. During replay, this is an + // index in the replay-time queues, not the capture-time queues (i.e. after remapping) + uint32_t m_QueueFamilyIdx; + // the queue used for our own command buffer work + VkQueue m_Queue; + // the last queue that submitted something during replay, to allow correct sync between + // submissions + VkQueue m_PrevQueue; // the physical devices. At capture time this is trivial, just the enumerated devices. // At replay time this is re-ordered from the real list to try and match @@ -350,14 +363,33 @@ private: vector m_ReplayPhysicalDevices; vector m_ReplayPhysicalDevicesUsed; - // the single queue family supported for each physical device - vector > m_SupportedQueueFamilies; - - // the supported queue family for the created device - uint32_t m_SupportedQueueFamily; - // the queue families (an array of count for each) for the created device vector m_QueueFamilies; + vector m_QueueFamilyCounts; + + // a small amount of helper code during capture for handling resources on different queues in init + // states + struct ExternalQueue + { + VkQueue queue = VK_NULL_HANDLE; + VkCommandPool pool = VK_NULL_HANDLE; + VkCommandBuffer buffer = VK_NULL_HANDLE; + }; + vector m_ExternalQueues; + + VkCommandBuffer GetExtQueueCmd(uint32_t queueFamilyIdx); + void SubmitAndFlushExtQueue(uint32_t queueFamilyIdx); + + struct QueueRemap + { + uint32_t family; + uint32_t index; + }; + + // for each queue family in the original captured physical device, we have a remapping vector. + // Each element in the vector is an available queue in that family, and the uint64 is packed as + // (targetQueueFamily << 32) | (targetQueueIndex) + std::vector m_QueueRemapping[16]; vector m_MemIdxMaps; void RemapMemoryIndices(VkPhysicalDeviceMemoryProperties *memProps, uint32_t **memIdxMap); @@ -405,7 +437,7 @@ private: // Per memory scope, the size of the next allocation. This allows us to balance number of memory // allocation objects with size by incrementally allocating larger blocks. - VkDeviceSize m_MemoryBlockSize[arraydim()]; + VkDeviceSize m_MemoryBlockSize[arraydim()] = {}; MemoryAllocation AllocateMemoryForResource(VkImage im, MemoryScope scope, MemoryType type); MemoryAllocation AllocateMemoryForResource(VkBuffer buf, MemoryScope scope, MemoryType type); @@ -450,7 +482,7 @@ private: int markerCount; - std::vector > resourceUsage; + std::vector> resourceUsage; struct CmdBufferState { @@ -472,7 +504,7 @@ private: uint32_t subpass = 0; } state; - std::vector > imgbarriers; + std::vector> imgbarriers; ResourceId pushDescriptorID[64]; @@ -546,7 +578,7 @@ private: // event IDs, since they could be submitted multiple times in the frame and we don't want to // rebase all of them each time. // Map from bakeID -> vector - std::map > cmdBufferSubmits; + std::map> cmdBufferSubmits; // identifies the baked ID of the command buffer that's actually partial at each level. ResourceId partialParent; @@ -573,7 +605,7 @@ private: // we store the list here, since we need to keep all command buffers until the whole replay is // finished, but if a command buffer is re-recorded multiple times it would be overwritten in the // above map - std::vector m_RerecordCmdList; + std::vector> m_RerecordCmdList; // There is only a state while currently partially replaying, it's // undefined/empty otherwise. @@ -629,7 +661,7 @@ private: // immutable creation data VulkanCreationInfo m_CreationInfo; - map > m_ResourceUses; + map> m_ResourceUses; // returns thread-local temporary memory byte *GetTempMemory(size_t s); @@ -781,6 +813,8 @@ public: void Create_InitialState(ResourceId id, WrappedVkRes *live, bool hasData); void Apply_InitialState(WrappedVkRes *live, VkInitialContents initial); + void RemapQueueFamilyIndices(uint32_t &srcQueueFamily, uint32_t &dstQueueFamily); + bool ReleaseResource(WrappedVkRes *res); ReplayStatus Initialise(VkInitParams ¶ms, uint64_t sectionVersion); diff --git a/renderdoc/driver/vulkan/vk_info.h b/renderdoc/driver/vulkan/vk_info.h index 7fd4a2aa0..b0a28988b 100644 --- a/renderdoc/driver/vulkan/vk_info.h +++ b/renderdoc/driver/vulkan/vk_info.h @@ -438,4 +438,7 @@ struct VulkanCreationInfo map m_SwapChain; map m_DescSetLayout; map m_DescUpdateTemplate; + + // just contains the queueFamilyIndex (after remapping) + map m_Queue; }; diff --git a/renderdoc/driver/vulkan/vk_initstate.cpp b/renderdoc/driver/vulkan/vk_initstate.cpp index 27556e309..c5abc68b8 100644 --- a/renderdoc/driver/vulkan/vk_initstate.cpp +++ b/renderdoc/driver/vulkan/vk_initstate.cpp @@ -109,12 +109,20 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) // INITSTATEBATCH VkCommandBuffer cmd = GetNextCmd(); + VkCommandBuffer extQCmd = VK_NULL_HANDLE; + ImageLayouts *layout = NULL; { SCOPED_LOCK(m_ImageLayoutsLock); layout = &m_ImageLayouts[im->id]; } + if(layout->queueFamilyIndex != m_QueueFamilyIdx) + { + // get a command buffer for giving up ownership before the copy and acquiring it afterwards. + extQCmd = GetExtQueueCmd(layout->queueFamilyIndex); + } + // must ensure offset remains valid. Must be multiple of block size, or 4, depending on format VkDeviceSize bufAlignment = 4; if(IsBlockFormat(layout->format)) @@ -212,6 +220,12 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) vkr = ObjDisp(d)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); RDCASSERTEQUAL(vkr, VK_SUCCESS); + if(extQCmd != VK_NULL_HANDLE) + { + vkr = ObjDisp(d)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + } + VkImageAspectFlags aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT; if(IsStencilOnlyFormat(layout->format)) aspectFlags = VK_IMAGE_ASPECT_STENCIL_BIT; @@ -225,10 +239,11 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) 0, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_QUEUE_FAMILY_IGNORED, - VK_QUEUE_FAMILY_IGNORED, + layout->queueFamilyIndex, + m_QueueFamilyIdx, realim, - {aspectFlags, 0, (uint32_t)layout->levelCount, 0, (uint32_t)numLayers}}; + {aspectFlags, 0, (uint32_t)layout->levelCount, 0, (uint32_t)numLayers}, + }; if(aspectFlags == VK_IMAGE_ASPECT_DEPTH_BIT && !IsDepthOnlyFormat(layout->format)) srcimBarrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; @@ -248,21 +263,34 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) srcimBarrier.subresourceRange = layout->subresourceStates[si].subresourceRange; srcimBarrier.oldLayout = layout->subresourceStates[si].newLayout; DoPipelineBarrier(cmd, 1, &srcimBarrier); + + if(srcimBarrier.srcQueueFamilyIndex != srcimBarrier.dstQueueFamilyIndex) + DoPipelineBarrier(extQCmd, 1, &srcimBarrier); + } + + if(extQCmd != VK_NULL_HANDLE) + { + vkr = ObjDisp(d)->EndCommandBuffer(Unwrap(extQCmd)); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + SubmitAndFlushExtQueue(layout->queueFamilyIndex); } if(arrayIm != VK_NULL_HANDLE) { - VkImageMemoryBarrier arrayimBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - NULL, - 0, - 0, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL, - VK_QUEUE_FAMILY_IGNORED, - VK_QUEUE_FAMILY_IGNORED, - Unwrap(arrayIm), - {srcimBarrier.subresourceRange.aspectMask, 0, - VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}}; + VkImageMemoryBarrier arrayimBarrier = { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + NULL, + 0, + 0, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + Unwrap(arrayIm), + {srcimBarrier.subresourceRange.aspectMask, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }; DoPipelineBarrier(cmd, 1, &arrayimBarrier); @@ -346,15 +374,35 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) // transfer back to whatever it was srcimBarrier.oldLayout = srcimBarrier.newLayout; + // on whatever queue + std::swap(srcimBarrier.srcQueueFamilyIndex, srcimBarrier.dstQueueFamilyIndex); + srcimBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; srcimBarrier.dstAccessMask = 0; + if(extQCmd != VK_NULL_HANDLE) + { + vkr = ObjDisp(d)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + } + for(size_t si = 0; si < layout->subresourceStates.size(); si++) { srcimBarrier.subresourceRange = layout->subresourceStates[si].subresourceRange; srcimBarrier.newLayout = layout->subresourceStates[si].newLayout; srcimBarrier.dstAccessMask = MakeAccessMask(srcimBarrier.newLayout); DoPipelineBarrier(cmd, 1, &srcimBarrier); + + if(srcimBarrier.srcQueueFamilyIndex != srcimBarrier.dstQueueFamilyIndex) + DoPipelineBarrier(extQCmd, 1, &srcimBarrier); + } + + if(extQCmd != VK_NULL_HANDLE) + { + vkr = ObjDisp(d)->EndCommandBuffer(Unwrap(extQCmd)); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + SubmitAndFlushExtQueue(layout->queueFamilyIndex); } vkr = ObjDisp(d)->EndCommandBuffer(Unwrap(cmd)); @@ -401,9 +449,17 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, }; + // we make the buffer concurrently accessible by all queue families to not invalidate the + // contents of the memory we're reading back from. + bufInfo.sharingMode = VK_SHARING_MODE_CONCURRENT; + bufInfo.queueFamilyIndexCount = m_PhysicalDeviceData.queueCount; + std::vector queues; + for(uint32_t i = 0; i < bufInfo.queueFamilyIndexCount; i++) + queues.push_back(i); + bufInfo.pQueueFamilyIndices = queues.data(); + // since this happens during capture, we don't want to start serialising extra buffer creates, - // so - // we manually create & then just wrap. + // so we manually create & then just wrap. VkBuffer srcBuf, dstBuf; bufInfo.size = datasize; diff --git a/renderdoc/driver/vulkan/vk_manager.cpp b/renderdoc/driver/vulkan/vk_manager.cpp index 942ea970c..32264d856 100644 --- a/renderdoc/driver/vulkan/vk_manager.cpp +++ b/renderdoc/driver/vulkan/vk_manager.cpp @@ -60,6 +60,8 @@ void VulkanResourceManager::RecordSingleBarrier(vectorfirst != id) break; + it->second.dstQueueFamilyIndex = t.dstQueueFamilyIndex; + { // we've found a range that completely matches our region, doesn't matter if that's // a whole image and the barrier is the whole image, or it's one subresource. @@ -185,7 +187,8 @@ void VulkanResourceManager::RecordSingleBarrier(vector > &states, @@ -280,9 +283,9 @@ void VulkanResourceManager::SerialiseImageStates(SerialiserType &ser, // to get images into the right layout t.srcAccessMask = 0; t.dstAccessMask = 0; - // MULTIDEVICE need to handle multiple queues - t.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - t.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + t.srcQueueFamilyIndex = ImageState.queueFamilyIndex; + t.dstQueueFamilyIndex = ImageState.queueFamilyIndex; + m_Core->RemapQueueFamilyIndices(t.srcQueueFamilyIndex, t.dstQueueFamilyIndex); t.image = Unwrap(GetCurrentHandle(liveid)); t.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; ReplacePresentableImageLayout(state.newLayout); @@ -297,7 +300,8 @@ void VulkanResourceManager::SerialiseImageStates(SerialiserType &ser, srcit++; } - ApplyBarriers(vec, states); + // we don't have to specify a queue here because all of the images have a specific queue above + ApplyBarriers(VK_QUEUE_FAMILY_IGNORED, vec, states); for(size_t i = 0; i < vec.size(); i++) barriers[i].oldLayout = vec[i].second.oldLayout; @@ -376,7 +380,8 @@ void VulkanResourceManager::MarkSparseMapReferenced(SparseMapping *sparse) } } -void VulkanResourceManager::ApplyBarriers(vector > &states, +void VulkanResourceManager::ApplyBarriers(uint32_t queueFamilyIndex, + vector > &states, map &layouts) { TRDBG("Applying %u barriers", (uint32_t)states.size()); @@ -396,6 +401,13 @@ void VulkanResourceManager::ApplyBarriers(vectorsecond.queueFamilyIndex = t.dstQueueFamilyIndex; + + // if there's no ownership transfer, it's implicitly owned by the current queue + if(t.dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED) + stit->second.queueFamilyIndex = queueFamilyIndex; + uint32_t nummips = t.subresourceRange.levelCount; uint32_t numslices = t.subresourceRange.layerCount; if(nummips == VK_REMAINING_MIP_LEVELS) diff --git a/renderdoc/driver/vulkan/vk_manager.h b/renderdoc/driver/vulkan/vk_manager.h index f99161b94..35376b0b8 100644 --- a/renderdoc/driver/vulkan/vk_manager.h +++ b/renderdoc/driver/vulkan/vk_manager.h @@ -253,7 +253,7 @@ public: void MergeBarriers(vector > &dststates, vector > &srcstates); - void ApplyBarriers(vector > &states, + void ApplyBarriers(uint32_t queueFamilyIndex, vector > &states, map &layouts); template diff --git a/renderdoc/driver/vulkan/vk_resources.h b/renderdoc/driver/vulkan/vk_resources.h index aa7cca4bc..297afa633 100644 --- a/renderdoc/driver/vulkan/vk_resources.h +++ b/renderdoc/driver/vulkan/vk_resources.h @@ -830,7 +830,10 @@ VkResourceType IdentifyTypeByPtr(WrappedVkRes *ptr); struct ImageRegionState { - ImageRegionState() : oldLayout(UNKNOWN_PREV_IMG_LAYOUT), newLayout(UNKNOWN_PREV_IMG_LAYOUT) + ImageRegionState() + : dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED), + oldLayout(UNKNOWN_PREV_IMG_LAYOUT), + newLayout(UNKNOWN_PREV_IMG_LAYOUT) { subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseArrayLayer = 0; @@ -838,11 +841,12 @@ struct ImageRegionState subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = 0; } - ImageRegionState(VkImageSubresourceRange r, VkImageLayout pr, VkImageLayout st) - : subresourceRange(r), oldLayout(pr), newLayout(st) + ImageRegionState(uint32_t queueIndex, VkImageSubresourceRange r, VkImageLayout pr, VkImageLayout st) + : dstQueueFamilyIndex(queueIndex), subresourceRange(r), oldLayout(pr), newLayout(st) { } + uint32_t dstQueueFamilyIndex; VkImageSubresourceRange subresourceRange; VkImageLayout oldLayout; VkImageLayout newLayout; @@ -1101,6 +1105,7 @@ public: PipelineLayoutData *pipeLayoutInfo; // only for pipeline layouts DescriptorSetData *descInfo; // only for descriptor sets and descriptor set layouts DescUpdateTemplate *descTemplateInfo; // only for descriptor update templates + uint32_t queueFamilyIndex; // only for queues }; VkResourceRecord *bakedCommands; @@ -1193,6 +1198,7 @@ struct ImageLayouts extent.width = extent.height = extent.depth = 1; } + uint32_t queueFamilyIndex = 0; vector subresourceStates; int layerCount, levelCount, sampleCount; VkExtent3D extent; diff --git a/renderdoc/driver/vulkan/vk_serialise.cpp b/renderdoc/driver/vulkan/vk_serialise.cpp index 20d311e3d..0070b44f8 100644 --- a/renderdoc/driver/vulkan/vk_serialise.cpp +++ b/renderdoc/driver/vulkan/vk_serialise.cpp @@ -610,7 +610,7 @@ void DoSerialise(SerialiserType &ser, VkPhysicalDeviceSparseProperties &el) template void DoSerialise(SerialiserType &ser, VkQueueFamilyProperties &el) { - SERIALISE_MEMBER(queueFlags); + SERIALISE_MEMBER_TYPED(VkQueueFlagBits, queueFlags); SERIALISE_MEMBER(queueCount); SERIALISE_MEMBER(timestampValidBits); SERIALISE_MEMBER(minImageTransferGranularity); @@ -2132,9 +2132,19 @@ void DoSerialise(SerialiserType &ser, VkSwapchainCreateInfoKHR &el) SERIALISE_MEMBER(imageUsage); SERIALISE_MEMBER(imageSharingMode); - // SHARING: queueFamilyCount, pQueueFamilyIndices - SERIALISE_MEMBER_EMPTY(queueFamilyIndexCount); - SERIALISE_MEMBER_ARRAY_EMPTY(pQueueFamilyIndices); + // pQueueFamilyIndices should *only* be read if the sharing mode is concurrent, and if the capture + // is new (old captures always ignored these fields) + if(ser.VersionAtLeast(0xD) && el.imageSharingMode == VK_SHARING_MODE_CONCURRENT) + { + SERIALISE_MEMBER(queueFamilyIndexCount); + SERIALISE_MEMBER_ARRAY(pQueueFamilyIndices, queueFamilyIndexCount); + } + else + { + // otherwise do a dummy serialise so the struct is the same either way + SERIALISE_MEMBER_EMPTY(queueFamilyIndexCount); + SERIALISE_MEMBER_ARRAY_EMPTY(pQueueFamilyIndices); + } SERIALISE_MEMBER(preTransform); SERIALISE_MEMBER(compositeAlpha); @@ -2189,6 +2199,11 @@ void DoSerialise(SerialiserType &ser, DescriptorSetSlot &el) template void DoSerialise(SerialiserType &ser, ImageRegionState &el) { + if(ser.VersionAtLeast(0xD)) + { + // added in 0xD + SERIALISE_MEMBER(dstQueueFamilyIndex); + } SERIALISE_MEMBER(subresourceRange); SERIALISE_MEMBER(oldLayout); SERIALISE_MEMBER(newLayout); @@ -2197,6 +2212,11 @@ void DoSerialise(SerialiserType &ser, ImageRegionState &el) template void DoSerialise(SerialiserType &ser, ImageLayouts &el) { + if(ser.VersionAtLeast(0xD)) + { + // added in 0xD + SERIALISE_MEMBER(queueFamilyIndex); + } SERIALISE_MEMBER(subresourceStates); SERIALISE_MEMBER(layerCount); SERIALISE_MEMBER(levelCount); diff --git a/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp index 6acadeac9..2e55e4c89 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp @@ -361,6 +361,9 @@ bool WrappedVulkan::Serialise_vkCreateCommandPool(SerialiserType &ser, VkDevice { VkCommandPool pool = VK_NULL_HANDLE; + // remap the queue family index + CreateInfo.queueFamilyIndex = m_QueueRemapping[CreateInfo.queueFamilyIndex][0].family; + VkResult ret = ObjDisp(device)->CreateCommandPool(Unwrap(device), &CreateInfo, NULL, &pool); if(ret != VK_SUCCESS) @@ -677,10 +680,6 @@ bool WrappedVulkan::Serialise_vkBeginCommandBuffer(SerialiserType &ser, VkComman if(rerecord) { - // pull all re-recorded commands from our own device and command pool for easier cleanup - device = GetDev(); - AllocateInfo.commandPool = m_InternalCmds.cmdpool; - VkCommandBuffer cmd = VK_NULL_HANDLE; VkCommandBufferAllocateInfo unwrappedInfo = AllocateInfo; unwrappedInfo.commandPool = Unwrap(unwrappedInfo.commandPool); @@ -708,7 +707,7 @@ bool WrappedVulkan::Serialise_vkBeginCommandBuffer(SerialiserType &ser, VkComman m_RerecordCmds[BakedCommandBuffer] = cmd; m_RerecordCmds[m_LastCmdBufferID] = cmd; - m_RerecordCmdList.push_back(cmd); + m_RerecordCmdList.push_back({AllocateInfo.commandPool, cmd}); m_BakedCmdBufferInfo[GetResID(cmd)].level = AllocateInfo.level; m_BakedCmdBufferInfo[GetResID(cmd)].beginFlags = BeginInfo.flags; @@ -2079,6 +2078,9 @@ bool WrappedVulkan::Serialise_vkCmdPipelineBarrier( { bufBarriers.push_back(pBufferMemoryBarriers[i]); bufBarriers.back().buffer = Unwrap(bufBarriers.back().buffer); + + RemapQueueFamilyIndices(bufBarriers.back().srcQueueFamilyIndex, + bufBarriers.back().dstQueueFamilyIndex); } } @@ -2093,8 +2095,8 @@ bool WrappedVulkan::Serialise_vkCmdPipelineBarrier( ReplacePresentableImageLayout(imgBarriers.back().oldLayout); ReplacePresentableImageLayout(imgBarriers.back().newLayout); - ReplaceExternalQueueFamily(imgBarriers.back().srcQueueFamilyIndex, - imgBarriers.back().dstQueueFamilyIndex); + RemapQueueFamilyIndices(imgBarriers.back().srcQueueFamilyIndex, + imgBarriers.back().dstQueueFamilyIndex); if(IsLoading(m_State)) { @@ -2518,10 +2520,13 @@ bool WrappedVulkan::Serialise_vkCmdExecuteCommands(SerialiserType &ser, VkComman ->CmdExecuteCommands(Unwrap(commandBuffer), commandBufferCount, UnwrapArray(pCommandBuffers, commandBufferCount)); - // apply barriers + // merge barriers into parent command buffer for(uint32_t i = 0; i < commandBufferCount; i++) - GetResourceManager()->ApplyBarriers( - m_BakedCmdBufferInfo[GetResID(pCommandBuffers[i])].imgbarriers, m_ImageLayouts); + { + GetResourceManager()->MergeBarriers( + m_BakedCmdBufferInfo[GetResID(commandBuffer)].imgbarriers, + m_BakedCmdBufferInfo[GetResID(pCommandBuffers[i])].imgbarriers); + } AddEvent(); @@ -2702,8 +2707,9 @@ bool WrappedVulkan::Serialise_vkCmdExecuteCommands(SerialiserType &ser, VkComman #endif rerecordedCmds.push_back(Unwrap(cmd)); - GetResourceManager()->ApplyBarriers(m_BakedCmdBufferInfo[rerecord].imgbarriers, - m_ImageLayouts); + GetResourceManager()->MergeBarriers( + m_BakedCmdBufferInfo[GetResID(commandBuffer)].imgbarriers, + m_BakedCmdBufferInfo[rerecord].imgbarriers); } else { diff --git a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp index 0f4e20be7..4b02c19cf 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp @@ -42,6 +42,31 @@ static VkApplicationInfo renderdocAppInfo = { VK_API_VERSION_1_0, }; +// we store the index in the loader table, since it won't be dereferenced and other parts of the +// code expect to copy it into a wrapped object +static VkPhysicalDevice MakePhysicalDeviceHandleFromIndex(uint32_t physDeviceIndex) +{ + static uintptr_t loaderTable(0x100 + physDeviceIndex); + return VkPhysicalDevice(&loaderTable); +} + +static uint32_t GetPhysicalDeviceIndexFromHandle(VkPhysicalDevice physicalDevice) +{ + return uint32_t((uintptr_t)LayerDisp(physicalDevice) - 0x100); +} + +static bool CheckTransferGranularity(VkExtent3D required, VkExtent3D check) +{ + // if the required granularity is (0,0,0) then any is fine - the requirement is always satisfied. + if(required.width == required.height && required.height == required.depth && required.depth == 0) + return true; + + // otherwise, each dimension must be <= the required dimension (i.e. more fine-grained) to support + // any copies we might do. + return check.width <= required.width && check.height <= required.height && + check.depth <= required.depth; +} + // vk_dispatchtables.cpp void InitDeviceTable(VkDevice dev, PFN_vkGetDeviceProcAddr gpa); void InitInstanceTable(VkInstance inst, PFN_vkGetInstanceProcAddr gpa); @@ -292,7 +317,7 @@ ReplayStatus WrappedVulkan::Initialise(VkInitParams ¶ms, uint64_t sectionVer m_PhysicalDevice = VK_NULL_HANDLE; m_Device = VK_NULL_HANDLE; m_QueueFamilyIdx = ~0U; - m_Queue = VK_NULL_HANDLE; + m_PrevQueue = m_Queue = VK_NULL_HANDLE; m_InternalCmds.Reset(); if(ObjDisp(m_Instance)->CreateDebugReportCallbackEXT) @@ -483,7 +508,7 @@ VkResult WrappedVulkan::vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo m_PhysicalDevice = VK_NULL_HANDLE; m_Device = VK_NULL_HANDLE; m_QueueFamilyIdx = ~0U; - m_Queue = VK_NULL_HANDLE; + m_PrevQueue = m_Queue = VK_NULL_HANDLE; m_InternalCmds.Reset(); if(ObjDisp(m_Instance)->CreateDebugReportCallbackEXT) @@ -540,6 +565,14 @@ void WrappedVulkan::Shutdown() GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.freesems[i]); } + for(size_t i = 0; i < m_ExternalQueues.size(); i++) + { + GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].buffer); + + ObjDisp(m_Device)->DestroyCommandPool(Unwrap(m_Device), Unwrap(m_ExternalQueues[i].pool), NULL); + GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].pool); + } + FreeAllMemory(MemoryScope::InitialContents); // we do more in Shutdown than the equivalent vkDestroyInstance since on replay there's @@ -626,7 +659,7 @@ bool WrappedVulkan::Serialise_vkEnumeratePhysicalDevices(SerialiserType &ser, Vk VkPhysicalDeviceMemoryProperties memProps; VkPhysicalDeviceFeatures physFeatures; uint32_t queueCount = 0; - VkQueueFamilyProperties queueProps[16]; + VkQueueFamilyProperties queueProps[16] = {}; if(ser.IsWriting()) { @@ -641,7 +674,7 @@ bool WrappedVulkan::Serialise_vkEnumeratePhysicalDevices(SerialiserType &ser, Vk if(queueCount > 16) { - RDCWARN("More than 16 queues"); + RDCERR("More than 16 queue families"); queueCount = 16; } @@ -676,6 +709,9 @@ bool WrappedVulkan::Serialise_vkEnumeratePhysicalDevices(SerialiserType &ser, Vk m_OriginalPhysicalDevices[PhysicalDeviceIndex].props = physProps; m_OriginalPhysicalDevices[PhysicalDeviceIndex].memProps = memProps; m_OriginalPhysicalDevices[PhysicalDeviceIndex].features = physFeatures; + m_OriginalPhysicalDevices[PhysicalDeviceIndex].queueCount = queueCount; + memcpy(m_OriginalPhysicalDevices[PhysicalDeviceIndex].queueProps, queueProps, + sizeof(queueProps)); } // match up physical devices to those available on replay as best as possible. In general @@ -754,11 +790,25 @@ bool WrappedVulkan::Serialise_vkEnumeratePhysicalDevices(SerialiserType &ser, Vk pd = m_ReplayPhysicalDevices[bestIdx]; - if(!m_ReplayPhysicalDevicesUsed[bestIdx]) - GetResourceManager()->AddLiveResource(PhysicalDevice, pd); - else - GetResourceManager()->ReplaceResource(PhysicalDevice, - GetResourceManager()->GetOriginalID(GetResID(pd))); + { + VkPhysicalDevice fakeDevice = MakePhysicalDeviceHandleFromIndex(PhysicalDeviceIndex); + + ResourceId id = ResourceIDGen::GetNewUniqueID(); + WrappedVkPhysicalDevice *wrapped = new WrappedVkPhysicalDevice(fakeDevice, id); + + GetResourceManager()->AddCurrentResource(id, wrapped); + + if(IsReplayMode(m_State)) + GetResourceManager()->AddWrapper(wrapped, ToTypedHandle(fakeDevice)); + + fakeDevice = (VkPhysicalDevice)wrapped; + + // we want to preserve the separate physical devices until we actually need the real handle, + // so don't remap multiple capture-time physical devices to one replay-time physical device + // yet. See below in Serialise_vkCreateDevice where this is decoded. + // Note this allocation is pooled so we don't have to explicitly delete it. + GetResourceManager()->AddLiveResource(PhysicalDevice, fakeDevice); + } AddResource(PhysicalDevice, ResourceType::Device, "Physical Device"); DerivedResource(m_Instance, PhysicalDevice); @@ -810,7 +860,6 @@ VkResult WrappedVulkan::vkEnumeratePhysicalDevices(VkInstance instance, RDCASSERTEQUAL(vkr, VK_SUCCESS); m_PhysicalDevices.resize(count); - m_SupportedQueueFamilies.resize(count); for(uint32_t i = 0; i < count; i++) { @@ -863,92 +912,6 @@ VkResult WrappedVulkan::vkEnumeratePhysicalDevices(VkInstance instance, } } } - - // find the queue with the most bits set and only report that one - - { - uint32_t queuecount = 0; - ObjDisp(m_PhysicalDevices[i]) - ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(m_PhysicalDevices[i]), &queuecount, NULL); - - VkQueueFamilyProperties *props = new VkQueueFamilyProperties[queuecount]; - ObjDisp(m_PhysicalDevices[i]) - ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(m_PhysicalDevices[i]), &queuecount, props); - - uint32_t best = 0; - - // don't need to explicitly check for transfer, because graphics bit - // implies it. We do have to check for compute bit, because there might - // be a graphics only queue - it just means we have to keep looking - // to find the grpahics & compute queue family which is guaranteed. - for(uint32_t q = 1; q < queuecount; q++) - { - // compare current against the known best - VkQueueFamilyProperties ¤tProps = props[q]; - VkQueueFamilyProperties &bestProps = props[best]; - - const bool currentGraphics = (currentProps.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0; - const bool currentCompute = (currentProps.queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; - const bool currentSparse = (currentProps.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; - - const bool bestGraphics = (bestProps.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0; - const bool bestCompute = (bestProps.queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; - const bool bestSparse = (bestProps.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; - - // if one has graphics bit set, but the other doesn't - if(currentGraphics != bestGraphics) - { - // if current has graphics but best doesn't, we have a new best - if(currentGraphics) - best = q; - continue; - } - - if(currentCompute != bestCompute) - { - // if current has compute but best doesn't, we have a new best - if(currentCompute) - best = q; - continue; - } - - // if we've gotten here, both best and current have graphics and compute. Check - // to see if the current is somehow better than best (in the case of a tie, we - // keep the lower index of queue). - - if(currentSparse != bestSparse) - { - if(currentSparse) - best = q; - continue; - } - - if(currentProps.timestampValidBits != bestProps.timestampValidBits) - { - if(currentProps.timestampValidBits > bestProps.timestampValidBits) - best = q; - continue; - } - - if(currentProps.minImageTransferGranularity.width < - bestProps.minImageTransferGranularity.width || - currentProps.minImageTransferGranularity.height < - bestProps.minImageTransferGranularity.height || - currentProps.minImageTransferGranularity.depth < - bestProps.minImageTransferGranularity.depth) - { - best = q; - continue; - } - } - - // only report a single available queue in this family - props[best].queueCount = 1; - - m_SupportedQueueFamilies[i] = std::make_pair(best, props[best]); - - SAFE_DELETE_ARRAY(props); - } } if(pPhysicalDeviceCount) @@ -971,12 +934,25 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi SERIALISE_ELEMENT_LOCAL(CreateInfo, *pCreateInfo); SERIALISE_ELEMENT_OPT(pAllocator); SERIALISE_ELEMENT_LOCAL(Device, GetResID(*pDevice)).TypedAs("VkDevice"); - SERIALISE_ELEMENT(m_SupportedQueueFamily).Hidden(); + + if(ser.VersionLess(0xD)) + { + uint32_t supportedQueueFamily; // no longer used + SERIALISE_ELEMENT(supportedQueueFamily).Hidden(); + } SERIALISE_CHECK_READ_ERRORS(); if(IsReplayingAndReading()) { + // kept around only to call DerivedResource below, as this is the resource that actually has an + // original resource ID. + VkPhysicalDevice origPhysDevice = physicalDevice; + + // see above in Serialise_vkEnumeratePhysicalDevices where this is encoded + uint32_t physicalDeviceIndex = GetPhysicalDeviceIndexFromHandle(Unwrap(physicalDevice)); + physicalDevice = m_ReplayPhysicalDevices[physicalDeviceIndex]; + // we must make any modifications locally, so the free of pointers // in the serialised VkDeviceCreateInfo don't double-free VkDeviceCreateInfo createInfo = CreateInfo; @@ -1099,18 +1075,224 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi uint32_t qCount = 0; ObjDisp(physicalDevice)->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), &qCount, NULL); - VkQueueFamilyProperties *props = new VkQueueFamilyProperties[qCount]; + if(qCount > 16) + { + RDCERR("Unexpected number of queue families: %u", qCount); + qCount = 16; + } + + VkQueueFamilyProperties props[16] = {}; ObjDisp(physicalDevice) ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), &qCount, props); + // to aid the search algorithm below, we apply implied transfer bit onto the queue properties. + for(uint32_t i = 0; i < qCount; i++) + { + if(props[i].queueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) + props[i].queueFlags |= VK_QUEUE_TRANSFER_BIT; + } + + PhysicalDeviceData &origData = m_OriginalPhysicalDevices[physicalDeviceIndex]; + + uint32_t origQCount = origData.queueCount; + VkQueueFamilyProperties *origprops = origData.queueProps; + + // create queue remapping + for(uint32_t origQIndex = 0; origQIndex < origQCount; origQIndex++) + { + m_QueueRemapping[origQIndex].resize(origprops[origQIndex].queueCount); + RDCLOG("Capture describes queue family %u:", origQIndex); + RDCLOG(" - %u queues available with %s", origprops[origQIndex].queueCount, + ToStr(VkQueueFlagBits(origprops[origQIndex].queueFlags)).c_str()); + RDCLOG(" %u timestamp bits (%u,%u,%u) granularity", + origprops[origQIndex].timestampValidBits, + origprops[origQIndex].minImageTransferGranularity.width, + origprops[origQIndex].minImageTransferGranularity.height, + origprops[origQIndex].minImageTransferGranularity.depth); + + // find the best queue family to map to. We try and find the closest match that is at least + // good enough. We want to try and preserve families that were separate before but we need to + // ensure the remapped queue family is at least as good as it was at capture time. + uint32_t destFamily = 0; + + { + // we categorise the original queue as one of four types: universal + // (graphics/compute/transfer), graphics/transfer only (rare), compute-only + // (compute/transfer) or transfer-only (transfer). We try first to find an exact match, then + // move progressively up the priority list to find a broader and broader match. + // We don't care about sparse binding - it's just treated as a requirement. + enum class SearchType + { + Failed, + Universal, + GraphicsTransfer, + ComputeTransfer, + GraphicsOrComputeTransfer, + TransferOnly, + } search; + + VkQueueFlags mask = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT); + + switch(origprops[origQIndex].queueFlags & mask) + { + case VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT: + case VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT: + search = SearchType::Universal; + break; + case VK_QUEUE_GRAPHICS_BIT: + case VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT: + search = SearchType::GraphicsTransfer; + break; + case VK_QUEUE_COMPUTE_BIT: + case VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT: + search = SearchType::ComputeTransfer; + break; + case VK_QUEUE_TRANSFER_BIT: search = SearchType::TransferOnly; break; + default: + search = SearchType::Failed; + RDCERR("Unexpected set of flags: %s", + ToStr(VkQueueFlagBits(origprops[origQIndex].queueFlags & mask)).c_str()); + break; + } + + bool needSparse = (origprops[origQIndex].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; + VkExtent3D needGranularity = origprops[origQIndex].minImageTransferGranularity; + + while(search != SearchType::Failed) + { + bool found = false; + + for(uint32_t replayQIndex = 0; replayQIndex < qCount; replayQIndex++) + { + // ignore queues that couldn't satisfy the required transfer granularity + if(!CheckTransferGranularity(needGranularity, + props[replayQIndex].minImageTransferGranularity)) + continue; + + // ignore queues that don't have sparse binding, if we need that + if(needSparse && ((props[replayQIndex].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) == 0)) + continue; + + switch(search) + { + case SearchType::Failed: break; + case SearchType::Universal: + if((props[replayQIndex].queueFlags & mask) == + (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT)) + { + destFamily = replayQIndex; + found = true; + } + break; + case SearchType::GraphicsTransfer: + if((props[replayQIndex].queueFlags & mask) == + (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT)) + { + destFamily = replayQIndex; + found = true; + } + break; + case SearchType::ComputeTransfer: + if((props[replayQIndex].queueFlags & mask) == + (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT)) + { + destFamily = replayQIndex; + found = true; + } + break; + case SearchType::GraphicsOrComputeTransfer: + if((props[replayQIndex].queueFlags & mask) == + (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT) || + (props[replayQIndex].queueFlags & mask) == + (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT)) + { + destFamily = replayQIndex; + found = true; + } + break; + case SearchType::TransferOnly: + if((props[replayQIndex].queueFlags & mask) == VK_QUEUE_TRANSFER_BIT) + { + destFamily = replayQIndex; + found = true; + } + break; + } + + if(found) + break; + } + + if(found) + break; + + // no such queue family found, fall back to the next type of queue to search for + switch(search) + { + case SearchType::Failed: break; + case SearchType::Universal: search = SearchType::Failed; break; + case SearchType::GraphicsTransfer: + case SearchType::ComputeTransfer: + case SearchType::GraphicsOrComputeTransfer: + // if we didn't find a graphics or compute (and transfer) queue, we have to look for a + // universal one + search = SearchType::Universal; + break; + case SearchType::TransferOnly: + // when falling back from looking for a transfer-only queue, we consider either + // graphics-only or compute-only as better candidates before universal + search = SearchType::GraphicsOrComputeTransfer; + break; + } + } + } + + RDCLOG("Remapping to queue family %u:", destFamily); + RDCLOG(" - %u queues available with %s", props[destFamily].queueCount, + ToStr(VkQueueFlagBits(props[destFamily].queueFlags)).c_str()); + RDCLOG(" %u timestamp bits (%u,%u,%u) granularity", props[destFamily].timestampValidBits, + props[destFamily].minImageTransferGranularity.width, + props[destFamily].minImageTransferGranularity.height, + props[destFamily].minImageTransferGranularity.depth); + + // loop over the queues, wrapping around if necessary to provide enough queues. The idea being + // an application is more likely to use early queues than later ones, so if there aren't + // enough queues in the family then we should prioritise giving unique queues to the early + // indices + for(uint32_t q = 0; q < origprops[origQIndex].queueCount; q++) + { + m_QueueRemapping[origQIndex][q] = {destFamily, q % props[destFamily].queueCount}; + } + } + + // now apply the remapping to the requested queues + for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++) + { + VkDeviceQueueCreateInfo &queueCreate = + (VkDeviceQueueCreateInfo &)createInfo.pQueueCreateInfos[i]; + + uint32_t queueFamily = queueCreate.queueFamilyIndex; + queueFamily = m_QueueRemapping[queueFamily][0].family; + queueCreate.queueFamilyIndex = queueFamily; + uint32_t queueCount = RDCMIN(queueCreate.queueCount, props[queueFamily].queueCount); + + if(queueCount < queueCreate.queueCount) + RDCWARN("Truncating queue family request from %u queues to %u queues", + queueCreate.queueCount, queueCount); + + queueCreate.queueCount = queueCount; + } + bool found = false; uint32_t qFamilyIdx = 0; - VkQueueFlags search = (VK_QUEUE_GRAPHICS_BIT); + + // we need graphics, and if there is a graphics queue there must be a graphics & compute queue. + VkQueueFlags search = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); // for queue priorities, if we need it float one = 1.0f; - // if we need to change the requested queues, it will point to this + // if we need to add a new requested queues, it will point to this VkDeviceQueueCreateInfo *modQueues = NULL; for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++) @@ -1144,7 +1326,6 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi if(!found) { - SAFE_DELETE_ARRAY(props); RDCERR( "Can't add a queue with required properties for RenderDoc! Unsupported configuration"); } @@ -1164,8 +1345,6 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi } } - SAFE_DELETE_ARRAY(props); - VkPhysicalDeviceFeatures enabledFeatures = {0}; if(createInfo.pEnabledFeatures != NULL) enabledFeatures = *createInfo.pEnabledFeatures; @@ -1323,7 +1502,7 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi GetResourceManager()->AddLiveResource(Device, device); AddResource(Device, ResourceType::Device, "Device"); - DerivedResource(physicalDevice, Device); + DerivedResource(origPhysDevice, Device); InstanceDeviceInfo extInfo; @@ -1379,6 +1558,9 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi ->GetPhysicalDeviceFormatProperties(Unwrap(physicalDevice), VkFormat(i), &m_PhysicalDeviceData.fmtprops[i]); + m_PhysicalDeviceData.queueCount = qCount; + memcpy(m_PhysicalDeviceData.queueProps, props, qCount * sizeof(VkQueueFamilyProperties)); + m_PhysicalDeviceData.readbackMemIndex = m_PhysicalDeviceData.GetMemoryIndex(~0U, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, 0); m_PhysicalDeviceData.uploadMemIndex = @@ -1428,7 +1610,9 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, // find a queue that supports all capabilities, and if one doesn't exist, add it. bool found = false; uint32_t qFamilyIdx = 0; - VkQueueFlags search = (VK_QUEUE_GRAPHICS_BIT); + + // we need graphics, and if there is a graphics queue there must be a graphics & compute queue. + VkQueueFlags search = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); // for queue priorities, if we need it float one = 1.0f; @@ -1485,25 +1669,21 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, createInfo.queueCreateInfoCount++; } - SAFE_DELETE_ARRAY(props); - m_QueueFamilies.resize(createInfo.queueCreateInfoCount); + m_QueueFamilyCounts.resize(createInfo.queueCreateInfoCount); for(size_t i = 0; i < createInfo.queueCreateInfoCount; i++) { uint32_t family = createInfo.pQueueCreateInfos[i].queueFamilyIndex; uint32_t count = createInfo.pQueueCreateInfos[i].queueCount; m_QueueFamilies.resize(RDCMAX(m_QueueFamilies.size(), size_t(family + 1))); + m_QueueFamilyCounts.resize(RDCMAX(m_QueueFamilies.size(), size_t(family + 1))); m_QueueFamilies[family] = new VkQueue[count]; + m_QueueFamilyCounts[family] = count; for(uint32_t q = 0; q < count; q++) m_QueueFamilies[family][q] = VK_NULL_HANDLE; } - // find the matching physical device - for(size_t i = 0; i < m_PhysicalDevices.size(); i++) - if(m_PhysicalDevices[i] == physicalDevice) - m_SupportedQueueFamily = m_SupportedQueueFamilies[i].first; - VkLayerDeviceCreateInfo *layerCreateInfo = (VkLayerDeviceCreateInfo *)pCreateInfo->pNext; // step through the chain of pNext until we get to the link info @@ -1516,6 +1696,7 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, if(layerCreateInfo == NULL) { + SAFE_DELETE_ARRAY(props); RDCERR("Couldn't find loader device create info, which is required. Incompatible loader?"); return VK_ERROR_INITIALIZATION_FAILED; } @@ -1679,6 +1860,38 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, GetResourceManager()->WrapResource(Unwrap(device), m_InternalCmds.cmdpool); } + // for each queue family that isn't our own, create a command pool and command buffer on that + // queue + for(uint32_t i = 0; i < createInfo.queueCreateInfoCount; i++) + { + uint32_t qidx = createInfo.pQueueCreateInfos[i].queueFamilyIndex; + m_ExternalQueues.resize(RDCMAX((uint32_t)m_ExternalQueues.size(), qidx + 1)); + + VkCommandPoolCreateInfo poolInfo = { + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, NULL, + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, qidx, + }; + vkr = ObjDisp(device)->CreateCommandPool(Unwrap(device), &poolInfo, NULL, + &m_ExternalQueues[qidx].pool); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + GetResourceManager()->WrapResource(Unwrap(device), m_ExternalQueues[qidx].pool); + + VkCommandBufferAllocateInfo cmdInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + NULL, + Unwrap(m_ExternalQueues[qidx].pool), + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1, + }; + + vkr = ObjDisp(device)->AllocateCommandBuffers(Unwrap(device), &cmdInfo, + &m_ExternalQueues[qidx].buffer); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + GetResourceManager()->WrapResource(Unwrap(device), m_ExternalQueues[qidx].buffer); + } + ObjDisp(physicalDevice) ->GetPhysicalDeviceProperties(Unwrap(physicalDevice), &m_PhysicalDeviceData.props); @@ -1700,6 +1913,9 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, m_PhysicalDeviceData.GPULocalMemIndex = m_PhysicalDeviceData.GetMemoryIndex( ~0U, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + m_PhysicalDeviceData.queueCount = qCount; + memcpy(m_PhysicalDeviceData.queueProps, props, qCount * sizeof(VkQueueFamilyProperties)); + m_PhysicalDeviceData.fakeMemProps = GetRecord(physicalDevice)->memProps; m_ShaderCache = new VulkanShaderCache(this); @@ -1709,6 +1925,7 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, m_DebugManager = new VulkanDebugManager(this); } + SAFE_DELETE_ARRAY(props); SAFE_DELETE_ARRAY(modQueues); return ret; @@ -1751,10 +1968,21 @@ void WrappedVulkan::vkDestroyDevice(VkDevice device, const VkAllocationCallbacks GetResourceManager()->ReleaseWrappedResource(m_InternalCmds.freesems[i]); } + for(size_t i = 0; i < m_ExternalQueues.size(); i++) + { + if(m_ExternalQueues[i].buffer != VK_NULL_HANDLE) + { + GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].buffer); + + ObjDisp(m_Device)->DestroyCommandPool(Unwrap(m_Device), Unwrap(m_ExternalQueues[i].pool), NULL); + GetResourceManager()->ReleaseWrappedResource(m_ExternalQueues[i].pool); + } + } + m_InternalCmds.Reset(); m_QueueFamilyIdx = ~0U; - m_Queue = VK_NULL_HANDLE; + m_PrevQueue = m_Queue = VK_NULL_HANDLE; // destroy the API device immediately. There should be no more // resources left in the resource manager device/physical device/instance. diff --git a/renderdoc/driver/vulkan/wrappers/vk_get_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_get_funcs.cpp index 28819f62c..85ac66dcb 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_get_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_get_funcs.cpp @@ -87,17 +87,16 @@ void WrappedVulkan::vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevic void WrappedVulkan::vkGetPhysicalDeviceQueueFamilyProperties( VkPhysicalDevice physicalDevice, uint32_t *pCount, VkQueueFamilyProperties *pQueueFamilyProperties) { - // pretend to only have one queue, the one with graphics capability - if(pCount) - *pCount = 1; + // report the actual physical device properties - this will be remapped on replay if necessary + ObjDisp(physicalDevice) + ->GetPhysicalDeviceQueueFamilyProperties(Unwrap(physicalDevice), pCount, + pQueueFamilyProperties); - if(pQueueFamilyProperties) + // remove any protected bits that might be set + if(pCount && pQueueFamilyProperties) { - // find the matching physical device - for(size_t i = 0; i < m_PhysicalDevices.size(); i++) - if(m_PhysicalDevices[i] == physicalDevice) - *pQueueFamilyProperties = m_SupportedQueueFamilies[i].second; - return; + for(uint32_t i = 0; i < *pCount; i++) + pQueueFamilyProperties[i].queueFlags &= ~VK_QUEUE_PROTECTED_BIT; } } diff --git a/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp index 762d3fdd4..457d8bf1a 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp @@ -39,13 +39,20 @@ bool WrappedVulkan::Serialise_vkGetDeviceQueue(SerialiserType &ser, VkDevice dev if(IsReplayingAndReading()) { VkQueue queue; - // MULTIQUEUE - re-map the queue family/index instead of using the supported family - ObjDisp(device)->GetDeviceQueue(Unwrap(device), m_SupportedQueueFamily, 0, &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); GetResourceManager()->WrapResource(Unwrap(device), queue); GetResourceManager()->AddLiveResource(Queue, queue); - if(queueFamilyIndex == m_QueueFamilyIdx) + if(remapFamily == m_QueueFamilyIdx && m_Queue == VK_NULL_HANDLE) { m_Queue = queue; @@ -54,6 +61,8 @@ bool WrappedVulkan::Serialise_vkGetDeviceQueue(SerialiserType &ser, VkDevice dev SubmitCmds(); } + m_CreationInfo.m_Queue[GetResID(queue)] = remapFamily; + AddResource(Queue, ResourceType::Queue, "Queue"); DerivedResource(device, Queue); } @@ -101,6 +110,8 @@ void WrappedVulkan::vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, 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) @@ -115,6 +126,16 @@ void WrappedVulkan::vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, 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; @@ -142,6 +163,18 @@ bool WrappedVulkan::Serialise_vkQueueSubmit(SerialiserType &ser, VkQueue queue, 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 %llu, now executing %llu, syncing GPU", + GetResID(m_PrevQueue), GetResID(queue)); + 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++) @@ -205,7 +238,8 @@ bool WrappedVulkan::Serialise_vkQueueSubmit(SerialiserType &ser, VkQueue queue, BakedCmdBufferInfo &cmdBufInfo = m_BakedCmdBufferInfo[cmd]; - GetResourceManager()->ApplyBarriers(m_BakedCmdBufferInfo[cmd].imgbarriers, m_ImageLayouts); + GetResourceManager()->ApplyBarriers(m_CreationInfo.m_Queue[GetResID(queue)], + m_BakedCmdBufferInfo[cmd].imgbarriers, m_ImageLayouts); std::string name = StringFormat::Fmt("=> %s[%u]: vkBeginCommandBuffer(%s)", basename.c_str(), c, ToStr(cmd).c_str()); @@ -334,7 +368,8 @@ bool WrappedVulkan::Serialise_vkQueueSubmit(SerialiserType &ser, VkQueue queue, #endif rerecordedCmds.push_back(Unwrap(cmd)); - GetResourceManager()->ApplyBarriers(m_BakedCmdBufferInfo[rerecord].imgbarriers, + GetResourceManager()->ApplyBarriers(m_CreationInfo.m_Queue[GetResID(queue)], + m_BakedCmdBufferInfo[rerecord].imgbarriers, m_ImageLayouts); } else @@ -509,6 +544,8 @@ VkResult WrappedVulkan::vkQueueSubmit(VkQueue queue, uint32_t submitCount, bool capframe = false; set refdIDs; + VkResourceRecord *queueRecord = GetRecord(queue); + for(uint32_t s = 0; s < submitCount; s++) { for(uint32_t i = 0; i < pSubmits[s].commandBufferCount; i++) @@ -519,7 +556,8 @@ VkResult WrappedVulkan::vkQueueSubmit(VkQueue queue, uint32_t submitCount, { SCOPED_LOCK(m_ImageLayoutsLock); - GetResourceManager()->ApplyBarriers(record->bakedCommands->cmdInfo->imgbarriers, + GetResourceManager()->ApplyBarriers(queueRecord->queueFamilyIndex, + record->bakedCommands->cmdInfo->imgbarriers, m_ImageLayouts); } @@ -1202,17 +1240,24 @@ bool WrappedVulkan::Serialise_vkGetDeviceQueue2(SerialiserType &ser, VkDevice de 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; - // MULTIQUEUE - re-map the queue family/index instead of using the supported family - QueueInfo.queueFamilyIndex = m_SupportedQueueFamily; - QueueInfo.queueIndex = 0; + QueueInfo.queueFamilyIndex = remapFamily; + QueueInfo.queueIndex = remapIndex; ObjDisp(device)->GetDeviceQueue2(Unwrap(device), &QueueInfo, &queue); GetResourceManager()->WrapResource(Unwrap(device), queue); GetResourceManager()->AddLiveResource(Queue, queue); - if(queueFamilyIndex == m_QueueFamilyIdx) + if(remapFamily == m_QueueFamilyIdx && m_Queue == VK_NULL_HANDLE) { m_Queue = queue; @@ -1221,6 +1266,8 @@ bool WrappedVulkan::Serialise_vkGetDeviceQueue2(SerialiserType &ser, VkDevice de SubmitCmds(); } + m_CreationInfo.m_Queue[GetResID(queue)] = remapFamily; + AddResource(Queue, ResourceType::Queue, "Queue"); DerivedResource(device, Queue); } @@ -1267,6 +1314,8 @@ void WrappedVulkan::vkGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 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) @@ -1281,6 +1330,16 @@ void WrappedVulkan::vkGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 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; diff --git a/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp index f1847c1f0..a443d1900 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp @@ -1009,6 +1009,14 @@ bool WrappedVulkan::Serialise_vkCreateBuffer(SerialiserType &ser, VkDevice devic // ensure we can always readback from buffers CreateInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + // remap the queue family indices + if(CreateInfo.sharingMode == VK_SHARING_MODE_EXCLUSIVE) + { + uint32_t *queueFamiles = (uint32_t *)CreateInfo.pQueueFamilyIndices; + for(uint32_t q = 0; q < CreateInfo.queueFamilyIndexCount; q++) + queueFamiles[q] = m_QueueRemapping[queueFamiles[q]][0].family; + } + VkBufferCreateInfo patched = CreateInfo; byte *tempMem = GetTempMemory(GetNextPatchSize(patched.pNext)); @@ -1062,8 +1070,6 @@ VkResult WrappedVulkan::vkCreateBuffer(VkDevice device, const VkBufferCreateInfo SERIALISE_TIME_CALL( ret = ObjDisp(device)->CreateBuffer(Unwrap(device), &adjusted_info, pAllocator, pBuffer)); - // SHARING: pCreateInfo sharingMode, queueFamilyCount, pQueueFamilyIndices - if(ret == VK_SUCCESS) { ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), *pBuffer); @@ -1255,6 +1261,14 @@ bool WrappedVulkan::Serialise_vkCreateImage(SerialiserType &ser, VkDevice device VK_IMAGE_USAGE_TRANSFER_DST_BIT; CreateInfo.usage &= ~VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; + // remap the queue family indices + if(CreateInfo.sharingMode == VK_SHARING_MODE_EXCLUSIVE) + { + uint32_t *queueFamiles = (uint32_t *)CreateInfo.pQueueFamilyIndices; + for(uint32_t q = 0; q < CreateInfo.queueFamilyIndexCount; q++) + queueFamiles[q] = m_QueueRemapping[queueFamiles[q]][0].family; + } + // ensure we can cast multisampled images, for copying to arrays if((int)CreateInfo.samples > 1) { @@ -1328,8 +1342,8 @@ bool WrappedVulkan::Serialise_vkCreateImage(SerialiserType &ser, VkDevice device else if(IsDepthOrStencilFormat(CreateInfo.format)) range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - layouts.subresourceStates.push_back( - ImageRegionState(range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); + layouts.subresourceStates.push_back(ImageRegionState( + VK_QUEUE_FAMILY_IGNORED, range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); } const char *prefix = "Image"; @@ -1412,8 +1426,6 @@ VkResult WrappedVulkan::vkCreateImage(VkDevice device, const VkImageCreateInfo * SERIALISE_TIME_CALL( ret = ObjDisp(device)->CreateImage(Unwrap(device), &createInfo_adjusted, pAllocator, pImage)); - // SHARING: pCreateInfo sharingMode, queueFamilyCount, pQueueFamilyIndices - if(ret == VK_SUCCESS) { ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), *pImage); @@ -1556,8 +1568,8 @@ VkResult WrappedVulkan::vkCreateImage(VkDevice device, const VkImageCreateInfo * else if(IsDepthOrStencilFormat(pCreateInfo->format)) range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - layout->subresourceStates.push_back( - ImageRegionState(range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); + layout->subresourceStates.push_back(ImageRegionState( + VK_QUEUE_FAMILY_IGNORED, range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); } return ret; diff --git a/renderdoc/driver/vulkan/wrappers/vk_sync_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_sync_funcs.cpp index 1ea218b59..0376c12d0 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_sync_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_sync_funcs.cpp @@ -71,6 +71,30 @@ * */ +void WrappedVulkan::RemapQueueFamilyIndices(uint32_t &srcQueueFamily, uint32_t &dstQueueFamily) +{ + if(srcQueueFamily == VK_QUEUE_FAMILY_EXTERNAL || dstQueueFamily == VK_QUEUE_FAMILY_EXTERNAL) + { + // we should ignore this family transition since we're not synchronising with an + // external access. + srcQueueFamily = dstQueueFamily = VK_QUEUE_FAMILY_IGNORED; + } + else + { + if(srcQueueFamily != VK_QUEUE_FAMILY_IGNORED) + { + RDCASSERT(srcQueueFamily < ARRAY_COUNT(m_QueueRemapping), srcQueueFamily); + srcQueueFamily = m_QueueRemapping[srcQueueFamily][0].family; + } + + if(dstQueueFamily != VK_QUEUE_FAMILY_IGNORED) + { + RDCASSERT(dstQueueFamily < ARRAY_COUNT(m_QueueRemapping), dstQueueFamily); + dstQueueFamily = m_QueueRemapping[dstQueueFamily][0].family; + } + } +} + template bool WrappedVulkan::Serialise_vkCreateFence(SerialiserType &ser, VkDevice device, const VkFenceCreateInfo *pCreateInfo, @@ -740,6 +764,9 @@ bool WrappedVulkan::Serialise_vkCmdWaitEvents( { bufBarriers.push_back(pBufferMemoryBarriers[i]); bufBarriers.back().buffer = Unwrap(bufBarriers.back().buffer); + + RemapQueueFamilyIndices(bufBarriers.back().srcQueueFamilyIndex, + bufBarriers.back().dstQueueFamilyIndex); } } @@ -752,8 +779,8 @@ bool WrappedVulkan::Serialise_vkCmdWaitEvents( ReplacePresentableImageLayout(imgBarriers.back().oldLayout); ReplacePresentableImageLayout(imgBarriers.back().newLayout); - ReplaceExternalQueueFamily(imgBarriers.back().srcQueueFamilyIndex, - imgBarriers.back().dstQueueFamilyIndex); + RemapQueueFamilyIndices(imgBarriers.back().srcQueueFamilyIndex, + imgBarriers.back().dstQueueFamilyIndex); } } diff --git a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp index 063fd3b73..b627d1533 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp @@ -338,8 +338,8 @@ bool WrappedVulkan::Serialise_vkCreateSwapchainKHR(SerialiserType &ser, VkDevice VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | CreateInfo.imageUsage, CreateInfo.imageSharingMode, - 0, - NULL, + CreateInfo.queueFamilyIndexCount, + CreateInfo.pQueueFamilyIndices, VK_IMAGE_LAYOUT_UNDEFINED, }; @@ -407,8 +407,8 @@ bool WrappedVulkan::Serialise_vkCreateSwapchainKHR(SerialiserType &ser, VkDevice m_ImageLayouts[liveId].format = iminfo.format; m_ImageLayouts[liveId].subresourceStates.clear(); - m_ImageLayouts[liveId].subresourceStates.push_back( - ImageRegionState(range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); + m_ImageLayouts[liveId].subresourceStates.push_back(ImageRegionState( + VK_QUEUE_FAMILY_IGNORED, range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); } } @@ -540,8 +540,8 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device, { SCOPED_LOCK(m_ImageLayoutsLock); m_ImageLayouts[imid].subresourceStates.clear(); - m_ImageLayouts[imid].subresourceStates.push_back( - ImageRegionState(range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); + m_ImageLayouts[imid].subresourceStates.push_back(ImageRegionState( + VK_QUEUE_FAMILY_IGNORED, range, UNKNOWN_PREV_IMG_LAYOUT, VK_IMAGE_LAYOUT_UNDEFINED)); } { @@ -679,6 +679,8 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR VkImage im = swapInfo.images[pPresentInfo->pImageIndices[0]].im; VkFramebuffer fb = swapInfo.images[pPresentInfo->pImageIndices[0]].fb; + uint32_t swapQueueIndex = m_ImageLayouts[GetResID(im)].queueFamilyIndex; + VkLayerDispatchTable *vt = ObjDisp(GetDev()); TextPrintState textstate = { @@ -696,22 +698,38 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR VkResult vkr = vt->BeginCommandBuffer(Unwrap(textstate.cmd), &beginInfo); RDCASSERTEQUAL(vkr, VK_SUCCESS); - VkImageMemoryBarrier bbBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - NULL, - 0, - 0, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_QUEUE_FAMILY_IGNORED, - VK_QUEUE_FAMILY_IGNORED, - Unwrap(im), - {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + VkImageMemoryBarrier bbBarrier = { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + NULL, + 0, + 0, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + swapQueueIndex, + m_QueueFamilyIdx, + Unwrap(im), + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + }; bbBarrier.srcAccessMask = VK_ACCESS_ALL_READ_BITS; bbBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; DoPipelineBarrier(textstate.cmd, 1, &bbBarrier); + if(swapQueueIndex != m_QueueFamilyIdx) + { + VkCommandBuffer extQCmd = GetExtQueueCmd(swapQueueIndex); + + vkr = vt->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + DoPipelineBarrier(extQCmd, 1, &bbBarrier); + + ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd)); + + SubmitAndFlushExtQueue(swapQueueIndex); + } + m_TextRenderer->BeginText(textstate); int flags = activeWindow ? RenderDoc::eOverlay_ActiveWindow : 0; @@ -722,6 +740,7 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR m_TextRenderer->EndText(textstate); + std::swap(bbBarrier.srcQueueFamilyIndex, bbBarrier.dstQueueFamilyIndex); std::swap(bbBarrier.oldLayout, bbBarrier.newLayout); bbBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; bbBarrier.dstAccessMask = VK_ACCESS_ALL_READ_BITS; @@ -732,6 +751,20 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR SubmitCmds(); + if(swapQueueIndex != m_QueueFamilyIdx) + { + VkCommandBuffer extQCmd = GetExtQueueCmd(swapQueueIndex); + + vkr = vt->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + DoPipelineBarrier(extQCmd, 1, &bbBarrier); + + ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd)); + + SubmitAndFlushExtQueue(swapQueueIndex); + } + FlushQ(); } } diff --git a/renderdoc/serialise/serialiser.h b/renderdoc/serialise/serialiser.h index 4fdc98352..404521141 100644 --- a/renderdoc/serialise/serialiser.h +++ b/renderdoc/serialise/serialiser.h @@ -131,7 +131,8 @@ public: void SetVersion(uint64_t version) { m_Version = version; } // assume that we always write the latest version, so on writing the version check always passes. - bool VersionCheck(uint64_t req) { return IsWriting() || m_Version >= req; } + bool VersionAtLeast(uint64_t req) const { return IsWriting() || m_Version >= req; } + bool VersionLess(uint64_t req) const { return IsReading() && m_Version < req; } // enable 'streaming mode' for ephemeral transfers like temporary I/O over sockets, where there's // no need for the chunk length - avoids needing to seek internally in a stream that might not // support seeking to fixup lengths, while also not requiring conservative length estimates