본문 바로가기

Vulkan

Swap chain recreation

이전 글 : Drawing - Rendering and presentation

다음 글 : Vertex input description


Swap chain recreation
스왑 체인 레크리에이션

Introduction


우리는 이제 삼각형을 성공적으로 그렸지만 아직 제대로 처리하지 못하는 상황이 있습니다.  스왑 체인이 더 이상 서피스에 호환되지 않을때가 있습니다. 이 문제를 일으킬 수 있는 이유 중 하나는 창 크기가 변하는 것입니다. 우리는 이러한 사건이 발생하면 스왑 체인을 다시 만들어야 합니다.


Recreating the swap chain
스왑 체인 다시 만들기


createSwapChain을 호출하는 새로운 recreateSwapChain 함수를 만들고 스왑 체인이나 창 크기에 의존하는 객체의 모든 생성 함수를 만듭니다.


void recreateSwapChain() {
   vkDeviceWaitIdle(device);

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


마지막 챕터와 마찬가지로 vkDeviceWaitIdle 을 호출하기 때문에 여전히 사용중인 리소스를 만지지 않아야 합니다.


  • 분명히 우리가 해야 할 첫 번째 일은 스왑 체인 자체를 다시 만드는 것입니다.

  • 이미지 뷰는 스왑 체인 이미지를 직접 기반으로 하므로 다시 만들어야 합니다.

  • 렌더 패스는 스왑 체인 이미지 형식에 따라 다르기 때문에 다시 작성해야 합니다.

  • 스왑 체인 이미지 형식은 창 크기 조정과 같은 작업 중에 변경되는 경우는 거의 없지만 여전히 처리해야 합니다.

  • 뷰포트 및 시저스 크기는 그래픽 파이프 라인 생성 중에 지정되므로 파이프 라인도 다시 작성해야합니다. 뷰포트와 시저 사각형에 동적 상태를 사용하여 이를 피할 수 있습니다.

  • 마지막으로 프레임 버퍼와 명령 버퍼는 스왑 체인 이미지에 직접 의존합니다.


이러한 객체들을  다시 만들기 전에 기존의 리소스를 정리해야 하는데 이를 하려면 cleanup 코드 중 일부를 recreateSwapChain 함수에서 호출 할 수있는 별도의 함수로 옮겨야 합니다. 그것을 cleanupSwapChain 이라고 부르도록 하겠습니다.


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

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


명령 풀을 처음부터 다시 만들 수는 있지만, 그것은 낭비입니다. 대신 vkFreeCommandBuffers 함수를 사용하여 기존 명령 버퍼를 정리하도록 선택했습니다. 이렇게하면 기존 풀을 다시 사용하여 새 명령 버퍼를 할당 할 수 있습니다.


이것이 스왑 체인을 다시 만드는 데 필요한 전부입니다! 그러나 이 방법의 단점은 새 스왑 체인을 만들기 전에 모든 렌더링을 중지해야 한다는 것입니다. 이전 스왑 체인의 이미지에 대한 명령을 그리는 동안 여전히 새로운 스왑 체인을 만들 수 있습니다. 이전 스왑 체인을 VkSwapchainCreateInfoKHR 구조체의 oldSwapChain 필드에 전달하고 사용을 마친 즉시 이전 스왑 체인을 삭제해야 합니다.



Window resizing

이제 스왑 체인 을 다시 만들어야 하는 필요가 있는 시기를 파악하고 새로운 recreateSwapChain 함수를 호출하면 됩니다. 가장 일반적인 조건 중 하나는 창 크기를 조정하는 것입니다. 창 크기를 조정하고 해당 이벤트를 처리 해 봅시다. initWindow 함수에   GLFW_RESIZABLE 행을 포함 시키거나 해당 인수를 GLFW_FALSE에서 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();
}


glfwSetWindowSizeCallback 함수를 사용하여 window resize 이벤트에 대한 콜백을 지정할 수 있습니다. 불행히도 인수로만 함수 포인터를 받아들이므로 멤버 함수를 직접 사용할 수는 없습니다. 다행히 GLFW를 사용하면 glfwSetWindowUserPointer를 사용하여 창 객체에 임의의 포인터를 저장할 수 있으므로 정적 클래스 멤버를 지정하고 glfwGetWindowUserPointer 를 사용하여 원래 클래스 인스턴스를 다시 가져올 수 있습니다. 그런 다음 윈도우의 크기가 0이 아닌 경우에만 recreateSwapChain을 호출 할 수 있습니다. 이 경우는 창이 최소화되어 스왑 체인 작성이 실패 할 수 있습니다.


chooseSwapExtent 함수는 초기 WIDTH 및 HEIGHT 대신 창의 현재 너비 및 높이를 고려하여 업데이트해야 합니다.


int width, height;
glfwGetWindowSize(window, &width, &height);

VkExtent2D actualExtent = {width, height};



Suboptimal or out-of-date swap chain

차선 또는 만료 된 스왑 체인


또한 Vulkan은  프리젠테이션 도중 스왑 체인이 더 이상 호환되지 않는다고 알려 줄 수 있습니다. vkAcquireNextImageKHR 및 vkQueuePresentKHR 함수는 이를 나타내기 위해 다음 특수 값을 반환 할 수 있습니다.


  • VK_ERROR_OUT_OF_DATE_KHR : 스왑 체인이 서피스와 호환되지 않아 더 이상 렌더링에 사용할 수 없습니다.

  • VK_SUBOPTIMAL_KHR : 스왑 체인을 사용하여 표면에 성공적으로 표시 할 수 있지만 표면 특성이 더 이상 정확하게 일치하지 않습니다. 예를 들어, 플랫폼은 이미지를 창의 크기에 맞게 크기 조정할 수 있습니다.


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


스왑 체인이 이미지를 획득하려고 시도 할 때 오래된 것으로 판명되면 더 이상 프레즌테이션 할 수 없습니다. 따라서 우리는 즉시 스왑 체인을 다시 만들고 다음 drawFrame 호출을 다시 시도해야 합니다.


스왑 체인이 부차적 인 경우 이를 수행하기로 결정할 수도 있지만 이미 이미지를 획득했기 때문에 계속 진행하도록 선택했습니다. VK_SUCCESS와 VK_SUBOPTIMAL_KHR 모두 "success" 리턴 코드로 간주됩니다.

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


vkQueuePresentKHR 함수는 같은 의미의 동일한 값을 반환합니다. 이 경우 최적의 결과를 원하기 때문에 최적이 아닌 경우 스왑 체인을 다시 만듭니다. 프레임 버퍼를 실행하고 창 크기를 조정하여 프레임 버퍼가 실제로 창과 함께 제대로 크기가 조정되었는지 확인하십시오.


축하합니다. 이제 첫 번째로 잘된 Vulkan 프로그램을 마쳤습니다!


다음 장에서는 버텍스 쉐이더에 직접 코딩 된 버텍스 정보를 없애고 실제로 버텍스 버퍼를 사용할 것입니다.


C++ code / Vertex shader / Fragment shader


이전 글 : Drawing - Rendering and presentation

다음 글 : Vertex input description



'Vulkan' 카테고리의 다른 글

Vertex buffer creation  (0) 2018.01.25
Vertex input description  (0) 2018.01.25
Drawing - Rendering and presentation  (0) 2018.01.25
Drawing - Command buffers  (1) 2018.01.25
Drawing - Framebuffers  (1) 2018.01.24