重建交换链

我们添加一个叫做recreateSwapChain的函数,它会调用createSwapChain函数和其它一些依赖于交换链和窗口大小的对象的创建函数:

void recreateSwapChain() {
    vkDeviceWaitIdle(device);

    createSwapChain();
    createImageViews();
    createRenderPass();
    createGraphicsPipeline();
    createFramebuffers();
    createCommandBuffers();
}

上面代码,我们首先调用vkDeviceWaitIdle函数等待设备处于空闲状态,避免在对象的使用过程中将其清除重建。接着,我们重新创建了交换链。图形视图是直接依赖于交换链图像的,所以也需要被重建图像视图。渲染流程依赖于交换链图像的格式,虽然像窗口大小改变不会引起使用的交换链图像格式改变,但我们还是应该对它进行处理。视口和裁剪矩形在管线创建时被指定,窗口大小改变,这些设置也需要修改,所以我们也需要重建管线。实际上,我们可以通过使用动态状态来设置视口和裁剪矩形来避免重建管线。帧缓冲和指令缓冲直接依赖于交换链图像,也需要重建。

我们需要在重建前,清除之前使用的对象,所以我们将交换链相关的清除代码从cleanup函数中分离出来作为一个独立的cleanupSwapChain函数,然后在recreateSwapChain函数中调用它:

void cleanupSwapChain() {

}

void recreateSwapChain() {
    vkDeviceWaitIdle(device);

    cleanupSwapChain();

    createSwapChain();
    createImageViews();
    createRenderPass();
    createGraphicsPipeline();
    createFramebuffers();
    createCommandBuffers();
}

将交换链相关的清除代码从cleanup中移出到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();

    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);
    }

    vkDestroyCommandPool(device, commandPool, nullptr);

    vkDestroyDevice(device, nullptr);

    if (enableValidationLayers) {
        DestroyDebugReportCallbackEXT(instance, callback, nullptr);
    }

    vkDestroySurfaceKHR(instance, surface, nullptr);
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}

对于指令池对象,我们不需要重建,只需要调用vkFreeCommandBuffers函数清除它分配的指令缓冲对象即可。

窗口大小改变后,我们需要重新设置交换链图像的大小,这一设置可以通过修改chooseSwapExtent函数,让它设置交换范围为当前的帧缓冲的实际大小,然后我们在需要的地方调用chooseSwapExtent函数即可:

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
        return capabilities.currentExtent;
    } else {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);

        VkExtent2D actualExtent = {
            static_cast<uint32_t>(width),
            static_cast<uint32_t>(height)
        };

        ...
    }
}

至此,我们就完成了交换链重建的所有工作!但是,我们使用的这一重建方法需要等待正在执行的所有设备操作结束才能进行。实际上,是可以在渲染操作执行,原来的交换链仍在使用时重建新的交换链,只需要在创建新的交换链时使用VkSwapchainCreateInfoKHR结构体的oldSwapChain成员变量引用原来的交换链即可。之后,在旧的交换链结束使用时就可以清除它。