Reputation: 8963
This vulkan tutorial discusses swapchain recreation:
You could also decide to [recreate the swapchain] that if the swap chain is suboptimal, but I've chosen to proceed anyway in that case because we've already acquired an image.
My question is: how would one recreate the swapchain and not proceed in this case of VK_SUBOPTIMAL_KHR?
To see what I mean, let's look at the tutorial's render function:
void drawFrame() {
vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
uint32_t imageIndex;
VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return;
/* else if (result == VK_SUBOPTIMAL_KHR) { createSwapchain(); ??? } */
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("failed to acquire swap chain image!");
}
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
}
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
vkResetFences(device, 1, &inFlightFences[currentFrame]);
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = {swapChain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
result = vkQueuePresentKHR(presentQueue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
framebufferResized = false;
recreateSwapChain();
} else if (result != VK_SUCCESS) {
throw std::runtime_error("failed to present swap chain image!");
}
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
The trouble is as follows:
VUID-VkPresentInfoKHR-pImageIndices-01430
. We need to call vkAcquireImageKHR again to get a new image.VUID-vkAcquireNextImageKHR-semaphore-01286
), we need to 'unsignal' it.Is the best solution here to destroy and recreate the semaphore?
Upvotes: 2
Views: 2092
Reputation: 417
I know this question is a tad dated, but I came across the same issue and ended up here from Google searches.
To build on krOoze's suggestion of destroying and re-creating synchronization objects, I definitely agree. This portion of the application is most likely an outlier path, and certainly not something you are doing all the time.
If following vulkan-tutorial (e.g., the C API), you could factor out a cleanupSyncObjects
function:
void cleanupSyncObjects() {
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr);
vkDestroyFence(device, inFlightFences[i], nullptr);
}
}
You could then utilize this in your recreateSwapChain
method:
void recreateSwapChain() {
// handle minimized window logic
vkDeviceWaitIdle(device);
cleanupSwapChain();
cleanupSyncObjects(); // add
createSwapChain();
createImageViews();
createFramebuffers();
createSyncObjects(); // add
}
And in your cleanup
:
void cleanup() {
// ...
cleanupSyncObjects();
// ...
}
The only part I might caveat in the above is that depending on what you are doing, you may want to specifically wait on every queue (you may only have one for graphics) with some kind of timeout. vkDeviceWaitIdle
is a bit of a sledgehammer that will wait on every queue with an infinite timeout:
vkDeviceWaitIdle
is equivalent to callingvkQueueWaitIdle
for all queues owned by device.
vkQueueWaitIdle
is equivalent to having submitted a valid fence to every previously executed queue submission command that accepts a fence, then waiting for all of those fences to signal usingvkWaitForFences
with an infinite timeout andwaitAll
set toVK_TRUE
.
Essentially you could deadlock your application, which wouldn't be great. If just following the tutorial it's probably not a concern while you try to wrap your head around the API.
If using Vulkan-Hpp, vk::UniqueSemaphore
and vk::UniqueFence
are unique pointer analogs that can be destroyed by simply assigning a new value. If not using the unique pointers, vk::Semaphore
and vk::Fence
need to be destroyed by vk::Device::destroySemaphore
method vk::Device::destroyFence
.
The same logic as the C API follows, just with a C++ API (wait for things to not be in use -> destroy -> create again).
Upvotes: 2
Reputation: 13246
Ad 3: you can use the old images (and swapchain) if you properly use the oldSwapchain
parameter when creating the new swapchain. Which is what I assume the tutorial suggests.
Anyway. What I do is that I paranoidly sanitize that toxic semaphore like this:
// cleanup dangerous semaphore with signal pending from vkAcquireNextImageKHR (tie it to a specific queue)
// https://github.com/KhronosGroup/Vulkan-Docs/issues/1059
void cleanupUnsafeSemaphore( VkQueue queue, VkSemaphore semaphore ){
const VkPipelineStageFlags psw = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &semaphore;
submit_info.pWaitDstStageMask;
vkQueueSubmit( queue, 1, &submit_info, VK_NULL_HANDLE );
}
After that the semaphore can be properly catched with a fence or vkQueueWaitIdle
, and then destroyed or reused.
I just destroy them, because the new semaphore count might differ, and I don't really consider swapchain recreation a hotspot (and also I just use vkDeviceWaitIdle
in such case).
Upvotes: 5