Scientia Conditorium

[Vulkan002][번역] Vulkan Tutorial - 개요(Overview) 본문

프로그래밍/컴퓨터 그래픽스

[Vulkan002][번역] Vulkan Tutorial - 개요(Overview)

크썸 2023. 11. 12. 16:15

원문 : https://vulkan-tutorial.com/Overview

[Vulkan002] Vulkan Tutorial - Overview

 

Overview - Vulkan Tutorial

This chapter will start off with an introduction of Vulkan and the problems it addresses. After that we're going to look at the ingredients that are required for the first triangle. This will give you a big picture to place each of the subsequent chapters

vulkan-tutorial.com

 


이 장에서는 Vulkan과 Vulkan이 다뤄야할 문제들을 소개하는 것으로 시작하겠습니다. 그 다음 첫 번째 삼각형에 필요한 구성 요소를 살펴볼 것입니다. 이를 통해 이후의 각 챕터들이 어느 부분인지 전체 구조를 그릴 수 있습니다. 마지막으로 Vulkan API의 구조와 일반적인 사용 패턴을 다루면서 마무리 하겠습니다.

 

Vulkan의 기원

이전 그래픽스 API와 마찬가지로 Vulkan은 GPU를 통한 크로스 플랫폼 추상화로 설계되었습니다. 이러한 API의 대부분은 설계했을 당시의 그래픽 카드에 맞추어져 있으며 설정 가능하지만 고정된 기능에 제한되어있습니다. 프로그래머는 정점 데이터를 표준 형식으로 제공해야 했고, 조명 및 음영 옵션과 관련해서는 GPU 제사들에 좌우되었습니다.

 

그래픽 카드 아키텍처가 발전함에 따라 GPU 제조사들은 더 많은 프로그래밍 가능한 기능을 제공하기 시작했습니다. 이 모든 새로운 기능은 어떻게든 기존 API와 통합되어야 했습니다. 이로 인해 이상적인 추상화와 멀어지고 최신 그래픽 아키텍처에서 프로그래머의 의도를 보여주기 위해 그래픽 드라이버 단에서 지레짐작하는 부분이 많아졌습니다. 그렇기 때문에 게임 성능을 향상시키 위한 드라이버 업데이트가 매우 많으며 때로는 큰 폭으로 개선되기도 합니다. 이러한 드라이버의 복잡성 때문에 애플리케이션 개발자는 제조사들마다 쉐이더에 허용되는 문법이 다른 문제도 해결해야 합니다. 게다가 지난 10년 동안 강력한 그래픽 하드웨어를 탑재한 모바일 장비가 쏟아져 나왔습니다. 모바일 GPU들은 사용하는 전력 및 장착에 필요한 공간에 따라 서로 다른 아키텍처를 가지고 있습니다. 대표적인 예로 타일 렌더링(tiled rendering)을 들 수 있는데, 프로그래머가 이 기능을 더 많이 제어할 수 있도록 하여 성능을 높일 수 있습니다. 이러한 API의 또 다른 한계는 제한된 멀티스레딩 지원으로 인해 CPU 측에서 병목 현상이 발생할 수 있다는 점입니다.

 

Vulkan은 처음부터 최신 그래픽 아키텍처를 위해 설계되어 위와 같은 문제들을 해결합니다. 보다 자세한 API를 사용하여 프로그래머의 의도를 명확하게 지정할 수 있도록 하여 드라이버 부하를 줄이고, 여러 스레드가 동시에 명령을 생성하고 수할 수 있도록 합니다. 단일 컴파일러로 표준화된 바이트 코드 포맷으로 전환하여 쉐이더 컴파일의 불일치를 줄입니다. 마지막으로 그래픽과 연산 기능을 단일 API로 통합하여 현대 그래픽 카드의 범용 처리 기능(GPGPU)을 수용합니다.


OpenGL은 암묵적 API라 할 수 있고, 리소스 관리는 드라이버가 담당하게 됩니다. 드라이버는 응용 프로그램 힌트를 이용해서 리소스를 추적해야 하는 불필요한 부하가 생기게 됩니다. 반면 Vulkan은 명시적인 API 입니다. 드라이버는 리소스와 그 관계를 추적할 책임이 없으며, 이는 응용 프로그램의 책임입니다. 다시 말해 좀 더 예측 가능한 깔끔한 접근 방식입니다. 이는 드라이버가 (OpenGL에서처럼) 리소스를 관리하기 위해 숨어서 묘기를 부리고 있지 않다는 것을 의미합니다. 결과적으로 작업 처리가 간소화되고 직관적으로 수행돼 최적의 성능과 예측 가능한 동작을 얻게 됩니다.

 

Vulkan은 OpenGL에 비해 높은 예측 가능성을 제공하고, 렌더링 하는 동안 지연이나 걸림 발생이 적습니다. 또한, 작업은 드라이버에 제출되는 즉시 선행(Upfront)돼 제출됩니다. 반면 OpenGL의 작업 제출 프로세스는 선행 작업이 아니며 드라이버의 스케줄러에 맡겨져 있습니다.

 

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

이제 제대로 동작하는 Vulkan 프로그램에서 삼각형을 렌더링하는 데 필요한 모든 단계에 대한 개요를 살펴보겠습니다. 여기에 소개된 모든 개념은 다음 장에서 자세히 설명하겠습니다. 이는 모든 개별 요소를 연관시킬 수 있는 전체 구조를 보여주기 위한 것입니다. 

 

 

Step 1 - Instance and physical device selection

인스턴스와 물리적 장치 선택

Vulkan 애플리케이션은 VkInstance를 통해 Vulkan API를 설정하는 것으로 시작됩니다. 인스턴스는 앞으로 사용할 애플리케이션과 API 확장판을 기하여 생성됩니다. 인스턴스를 생성한 후에는 Vulkan 지원 하드웨어를 쿼리하고 연산에 사용할 하나 이상의 VkPhysicalDevices를 선택할 수 있습니다. VRAM 크기 및 장치 기능과 같은 속성을 쿼리하여 원하는 장치를 선택할 수 있습니다.(예: 전용 그래픽 카드 사용 선호)

 

 


인스턴스(Instance) : 사전적 의미는 '경우, 사례'이며, 응용 프로그램에서 하나 이상의 구체적인 실행을 의미한다. 좀 더 쉽게 설명하자면 컴퓨터 그래픽스에서는 '복제 기능'으로 이해하면 된다. 다시 말해 어떤 이벤트, 메세지와 같은 작업들을 응용 프로그램에서 별도로 식별 처리하기 위해 복제된 개체이다. 그래서 특정 템플릿에서 개체를 복제하는 것을 인스턴싱(Instancing)이라고 한다.

 

시스템에는 하나 이상의 물리적 Vulkan 지원 하드웨어 장치를 가질 수 있습니다. 물리적 장치는 고유한 장치를 나타내지만, 장치는 응용 프로그램에서 물리적 장치의 논리적 표현을 의미합니다.

 

Step 2 - Logical device and queue families

논리적 장치와 큐 패밀리

사용할 올바른 하드웨어 장치를 선택한 후에는 멀티 뷰포트 렌더링 및 64비트 부동소수점과 같이 사용할 VkPhysicalDeviceFeatures를 보다 구체적으로 설명하는 VkDevice(논리적 장치)를 생성해야 합니다. 또한 사용할 큐 패밀리를 지정해야 합니다. 그리기 명령 및 메모리 연산과 같이 Vulkan으로 수행되는 대부분의 작업은 VkQueue에 제출하여 비동기적으로 실행됩니다. 큐는 큐 패밀리에 할당되며, 각 큐 패밀리는 해당 큐에서 특정 작업 집합을 지원합니다. 예를 들어 그래픽, 컴퓨팅 및 메모리 전송 작업을 위한 별도의 큐 패밀리가 있을 수 있습니다. 큐 패밀리의 가용성은 물리적 디바이스 선택의 구분 요소로도 사용될 수 있습니다. Vulkan을 지원하는 장치가 그래픽 기능을 제공하지 않을 수도 있지만, 오늘날 Vulkan을 지원하는 모든 그래픽 카드는 일반적으로 우리가 사용하려고 하 모든 큐 연산을 지원합니다.


큐(Queue, 대기열) : 실행 엔진과 응용 프로그램 사이의 인터페이스를 의미합니다. 물리적 장치는 항상 하나 이상의 큐(그래픽스, 컴퓨팅, DMA/전송 등)를 포함합니다. 큐의 역할은 작업(커맨드 버퍼)들을 모으고 이를 물리적 장치로 보내 처리하게 하는 것입니다. 즉, 커맨드 버퍼를 장치에 공급하는 매체 역할을 합니다. 

 

뷰포트(Viewport) : 프리미티브 렌더링 결과가 화면의 어떤 부분에 나타날지를 지정. 

 

 

Step 3 - Window surface and swap chain

창 생성과 스왑 체인

 

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

 

실제로 창에 렌더링하려면 두 개의 컴포넌트가 더 필요합니다. 창 서페이스(VkSurfaceKHR)과 스왑 체인(VkSwapchainKHR). KHR 후위표기에 주목하세요. 이는 이러한 오브젝트가 Vulkan 확장의 일부임을 의미합니다. Vulkan API 자체는 완전히 플랫폼에 구애받지 않으므로 창 관리자와 상호 작용하려면 표준화된 WSI(창 시스템 인터페이스) 확장을 사용해야 합니다. 서페이스는 렌더링할 창에 대한 크로스 플랫폼 추상화이며 일반적으로 네이티브 창 핸들(예, Windows의 HWND)에 대한 참조를 제공하여 인스턴스화 됩니다. 다행히도 GLFW 라이브러리에는 플랫폼별 세부 사항을 처리하는 함수가 내장되어 있습니다.

 

스왑 체인은 렌더링 대상의 모음입니다. 기본 목적은 현재 렌더링 중인 이미지가 현재 화면에 표시되는 이미지와 다른지 확인하는 것입니다. 이는 완전한 이미지만 표시되도록 하는 데 중요합니다. 매 프레임마다 스왑 체인에 렌더링할 이미지를 제공하도록 요청해야 합니다. 한 프레임을 다 그리고나면 이미지가 스왑 체인으로 반환되어 어느 시점에 화면에 표시되도록 합니다. 렌더링 대상의 수와 완성된 이미지를 화면에 표시하기 위한 조건은 현재 선택된 모드에 따라 다릅니다. 일반적인 모드는 더블 버퍼링(vsync)과 트리플 버퍼링입니다. 이에 대해서는 스왑 체인 생성 챕터에서 살펴보겠습니다. 

 

일부 플랫폼에서는 VK_KHR_display 및 VK_KHR_display_swapchain 확장판을 통해 창 관리자와 상호 작용하지 않고 디스플레이에 직접 렌더링할 수 있습니다. 예를 들어, 이를 통해 전체 화면을 나타내는 표면을 만들 수 있으며, 자체 창 관리자를 구현하는 데 사용할 수 있습니다.


오프스크린 렌더링(Off-screen rendering) : 렌더링의 결과를 내부적인 버퍼에 저장함으로써 렌더링 결과가 화면에 표현되지 않고 내부적으로 렌더링이 이루어지는 것.

서페이스(Surface) : 실제 렌더링 되는 결과물을 볼 수 있는 이미지 버퍼. 도화지 또는 화면이라고 생각하면 편하다.

WSI(Window System Integration) : Linux, MS Windows, Android와 같은 다양한 플랫폼에서 프레젠테이션 계층을 통합하기 위한 크로노스 그룹의 확장판 세트이며 디스플레이 화면에 이미지를 렌더링하는 기능을 함.

 

 

Step 4 - Image views and framebuffers

이미지 뷰와 프레임버퍼

스왑 체인에서 가져온 이미지 그리려면 VkImageView와 VkFramebuffer로 래핑(wrap)해야 합니다. 이미지 뷰는 사용할 이미지의 특정 부분을 참조하고 프레임 버퍼는 색상, 깊이 및 스텐실 타켓에 사용할 이미지 뷰를 참조합니다. 스왑 체인에는 다양한 이미지가 있을 수 있으므로 각 이미지에 대한 이미지 뷰와 프레임버퍼를 선제적으로 생성하고 그리기 시점에 적합한 이미지 뷰를 선택합니다.


OpenGL에서는 데이터가 정적인지, 동적으로 변경하는 렌더러에 알려주는 힌트(Hint)를 사용해 내부에서 자동으로 메모리를 관리합ㅈ니다. 그러나 Vulkan은 하위 계층의 메모리 액세스와 제어 기능 전체를 응용 프로그램에 공개합니다. Vulkan은 물리적 장치상 다양한 유형의 사용 가능한 메모리를 볼 수 있게 하고, 이 다양한 유형의 메모리를 명시적(직접적)으로 관리할 수 있는 정밀한 기능을 응용 프로그램에 제공합니다.

 

Vulkan은 다양한 메모리 유형을 사용할 수 있는데, 크게 보면 성능에 따라 호스트와 장치 두 개의 유형으로 볼 수 있습니다. 호스트 로컬(Host Local)은 느린 메모리 유형이고 장치 로컬(Device Local)은 높은 대역폭의 메모리 유형으로 상대적으로 고속입니다.  

 

 

Step 5 - Render passes

렌더패스

Vulkan의 렌더 패스는 렌더링 작업 중에 사용되는 이미지의 유형, 사용 방법, 콘텐츠 처리 방법을 기술합니다. 초기 삼각형 렌더링 응용 프로그램에서는 단일 이미지를 색상 대상으로 사용할 것이며 그리기 명령 직전에 단일 색으로 지우라고 Vulkan에게 알려줍니다. 렌더 패스는 이미지 유형만 명하는 반면, VkFameBuffer는 실제로 특정 이미지를 이러한 슬롯에 바인딩합니다.


렌더 패스(Render Pass) : 프레임 버퍼 캐시에 영향을 줄 수 있는 작업의 실행 프로세스를 정의합니다. 첨부, 서브패스, 서브패스 간의 의존성이 포함될 수 있습니다. 첨부는 드로잉이 수행될 이미지를 참조합니다. 서브패스에서 첨부와 비슷한 이미지는 다중 샘플이 수행을 위해 서브패스가 될 수 있습니다. 렌더 패스는 패스의 시작 부분에서 프레임 버퍼를 어떻게 처리할지 정할 수 있습니다. 이전에 드로잉된 내용을 그대로 유지하거나 지정된 색상으로 지울 수 있습니다. 마찬가지로 렌더 패스의 끝부분에서 렌더링 결과를 버리거나 저장할 수 있습니다.

 

Step 6 - Graphics pipeline

그래픽스 파이프라인

Vulkan의 그래픽스 파이프라인은 VkPipeline 개체를 생성하여 설정합니다. 이는 뷰포트 크기 및 깊이 버퍼 작업과 같은 그래픽 카드의 설 가능한 상태와 VkShaderModule 개체를 사용하여 프로그래밍 가능한 상태를 기술합니다. VkShaderModule 개체는 쉐이더 바이트 코드에서 생성됩니다. 또한 드라이버는 파이프라인에서 어떤 렌더 대상이 사용될지 알아야 하며, 이는 렌더 패스를 참조하여 지정합니다.

 

기존 API와 비교했을 때 Vulkan의 가장 큰 특징 중 하나는 그래픽 파이프라인의 거의 모든 구성을 미리 설정해야 한다는 점입니다. 즉, 다른 쉐이더로 전환하거나 정점 레이아웃을 약간 변경하려면 그래픽스 파이프라인을 완전히 다시 만들어야 합니다. 즉, 렌더링 작업에 필요한 모든 다양한 조합을 위해 많은 VkPipeline 개체를 미리 생성해야 합니다. 뷰포트 크기 및 투명 색상과 같은 일부 기본 구성만 동적으로 변경할 수 있습니다. 모든 상태를 명시적으로 기술해야 합니다. 예를 들어 기본 색상으로 혼합 상태 같은 건 없습니다.

 

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


파이프라인 : 응용 프로그램 로직에 의해 정의된 고정된 순서로 발생하는 이벤트들의 집합. 이벤트는 쉐이더 코드 제출, 쉐이더를 리소스에 바인딩, 스테이트 관리 등으로 구성됩니다. 

 

파이프라인 스테이트(Pipeline State) : 물리적 장치는 일련의 하드웨어 설정을 가지고 있습니다. 이 설정을 통해 입력된 기하(Geometry) 정보 입력 데이터를 어떤 방식으로 해석하고 그릴지를 결정합니다. 이러한 설정을 총칭해 파이프라인 스테이트라 하며, 여기에는 래스터라이저 스테이트, 블렌드 스테이트, 스텐실 스테이트 그리고 함꼐 제출된 기하의 기본 토폴로지(Topology) 유형과 렌더링에 사용될 쉐이더가 포함됩니다. 스테이트에는 동적 유형과 정적 유형 두 가지가 있습니다. 파이프라인 스테이트는 파이프라인 개체(그래픽스 또는 컴퓨팅)를 생성하는 데 사용됩니다. 이 생성 과정은 성능에 큰 영향을 주는 작업으로, 이 과정을 반복하는 것보다는 한 번 생성해 반복 사용하는 것이 바람직합니다.

 

파이프라인 개체 : 파이프라인 생성은 비용이 큰 작업으로, 쉐이더 재컴파일, 리소스 바인딩, 렌더 패스, 프레임 버퍼 관리와 기타 관련 작업들이 포함됩니다. 파이프라인 개체는 수백에서 수천 개의 번호가 매겨질 수 있습니다. 따라서 각각의 스테이트 조합은 별도의 파이프라인 개체로 저장됩니다.

 

 

 

Step 7 - Command pools and command buffers

커맨드와 커맨드 버퍼

앞서 언급했이, 그리기 연산과 같이 Vulkan 에서 실행하려는 많은 작업은 큐에 제출해야 합니다. 이러한 연산을 제출하기 전에 먼저 VkCommandBuffer에 기록해야 합니다. 이러한 명령 버퍼는 특정 큐 패밀리와 연결된 VkCommandPool에서 할당됩니다. 간단한 삼각형을 그리려면 다음 작업으로 명령 버퍼를 기록해야 합니다.

 

  • 렌더링 패스 시작
  • 그래픽 파이프라인 바인딩
  • 정점 3개 그리기
  • 렌더패스 종료

프레임버퍼의 이미지는 스왑 체인이 제공할 특정 이미지에 따라 달라지므로 가능한 각 이미지에 대한 커맨드 버퍼를 기록하고 그리기 시점에 적합한 이미지를 선택해야 합니다. 다른 방법은 매 프레임마다 커맨드 버퍼를 다시 기록하는 것이지만 효율적이지 않습니다. 


커맨드(Command) :  어떤 동작을 하기 위한 명령입니다. 커맨드는 동작(Action), 스테이트 설정(Set State), 동기화(Synchronization) 명령으로 구분할 수 있습니다.

  • 동작 명령 : 프리미티브(Primitive) 그리기, 화면 지욱, 버퍼 복사, 쿼리/타임스탬프 작업, 서브패스 작업의 시작/끝에 사용할 수 있습니다. 이 명령은 프레임 버퍼 첨부(Attachment)의 변경과 메모리 읽기 또는 쓰기(버퍼 또는 이미지), 쿼리 풀 작성을 할 수 있습니다.
  • 스테이트 설정 명령 : 파이프라인들, 디스크립터 세트와 버퍼들에 대한 바인딩을 지원합니다. 또한 동적 스테이트에 대한 설정과 패스/서브패스 스테이트의 렌더링을 지원합니다.
  • 동기화 명령 : 자원을 경쟁적으로 요구하거나 메모리 상호 의존성이 있는 두 개 이상의 동작 명령 요청을 맞춰 주기 위해 사용합니다. 여기에는 이벤트 설정 또는 대기, 파이프 라인 장벽 삽입, 패스/서브패스 종속성 렌더링이 포함됩니다.

커맨드 버퍼는 명령들의 모음으로, 명령들을 레코딩(Record)하고 이를 큐에 제출합니다. 커맨드 버퍼의 생성은 비용이 큰 작업으로 성능에 영향을 줍니다. 동일한 작업을 반복해서 실행해야 하는 경우 여러 번 재사용할 수 있고, 다시 레코딩할 필요없이 다시 제출할 수 있습니다. 또한, 다중 스레드를 사용해 여러 커맨드 버퍼를 동시에 만들 수 있습니다. Vulkan은 멀티 스레드 확장성을 위해 특별히 설계돼서 커맨드 풀이 다중 스레드 환경에서 사용되는 경우 잠금 경쟁(Lock Contention)이 발생하지 않게 돼 있습니다. 

 

단일 큐

  • 제출된 커맨드 버퍼의 순서와 실행이나 재생 순서가 동일하게 유지
  • 커맨드 버퍼는 순차적으로 실행

다중 큐

  • 둘 이상의 큐에서 커맨드 버퍼를 병렬 실행
  • 커맨드 버퍼의 제출과 실행 순서는 명시적으로 지정하지 않으면 보장되지 않음.
    이를 동기화하는 것은 응용 프로그램의 책임이며 동기화하지 않으면 전혀 무관한 순서로 실행될 수 있음.

 

 

Step 8 - Main loop

메인 루프

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

 

큐에 제출되는 작업은 비동기적으로 실행됩니다. 따라서 올바른 실행 순서를 보장하기 위해 세마포어와 같은 동기화 개체를 사용해야 합니다. 이미지 획득이 완료될 때까지 기다리도록 그리기 커맨드 버퍼의 실행을 설정해야 합니다. 그렇지 않으면 화면에 표시하기 위해 아직 읽혀지고 있는 이미지로 렌더링을 시작할 수 있습니다. vkQueuePresentKHR 호출은 차례로 렌더링이 끝날 때까지 기다려야 하며, 이를 위해 렌더링이 완료된 후 신호를 받는 두 번째 세마포어를 사용할 것입니다. 


대조적으로 OpenGL 리소스 관리에서는 메모리에 대한 상세한 제어를 제공하지 않습니다. 호스트와 장치 메모리에 대한 개념이 존재하지 않으며, OpenGL 드라이버가 비밀스럽게 모든 할당을 수행합니다. 또한 이러한 할당과 하위 할당 처리를 투명하게 볼 수 없고, 드라이버에 따라 다른 방식으로 처리합니다. 이런 일관성 결여와 숨겨진 메모리 관리는 때로 예측하지 못한 동작을 발생하게 합니다. 반면 Vulkan은 선택한 메모리에 바로 개체를 할당하므로 예측 가능성이 높습니다. 

 

 

요약

정신없이 빠르게 진행된 이번 여정을 통해 첫 번째 삼각형을 그리기 위한 작업에 대한 기본적인 이해를 할 수 있을 것입니다. 실제 프로그램에서는 정점 버퍼 할당, 유니 버퍼 생성, 텍스처 이미지업로드 등 다음 장에서 다룰 더 많은 단계가 포함되어 있지만, Vulkan은 현재 학습 곡선이 가파르므 간단하게 시작하겠습니다. 처음에는 정점 버퍼를 사용하는 대신 정점 쉐이더에 정점 좌표를 임베하여 약간의 속임수를 사용하겠습니다. 정점 버퍼를 관리하려면 먼저 커맨드 버퍼에 익숙해져야 하기 때문입니다.   

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

 

  • VkInstance 생성
  • 지원되는 그래픽카드 선택(VkPhsicalDevice)
  • 그리기 및 프레젠테이션을 위한 VkDevice 및 VkQueue 생성
  • 창, 창 서페이스, 스왑 체인 생성
  • 스왑 체인 이미지를 VkImageView로 래핑
  • 렌더링 대상과 사용법을 지정하는 렌더링 패스 생성
  • 렌더링 패스에 대한 프레임 버퍼 생성
  • 그래픽 파이프라인 설정
  • 가능한 모든 스왑 체인 이미지에 대한 그리기 명령으로 커맨드 버퍼를 할당하고 기록
  • 이미지를 획득하고 올바 그리기 커맨드 버퍼를 제출하고, 이미지를 다시 스왑체인으로 반환하여 프레임 그리기

단계가 많지만 각 개별 단계의 목적은 다음 장에서 매우 간단하고 명확하게 설명할 것입니다. 전체 프로그램과 단일 단계의 관계가 헷갈리는 경우 이 장을 다시 참조하세요. 

 

 

API concepts

이 장에서는 Vulkan API가 하위 수준에서 어떻게 구조화되어 있는지에 대한 간략한 개요로 마무리하겠습니다.

 

Coding conventions

모든 Vulkan 함수, 열거형 및 구조체는 LunarG에서 개발한 Vulkan SDK에 포함된 vulkan.h 헤더에 정의되어있습니다. 다음 장에서 이 SDK를 설치하는 방법을 살펴보겠습니다.

 

함수에는 소문자 vk 접두사가 있고, 열거형 및 구조체와 같은 유형에는 Vk 접두사가 있으며, 열거값에는 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에 비해 화면이 검은색인 이유를 찾는 것이 훨씬 쉬울 수 있습니다!

코드 작성을 시작하기 전에 개발 환경을 설정하는 단계가 하나 더 있습니다.

 

 

 

 


이 블로그에서는 Vulkan에서 사용하는 특수한 용어는 혼란을 막기 위해 가급적 다음과 같이 번역합니다.

특히 Vulkan 명령과 연관된 경우 번역하지 않고 용어 발음을 그대로 사용(vkBind - 연결이 아니라 바인딩으로 번역)합니다.

 

Attachment 첨부 - (주로 이미지) 버퍼 데이터를 원하는 용도로 장치가 사용할 수 있게 제공한다.

Binding 바인딩 - 장치, 데이터 등을 원하는 참조에 연결하는 동작이다.

Device 장치 - 일반적으로 GPU 처리 장치를 의미

Extention 확장판 - 표준에 정의돼 있지 않으나 특별한 기능을 제공하기 위해 제공되는 확장 기능으로, 크로노스 그룹의 공식 확장판과 각 기업이 추가한 벤더 확장판이 있음

Expose 공개 혹은 제공 - (드라이버가 응요 프로그램이) 사용할 수 있도록 정보를 노출한다는 의미

Layer 레이어 - 특정한 (주로 검증) 목적을 위해 구성된 Vulkan 소프트웨어 계층(Level은 계층을 의미하는 일반 용어)

Layout 레이아웃 - 데이터 (주로 이미지)의 형식이나 종류를 정의

Render Pass 렌더패스 - 실제 그리는 동작을 규정하는 하나의 작업 단위를 정의

Sparse Memory  희소 메모리 - 큰 크기의 이미지로 정의하지만, 실제로는 작은 부분의 이미지만을 사용하는 메모리 관리 방식

State 스테이트 - 파이프 라인 스테이트 변수를 의미(Status는 상태를 의미하는 일반 용어)

Stage 스테이지 - 파이프 라인의 처리 단계를 의미