Reconstruction of exchange chain by Vulkan
We have successfully written code to use Vulkan to draw a triangle on the screen, but there are many details in this program that we haven't dealt with. For example, the change of window size will lead to the mismatch between the switch chain and the window. We need to deal with the switch chain again.
1, Recreate the exchange chain
Add a new function recreateSwapChain and call createSwapChain and all creation functions related to objects that depend on the size of the exchange chain or form.
void recreateSwapChain() { vkDeviceWaitIdle(device); createSwapChain(); createImageViews(); createRenderPass(); createGraphicsPipeline(); createFramebuffers(); createCommandBuffers(); }
Let's call vkDeviceIdle first. As mentioned in the previous chapter, we can't touch the resources in use. Obviously, the first thing to do is to recreate the exchange chain itself. Image views also need to be recreated because they are directly based on the swap chain image. The render channel needs to be recreated because it depends on the format of the swap chain image. During the window resizing operation, the format of the swap chain image rarely changes, but it should still be processed. The Viewport and scissor rectangle sizes are specified during the creation of the drawing route, so the route needs to be rebuilt. You can use dynamic state to change viewports and scissor rectangles to avoid re creation. The last framebuffer and command buffer also need to be recreated because they also depend on the image of the swap chain.
In order to ensure that the old version of the object is properly recycled and cleaned by the system before re creating the related object, we need to move some cleanup code into different functions, which can be called in the recreateSwapChain function. This function is defined as cleanupSwapChain:
void recreateSwapChain() { vkDeviceWaitIdle(device); cleanupSwapChain() createSwapChain(); createImageViews(); createRenderPass(); createGraphicsPipeline(); createFramebuffers(); createCommandBuffers(); }
void cleanupSwapChain() { }
From cleanup, we move the cleanup code corresponding to the object to be recreated to cleanupSwapChain:
void cleanupSwapChain() { for (size_t i = 0; i < swapChainFramebuffers.size(); i++) { vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr); } vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data()); vkDestroyPipeline(device, graphicsPipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyRenderPass(device, renderPass, nullptr); for (size_t i = 0; i < swapChainImageViews.size(); i++) { vkDestroyImageView(device, swapChainImageViews[i], nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); } void cleanup() { cleanupSwapChain(); vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyDevice(device, nullptr); DestroyDebugReportCallbackEXT(instance, callback, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate(); }
We re create the command object pool command pool, but it seems wasteful. Instead, we choose to use the vkFreeCommandBuffers function to clean up the existing command buffers. This way, you can reuse the command buffers already allocated in the object pool.
The above part is to recreate the exchange chain! However, the disadvantage of this is that before the completion of re creating the swap chain, rendering will stop. Creates a new swap chain while allowing the command to continue drawing on the image of the old swap chain. You need to pass the previous exchange chain to the oldSwapChain field in the VkSwapchainCreateInfoKHR structure and destroy it immediately after use.
2, Window resizing
Now we need to figure out what it is necessary to recreate the exchange chain and call the recreateSwapChain function. A common condition is a change in the size of the form. Let's resize the form and observe the captured events. Modify the initWindow function to no longer contain the glfw? Resettable line, or change its parameter from glfw? False to glfw? True.
void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); glfwSetWindowUserPointer(window, this); glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); } ... static void onWindowResized(GLFWwindow* window, int width, int height) { if (width == 0 || height == 0) return; HelloTriangleApplication* app = reinterpret_cast<HelloTriangleApplication*>(glfwGetWindowUserPointer(window)); app->recreateSwapChain(); }
The glfwSetWindowSizeCallback function is called back by an event when the size of the form changes. Unfortunately, it can only accept a pointer as an argument, so we can't use member functions directly. Fortunately, GLFW allows us to use glfwSetWindowUserPointer to store any pointer in the form object, so we can specify that the static class member calls glfwGetWindowUserPointer to return the original instance object. Then we can continue to call recreateSwapChain, which usually happens when the form is minimized and the exchange chain creation fails
The choosewapextent function should add update logic and replace the original width and height with the latest width and height of the form:
int width, height; glfwGetWindowSize(window, &width, &height); VkExtent2D actualExtent = {width, height};
3, Suboptimal or expired exchange chain
Sometimes Vulkan may tell us that the current exchange chain is no longer compatible during presentation. The vkaquirenextimagekhr and vkQueuePresentKHR functions can return specific values.
- VK? Error? Out? Date? KHR: the exchange chain is no longer compatible with the surface and cannot be rendered
- VK_SUBOPTIMAL_KHR:
The swap chain can still submit images to the surface, but the attributes of the surface no longer match accurately. For example, the platform may resize the image to fit the window size.
VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); return; } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { throw std::runtime_error("failed to acquire swap chain image!"); }
If the swap chain gets the image timeout, it is no longer available. So we need to recreate the exchange chain immediately and try to get it in the next drawFrame call.
You can also choose to re create when the exchange chain is not in the best state, such as the size mismatch problem just mentioned. Here, because we've got an image, let's move on. Both VK? Success and VK? Suboptimal? KHR are considered "success" return codes.
result = vkQueuePresentKHR(presentQueue, &presentInfo); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } vkQueueWaitIdle(presentQueue);
The vkQueuePresentKHR function returns the same value. In our case, we also choose to recreate the exchange chain if it is not in the best state. Because we need the best results. Try to resize the form, the size of the framebuffer changes to match the form.
In the next chapter, we try to get rid of the hard coding and use vertex buffer instead of vertex shader to write dead vertex data.