본문 바로가기

Vulkan

Overview

이전 글 : Introduction

다음 글 : Development environment


Overview


  • Origin of Vulkan

  • What it takes to draw a triangle

  • Step 1 - Instance and physical device selection

  • Step 2 - Logical device and queue families

  • Step 3 - Window surface and swap chain

  • Step 4 - Image views and framebuffers

  • Step 5 - Render passes

  • Step 6 - Graphics pipeline

  • Step 7 - Command pools and command buffers

  • Step 8 - Main loop

  • Summary

  • API concepts

  • Coding conventions

  • Validation layers


이번 장에서는 Vulkan 에 대한 소개와 해결해야 할 문제들을 설명할 것입니다. 그 후에 우리의 첫번째 그림인 “삼각형” 그리기에 필요한 조건들을 살펴 보겠습니다. 이렇게 하면 다음으로 진행할 수 있는 큰 그림을 얻을 수 있습니다. 우리는 Vulkan API의 구조와 일반적인 사용 패턴에 대하여 살펴볼 것입니다.




Vulkan 의 기원


이전의 그래픽 API(OpenGL) 와 마찬가지로 Vulkan 역시 GPU에 대한 크로스 플랫폼 추상화 기법으로 설계 되었습니다. 이러한 API의 대부분의 문제점은 설정 가능한 고정된 기능에 의해 설계된 그래픽 하드웨어에서 나타 납니다. 프로그래머등은 정점 데이터를 원하는 표준 형식으로 제공해야 했으며, 조명, 음영 옵션과 같은것을 GPU 제조업체의 지원에 의존 했어야 했습니다.


그래픽 카드 아키텍처가 성숙해짐에 따라, 제조 업체들은 프로그램에 적용 가능한 더 많은 기능들을 제공하기 시작했습니다. 이 모든 새로운 기능은 기존의 API와 어떻게든 통합 되어야 했습니다. 이로 인해 많은 프로그램어의 의지를 그래픽 아키텍춰에 접목하기 위한 이상적인 추상화 기법과 예측들이 이루어 지지 못했습니다. 그래서 게임의 성능을 향상 시키기 위하여 드라이브 업데이트가 많이 있어야 했으며, 때로는 많은 이윤을 창출 했습니다. 이러한 드라이버의  복잡성으로 인해 응용 프로그램 개발자들은 공급 업체가 변경한 것에 의해 나타나는 문제점들, 예를 들면 쉐어더에서 허용되는 구문 같은, 을 처리 해야 했습니다. 이러한 새로운 기능 외에도 지난 10년동안 강력한 그래픽 하드웨어가 탑재 된 모바일 장치가 등장 하였습니다. 이러한 모바일 GPU는 사용하는 전력 및 장착에 필요한 공간적 제약에 따라 다른 아키텍처를 가지게 됬습니다. tiled rendering 이라는 기법은 프로그래머들에 의하여 제안된 기능으로 성능을 크게 향상시키는 역할을 했습니다. 이러한 API의 시대를 기점으로 나타난 또 다른 제한 사항은 제한된 멀티 스레딩 지원에 있습니다. CPU 측면에서 병목 현상을 초래 할 수 있습니다.


Vulkan은 최신의 그래픽 아키텍처를 위해 처음부터 고안하여 이러한 문제를 해결 하였습니다. 프로그래머가 보다 자세한 API를 사용하여 의도를 명확하게 지정할 수 있게 하여 드라이버의 오버헤드를 줄이고, 여러 스레드가 병렬로 명령을 수행할 수 있도록 하였습니다.

또한 단일 컴파일러로 표준화 된 바이트 코드 형식으로 전환하여 쉐이더 컴파일의 불일치를 줄였습니다(GLSL 및 HLSL모두 지원가능).


마지막으로 그래픽과 기능을 단일 API로 통합하여 최신 그래픽 카드의 일반적인 처리 기능을 향상 시켰다는것을 알기 바랍니다.




삼각형을 그리는데 필요한 것.


지금부터 Vulkan 프로그램에서 삼각형을 렌더링 하는데 필요한 모든 단계의 개요를 살펴보도록 하겠습니다. 여기에 소개된 모든 개념은 다음 장에서 자세하게 설명될 것입니다. 또한 이는 최종적으로 모든 각각의 개별 구성 요소를 서로 연결 시킬 수 있는 보다 큰 그림을 제공하기 위한 것 입니다.


Step1 - Instance and physical device selection


Vulkan 응용 프로그램은 VkInstance 를 통해 Vulkan API를 설정하여 시작합니다.

인스턴스는 사용하려는 응용 프로그램 및 API 확장에 대한 내용을 기술 하여 작성됩니다. 인스턴스를 만든 후 Vulkan 지원 하드웨어를 쿼리하고 작업에 사용할 VkPhysicalDevices 를 하나 이상 선택할 수 있습니다.

비디오 메모리의 크기 및 장치의 기능과 같은 속성을 쿼리하여 원하는 장치를 선택할 수 있습니다. 그래픽 카드가 다수 일 경우 최적의 그래픽 카드를 선택하여 사용하기 위함 입니다.


Step 2 - Logical device and queue families


사용할 올바른 하드웨어 장치를 선택한 후에는 VkDevice (논리 장치)를 만들어야 합니다. 여기서는 멀티 뷰포트 렌더링 및 64비트 부동 소수점과 같이 사용할 VkPhysicalDeviceFeatures 를 더 구체적으로 기술 합니다.

Vulkan 에 의한 그리기 명령들과 메모리 관련 명령들과 같은 대부분의 작업은 VkQueue 에 전달하여 비동기적으로 실행 됩니다. 이러한 대기열(Queue) 은 대기열 페밀리(Queue Families) 에서 할당 되어 지고,  대기열 페밀리는 대기열들을 관리하는 명령셋을 지원 합니다.

예를 들어, 그래픽, 컴퓨팅 및 메모리 전송 작업을 위한 별도의 대기열 패밀리가 있을 수 있습니다. Vulkan 지원 장치는 그래픽 기능을 제공하지 않을 수도 있지만, Vulkan을 지원하는 모든 그래픽 카드는 일반적으로 우리가 관심을 갖고 있는 모든 대기열 작업을 지원 합니다.


Step 3 - Window surface and swap chain


오프 스크린 렌더링에만 관심이 있는 경우가 아니라면 렌더링 된 이미지를 표시 할 창을 만들어야 합니다. Windows는 GLFW 및 SDL과 같은 기본 플랫폼 API 또는 라이브러리를 사용하여 만들 수 있습니다. 우리는이 튜토리얼에서 GLFW를 사용할 것이지만 다음 장에서 더 자세히 설명 할 것입니다.


실제로 창에 렌더링하는 데는 창 표면 (VkSurfaceKHR)과 스왑 체인 (VkSwapChainKHR)이 두 가지 더 필요합니다. KHR 접미사는 이러한 개체가 Vulkan 확장의 일부임을 나타냅니다. Vulkan API 자체는 완전히 특정 플랫폼에 의존하지 않기 때문에 표준화 된 WSI (Window System Interface) 확장을 사용하여 창 관리자와 상호 작용 해야 합니다. 서피스는 렌더링 할 윈도우에 대한 크로스 플랫폼을 위한 추상화이며 일반적으로 Windows의 HWND와 같은 기본 윈도우 핸들에 대한 참조를 제공하여 인스턴스화 됩니다. 다행히도 GLFW 라이브러리에는 플랫폼의 특정한 세부 사항들을 다루는 내장 함수가 있습니다.


스왑 체인은 렌더링 대상의 컬렉션 입니다. 그것의 기본 목적은 우리가 현재 렌더링 하고 있는 이미지가 현재 스크린 상에 있는 이미지와 다른지 확인하는 것입니다. 우리는 완전한 이미지 만 표시 되도록 하는 것이 중요 합니다. 프레임을 그릴 때마다 스왑 체인에 렌더링 할 이미지를 제공 해야 합니다. 프레임 그리기가 끝나면 이미지가 스왑 체인으로 반환되어 어느 시점에서 화면에 표시 됩니다. 완성 된 이미지를 화면에 표시 하기 위한 렌더링 대상 및 조건의 수는 현재 선택된 모드에 따라 다를 수 있습니다. 일반적으로 자주 사용하는 모드는 더블 버퍼링 (vsync) 및 트리플 버퍼링입니다. 스왑 체인 생성을 설명하는 글에서 이러한 내용을 살펴 보겠습니다.


Step 4 - Image views and framebuffers


스왑 체인에서 획득한 이미지를 그리기 위해서는  VkImageView VkFramebuffer 로 래핑 (wrap) 해야 합니다. 이미지 뷰는 사용할 이미지의 특정 부분을 참조하고, 프레임 버퍼는 색상, 심도 및 스텐실 타겟에 사용할 이미지 뷰를 참조합니다. 스왑 체인에는 다양한 이미지가 있을 수 있으므로 각 이미지 뷰와 프레임 버퍼를 선제 적으로 만들고 드로잉 할 때 올바른 이미지 버퍼를 선택해야 합니다.



Step 5 - Render passes


Vulkan의 렌더링 패스는 렌더링 작업 중에 사용되는 이미지의 유형, 사용 방법 및 내용을 처리하는 방법을 기술 합니다. 초기 삼각형 렌더링 응용 프로그램에서는 Vulkan에게 단일 이미지를 색상 타겟으로 사용하고 그리기 작업 직전에 단색으로 지울 것을 지정합니다. 렌더 패스는 이미지 유형만을 설명하지만, VkFramebuffer 는 실제로 특정 이미지를을 슬롯에 바인딩 합니다.



Step 6 - Graphics pipeline


Vulkan의 그래픽 파이프 라인은 VkPipeline 객체를 만드는 것으로 시작 합니다.. 여기에는 그리팩 카드의 설정 가능한 상태를 기술해 주어야 하는데,  VkShaderModule 객체를 사용하여 뷰포트의 크기 및 깊이 버퍼 등 프로그래밍 가능한 상태들을 기술 합니다. VkShaderModule 객체는 셰이더 바이트 코드로 부터 생성 됩니다. 또한 드라이버가 이러한 렌더링 단계를 참조하여 파이프 라인에 사용하도록  렌더링 대상을 지정 해야 합니다.


기존 API에 비해 Vulkan의 가장 큰 특징 중 하나는 그래픽 파이프 라인의 거의 모든 구성이 사전에 준비 되어 있어야 한다는 것 입니다. 즉, 다른 셰이더로 전환하거나 버텍스 레이아웃을 약간 변경 하려면 그래픽 파이프 라인을 완전히 다시 만들어야 합니다. 즉, 렌더링 작업에 필요한 모든 다른 조합에 대해 미리 많은 VkPipeline 객체를 만들어야 합니다. 뷰포트 크기 및 선명한 색상과 같은 일부 기본 구성들은 동적으로 변경할 수 있습니다. 모든 상태도 명시 적으로 설정 해주어야 합니다.  예를 들어 기본값으로 사용할 색상 혼합(blend) 상태 같은  것은  없습니다.


좋은 소식은 미리 컴파일 한 것과 실시간으로 컴파일이 되는 것이 동등한 작업을 수행하기 때문에 드라이버에 대한 최적화 기회가 더 많고 런타임 성능이 더 예측 가능하다는 것 입니다. 이는 다른 그래픽 파이프 라인으로 전환 하는 것과 같은 큰 상태 변경이 매우 명확하게 이루어 지기 때문 입니다.



Step 7- Command pool and command buffers


앞서 언급 했듯이 드로잉 작업과 같이 Vulkan에서 실행하려는 많은 작업을 대기열(Queue)에 넣어 주어야  합니다. 이러한 작업은 큐에 먼저 입력 하기 전에 VkCommandBuffer 에 기록 해야 합니다. 이러한 명령 버퍼는 특정 대기열 패밀리와 연관된 VkCommandPool 에서 할당 됩니다. 간단한 삼각형을 그리려면 다음과 같은 작업으로 명령 버퍼를 기록해야 합니다.


  • Begin the render pass

  • Bind the graphics pipeline

  • Draw 3 vertices

  • End the render pass

프레임 버퍼의 이미지는 스왑 체인이 제공 할 특정 이미지에 따라 달라 지므로 가능한 각 이미지에 대해 명령 버퍼를 기록하고 이것을 그리는 때에 올바른 이미지 버퍼를 선택 해야 합니다. 매 프레임마다 명령 버퍼를 다시 기록하는 것이 효율적이지 않습니다.


Step 8 - Main Loop


이제 드로잉 명령이 명령 버퍼로 래핑 되었으므로 메인 루프는 매우 간단 합니다. 먼저 vkAcquireNextImageKHR 을 사용하여 스왑 체인에서 이미지를 얻습니다. 그런 다음 해당 이미지에 적절한 명령 버퍼를 선택하고 vkQueueSubmit 을 사용하여 실행할 수 있습니다. 마지막으로 이미지를 스왑 체인으로 반환하여 vkQueuePresentKHR 로 화면에 표시합니다.


대기열에 저장 된 모든 작업은 비동기적으로 실행 됩니다. 따라서 올바른 순서의 실행을 보장하기 위해 세마포어와 같은 동기화 객체를 사용 해야 합니다. 그리기 명령 버퍼의 실행은 이미지 획득이 완료 될 때까지 대기하도록 설정해야 합니다. 그렇지 않으면 화면에 표시하기 위해 계속 읽는 이미지로 렌더링이 시작될 수 있습니다. vkQueuePresentKHR 호출은 렌더링이 끝나기를 기다려야 하는데, 이 경우 렌더링을 완료 한 후 신호를 받는 두 번째 세마포어를 사용합니다.



Summary


정신 없었던 이번 여정은 첫 번째 삼각형을 그리기 위한 작업에 대한 기본적인 이해를 제공합니다. 실제 프로그램에서는  정점 버퍼 할당, 균등 한 버퍼 생성 및 텍스처 이미지 업로드와 같은 더 많은 단계가 포함되어 있지만 이후 Vulkan은 보다 빠른  학습 속도를 그대로 유지하기 때문에 간단하게 시작할 것입니다. 우리는 초기에 정점 버퍼를 사용하는 대신 정점 쉐이더에 정점 좌표를 처음으로 임베드하여 약간의 주의를 기울일 것임을 주목하십시오. 정점 버퍼를 관리하려면 먼저 명령 버퍼에 익숙해야 하기 때문입니다.


간단히 말해서 첫 번째 삼각형을 그리려면 다음과 같이해야합니다.


  • Create a VkInstance

  • Select a supported graphics card (VkPhysicalDevice)

  • Create a VkDevice and VkQueue for drawing and presentation

  • Create a window, window surface and swap chain

  • Wrap the swap chain images into VkImageView

  • Create a render pass that specifies the render targets and usage

  • Create framebuffers for the render pass

  • Set up the graphics pipeline

  • Allocate and record a command buffer with the draw commands for every possible swap chain image

  • Draw frames by acquiring images, submitting the right draw command buffer and returning the images back to the swap chain

다소 많은 단계지만, 각 단계의 목적은 다음 장에서 매우 간단하고 명확해질 것입니다. 전체 프로그램에 비해 한 단계의 관계에 대해 혼란 스러우면 앞 페이지의 내용을 다시 참조하십시오.


API concepts


Vulkan 함수, 나열 형 및 구조체는 모두 LunarG에서 개발 한 Vulkan SDK 에 포함 된 vulkan.h 헤더에 정의되어 있습니다. 다음 장에서 이 SDK를 설치 할 것 입니다.


함수는 소문자 vk 접두사를 가지며, 구조체와 같은 유형은 Vk 접두사를 가지며 enum 형 값은 VK_ 접두사를 갖습니다. API는 함수에 매개 변수를 제공하기 위해 구조체를 많이 사용합니다. 예를 들어, 객체 생성은 일반적으로 다음 패턴을 따릅니다.


VkXXXCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.foo = ...;
createInfo.bar = ...;

VkXXX object;
if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) {
   std::cerr << "failed to create object" << std::endl;
   return false;
}



Vulkan의 많은 구조에서는 sType 멤버에서 구조 유형을 명시 적으로 지정 해야 합니다. pNext 멤버는 확장 구조체를 가리킬 수 있으며 이 튜토리얼 에서는 항상 nullptr이 됩니다. 객체를 생성 하거나 소멸 시키는 함수는 VkAllocationCallbacks 매개 변수를 가지므로 드라이버 메모리에 사용자 정의 할당자를 사용할 수 있습니다.이 매개 변수는이 튜토리얼 에서 nullptr로 되어 있습니다.


거의 모든 함수는 VK_SUCCESS 또는 오류 코드 인 VkResult를 반환 합니다. 각 함수가 반환 할 수있는 오류 코드와 그 의미를 설명합니다.




Validation layers


앞서 언급했듯이 Vulkan은 높은 성능과 낮은 드라이버 오버 헤드를 위해 설계 되었습니다. 따라서 기본적으로 매우 제한된 오류 검사 및 디버깅 기능이 포함됩니다. 드라이버가 오류 코드를 반환하는 대신 오류가 발생 합니다. 오류가 발생하면 그래픽 카드에서 작동하고 다른 드라이버에서도 오류가 발생할 수 있습니다.


Vulkan을 사용하면 유효성 검사 레이어라는 기능을 통해 광범위한 검사를 수행 할 수 있습니다. 유효성 검사 레이어는 함수 매개 변수에 대한 추가 검사 실행 및 메모리 관리 문제 추적과 같은 작업을 수행하기 위해 API와 그래픽 드라이버 사이에 삽입 할 수있는 코드 조각입니다. 좋은 점은 개발 중에 응용 프로그램을 활성화 한 다음 응용 프로그램을 해제 할 때 완전히 오버플로 할 수 있다는 것입니다. 누구나 자신의 유효성 검사 레이어를 작성할 수 있지만 LunarG의 Vulkan SDK는 이 자습서에서 사용할 표준 유효성 검사 레이어 집합을 제공합니다. 또한 이를 사용하기 위해서는 레이어에 디버그 메시지를 수신하는 콜백 함수를 등록 해줘야 합니다.



Vulkan은 모든 작업에 대해 매우 명시 적이며 유효성 검사 레이어가 너무 광범위 하기 때문에 실제로 OpenGL 및 Direct3D와 비교하여 화면이 검은 색으로 보이는 이유를 찾는 것이 훨씬 쉽습니다!


코드 작성을 시작하기 전에 우선 준비해야 할 것으로 개발 환경을 설정해야 합니다.


이전 글 : Introduction

다음 글 : Development environment


'Vulkan' 카테고리의 다른 글

Setup - Validation layers  (0) 2018.01.21
Setup - Creating an instance  (0) 2018.01.20
Setup - Base code  (0) 2018.01.20
Development environment  (0) 2018.01.19
Introduction  (2) 2018.01.19