소개

우리 어플리케이션은 이제 삼각형을 성공적으로 그립니다. 하지만 아직 제대로 처리되지 않는 몇가지 상황이 있습니다. 스왑체인이 더 이상 호환되지 않도록 윈도우 창이 변경될 수도 있습니다. 이 문제를 일으킬 수 있는 이유 중 하나는 창 크기가 변경되기 때문입니다. 우리는 이러한 이벤트를 포착하고 스왑체인을 다시 만들어야 합니다.

스왑체인 재생성

createSwapChain과 스왑체인 또는 창 크기에 의존하는 객체를 생성하는 모든 함수를 호출하는 recreateSwapChain 함수를 만듭니다.

void recreateSwapChain() {
    vkDeviceWaitIdle(device);

    createSwapChain();
    createImageViews();
    createFramebuffers();
}

지난 챕터에서와 마찬가지로 아직 사용 중인 리소스를 건들면 안 되기 때문에 먼저 vkDeviceWaitIdle을 호출합니다. 스왑체인 자체를 다시 만드는 것은 분명합니다. 이미지 뷰는 스왑체인 이미지에 직접 기반으로 하기 때문에 재생성해야 합니다. 마지막으로 프레임 버퍼는 스왑체인 이미지에 직접 의존하므로 재생성해야 합니다.

이러한 객체를 다시 만들기 전에 이전 버전을 확실히 정리하기 위해, 일부 정리 코드를 분리하여 recreateSwapChain에서 호출해야 합니다. 이 함수를 cleanupSwapChain이라고 부르겠습니다:

void cleanupSwapChain() {

}

void recreateSwapChain() {
    vkDeviceWaitIdle(device);

    cleanupSwapChain();

    createSwapChain();
    createImageViews();
    createFramebuffers();
}

여기서는 단순성을 위해 렌더패스를 재생성하지 않습니다. 이론적으로, 스왑체인 이미지 형식은 어플리케이션 수명 동안 변경될 수 있습니다. 예를 들면 표준 범위에서 높은 동적 범위 모니터로 윈도우 창이 이동할 때입니다. 이는 응용프로그램에서 동적 범위간의 변경 사항이 제대로 반영되도록 렌더패스를 다시 만들어야 할 수 있습니다.

cleanup에서 cleanupSwapChain으로 스왑체인 새로고침의 일부로 재생성된 객체의 정리 코드를 이동시키겠습니다:

void cleanupSwapChain() {
    for (size_t i = 0; i < swapChainFramebuffers.size(); i++) {
        vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr);
    }

    for (size_t i = 0; i < swapChainImageViews.size(); i++) {
        vkDestroyImageView(device, swapChainImageViews[i], nullptr);
    }

    vkDestroySwapchainKHR(device, swapChain, nullptr);
}

void cleanup() {
    cleanupSwapChain();

    vkDestroyPipeline(device, graphicsPipeline, nullptr);
    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);

    vkDestroyRenderPass(device, renderPass, nullptr);

    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) {
        DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
    }

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

    glfwDestroyWindow(window);

    glfwTerminate();
}

chooseSwapExtent에서 이미 우리는 스왑체인 이미지가 올바른 사이즈인지 확인하기 위해 새 창 해상도를 쿼리하므로, chooseSwapExtent를 수정할 필요가 없습니다. (스왑체인을 만들 때 surface 해상도를 픽셀 단위로 가져오기 위해 glfwGetFramebufferSize를 이미 사용하고 있다는 것을 기억하세요.)

이것이 스왑체인을 재생성하는데 필요한 전부입니다! 하지만 이 접근 방식의 단점은 새 스왑체인을 만들기 전에 모든 렌더링을 중지해야 하는 것입니다. 이전 스왑체인에서 이미지를 그리는 동안 새 스왑체인을 만들 수 있습니다. 이전 스왑체인을 VkSwapchainCreateInfoKHR 구조체의 oldSwapChain 필드에 전달하고 사용이 끝나는 즉시 이전 스왑체인을 파괴해야 합니다.

차선책 또는 오래된 스왑체인

이제 우리는 스왑체인 재생성이 필요한 시점을 파악하고 새로운 recreateSwapChain 함수를 호출하기만 하면 됩니다. 운 좋게도 Vulkan은 일반적으로 프레젠테이션 중에 스왑체인이 더 이상 적절하지 않다고 알려줍니다.

vkAcquireNextImageKHR과 vkQueuePresentKHR 함수는 이를 위해 다음과 같은 특별한 값을 반환할 수 있습니다.

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_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
    throw std::runtime_error("failed to acquire swap chain image!");
}

이미지 획득을 시도할 때 스왑체인이 오래된 것으로 판명되면 더 이상 표시할 수 없습니다. 그러므로 즉시 스왑체인을 재생성하고 다음 drawFame 호출에서 시도해야 합니다.