Modernize and fix frame sync in QVulkanWindow

Follow 24d62ffd720b5bec5d07b07b8d2c9dda7635f3c0 for QVulkanWindow.
The Vulkan backend of QRhi has diverged from QVulkanWindow even
before that recent fix, having many changes over the years that
are not present in QVulkanWindow.

Introduce some of these now, to make things work with the newer
validation layer bundled with the Vulkan 1.4 SDKs.

Task-number: QTBUG-135645
Change-Id: Ic630e9a57c344843c171871b5a0386ed18027b22
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2025-04-09 15:00:19 +02:00 committed by Laszlo Agocs
parent 53756dcfbc
commit 8fc3bc67df
2 changed files with 55 additions and 76 deletions

View File

@ -1196,13 +1196,6 @@ void QVulkanWindowPrivate::recreateSwapChain()
return;
}
err = devFuncs->vkCreateFence(dev, &fenceInfo, nullptr, &image.cmdFence);
if (err != VK_SUCCESS) {
qWarning("QVulkanWindow: Failed to create command buffer fence: %d", err);
return;
}
image.cmdFenceWaitable = true; // fence was created in signaled state
VkImageView views[3] = { image.imageView,
dsView,
msaa ? image.msaaImageView : VK_NULL_HANDLE };
@ -1270,13 +1263,17 @@ void QVulkanWindowPrivate::recreateSwapChain()
frame.imageAcquired = false;
frame.imageSemWaitable = false;
devFuncs->vkCreateFence(dev, &fenceInfo, nullptr, &frame.fence);
frame.fenceWaitable = true; // fence was created in signaled state
devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem);
if (gfxQueueFamilyIdx != presQueueFamilyIdx)
devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.presTransSem);
err = devFuncs->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence);
if (err != VK_SUCCESS) {
qWarning("QVulkanWindow: Failed to create command buffer fence: %d", err);
return;
}
frame.cmdFenceWaitable = true; // fence was created in signaled state
}
currentFrame = 0;
@ -1432,12 +1429,9 @@ void QVulkanWindowPrivate::releaseSwapChain()
for (int i = 0; i < frameLag; ++i) {
FrameResources &frame(frameRes[i]);
if (frame.fence) {
if (frame.fenceWaitable)
devFuncs->vkWaitForFences(dev, 1, &frame.fence, VK_TRUE, UINT64_MAX);
devFuncs->vkDestroyFence(dev, frame.fence, nullptr);
frame.fence = VK_NULL_HANDLE;
frame.fenceWaitable = false;
if (frame.cmdBuf) {
devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
frame.cmdBuf = VK_NULL_HANDLE;
}
if (frame.imageSem) {
devFuncs->vkDestroySemaphore(dev, frame.imageSem, nullptr);
@ -1451,17 +1445,17 @@ void QVulkanWindowPrivate::releaseSwapChain()
devFuncs->vkDestroySemaphore(dev, frame.presTransSem, nullptr);
frame.presTransSem = VK_NULL_HANDLE;
}
if (frame.cmdFence) {
if (frame.cmdFenceWaitable)
devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
devFuncs->vkDestroyFence(dev, frame.cmdFence, nullptr);
frame.cmdFence = VK_NULL_HANDLE;
frame.cmdFenceWaitable = false;
}
}
for (int i = 0; i < swapChainBufferCount; ++i) {
ImageResources &image(imageRes[i]);
if (image.cmdFence) {
if (image.cmdFenceWaitable)
devFuncs->vkWaitForFences(dev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
devFuncs->vkDestroyFence(dev, image.cmdFence, nullptr);
image.cmdFence = VK_NULL_HANDLE;
image.cmdFenceWaitable = false;
}
if (image.fb) {
devFuncs->vkDestroyFramebuffer(dev, image.fb, nullptr);
image.fb = VK_NULL_HANDLE;
@ -1470,10 +1464,6 @@ void QVulkanWindowPrivate::releaseSwapChain()
devFuncs->vkDestroyImageView(dev, image.imageView, nullptr);
image.imageView = VK_NULL_HANDLE;
}
if (image.cmdBuf) {
devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &image.cmdBuf);
image.cmdBuf = VK_NULL_HANDLE;
}
if (image.presTransCmdBuf) {
devFuncs->vkFreeCommandBuffers(dev, presCmdPool, 1, &image.presTransCmdBuf);
image.presTransCmdBuf = VK_NULL_HANDLE;
@ -1920,24 +1910,21 @@ void QVulkanWindowPrivate::beginFrame()
return;
}
// wait if we are too far ahead
FrameResources &frame(frameRes[currentFrame]);
if (frame.cmdFenceWaitable) {
devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
devFuncs->vkResetFences(dev, 1, &frame.cmdFence);
frame.cmdFenceWaitable = false;
}
// move on to next swapchain image
if (!frame.imageAcquired) {
// Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate
// (note that we are using FIFO mode -> vsync)
if (frame.fenceWaitable) {
devFuncs->vkWaitForFences(dev, 1, &frame.fence, VK_TRUE, UINT64_MAX);
devFuncs->vkResetFences(dev, 1, &frame.fence);
frame.fenceWaitable = false;
}
// move on to next swapchain image
VkResult err = vkAcquireNextImageKHR(dev, swapChain, UINT64_MAX,
frame.imageSem, frame.fence, &currentImage);
frame.imageSem, VK_NULL_HANDLE, &currentImage);
if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
frame.imageSemWaitable = true;
frame.imageAcquired = true;
frame.fenceWaitable = true;
} else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
q->requestUpdate();
@ -1950,23 +1937,15 @@ void QVulkanWindowPrivate::beginFrame()
}
}
// make sure the previous draw for the same image has finished
ImageResources &image(imageRes[currentImage]);
if (image.cmdFenceWaitable) {
devFuncs->vkWaitForFences(dev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
devFuncs->vkResetFences(dev, 1, &image.cmdFence);
image.cmdFenceWaitable = false;
}
// build new draw command buffer
if (image.cmdBuf) {
devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &image.cmdBuf);
image.cmdBuf = nullptr;
if (frame.cmdBuf) {
devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
frame.cmdBuf = nullptr;
}
VkCommandBufferAllocateInfo cmdBufInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
VkResult err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &image.cmdBuf);
VkResult err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &frame.cmdBuf);
if (err != VK_SUCCESS) {
if (!checkDeviceLost(err))
qWarning("QVulkanWindow: Failed to allocate frame command buffer: %d", err);
@ -1975,7 +1954,7 @@ void QVulkanWindowPrivate::beginFrame()
VkCommandBufferBeginInfo cmdBufBeginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, 0, nullptr };
err = devFuncs->vkBeginCommandBuffer(image.cmdBuf, &cmdBufBeginInfo);
err = devFuncs->vkBeginCommandBuffer(frame.cmdBuf, &cmdBufBeginInfo);
if (err != VK_SUCCESS) {
if (!checkDeviceLost(err))
qWarning("QVulkanWindow: Failed to begin frame command buffer: %d", err);
@ -1985,6 +1964,7 @@ void QVulkanWindowPrivate::beginFrame()
if (frameGrabbing)
frameGrabTargetImage = QImage(swapChainImageSize, QImage::Format_RGBA8888); // the format is as documented
ImageResources &image(imageRes[currentImage]);
if (renderer) {
framePending = true;
renderer->startNextFrame();
@ -2006,8 +1986,8 @@ void QVulkanWindowPrivate::beginFrame()
rpBeginInfo.renderArea.extent.height = swapChainImageSize.height();
rpBeginInfo.clearValueCount = sampleCount > VK_SAMPLE_COUNT_1_BIT ? 3 : 2;
rpBeginInfo.pClearValues = clearValues;
devFuncs->vkCmdBeginRenderPass(image.cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
devFuncs->vkCmdEndRenderPass(image.cmdBuf);
devFuncs->vkCmdBeginRenderPass(frame.cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
devFuncs->vkCmdEndRenderPass(frame.cmdBuf);
endFrame();
}
@ -2033,7 +2013,7 @@ void QVulkanWindowPrivate::endFrame()
presTrans.image = image.image;
presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0, 0, nullptr, 0, nullptr,
@ -2044,7 +2024,7 @@ void QVulkanWindowPrivate::endFrame()
if (frameGrabbing)
addReadback();
VkResult err = devFuncs->vkEndCommandBuffer(image.cmdBuf);
VkResult err = devFuncs->vkEndCommandBuffer(frame.cmdBuf);
if (err != VK_SUCCESS) {
if (!checkDeviceLost(err))
qWarning("QVulkanWindow: Failed to end frame command buffer: %d", err);
@ -2056,7 +2036,7 @@ void QVulkanWindowPrivate::endFrame()
memset(&submitInfo, 0, sizeof(submitInfo));
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &image.cmdBuf;
submitInfo.pCommandBuffers = &frame.cmdBuf;
if (frame.imageSemWaitable) {
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &frame.imageSem;
@ -2068,12 +2048,12 @@ void QVulkanWindowPrivate::endFrame()
VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
submitInfo.pWaitDstStageMask = &psf;
Q_ASSERT(!image.cmdFenceWaitable);
Q_ASSERT(!frame.cmdFenceWaitable);
err = devFuncs->vkQueueSubmit(gfxQueue, 1, &submitInfo, image.cmdFence);
err = devFuncs->vkQueueSubmit(gfxQueue, 1, &submitInfo, frame.cmdFence);
if (err == VK_SUCCESS) {
frame.imageSemWaitable = false;
image.cmdFenceWaitable = true;
frame.cmdFenceWaitable = true;
} else {
if (!checkDeviceLost(err))
qWarning("QVulkanWindow: Failed to submit to graphics queue: %d", err);
@ -2228,6 +2208,7 @@ void QVulkanWindowPrivate::addReadback()
return;
}
FrameResources &frame(frameRes[currentFrame]);
ImageResources &image(imageRes[currentImage]);
VkImageMemoryBarrier barrier;
@ -2242,7 +2223,7 @@ void QVulkanWindowPrivate::addReadback()
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.image = image.image;
devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr,
@ -2254,7 +2235,7 @@ void QVulkanWindowPrivate::addReadback()
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.image = frameGrabImage;
devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr,
@ -2268,7 +2249,7 @@ void QVulkanWindowPrivate::addReadback()
copyInfo.extent.height = frameGrabTargetImage.height();
copyInfo.extent.depth = 1;
devFuncs->vkCmdCopyImage(image.cmdBuf, image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
devFuncs->vkCmdCopyImage(frame.cmdBuf, image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
frameGrabImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyInfo);
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
@ -2277,7 +2258,7 @@ void QVulkanWindowPrivate::addReadback()
barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
barrier.image = frameGrabImage;
devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_HOST_BIT,
0, 0, nullptr, 0, nullptr,
@ -2286,14 +2267,14 @@ void QVulkanWindowPrivate::addReadback()
void QVulkanWindowPrivate::finishBlockingReadback()
{
ImageResources &image(imageRes[currentImage]);
// Block until the current frame is done. Normally this wait would only be
// done in current + concurrentFrameCount().
devFuncs->vkWaitForFences(dev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
devFuncs->vkResetFences(dev, 1, &image.cmdFence);
// will reuse the same image for the next "real" frame, do not wait then
image.cmdFenceWaitable = false;
FrameResources &frame(frameRes[currentFrame]);
if (frame.cmdFenceWaitable) {
devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
devFuncs->vkResetFences(dev, 1, &frame.cmdFence);
frame.cmdFenceWaitable = false;
}
VkImageSubresource subres = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0 };
VkSubresourceLayout layout;
@ -2523,7 +2504,7 @@ QSize QVulkanWindow::swapChainImageSize() const
}
/*!
Returns The active command buffer for the current swap chain image.
Returns The active command buffer for the current swap chain frame.
Implementations of QVulkanWindowRenderer::startNextFrame() are expected to
add commands to this command buffer.
@ -2537,7 +2518,7 @@ VkCommandBuffer QVulkanWindow::currentCommandBuffer() const
qWarning("QVulkanWindow: Attempted to call currentCommandBuffer() without an active frame");
return VK_NULL_HANDLE;
}
return d->imageRes[d->currentImage].cmdBuf;
return d->frameRes[d->currentFrame].cmdBuf;
}
/*!

View File

@ -109,9 +109,6 @@ public:
struct ImageResources {
VkImage image = VK_NULL_HANDLE;
VkImageView imageView = VK_NULL_HANDLE;
VkCommandBuffer cmdBuf = VK_NULL_HANDLE;
VkFence cmdFence = VK_NULL_HANDLE;
bool cmdFenceWaitable = false;
VkFramebuffer fb = VK_NULL_HANDLE;
VkCommandBuffer presTransCmdBuf = VK_NULL_HANDLE;
VkImage msaaImage = VK_NULL_HANDLE;
@ -123,13 +120,14 @@ public:
uint32_t currentImage;
struct FrameResources {
VkFence fence = VK_NULL_HANDLE;
bool fenceWaitable = false;
VkSemaphore imageSem = VK_NULL_HANDLE;
VkSemaphore drawSem = VK_NULL_HANDLE;
VkSemaphore presTransSem = VK_NULL_HANDLE;
VkFence cmdFence = VK_NULL_HANDLE;
VkCommandBuffer cmdBuf = VK_NULL_HANDLE;
bool imageAcquired = false;
bool imageSemWaitable = false;
bool cmdFenceWaitable = false;
} frameRes[MAX_FRAME_LAG];
uint32_t currentFrame;