When descriptor buffers are used, intercept swapchain images

* This is the only way we can handle the potential for creating descriptors for
  swapchain images as there is no way otherwise to fetch and replay the opaque
  descriptor data on the image that would be needed. We add an extra
  interception step and let the application write to a 'real' image that we
  allocate, prevent it from going into the present layout, and on present do a
  copy & fixup.
This commit is contained in:
baldurk
2025-07-30 22:10:25 +01:00
parent 315d4a8d4f
commit 52fa006beb
8 changed files with 542 additions and 100 deletions
+26 -1
View File
@@ -433,7 +433,7 @@ bool VkInitParams::IsSupportedVersion(uint64_t ver)
if(ver == CurrentVersion)
return true;
// 0x16 -> 0x17 - added indication of reserved descriptors
// 0x16 -> 0x17 - added indication of reserved descriptors and descriptor buffer support for swapchains
if(ver == 0x16)
return true;
@@ -489,6 +489,13 @@ bool VkInitParams::IsSupportedVersion(uint64_t ver)
return false;
}
void SanitiseDescriptorBufferImageLayout(VkImageLayout &layout)
{
// for descriptor buffers we intercept swapchain images so we have to remap present layouts to general
if(layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR || layout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR)
layout = VK_IMAGE_LAYOUT_GENERAL;
}
void SanitiseReplayImageLayout(VkImageLayout &layout)
{
// we don't replay with present layouts since we don't create actual swapchains. So change any
@@ -1060,6 +1067,24 @@ void OpaqueDataForSerialising::fill(VkDevice wrappedDevice, VkBuffer wrappedBuff
RDCERR("Couldn't get opaque capture/replay data: %s", ToStr(opaqueQuery).c_str());
}
void OpaqueDataForSerialising::fillUnwrapped(VkDevice wrappedDevice, VkImage unwrappedImage,
VkPhysicalDeviceDescriptorBufferPropertiesEXT &props)
{
VkImageCaptureDescriptorDataInfoEXT getInfo = {
VK_STRUCTURE_TYPE_IMAGE_CAPTURE_DESCRIPTOR_DATA_INFO_EXT,
NULL,
unwrappedImage,
};
sz = props.imageCaptureReplayDescriptorDataSize;
VkResult opaqueQuery =
ObjDisp(wrappedDevice)
->GetImageOpaqueCaptureDescriptorDataEXT(Unwrap(wrappedDevice), &getInfo, data);
if(opaqueQuery != VK_SUCCESS)
RDCERR("Couldn't get opaque capture/replay data: %s", ToStr(opaqueQuery).c_str());
}
void OpaqueDataForSerialising::fill(VkDevice wrappedDevice, VkImage wrappedImage,
VkPhysicalDeviceDescriptorBufferPropertiesEXT &props)
{
+4
View File
@@ -123,6 +123,7 @@ rdcstr HumanDriverName(VkDriverId driverId);
void SanitiseOldImageLayout(VkImageLayout &layout);
void SanitiseNewImageLayout(VkImageLayout &layout);
void SanitiseReplayImageLayout(VkImageLayout &layout);
void SanitiseDescriptorBufferImageLayout(VkImageLayout &layout);
void CombineDepthStencilLayouts(rdcarray<VkImageMemoryBarrier> &barriers);
@@ -946,6 +947,9 @@ struct OpaqueDataForSerialising : VkOpaqueCaptureDescriptorDataCreateInfoEXT
void fill(VkDevice wrappedDevice, VkAccelerationStructureKHR wrappedAS,
VkPhysicalDeviceDescriptorBufferPropertiesEXT &props);
void fillUnwrapped(VkDevice wrappedDevice, VkImage unwrappedImage,
VkPhysicalDeviceDescriptorBufferPropertiesEXT &props);
void addForSerialising(VkBaseInStructure *serialisedCreateInfo);
byte data[FixedOpaqueDescriptorCaptureSize] = {};
+12 -5
View File
@@ -2672,7 +2672,7 @@ bool WrappedVulkan::EndFrameCapture(DeviceOwnedWindow devWnd)
const SwapchainInfo &swapInfo = *swaprecord->swapInfo;
backbuffer = swapInfo.images[swapInfo.lastPresent.imageIndex].im;
backbuffer = swapInfo.images[swapInfo.lastPresent.imageIndex].userSwapImage;
swapImageInfo = &swapInfo.imageInfo;
swapQueueIndex = GetRecord(swapInfo.lastPresent.presentQueue)->queueFamilyIndex;
swapLayout =
@@ -2680,8 +2680,12 @@ bool WrappedVulkan::EndFrameCapture(DeviceOwnedWindow devWnd)
// mark all images referenced as well
for(size_t i = 0; i < swapInfo.images.size(); i++)
GetResourceManager()->MarkResourceFrameReferenced(GetResID(swapInfo.images[i].im),
GetResourceManager()->MarkResourceFrameReferenced(GetResID(swapInfo.images[i].userSwapImage),
eFrameRef_Read);
// we're using the fake image for descriptor buffers, which is in layout general not present
if(DescriptorBuffers())
swapLayout = VK_IMAGE_LAYOUT_GENERAL;
}
else
{
@@ -2697,7 +2701,7 @@ bool WrappedVulkan::EndFrameCapture(DeviceOwnedWindow devWnd)
const SwapchainInfo &swapInfo = *swaprecord->swapInfo;
backbuffer = swapInfo.images[swapInfo.lastPresent.imageIndex].im;
backbuffer = swapInfo.images[swapInfo.lastPresent.imageIndex].userSwapImage;
swapImageInfo = &swapInfo.imageInfo;
swapQueueIndex = GetRecord(swapInfo.lastPresent.presentQueue)->queueFamilyIndex;
swapLayout =
@@ -2705,8 +2709,11 @@ bool WrappedVulkan::EndFrameCapture(DeviceOwnedWindow devWnd)
// mark all images referenced as well
for(size_t i = 0; i < swapInfo.images.size(); i++)
GetResourceManager()->MarkResourceFrameReferenced(GetResID(swapInfo.images[i].im),
eFrameRef_Read);
GetResourceManager()->MarkResourceFrameReferenced(
GetResID(swapInfo.images[i].userSwapImage), eFrameRef_Read);
if(DescriptorBuffers())
swapLayout = VK_IMAGE_LAYOUT_GENERAL;
}
else if(VRBackbufferRecord)
{
+17 -8
View File
@@ -979,9 +979,9 @@ DECLARE_REFLECTION_STRUCT(ImageInfo);
struct PresentInfo
{
VkQueue presentQueue;
VkQueue presentQueue = VK_NULL_HANDLE;
rdcarray<VkSemaphore> waitSemaphores;
uint32_t imageIndex;
uint32_t imageIndex = 0;
};
struct SwapchainInfo
@@ -996,16 +996,25 @@ struct SwapchainInfo
struct SwapImage
{
VkImage im;
// the handle returned to the user, whether real or fake
VkImage userSwapImage = VK_NULL_HANDLE;
// if we made a fake swap image then this is the real unwrapped swapchain image
VkImage unwrappedRealSwapImage = VK_NULL_HANDLE;
VkImageView view;
VkFramebuffer fb;
uint64_t fakeImageMemoryOffset = 0;
VkFence fence;
VkCommandBuffer cmd;
VkSemaphore overlaydone;
VkImageView view = VK_NULL_HANDLE;
VkFramebuffer fb = VK_NULL_HANDLE;
VkFence fence = VK_NULL_HANDLE;
VkCommandBuffer cmd = VK_NULL_HANDLE;
VkSemaphore overlaydone = VK_NULL_HANDLE;
};
rdcarray<SwapImage> images;
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
uint32_t imageMemoryIndex = ~0U;
uint64_t imageMemorySize = 0;
PresentInfo lastPresent;
};
@@ -4182,6 +4182,9 @@ void WrappedVulkan::vkCmdPipelineBarrier(
{
im[i] = pImageMemoryBarriers[i];
im[i].image = Unwrap(im[i].image);
SanitiseDescriptorBufferImageLayout(im[i].newLayout);
SanitiseDescriptorBufferImageLayout(im[i].newLayout);
}
SERIALISE_TIME_CALL(ObjDisp(commandBuffer)
@@ -4416,6 +4419,13 @@ void WrappedVulkan::vkCmdPipelineBarrier2(VkCommandBuffer commandBuffer,
byte *tempMem = GetTempMemory(GetNextPatchSize(pDependencyInfo));
VkDependencyInfo *unwrappedInfo = UnwrapStructAndChain(m_State, tempMem, pDependencyInfo);
for(uint32_t im = 0; im < unwrappedInfo->imageMemoryBarrierCount; im++)
{
VkImageMemoryBarrier2 &b = (VkImageMemoryBarrier2 &)unwrappedInfo->pImageMemoryBarriers[im];
SanitiseDescriptorBufferImageLayout(b.newLayout);
SanitiseDescriptorBufferImageLayout(b.newLayout);
}
SERIALISE_TIME_CALL(
ObjDisp(commandBuffer)->CmdPipelineBarrier2(Unwrap(commandBuffer), unwrappedInfo));
@@ -348,15 +348,18 @@ void WrappedVulkan::vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR obj,
for(size_t i = 0; i < info.images.size(); i++)
{
VkFramebuffer unwrappedFB = Unwrap(info.images[i].fb);
VkImage unwrappedImage = Unwrap(info.images[i].userSwapImage);
VkImageView unwrappedView = Unwrap(info.images[i].view);
VkSemaphore unwrappedSem = Unwrap(info.images[i].overlaydone);
VkFence unwrappedFence = Unwrap(info.images[i].fence);
GetResourceManager()->ReleaseWrappedResource(info.images[i].fb, true);
// note, image doesn't have to be destroyed, just untracked
GetResourceManager()->ReleaseWrappedResource(info.images[i].im, true);
GetResourceManager()->ReleaseWrappedResource(info.images[i].userSwapImage, true);
GetResourceManager()->ReleaseWrappedResource(info.images[i].view, true);
GetResourceManager()->ReleaseWrappedResource(info.images[i].overlaydone);
GetResourceManager()->ReleaseWrappedResource(info.images[i].fence);
// if we had a separate real image, then the unwrappedImage is a fake one we created and must be destroyed
if(info.images[i].unwrappedRealSwapImage != VK_NULL_HANDLE)
ObjDisp(device)->DestroyImage(Unwrap(device), unwrappedImage, NULL);
ObjDisp(device)->DestroyFramebuffer(Unwrap(device), unwrappedFB, NULL);
ObjDisp(device)->DestroyImageView(Unwrap(device), unwrappedView, NULL);
ObjDisp(device)->DestroySemaphore(Unwrap(device), unwrappedSem, NULL);
@@ -365,6 +368,12 @@ void WrappedVulkan::vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR obj,
// return the command buffers to the pool
AddFreeCommandBuffer(info.images[i].cmd);
}
if(info.imageMemory != VK_NULL_HANDLE)
{
ObjDisp(device)->FreeMemory(Unwrap(device), Unwrap(info.imageMemory), NULL);
GetResourceManager()->ReleaseWrappedResource(info.imageMemory, true);
}
}
VkSwapchainKHR unwrappedObj = Unwrap(obj);
@@ -1313,9 +1322,27 @@ bool WrappedVulkan::Serialise_vkCreateRenderPass(SerialiserType &ser, VkDevice d
VkResult WrappedVulkan::vkCreateRenderPass(VkDevice device, const VkRenderPassCreateInfo *pCreateInfo,
const VkAllocationCallbacks *, VkRenderPass *pRenderPass)
{
VkRenderPassCreateInfo patchedCreateInfo = *pCreateInfo;
// descriptor buffers intercepts all swapchain images during capture so we change any
// reference to PRESENT layout into GENERAL since that's what is valid for our image
if(DescriptorBuffers())
{
byte *tempMem = GetTempMemory(GetNextPatchSize(patchedCreateInfo.pNext));
CopyNextChainForPatching("VkRenderPassCreateInfo", tempMem,
(VkBaseInStructure *)&patchedCreateInfo);
for(uint32_t a = 0; a < patchedCreateInfo.attachmentCount; a++)
{
VkAttachmentDescription &att = (VkAttachmentDescription &)patchedCreateInfo.pAttachments[a];
SanitiseDescriptorBufferImageLayout(att.initialLayout);
SanitiseDescriptorBufferImageLayout(att.finalLayout);
}
}
VkResult ret;
SERIALISE_TIME_CALL(
ret = ObjDisp(device)->CreateRenderPass(Unwrap(device), pCreateInfo, NULL, pRenderPass));
SERIALISE_TIME_CALL(ret = ObjDisp(device)->CreateRenderPass(Unwrap(device), &patchedCreateInfo,
NULL, pRenderPass));
if(ret == VK_SUCCESS)
{
@@ -1573,6 +1600,24 @@ VkResult WrappedVulkan::vkCreateRenderPass2(VkDevice device,
const VkRenderPassCreateInfo2 *pCreateInfo,
const VkAllocationCallbacks *, VkRenderPass *pRenderPass)
{
VkRenderPassCreateInfo2 patchedCreateInfo = *pCreateInfo;
// descriptor buffers intercepts all swapchain images during capture so we change any
// reference to PRESENT layout into GENERAL since that's what is valid for our image
if(DescriptorBuffers())
{
byte *tempMem = GetTempMemory(GetNextPatchSize(patchedCreateInfo.pNext));
CopyNextChainForPatching("VkRenderPassCreateInfo2", tempMem,
(VkBaseInStructure *)&patchedCreateInfo);
for(uint32_t a = 0; a < patchedCreateInfo.attachmentCount; a++)
{
VkAttachmentDescription2 &att = (VkAttachmentDescription2 &)patchedCreateInfo.pAttachments[a];
SanitiseDescriptorBufferImageLayout(att.initialLayout);
SanitiseDescriptorBufferImageLayout(att.finalLayout);
}
}
VkResult ret;
SERIALISE_TIME_CALL(
ret = ObjDisp(device)->CreateRenderPass2(Unwrap(device), pCreateInfo, NULL, pRenderPass));
@@ -923,6 +923,14 @@ void WrappedVulkan::vkCmdWaitEvents(VkCommandBuffer commandBuffer, uint32_t even
{
im[i] = pImageMemoryBarriers[i];
im[i].image = Unwrap(im[i].image);
// descriptor buffers intercepts all swapchain images during capture so we change any
// reference to PRESENT layout into GENERAL since that's what is valid for our image
if(DescriptorBuffers())
{
SanitiseDescriptorBufferImageLayout(im[i].newLayout);
SanitiseDescriptorBufferImageLayout(im[i].newLayout);
}
}
SERIALISE_TIME_CALL(ObjDisp(commandBuffer)
@@ -1182,6 +1190,13 @@ void WrappedVulkan::vkCmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event,
byte *tempMem = GetTempMemory(GetNextPatchSize(pDependencyInfo));
VkDependencyInfo *unwrappedInfo = UnwrapStructAndChain(m_State, tempMem, pDependencyInfo);
for(uint32_t im = 0; im < unwrappedInfo->imageMemoryBarrierCount; im++)
{
VkImageMemoryBarrier2 &b = (VkImageMemoryBarrier2 &)unwrappedInfo->pImageMemoryBarriers[im];
SanitiseDescriptorBufferImageLayout(b.newLayout);
SanitiseDescriptorBufferImageLayout(b.newLayout);
}
SERIALISE_TIME_CALL(
ObjDisp(commandBuffer)->CmdSetEvent2(Unwrap(commandBuffer), Unwrap(event), unwrappedInfo));
@@ -1431,6 +1446,13 @@ void WrappedVulkan::vkCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eve
{
ev[i] = Unwrap(pEvents[i]);
depInfo[i] = *UnwrapStructAndChain(m_State, tempMem, &pDependencyInfos[i]);
for(uint32_t im = 0; im < depInfo[i].imageMemoryBarrierCount; im++)
{
VkImageMemoryBarrier2 &b = (VkImageMemoryBarrier2 &)depInfo[i].pImageMemoryBarriers[im];
SanitiseDescriptorBufferImageLayout(b.newLayout);
SanitiseDescriptorBufferImageLayout(b.newLayout);
}
}
SERIALISE_TIME_CALL(
+402 -82
View File
@@ -238,7 +238,8 @@ bool WrappedVulkan::Serialise_vkGetSwapchainImagesKHR(SerialiserType &ser, VkDev
RDCASSERT(SwapchainImageIndex < swapInfo.images.size(), SwapchainImageIndex,
swapInfo.images.size());
GetResourceManager()->AddLiveResource(SwapchainImage, swapInfo.images[SwapchainImageIndex].im);
GetResourceManager()->AddLiveResource(SwapchainImage,
swapInfo.images[SwapchainImageIndex].userSwapImage);
AddResource(SwapchainImage, ResourceType::SwapchainImage, "Swapchain Image");
DerivedResource(device, SwapchainImage);
@@ -248,7 +249,7 @@ bool WrappedVulkan::Serialise_vkGetSwapchainImagesKHR(SerialiserType &ser, VkDev
GetResourceDesc(Swapchain).derivedResources.push_back(SwapchainImage);
GetResourceDesc(SwapchainImage).parentResources.push_back(Swapchain);
m_CreationInfo.m_Image[GetResID(swapInfo.images[SwapchainImageIndex].im)] =
m_CreationInfo.m_Image[GetResID(swapInfo.images[SwapchainImageIndex].userSwapImage)] =
m_CreationInfo.m_Image[Swapchain];
}
@@ -267,23 +268,45 @@ VkResult WrappedVulkan::vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR
SERIALISE_TIME_CALL(ret = ObjDisp(device)->GetSwapchainImagesKHR(
Unwrap(device), Unwrap(swapchain), pCount, pSwapchainImages));
// during swapchain create this function is called once to initialise and wrap the images,
// thereafter we just return the already wrapped image handles. When descriptor buffers are
// enabled we are wrapping fake images we created to be able to descriptor-ise as well as
// initialising the real image handles
if(pSwapchainImages && IsCaptureMode(m_State))
{
uint32_t numImages = *pCount;
VkResourceRecord *swapRecord = GetRecord(swapchain);
const bool fakeBackbuffers = AccelerationStructures() || DescriptorBuffers();
for(uint32_t i = 0; i < numImages; i++)
{
// these were all wrapped and serialised on swapchain create - we just have to
// return the wrapped image in that case
if(swapRecord->swapInfo->images[i].im != VK_NULL_HANDLE)
// for descriptor buffers or AS, if we already have the unwrapped real image then we've fully
// initialised so we can return the user swap image
if(fakeBackbuffers && swapRecord->swapInfo->images[i].unwrappedRealSwapImage != VK_NULL_HANDLE)
{
pSwapchainImages[i] = swapRecord->swapInfo->images[i].im;
pSwapchainImages[i] = swapRecord->swapInfo->images[i].userSwapImage;
}
// if we're not using fake backbuffers and we've fetched & wrapped the user swap image we
// can also return that too.
else if(!fakeBackbuffers && swapRecord->swapInfo->images[i].userSwapImage != VK_NULL_HANDLE)
{
pSwapchainImages[i] = swapRecord->swapInfo->images[i].userSwapImage;
}
else
{
ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), pSwapchainImages[i]);
VkImage imageToWrap = pSwapchainImages[i];
// if using fake backbuffers, we store the real swapchain image and wrap our fake
if(fakeBackbuffers)
{
swapRecord->swapInfo->images[i].unwrappedRealSwapImage = pSwapchainImages[i];
imageToWrap = swapRecord->swapInfo->images[i].userSwapImage;
RDCASSERT(imageToWrap != VK_NULL_HANDLE);
}
ResourceId id = GetResourceManager()->WrapResource(Unwrap(device), imageToWrap);
Chunk *chunk = NULL;
@@ -291,12 +314,12 @@ VkResult WrappedVulkan::vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkGetSwapchainImagesKHR);
Serialise_vkGetSwapchainImagesKHR(ser, device, swapchain, &i, &pSwapchainImages[i]);
Serialise_vkGetSwapchainImagesKHR(ser, device, swapchain, &i, &imageToWrap);
chunk = scope.Get();
}
VkResourceRecord *record = GetResourceManager()->AddResourceRecord(pSwapchainImages[i]);
VkResourceRecord *record = GetResourceManager()->AddResourceRecord(imageToWrap);
VkResourceRecord *swaprecord = GetRecord(swapchain);
record->InternalResource = true;
@@ -312,6 +335,8 @@ VkResult WrappedVulkan::vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR
// swapchain, if the image is referenced the swapchain (and thus all the getimages) will be
// included.
swaprecord->AddChunk(chunk);
pSwapchainImages[i] = imageToWrap;
}
}
}
@@ -351,6 +376,51 @@ bool WrappedVulkan::Serialise_vkCreateSwapchainKHR(SerialiserType &ser, VkDevice
SERIALISE_ELEMENT(NumImages);
rdcarray<OpaqueDataForSerialising> opaqueDataFetch;
rdcarray<VkOpaqueCaptureDescriptorDataCreateInfoEXT> opaqueData;
rdcarray<uint64_t> imageMemOffsets;
uint64_t opaqueMemAddress = 0;
const bool fakeBackbuffers = AccelerationStructures() || DescriptorBuffers();
if(IsCaptureMode(m_State) && fakeBackbuffers)
{
if(DescriptorBuffers())
{
opaqueDataFetch.resize(NumImages);
opaqueData.resize(NumImages);
}
imageMemOffsets.resize(NumImages);
for(uint32_t i = 0; i < NumImages; i++)
{
if(DescriptorBuffers())
{
opaqueDataFetch[i].fillUnwrapped(device,
GetRecord(*pSwapChain)->swapInfo->images[i].userSwapImage,
m_DescriptorBufferProperties);
opaqueData[i] = opaqueDataFetch[i];
}
imageMemOffsets[i] = GetRecord(*pSwapChain)->swapInfo->images[i].fakeImageMemoryOffset;
}
VkDeviceMemoryOpaqueCaptureAddressInfo getInfo = {
VK_STRUCTURE_TYPE_DEVICE_MEMORY_OPAQUE_CAPTURE_ADDRESS_INFO,
NULL,
Unwrap(GetRecord(*pSwapChain)->swapInfo->imageMemory),
};
opaqueMemAddress = ObjDisp(device)->GetDeviceMemoryOpaqueCaptureAddress(Unwrap(device), &getInfo);
}
if(ser.VersionAtLeast(0x17))
{
SERIALISE_ELEMENT(opaqueData).Hidden();
SERIALISE_ELEMENT(imageMemOffsets).Hidden();
SERIALISE_ELEMENT(opaqueMemAddress).Hidden();
}
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading())
@@ -374,7 +444,7 @@ bool WrappedVulkan::Serialise_vkCreateSwapchainKHR(SerialiserType &ser, VkDevice
if(CreateInfo.flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR)
imageFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
const VkImageCreateInfo imInfo = {
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
imageFlags,
@@ -400,12 +470,21 @@ bool WrappedVulkan::Serialise_vkCreateSwapchainKHR(SerialiserType &ser, VkDevice
queueFamiles[q] = m_QueueRemapping[queueFamiles[q]][0].family;
}
uint32_t memIndices = ~0U;
uint64_t memSize = 0;
rdcarray<VkImage> ims;
ims.resize(NumImages);
for(uint32_t i = 0; i < NumImages; i++)
{
VkDeviceMemory mem = VK_NULL_HANDLE;
VkImage im = VK_NULL_HANDLE;
if(DescriptorBuffers())
{
RDCASSERT(i < opaqueData.size(), i, opaqueData.size());
imInfo.pNext = &opaqueData[i];
}
VkResult vkr = ObjDisp(device)->CreateImage(Unwrap(device), &imInfo, NULL, &im);
VkResult vkr = ObjDisp(device)->CreateImage(Unwrap(device), &imInfo, NULL, &ims[i]);
CHECK_VKR(this, vkr);
if(vkr != VK_SUCCESS)
@@ -416,49 +495,80 @@ bool WrappedVulkan::Serialise_vkCreateSwapchainKHR(SerialiserType &ser, VkDevice
return false;
}
ResourceId liveId = GetResourceManager()->WrapResource(Unwrap(device), im);
ResourceId liveId = GetResourceManager()->WrapResource(Unwrap(device), ims[i]);
VkMemoryRequirements mrq = {0};
ObjDisp(device)->GetImageMemoryRequirements(Unwrap(device), Unwrap(ims[i]), &mrq);
ObjDisp(device)->GetImageMemoryRequirements(Unwrap(device), Unwrap(im), &mrq);
memSize = AlignUp(memSize, mrq.alignment);
if(i < imageMemOffsets.size())
{
RDCASSERT(imageMemOffsets[i] == memSize);
}
else
{
RDCASSERT(imageMemOffsets.size() == i);
imageMemOffsets.push_back(memSize);
}
memSize += mrq.size;
memIndices &= mrq.memoryTypeBits;
}
VkDeviceMemory mem = VK_NULL_HANDLE;
ResourceId memid;
for(uint32_t i = 0; i < NumImages; i++)
{
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
NULL,
mrq.size,
GetGPULocalMemoryIndex(mrq.memoryTypeBits),
memSize,
GetGPULocalMemoryIndex(memIndices),
};
VkMemoryAllocateFlagsInfo memFlags = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO};
if(DescriptorBuffers())
VkMemoryOpaqueCaptureAddressAllocateInfo memoryDeviceAddress = {
VK_STRUCTURE_TYPE_MEMORY_OPAQUE_CAPTURE_ADDRESS_ALLOCATE_INFO,
};
if(fakeBackbuffers)
{
allocInfo.pNext = &memFlags;
memFlags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT |
VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT;
memFlags.pNext = &memoryDeviceAddress;
memoryDeviceAddress.opaqueCaptureAddress = opaqueMemAddress;
}
vkr = ObjDisp(device)->AllocateMemory(Unwrap(device), &allocInfo, NULL, &mem);
CHECK_VKR(this, vkr);
if(vkr != VK_SUCCESS)
VkResult vkr;
if(mem == VK_NULL_HANDLE)
{
SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIReplayFailed,
"Failed allocating fake backbuffer texture memory, VkResult: %s",
ToStr(vkr).c_str());
return false;
vkr = ObjDisp(device)->AllocateMemory(Unwrap(device), &allocInfo, NULL, &mem);
CHECK_VKR(this, vkr);
if(vkr != VK_SUCCESS)
{
SET_ERROR_RESULT(m_FailedReplayResult, ResultCode::APIReplayFailed,
"Failed allocating fake backbuffer texture memory, VkResult: %s",
ToStr(vkr).c_str());
return false;
}
memid = GetResourceManager()->WrapResource(Unwrap(device), mem);
// register as a live-only resource, so it is cleaned up properly
GetResourceManager()->AddLiveResource(memid, mem);
}
ResourceId memid = GetResourceManager()->WrapResource(Unwrap(device), mem);
// register as a live-only resource, so it is cleaned up properly
GetResourceManager()->AddLiveResource(memid, mem);
vkr = ObjDisp(device)->BindImageMemory(Unwrap(device), Unwrap(im), Unwrap(mem), 0);
vkr = ObjDisp(device)->BindImageMemory(Unwrap(device), Unwrap(ims[i]), Unwrap(mem),
imageMemOffsets[i]);
CHECK_VKR(this, vkr);
// image live ID will be assigned separately in Serialise_vkGetSwapChainInfoWSI
// memory doesn't have a live ID
swapinfo.images[i].im = im;
swapinfo.images[i].userSwapImage = ims[i];
// fill out image info so we track resource state barriers
// sneaky-cheeky use of the swapchain's ID here (it's not a live ID because
@@ -478,11 +588,11 @@ bool WrappedVulkan::Serialise_vkCreateSwapchainKHR(SerialiserType &ser, VkDevice
iminfo.cube = false;
iminfo.samples = VK_SAMPLE_COUNT_1_BIT;
m_CreationInfo.m_Names[liveId] = StringFormat::Fmt("Presentable Image %u", i);
m_CreationInfo.m_Names[GetResID(ims[i])] = StringFormat::Fmt("Presentable Image %u", i);
{
LockedImageStateRef state =
InsertImageState(im, liveId, ImageInfo(swapinfo.imageInfo), eFrameRef_Unknown);
LockedImageStateRef state = InsertImageState(
ims[i], GetResID(ims[i]), ImageInfo(swapinfo.imageInfo), eFrameRef_Unknown);
state->isMemoryBound = true;
}
}
@@ -501,17 +611,7 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device,
{
Chunk *chunk = NULL;
{
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkCreateSwapchainKHR);
Serialise_vkCreateSwapchainKHR(ser, device, pCreateInfo, NULL, pSwapChain);
chunk = scope.Get();
}
VkResourceRecord *record = GetResourceManager()->AddResourceRecord(*pSwapChain);
record->AddChunk(chunk);
record->swapInfo = new SwapchainInfo();
SwapchainInfo &swapInfo = *record->swapInfo;
@@ -530,7 +630,123 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device,
swapInfo.shared = (pCreateInfo->presentMode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
pCreateInfo->presentMode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
VkResult vkr = VK_SUCCESS;
uint32_t numSwapImages = 0;
VkResult vkr;
vkr = ObjDisp(device)->GetSwapchainImagesKHR(Unwrap(device), Unwrap(*pSwapChain),
&numSwapImages, NULL);
CHECK_VKR(this, vkr);
swapInfo.images.resize(numSwapImages);
const bool fakeBackbuffers = AccelerationStructures() || DescriptorBuffers();
// create fake images and memory if we're using descriptor buffers so that the user's handles
// that views and descriptors are created from are replayable
if(fakeBackbuffers)
{
VkImageCreateFlags imageFlags = DefaultImageCreateFlags();
if(pCreateInfo->flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR)
imageFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
imageFlags,
VK_IMAGE_TYPE_2D,
pCreateInfo->imageFormat,
{pCreateInfo->imageExtent.width, pCreateInfo->imageExtent.height, 1},
1,
pCreateInfo->imageArrayLayers,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
// we add SAMPLED_BIT to help hint that views created should be capture/replayable (as
// this could be used for a descriptor)
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT | pCreateInfo->imageUsage,
pCreateInfo->imageSharingMode,
pCreateInfo->queueFamilyIndexCount,
pCreateInfo->pQueueFamilyIndices,
VK_IMAGE_LAYOUT_UNDEFINED,
};
if(pCreateInfo->imageSharingMode == VK_SHARING_MODE_CONCURRENT)
{
uint32_t *queueFamiles = (uint32_t *)pCreateInfo->pQueueFamilyIndices;
for(uint32_t q = 0; q < pCreateInfo->queueFamilyIndexCount; q++)
queueFamiles[q] = m_QueueRemapping[queueFamiles[q]][0].family;
}
uint64_t memOffs = 0;
uint32_t memIndices = ~0U;
for(uint32_t i = 0; i < numSwapImages; i++)
{
vkr = ObjDisp(device)->CreateImage(Unwrap(device), &imInfo, NULL,
&swapInfo.images[i].userSwapImage);
CHECK_VKR(this, vkr);
if(vkr != VK_SUCCESS)
{
RDCERR("Failed creating fake backbuffer textures, VkResult: %s", ToStr(vkr).c_str());
return;
}
// we don't wrap the image, vkGetSwapchainImagesKHR below will do that
VkMemoryRequirements mrq = {0};
ObjDisp(device)->GetImageMemoryRequirements(Unwrap(device),
swapInfo.images[i].userSwapImage, &mrq);
memOffs = AlignUp(memOffs, mrq.alignment);
swapInfo.images[i].fakeImageMemoryOffset = memOffs;
memOffs += mrq.size;
memIndices &= mrq.memoryTypeBits;
}
VkMemoryAllocateFlagsInfo memFlags = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
NULL,
VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT | VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT,
};
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
&memFlags,
memOffs,
GetGPULocalMemoryIndex(memIndices),
};
vkr = ObjDisp(device)->AllocateMemory(Unwrap(device), &allocInfo, NULL, &swapInfo.imageMemory);
CHECK_VKR(this, vkr);
if(vkr != VK_SUCCESS)
{
RDCERR("Failed allocating fake backbuffer texture memory, VkResult: %s", ToStr(vkr).c_str());
return;
}
GetResourceManager()->WrapResource(Unwrap(device), swapInfo.imageMemory);
GetResourceManager()->SetInternalResource(GetResID(swapInfo.imageMemory));
for(uint32_t i = 0; i < numSwapImages; i++)
{
vkr = ObjDisp(device)->BindImageMemory(Unwrap(device), swapInfo.images[i].userSwapImage,
Unwrap(swapInfo.imageMemory),
swapInfo.images[i].fakeImageMemoryOffset);
CHECK_VKR(this, vkr);
}
}
{
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CHUNK(VulkanChunk::vkCreateSwapchainKHR);
Serialise_vkCreateSwapchainKHR(ser, device, pCreateInfo, NULL, pSwapChain);
chunk = scope.Get();
}
record->AddChunk(chunk);
const VkDevDispatchTable *vt = ObjDisp(device);
@@ -579,26 +795,11 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device,
// serialise out the swap chain images
{
uint32_t numSwapImages;
vkr = vt->GetSwapchainImagesKHR(Unwrap(device), Unwrap(*pSwapChain), &numSwapImages, NULL);
CHECK_VKR(this, vkr);
swapInfo.lastPresent.imageIndex = 0;
swapInfo.lastPresent.presentQueue = VK_NULL_HANDLE;
swapInfo.lastPresent.waitSemaphores.clear();
swapInfo.images.resize(numSwapImages);
for(uint32_t i = 0; i < numSwapImages; i++)
{
swapInfo.images[i].im = VK_NULL_HANDLE;
swapInfo.images[i].view = VK_NULL_HANDLE;
swapInfo.images[i].fb = VK_NULL_HANDLE;
}
VkImage *images = new VkImage[numSwapImages];
rdcarray<VkImage> images;
images.resize(numSwapImages);
// go through our own function so we assign these images IDs
vkr = vkGetSwapchainImagesKHR(device, *pSwapChain, &numSwapImages, images);
vkr = vkGetSwapchainImagesKHR(device, *pSwapChain, &numSwapImages, images.data());
CHECK_VKR(this, vkr);
for(uint32_t i = 0; i < numSwapImages; i++)
@@ -606,7 +807,7 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device,
SwapchainInfo::SwapImage &swapImInfo = swapInfo.images[i];
// memory doesn't exist for genuine WSI created images
swapImInfo.im = images[i];
swapImInfo.userSwapImage = images[i];
ResourceId imid = GetResID(images[i]);
@@ -649,12 +850,16 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device,
GetResourceManager()->SetInternalResource(GetResID(swapImInfo.overlaydone));
}
VkImage renderImage = Unwrap(images[i]);
if(swapImInfo.unwrappedRealSwapImage != VK_NULL_HANDLE)
renderImage = swapImInfo.unwrappedRealSwapImage;
{
VkImageViewCreateInfo info = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
DefaultImageViewCreateFlags(),
Unwrap(images[i]),
renderImage,
VK_IMAGE_VIEW_TYPE_2D,
pCreateInfo->imageFormat,
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -687,8 +892,6 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device,
GetResourceManager()->SetInternalResource(GetResID(swapImInfo.fb));
}
}
SAFE_DELETE_ARRAY(images);
}
}
else
@@ -756,7 +959,7 @@ bool WrappedVulkan::Serialise_vkQueuePresentKHR(SerialiserType &ser, VkQueue que
const bool activeWindow = RenderDoc::Inst().IsActiveWindow(devWnd);
if(activeWindow || PresentedImage == ResourceId())
PresentedImage = GetResID(swapInfo.images[pPresentInfo->pImageIndices[i]].im);
PresentedImage = GetResID(swapInfo.images[pPresentInfo->pImageIndices[i]].userSwapImage);
if(activeWindow)
break;
@@ -971,10 +1174,13 @@ void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresen
{
uint32_t overlay = RenderDoc::Inst().GetOverlayBits();
const bool fakeBackbuffers = AccelerationStructures() || DescriptorBuffers();
if(overlay & eRENDERDOC_Overlay_Enabled)
{
VkRenderPass rp = swapInfo.rp;
VkImage im = swapInfo.images[imgIndex].im;
VkImage unwrappedRealSwapImage = swapInfo.images[imgIndex].unwrappedRealSwapImage;
VkImage userSwapImage = swapInfo.images[imgIndex].userSwapImage;
VkFramebuffer fb = swapInfo.images[imgIndex].fb;
VkCommandBuffer cmd = swapInfo.images[imgIndex].cmd;
VkFence imfence = swapInfo.images[imgIndex].fence;
@@ -1010,29 +1216,55 @@ void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresen
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
CHECK_VKR(this, vkr);
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
if(swapInfo.shared)
presentLayout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
VkImageMemoryBarrier bbBarrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
0,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
presentLayout,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
swapQueueIndex,
m_QueueFamilyIdx,
Unwrap(im),
Unwrap(userSwapImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1},
};
if(swapInfo.shared)
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
if(swapInfo.concurrent)
bbBarrier.srcQueueFamilyIndex = bbBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
bbBarrier.srcAccessMask = VK_ACCESS_ALL_READ_BITS;
bbBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
bbBarrier.srcAccessMask = VK_ACCESS_ALL_READ_BITS | VK_ACCESS_ALL_WRITE_BITS;
DoPipelineBarrier(cmd, 1, &bbBarrier);
// if using descriptor buffers, we barrier both images - real and fake backbuffer to be ready
// to copy from fake to real
if(fakeBackbuffers)
{
bbBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
// we fully overwrite with a copy, so we can transition from undefined
bbBarrier.image = unwrappedRealSwapImage;
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
bbBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
DoPipelineBarrier(cmd, 1, &bbBarrier);
bbBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
bbBarrier.image = Unwrap(userSwapImage);
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
bbBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
DoPipelineBarrier(cmd, 1, &bbBarrier);
}
else
{
bbBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
DoPipelineBarrier(cmd, 1, &bbBarrier);
}
rdcarray<VkPipelineStageFlags> waitStage;
waitStage.fill(unwrappedWaitSems.size(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
@@ -1067,7 +1299,28 @@ void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresen
vkr = vt->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
CHECK_VKR(this, vkr);
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
// if using fake backbuffers, we barrier both images - real and fake backbuffer
if(fakeBackbuffers)
{
bbBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
bbBarrier.image = unwrappedRealSwapImage;
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
bbBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
bbBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
bbBarrier.image = Unwrap(userSwapImage);
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
bbBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
}
else
{
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
}
ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));
@@ -1107,6 +1360,30 @@ void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresen
overlayText += StringFormat::Fmt("\nCapture failed: %s",
ResultDetails(m_LastCaptureError).Message().c_str());
if(fakeBackbuffers)
{
VkImageCopy region = {};
region.dstSubresource.aspectMask = region.srcSubresource.aspectMask =
VK_IMAGE_ASPECT_COLOR_BIT;
region.dstSubresource.layerCount = region.srcSubresource.layerCount = 1;
region.extent = swapInfo.imageInfo.extent;
ObjDisp(cmd)->CmdCopyImage(Unwrap(cmd), Unwrap(userSwapImage),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, unwrappedRealSwapImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkImageMemoryBarrier tmpBarrier = bbBarrier;
// remove any queue family transfer
tmpBarrier.srcQueueFamilyIndex = tmpBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
// now do what the barrier above would have done, put the real swapchain in color attachment ready to render to
tmpBarrier.image = unwrappedRealSwapImage;
tmpBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
tmpBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
tmpBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
tmpBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
DoPipelineBarrier(cmd, 1, &tmpBarrier);
}
if(!overlayText.empty())
{
m_TextRenderer->BeginText(textstate);
@@ -1118,10 +1395,31 @@ void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresen
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;
DoPipelineBarrier(textstate.cmd, 1, &bbBarrier);
if(fakeBackbuffers)
{
bbBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
bbBarrier.image = unwrappedRealSwapImage;
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
bbBarrier.newLayout = presentLayout;
DoPipelineBarrier(textstate.cmd, 1, &bbBarrier);
bbBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
bbBarrier.image = Unwrap(userSwapImage);
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
bbBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
DoPipelineBarrier(textstate.cmd, 1, &bbBarrier);
}
else
{
bbBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
DoPipelineBarrier(textstate.cmd, 1, &bbBarrier);
}
ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
@@ -1141,7 +1439,29 @@ void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresen
vkr = vt->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
CHECK_VKR(this, vkr);
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
if(fakeBackbuffers)
{
bbBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
bbBarrier.image = unwrappedRealSwapImage;
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
bbBarrier.newLayout = presentLayout;
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
bbBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
bbBarrier.image = Unwrap(userSwapImage);
bbBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
bbBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
}
else
{
bbBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
DoPipelineBarrier(extQCmd, 1, &bbBarrier);
}
ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));