Post Lists

2019년 4월 17일 수요일

Vulkan Tutorial (1)

https://vulkan-tutorial.com/Introduction
https://vulkan-tutorial.com/Overview

공부하면서 기록할 것들을 적어보자.. 완전 번역하기엔 시간이 없으니, 빠르게 요점만 정리하자. Introduction 부분은 그냥 쉽게 그냥 읽으면 된다.

Overview

Origin of Vulkan
- 벌칸은 GPU에 대한 크로스 플랫폼 추상화로서 설계되었다.
- 벌칸은 프로그래머가 좀 더 장황한 API를 사용하여 그것들의 의도를 명확하게 명시하도록 하여 드라이버 오버헤드를 줄이고, 여러 쓰레드들이 병렬로 명령어를 생성하고 제출하도록 한다.
- 벌칸은 단일 컴파일러로 표준화된 바이트 코드 포맷으로 바꾸어서 쉐이더 컴파일에서 비일관성을 줄인다.
- 마지막으로, 벌칸은 그래픽스 와 연산 기능을 단일의 API로 통합하여 현대 그래픽 카드의 일반 목적 처리 능력을 수용한다.

What it takes to draw a triangle
Step1 - Instance and Physical Device Selection
- 벌칸 프로그램은 VkInstance를 통해 Vulkan API를 설정하여 시작한다.
- 한 instance는 너의 프로그램과, 너가 사용하려는 API Extensions을 설명하여 생성된다.
- 인스턴스를 생성한 후에, 너는 벌칸을 지원하는 하드웨어를 쿼리할 수 있고, 연산을 위해 사용될 한 개 이상의 VkPhysicalDevice를 선택할 수 있다.
- 너는 바람직한 장치를 선택하기 위해 VRAM size와 device capabilities같은 특성들을 쿼리할 수 있는데, 예를들어, 전용 그래픽스 카드를 사용하는 것을 선호하기 위해서이다.

Step2 - Logical device and queue families
- 사용할 올바른 하드웨어 장치를 선택한 후에, 너는 VkDevice(logical device)를 생성할 필요가 있다.
- VkDevice에서, 너는 너가 어떤 VkPhysicalDeviceFeatures를 사용할지를 좀 더 구체적으로 설명하게 된다. 그러한 특징들로는 multi viewport rendering과 64 bit floats 같은 것들이 있다.
- 또한 너는 어떤 queue families를 사용하고 싶은지를 명시할 필요가 있다.
- 왜냐하면, draw commands와 memory operations 같은 벌칸으로 수행되는 대부분의 연산들은 그것들을 VkQueue에 제출하여 비동기적으로 실행된다.
- Queues는 queue families로부터 할당되는데, 거기에서 각 queue family는 그것의 queues 안에서 특정한 연산들의 집합을 지원한다.
- 예를들어, 그래픽스, compute and memory transfer operations에 대해 별개의 queue families가 있을 수 있다.
- queue families의 이용가능성은 또한 physical device 선택시에 구분되는 요소로 사용될 수 있다.
- 벌칸 지원을 하는 한 장치가 어떠한 그래픽스 기능을 제공하지 않는 것이 가능하지만, 그러나 벌칸 지원을 가진 모든 그래픽 카드는 오늘날 일반적으로 우리가 관심있어 하는 모든 queue operations를 지원한다.

Step3 - Window surface and swap chain
-우리는 한 window에 실제로 렌더링할 두 개 이상의 컴포넌트들이 필요하다 : window surface(VkSurfaceKHR) 과 swap chain (VkSwapchainKHR). KHR 후위 표기에 주목해라. 그것은 이러한 오브젝트들이 Vulkan extension의 일부라는 것을 의미한다.
- 벌칸 API는 완전히 platform-agnostic인데, 우리가 표준화된 WSI(Window System Interface) extension을 window manager와 상호작용하기 우해 사용할 필요가 있는 이유이다. 그 surface는 렌더링할 windows에 대해 크로스 플랫폼 추상화이고, 일반적으로 native window handle에 대한 reference를 제공하여 instantiated 된다. 윈도우즈에서 예를들어 HWND이다. 운 좋게도, GLFW 라이브러리는 이 것에 대해 플랫폼 특정한 세부사항을 다루는 내장 함수를 가진다.
- swap chain은 렌더 타겟의 집합이다. 그것의 기본 목적은 우리가 현재 렌더링하고 있는 이미지를 혀냊 스크린에 있는 것과 다르게 하도록 하는 것이다.완전한 이미지들만 보이게 하는 것이 중요하다.
- 완성된 이미지를 스크린에 보이기 위해 렌더 타겟과 조건에 대한 개수는 present mode에 달려있다. 보통의 present modes는 double buffering (vsync)와 triple buffering이다.
- 어떤 플랫폼은 VH_KHR_display와 VK_KHR_display_swapchain을 통해 어떤 윈도우 매니저들과도 상호작용 하는 것 없이 디스플레이에 직접 렌더링하게 해준다.

Step4 - Image views and framebuffers
- swap chain으로부터 얻어진 이미지를 그리기 위해, 우리는 그것을 VkImageView와 VkFramebuffer에 넣어야 한다. 한 image view는 사용될 한 이미지의 특정한 부분을 참조하고, 한 프레임버퍼는 color, depth and stencil targets을 위해 사용될 image views를 참조한다. swap chain에 많은 다른 이미지들이 있을 수 있기 때문에, 우리는 우선적으로 한 image view와 그것들 각각에 대해 프레임버퍼를 만들 것이고, draw time에 올바른 것을 선택할 것이다.

Step5 - Render passes
벌칸에서의 Render passes는 렌더링 연산동안에 상요되는 이미지들의 유형들을 설명하고, 그것들이 어떻게 사용되는지와, 그것들의 내용이 어떻게 다뤄져야 하는지를 설명한다. 우리의 초기 삼각형 렌더링 어플리케이션에서, 우리는 벌카네엑 우리가 단일의 이미지를 color target으로 상요할 것이라고 말할 것이고, 우리가 그것이 drawing operation 직전에, solid color로 cleared되기를 원한다고 말할 것이다. 반면에 한 render pass는 오직 이미지들의 유형을 설명하지만, VkFramebuffer는 실제로 특정한 이미지를 이러한 slots에 바인드한다.

Step6 - Graphics pipeline
벌칸에서의 그래픽스 파이프라인은 VkPipeline object를 생성하여 설정된다. 그것은 그래픽카드의 설정가능한 상태를 설명하는데, viewport size와 depth buffer operation, VkShaderModule objects를 사용하여 programmable state 같은 상태들을 말한다. VkShaderModule objects는 shader byte code로부터 생성된다. 그 드라이버는 또한 어떤 렌더타겟들이 파이프라인에서 사용될지를 알 필요가 있다. 그래서 우리는 그 렌더패스를 참조하여 명시한다.

현존하는 API들과 비교해서 벌칸의 가장 구분되는 특징들 중 하나는 그래픽스 파이프라인의 거의 모든 환경설정이 미리 설정되어야 할 필요가 있다. 그것은 만약 너가 다른 쉐이더로 바꾸길 원하거나 약간 너의 vertex layout을 바꾸고 싶다면, 너는 전적으로 그래픽스 파이프라인을 재생성해야할 필요가 있다는 것을 의미한다.  그것은 너의 렌더링 연산을 위해 필요한 모드 ㄴ다른 조합을 위해 미리 많은 VkPipeline objects를 만들어야 한다는 것을 의미한다. viewport size와 clear color같은 오직 몇 가지 기본 설정만이 동적으로 바뀔 수 있다. 또한 모든 상태는 explicitly하게 설명되어질 필요가 있고, 예를들어, 어떠한 default color blend state가 없다.

좋은 소식은 너가 just in-time compilation에 대비하여 ahead-of-time compilation과 같은 것을 하기 떄문에, 드라이버에 대해 좀 더 최적화 기회가 있고, 런타임 성능은 좀 더 예측가능하다. 왜냐하면 다른 그래픽스 파이프라인으로 바꾼는 것 같은 많은 state changes가 매우 explicit하게 만들어지기 때문이다.

Step7 - Command pools and command buffers
이전에 언급되었듯이, drawing operations 같이 벌칸에서 우리가 실행하길 원하는 많은 연산들은 한 queue에 제출되어질 필요가 있다. 이러한 연산들은 그것들이 제출될 수 있기전에, VkCommandBuffer에 처음에 기록될 필요가 있다. 이러한 command buffers는 특정한 queue family와 연관된 VkCommonPool로 부터 할당된다. 간단한 삼각형을 그리기 위해, 우리는 다음의 연산들로 한 command buffer를 기록할 필요가 있다:

  • Begin render pass 
  • 그래픽스 파이프라인 바인드
  • 3개의 정점 그리기
  • End render pass
프레임버퍼에서의 이미지는  swap chain이 우리에게 어떤 특정한 이미지를 줄 지에 달려있기 때문에, 우리는 각 가능한 이미지에 대해 command buffer를 기록할 필요가 있고, draw time에 올바른 것을 선택할 필요가 있다. 대안은 매 프레임마다 그 command buffer를 다시 기록 하는 것인데, 효율적이지 않다.

Step8 - Main loop
drawing commands가 한 command buffer에 들어갔으니, 그 main loop는 꽤 간단하다. 우리는 처음에 swap chain으로부터 vkAcquireNextImageKHR로 한 이미지를 얻는다. 우리는 그러고나서 그 이미지에 대해 적절한 command buffer를 선택할 수 있고, vkQueueSubmit 으로 그것을 실행한다. 마지막으로, 우리는 스크린에 보이기위해 swap chain에 그 이미지를 vkQueuePresentKHR로 반환한다.

queues에 제출되는 연산들은 비동기적으로 실행된다. 그러므로, 우리는 정확한 실행순서를 보장하기 위해 세마포어 같은 동기화 오브젝트들을 사용해야만 한다.  draw command buffer의 실행은 image 획득이 마무리 되는 것을 기다리도록 설정되어야 한다. 만약 그렇지 않다면, 우리가 스크린에 보이기위해 여전히 읽혀지고 있는 이미지를 렌더링하기 시작하는 것이 발생할지도 모른다. vkQueuePresentKHR 호출은 차례대로 렌더링이 끝나기를 기다릴 필요가 있다. 왜냐하면 우리가 렌더링이 완료된 후에 signaled 되는 두 번째 세마포어를 사용할 것이기 때문이다.

Summary
- 요악해서, 첫 번째 삼각형을 그리기 위해서, 우리는 다음을 할 필요가 있다:
  • Create a VkInstance
  • 지원되는 그래픽 카드 선택 (VkPhysicalDevice)
  • drawing과 presentation을 위해 VkDevice와 VkQueue 생성
  • window, window surface, swap chain 생성
  • swap chain 이미지들을 VkImageView에 넣기
  • 렌더 타겟과 사용법을 명시하는 render pass 생성
  • 그래픽스 파이프라인 설정
  • 커맨드 버퍼를 할당하고, 모든 가능한 swap chain image에 대해 draw commands로 기록
  • 이미지를 획득하여 프레임들을 그리기, 그리고 올바른 draw command buffer를 제출하고, 그 이미지를 다시 swap chain에 반환하기.


Coding Conventions
- 한 오브젝트를 생성하거나 파괴하는 함수들은 드라이버 메모리에 대해 custom allocator를 사용하게 해주는 VkAllocationCallbacks 파라미터를 가질 것이고, 이것은 또한 이 튜토리얼에서 nullptr로 남아질 것이다.
- 거의 모든 함수들은 VK_SUCCESS or error code 둘 중 하나를 반환하는 VkResult를 반환한다. 명세 문서가 각 함수가 어떤 에러코드를 반환하고 그것들이 무엇을 의미하는지를 설명한다.

Validation layers
- 이전에 언급했듯이, 벌칸은 고성능과 낮은 드라이버 오버헤드를 위해 설계되었다. 그러므로, 그것은 기본적으로 매우 제한된 에러 체크와 디버깅 능력을 퐇마할 것이다. 그 드라이버는 만약 너가 잘못한다면 종종 에러코드를 반환하는 대신에 충돌이 날 것이다. 그것은 너의 그래픽카드에서는 작동하는 것처럼 보일 것이지만 다른 것에서 완전히 실패할 것이다.

벌칸은 너가 validation layers라고 알려진 한 기능을 통해서 광범위한 체크를 할 수 있게 해준다. Validation layers는 API와 그래픽스 드라이버 사이에 삽입될 수 있는 코드 조각들인데, 이것은 함수 파라미터에 대해 추가 체크를 하고, 메모리 관리 문제를 추적하는 것 같은 것을 하기 위해서이다. 좋은 것은, 너가 개발하는 동안 그것들을 활성화할 수 있고, 그러므로 너의 어플리케이션을 zero overhead를 위해 출시할 때 완전히 그것들을 비활성화할 수 있다. 누구든지 자신만의 validation layers를 쓸 수 있지만, LunarG가 만든 Vulkan SDK는 우리가 이 튜토리얼에서 사용할 validation layers의 표준 세트를 제공한다. 너는 또한 그 layers로 부터 debug messages를 받는 callback function을 등록할 필요가 있다.

벌칸은 모든 연산에 대해 매우 explicit하고, validation layers가 매우 광범위하기 때문에, 너의 스크린이 OpenGL과 Direct3D에 비해 왜 검정색이 되었는지를 알아내는 것이 훨씬 더 쉽다.










댓글 없음:

댓글 쓰기