본문으로 바로가기

Vulkan) 6. 스왑 체인 개요 (Swap Chain Overview)

category Graphics/Vulkan 2021. 9. 5. 19:47

모든 내용은 https://vulkan-tutorial.com/Development_environment 에서 발췌합니다.

Windows를 기본으로 합니다. 그 외 플랫폼의 개발 환경 구성은 튜토리얼 사이트를 확인해 주세요.

 

모든 완성된 코드는 github.com/kimduuukbae/VulkanTutorial에 업로드됩니다.


어떤 플랫폼에서 실행하던지, vulkan은 윈도우에 대한 시야를 참조한다.

해당 윈도우에 실제로 어떤 것을 그리기 위해서는 특별한 이미지를 생성해야 한다.

하지만 대부분의 플랫폼에선 이 특별한 이미지는 시스템이 소유하거나 긴밀이 숨겨져 있어서 우리가 접근할 수 없다.

(즉, 모니터에 그려지는 부분을 전면버퍼라고 하는데 이 전면버퍼를 우리는 접근할 수 없다.)

대신 스왑 체인으로 불리는 객체를 사용하여 하나 이상의 이미지 객체를 관리하고, 이를 화면에 그리게 한다.

 

스왑체인은 이미지를 표시하는데 사용되는 일련의 프레임 버퍼이다.

우리가 어떠한 vertex / index를 가지고 그래픽스 파이프라인에 흘려보내면 이는 프레임 버퍼에 작성이 된다.

프레임버퍼는 컬러 버퍼와 뎁스 버퍼 (꼭 그렇지 않을 수 있다.)를 가지고 있는데, 스왑체인은 이를 참조하여 화면에 그려내야하는 이미지를 제시(present)한다.

 

스왑 체인 객체는 고유 윈도우 시스템에 불칸 표면에 표현하는 데 사용하는 하나 이상의 이미지를 생성하는 것을 요청하는 데 사용된다. 이는 VK_KHR_swapchian extension을 사용하여 노출된다. 각 스왑 체인 객체는 이미지의 집합을 관리하며, 보통은 링 버퍼의 형태이다. 

애플리케이션이 스왑 체인에게 다음 사용 가능한 이미지를 요청하고, 렌더링하고, 그 뒤 이미지를 다시 스왑 체인에 돌려서 디스플레이 준비를 시키는 것이다.

하나의 이미지가 디스플레이에 제출되어 표현되는 동안, 다른 이미지를 스왑체인에 그리고 있는다.

 

스왑체인도 너무 길기때문에 간단하게만 설명한다.

스왑체인을 생성하기 위해선 vkCreateSwapChainKHR()을 호출하며, 원형은 다음과 같다.

VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR(
    VkDevice                                    device,
    const VkSwapchainCreateInfoKHR*             pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkSwapchainKHR*                             pSwapchain);

스왑체인의 정보는 VkSwapchainCreateInfoKHR 이라는 구조체로 넘겨진다. 정의는 다음과 같다.

typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType                  sType;
    const void*                      pNext;
    VkSwapchainCreateFlagsKHR        flags;
    VkSurfaceKHR                     surface;
    uint32_t                         minImageCount;
    VkFormat                         imageFormat;
    VkColorSpaceKHR                  imageColorSpace;
    VkExtent2D                       imageExtent;
    uint32_t                         imageArrayLayers;
    VkImageUsageFlags                imageUsage;
    VkSharingMode                    imageSharingMode;
    uint32_t                         queueFamilyIndexCount;
    const uint32_t*                  pQueueFamilyIndices;
    VkSurfaceTransformFlagBitsKHR    preTransform;
    VkCompositeAlphaFlagBitsKHR      compositeAlpha;
    VkPresentModeKHR                 presentMode;
    VkBool32                         clipped;
    VkSwapchainKHR                   oldSwapchain;
} VkSwapchainCreateInfoKHR;
  • sType : VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR로 설정되어야 한다.
  • surface : vkCreateWin32SurfaceKHR나 vkCreateXlibSurfaceKHR같은 생성 함수로 만들어진 surface여야 한다.
  • minImageCount : 스왑 체인의 이미지 수이다.
    예를 들어, 이중 혹은 삼중 버퍼링을 위해선 2와 3으로 세팅하여야 한다.완료된 백버퍼의 프리젠테이션을 실행한 뒤에, 프리젠테이션이 끝나기 전에 다른 버퍼에 렌더링을 실행할 수 없다.
    약간을 지연을 감수하더라도, 장치가 지원할 경우 minImageCount를 3이상으로 설정하자.
    minImageCount를 2로 설정하는 것은 전면버퍼 하나와 백버퍼 하나를 가진다는 것을 기억하자.
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR()을 호출하여 최소 / 최대 지원 버퍼 갯수를 알 수 있다. (나중에 다룸)
  • imageFormat, imageColorSpace : 표현 가능한 이미지의 형식과 컬러 공간이다. 
    타입은 반드시 장치가 프리젠테이션 기능을 사용할 때 작성한 타입이어야 하며, imageColorSpace는 VkColorSpaceKHR 타입이어야 한다. 하지만, 유일하게 사용할 수 있는 타입은 VK_COLORSPACE_SRGB_NONLINEAR_KHR이며, 이는 프리젠테이션 하는 객체의 imageFormat이 sRGB 타입 이미지 라면 sRGB 비선형 자료를 기대한다는 뜻이다.
  • imageExtent : 스왑 체인의 크기(버퍼의 크기) 이다.
  • imageArrayLayers : 각 이미지의 레이어의 수를 설정한다. 이는 레이어된 이미지를 렌더링하고, 그 뒤 특정 레이러를 사용자에게 표현하는 데 사용할 수 있다.
  • imageUsage : 이미지가 어떻게 사용될지(프리젠테이션 원본으로 사용하는 것에 추가하여)를 설정하는 표준 VkImageUsageFlags의 묶음이다.
    예를 들어, 만약 이미지를 일반 색으로 렌더링하기 원한다면 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT를 추가하고, 셰이더로 직접 쓰고 싶다면 VK_IMAGE_USAGE_STORAGE_BIT를 포함하면 된다.
    imageUsage에 포함되어 있는 사용법의 집합은 반드시 스왑 체인 이미지에서 지원하는 목록에서 선택되어야 한다. 이는 vkGetPhysicalDeviceSurfaceCapabilitiesKHR()를 호출하여 확인할 수 있다.
  • sharingMode : 이미지가 어떻게 큐 사이에서 공유되는지를 설명한다. 만약 이미지가 단지 한 번에 하나의 큐에 사용될 예정이면 VK_SHARING_MODE_EXCLUSIVE로 설정한다.
    만약 다중 큐에서 사용된다면, VK_SHARING_MODE_CONCURRENT 로 설정한다.
  • pQueueFamilyIndices : 이미지가 사용될 큐에 대한 포인터이다.
  • queueFamilyIndexCount : pQueueFamilyIndices 의 크기이다.
    만약 sharingMode가 VK_SHARING_MODE_EXCLUSIVE일 때 pQueueFamilyIndices와 queueFamilyIndexCount는 무시된다.
  • preTransform : 이미지가 프리젠테이션되기 전에 어떻게 변환되는지를 설명한다. 이미지를 회전하거나 뒤집거나 투사 등등... 경우를 처리할 수 있게 한다.
    VkSurfaceTransformFlagBitsKHR의 비트 단위 조합이다.
  • compositeAlpha : 알파 조합이 어떻게 되는지 처리한다. VkCompositeAlphaFlagBitsKHR 타입이어야 한다.
    만약 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR로 설정되면 알파채널은 무시된다.
    다른 타입들은 윈도우에서 알파값이 알아서 블렌딩된다.
  • presentMode : 프리젠테이션을 할 때, 윈도우와 스왑체인간의 동기화가 어떻게 될 지 정하는 것이다.

    VK_PRESENT_MODE_IMMEDIATE_KHR : 프리젠테이션될 때 이미지를 최대한 빨리 그려냅니다. vertical blanking 같은것을 기다리지 않으며, 빠른 주사율을 제공하지만 티어링이나 다른 오류를 발생시킬 수 있습니다.

    VK_PRESENT_MODE_MAILBOX_KHR : 이미지가 그려지면, 이를 대기 상태로 보냅니다. 그리고 다음 프리젠테이션 때 대기 중인 이미지를 그려냅니다. 만약에 대기중인 이미지가 있는데, 프리젠테이션 전에 새로운 이미지가 다시 그려진다면 기존에 대기중인 이미지를 버려서 항상 최신화 합니다. 
    화면에 표시되는 이미지가 항상 마지막으로 입력된 이미지 이므로 낮은 입력 주사율 (Input Latency)를 기대할 수 있습니다. 하지만 GPU가 쉬지않고 일을 합니다.

    VK_PRESENT_MODE_FIFO_KHR : 이미지는 내부 큐에 저장되어 순서대로 보여집니다. 

    VK_PRESENT_MODE_FIFO_RELAXED_KHR : VK_PRESENT_MODE_FIFO_KHR과 비슷하게 행동하지만, 내부 큐는 항상 비어있고 프리젠테이션 때 큐에 들어오는 다음 이미지를 바로 표시합니다.
    애플리케이션이 V-Sync보다 빠르게 실행될 때 티어링을 방지하면서도, 가능한 빠르게 동작합니다.

FIFO와 Mailbox 이다.

FIFO : 이미지에서 보이듯이, 한 버퍼가 일을 한 후 다음 프레임에 프리젠테이션을 실행하면 기존에 실행되지 않은 새로운 버퍼가 작업을 시작한다.. GPU의 Latency가 일정하지 않다.

Mailbox : 프리젠테이션용 버퍼를 제외한 모든 버퍼들이 GPU를 계속 사용하여 항상 최신화 시킨다.

 

일반적인 법칙으로, V-Sync를 활성화하고 싶으면 FIFO를 선택하고 가장 빨리 실행하고 싶으면 IMMEDIATE나 MAILBOX를 선택한다. IMMEDIATE는 티어링을 생성하지만, 가장 낮은 Latency를 제공한다.

 

  • clipped : surface가 보이지 않을 때 최적화를 위해서 사용된다. 만약 이미지가 background에서 실행되고 있거나, 부분적으로 화면을 가리게 된다면, 사용자가 보지 않을 곳에 대한 렌더링을 피할 수 있다.
    clipped가 VK_TRUE라면, 이미지의 해당 부분을 렌더링 연산에서 제거한다.
  • oldSwapChain : surface와 결합되어 존재하는 스왑 체인을 vulkan에 재활용을 위해서 전달하는 데 사용할 수 있다.
    한 스왑 체인이 다른 것으로 대체될 때 사용되며, 예를 들어 윈도우가 크기가 조절되면서 스왑 체인이 더 크게 만들어져야 할 때 사용된다.

VkSwapchainCreateInfoKHR 구조체에 포함된 모든 매개변수는 반드시 surface와 맞아야 한다.

 

이제 Render pass를 생성해야 한다.

Render Pass는 vkCreateRenderPass 함수로 생성하며, 정의는 다음과 같다.

VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass(
    VkDevice                                    device,
    const VkRenderPassCreateInfo*               pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkRenderPass*                               pRenderPass);

device는 논리 디바이스이다.

VkRenderPassCreateInfo는 RenderPass의 정보인데, 정의는 다음과 같다.

typedef struct VkRenderPassCreateInfo {
    VkStructureType                   sType;
    const void*                       pNext;
    VkRenderPassCreateFlags           flags;
    uint32_t                          attachmentCount;
    const VkAttachmentDescription*    pAttachments;
    uint32_t                          subpassCount;
    const VkSubpassDescription*       pSubpasses;
    uint32_t                          dependencyCount;
    const VkSubpassDependency*        pDependencies;
} VkRenderPassCreateInfo;
  • sType : VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO 이다.
  • attachmentCount, pAttachments : 해당 render pass를 설명하는 것이다. attachemnt는 리소스에 대한 설명(description)이라고 보면 편할 것이다.
    이를 통해 render pass를 구성할 수 있는데, 우리는 color 와 depth을 그릴것이므로 이를 알려주는 것이다.
    그림을 보자.

다음과같이 FrameBuffer에 color와 depth를 작성해야하는데, 현재 렌더패스의 리소스에 대한 설명을 attachment로 하는것이다. 

Render Pass를 만들면 VkCmdBeginRenderPass를 통해서 실제 렌더패스 인스턴스가 내부적으로 생성된다.

그리고 Render Pass의 Attachment에 대응해서 실제 리소스인 FrameBuffer가 바인딩 된다.
attachment들을 지정할때는 규칙이 지정된다. (포맷, 용도, 샘플카운트, 클리어, 기존 데이터 유지 등) 

이를 'attachmentDescription' 이라고 한다.

 

즉 attachment는 리소스에 대한 설명이며, attachmentDescription은 attachment에 대한 설명이다.

 

그 외의 정보는 추후에 다시 작성한다.

 

SwapChain을 만들었으니, 다음엔 CommandBuffer와 Vertex Buffer를 생성하겠다.

모든 소스는 https://github.com/kimduuukbae/VulkanTutorial 이곳에 있다.

모든 소스를 외워서 하기보다는, 필요할때만 문서를 찾아보고 따로 디바이스 초기화 소스는 다운로드받아 사용하자.

워낙 방대한 내용이라 설명이 생략된 부분이 있는데, 설명을 원하는 곳이 있다면 수정하여 작성하겠다. 댓글부탁