주의 : 이 문서는 초심자 튜토리얼이 아닙니다. 기본 개념 정도는 안다고 가정합니다. 초심자는 [ Vulkan Tutorial ] 이나 [ Vulkan Samples Tutorial ] 을 보면서 같이 보시기 바랍니다.

주의 : 완전히 이해하고 작성한 것이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 참조하세요.



Vulkan 의 오브젝트는 크게 디스패쳐블( Dispatchable ) 오브젝트와 논디스패쳐블( Non-Dispatchable ) 오브젝트로 나뉩니다. Vulkan 명세에서는 오브젝트라는 용어 대신 핸들( handle )이라는 용어를 사용하기도 합니다.


이 디스패치( dispatch )라는 용어는 Vulkan 소스 전반에 걸쳐 나오고 있기 때문에 기본적인 개념에 대해서 파악할 필요가 있습니다. 원래 "발송하다" 는 뜻을 가지고 있는데요, 보통 프로그래밍 언어에서는 데이터를 어디론가 전송하는 것을 의미합니다. 이런 개념에서 보면 "어딘가로 보낼 수 있는 오브젝트" 정도로 생각하는 것이 타당하겠죠. 하지만 그 목적지가 어딘지, 어떤 오브젝트를 보낼 수 있는지, 이런 정보들을 그냥 "디스패쳐블" 이라는 용어만 가지고 파악하기는 어렵습니다.


그러므로 여기에서 디스패쳐블 핸들과 논디스패쳐블 핸들의 차이에 대해서 간단하게 짚고 넘어 가도록 하겠습니다.


Dispatchable Object


명세에 따르면, Vulkan 에서 디스패쳐블 오브젝트라는 것은 불투명한( opaque ) 포인터이며, 레이어가 API command 를 간섭( interception )하는 데 사용될 수 있는 포인터를 의미합니다( 불투명한 타입의 개념이 이해가 안 된다면, [ Vulkan Opaque Type ] 를 참고하시기 바랍니다 ). 그리고 핸들은 유일해야만 합니다.


이러한 유형의 핸들이 어떻게 구현되어 있는지는 모릅니다. 하지만 명세를 통해서 그것에 접근하기 위한 인터페이스를 정의해 놨기 때문에, 그것을 사용하는 레이어나 ICD( Installable Client Driver ) 등은 그것들을 사용할 수가 있습니다.


디스패쳐블 오브젝트는 다음과 같습니다 :


  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkQueue
  • VkCommandBuffer


이것은 "vulkan_core.h" 에서 다음과 같이 정의됩니다.



Non-Dispatchable Object


명세에 따르면, 논디스패쳐블 오브젝트는 구현측( 예 : 드라이버 제조사 )에 종속적인 64 비트 정수형이며, 이것의 의미는 구현측에 따라서 달라지며, 기저 오브젝트에 대한 참조라기 보다는 오브젝트 정보를 핸들에 직접적으로 인코딩하고 있을 수 있습니다. 그리고 그 핸들이 유일할 필요는 없습니다. 


논디스패쳐블 오브젝트는 다음과 같습니다 :


  • VkSemaphore
  • VkFence
  • VkBuffer
  • VkImage
  • VkEvent
  • VkQueryPool
  • VkBufferView
  • VkImageView
  • VkShaderModule
  • VkPipelineLayout
  • VkRenderPass
  • VkPipeline
  • VkDescriptorSetLayout
  • VkSampler
  • VkDescriptorPool
  • VkDescriptorSet
  • VkFramebuffer
  • VkCommandPool


이것은 "vulkan_core.h" 에서 다음과 같이 정의됩니다.



Difference ???


대부분의 환경에서 VK_DEFINE_HANDLE 이든 VK_DEFINE_NON_DISPATCHABLE_HANDLE 이든 최종 결과는 다음과 같습니다.



x86 환경에서는 두 번째 VK_DEFINE_NON_DISPATCHABLE_HANDLE 정의에 걸릴 수도 있겠지만, 요즘은 거의 x64 환경이므로 잘 모르겠네요.


사실상 디스패쳐블이든 논디스패쳐블이든 응용프로그램 입장에서 보면 불투명한 타입을 가지고 있습니다. 그렇다면 다른 기준에 의해 구분될 수 있어야 하는데, 딱히 구분할 만한 차이점을 느끼기가 어렵습니다.


이와 관련해서 검색을 해 봤는데, 공식적으로 인정받을 만한 명료한 대답을 내 놓는 사람은 없는 것 같습니다( 명세 자체가 이해가 안 가는 상황입니다 ). 단지 그럴싸한 답변은 있었습니다.


The reason is that with 64 bits, many implementations can actually encode the GPU address for the non-dispatchable object directly into the 64-bits, so they don't have to do any sort of dereference or indirection when you hand that object back. They just literally scribble what you hand them.


It's a performance optimization, even on 32-bit OSes.


출처 : Why are non dispatchable objects always typedefed to 64bit?


출처인 스레드에서는 이와 관련해서 여러 논쟁이 있었지만, 정리하자면 논디스패쳐블 오브젝트들은 GPU 메모리 주소에 직접적으로 매핑되어야 하기 때문에 64 bit 메모리 주소를 가지고 있어야만 한다는 겁니다. 


위에서 열거된 디스패쳐블 오브젝트의 종류와 논디스패쳐블 오브젝트의 종류를 비교해 보면 그럴싸 합니다. 그렇게 되면 x86 머신에서도 GPU 데이터에 대해서는 64 bit 주소를 유지할 수 있죠. 저는 이 주장을 믿기로 했습니다.


그렇게 생각하면 논디스패쳐블 오브젝트( GPU 관련 데이터 )는 드라이버 구현에 종속적이므로 간섭할 수 없는 데이터이고, 그렇지 않은 디스패쳐블 오브젝트는 간섭할 수 있는 오브젝트라는 쪽으로 논리를 이어갈 수 있습니다( 물론 이 부분은 조금 애매하긴 하지만 말이죠... ). 이런 관점에서는 디스패치 체인( Dispatch Chain )과 관련이 있는 오브젝트가 디스패쳐블 오브젝트라고 생각할 수 있을 것 같습니다( Dispatch Chain 에 들어 가니까 Dispatchable Object 이다??? ).


Object Lifetime


Vulkan 오브젝트들은 vkCreate* 와 vkAllocate* 커맨드를 통해서 생성됩니다. 그리고 이것과 쌍을 이루는 vkDestroy* 와 vkFree* 커맨드를 통해서 파괴됩니다. 절대 new delete 를 사용하지 않습니다.


vkCreate*/Destroy* 쌍은 일반적인 오브젝트 생성/파괴를 위해서 사용되며, vkAllocate*/Free* 쌍은 리소스 생성/파괴를 위해서 사용됩니다. 


Vulkan 오브젝트들의 타입은 불투명하기 때문에, 응용프로그램에서 생성파괴하는 것은 사실상 불가능하고 API 를 통해서만 생성/파괴하는 것이 가능합니다. 메모리 할당자를 통해서 오브젝트를 위한 메모리를 할당해 줄 수는 있겠지만, 그것은 단순히 메모리 할당이지 오브젝트 생성이 아닙니다. 실제 생성자나 소멸자는 감춰져 있는 모듈에서 호출됩니다.


그림1. VkCreate* 에서 오브젝트를 생성하는 과정.


이러한 오브젝트의 생명주기를 관리하는 것은 응용프로그램의 책임하에 있습니다. 그렇기 때문에 사용중인 오브젝트가 파괴되지 않도록 관리를 잘 해야 합니다. 


이에 대한 세부사항이 [ 1 ] 의 [ 2.3.1. Object Lifetime ] 섹션에 자세하게 나와 있습니다. 하지만 초심자들은 지금 본다고 해서 이해가 갈 수 있는 내용이 아니기 때문에 굳이 안 보셔도 상관이 없습니다. 어떤 오브젝트가 어떤 오브젝트에 소유권을 가지고 있는지를 텍스트로 기술하는 문서입니다. 예를 들면 "VkPipelineLayout 오브젝트가 커맨드 버퍼가 사용중일 때는 파괴되지 말아야 한다" 등의 내용입니다.


제가 생각했을 때, 이런 부분들은 스마트 포인터를 이용해 해결해야 할 부분이지 외워서 해결할 부분은 아닌 것 같습니다. 물론 불투명한 오브젝트의 생성자/소멸자를 응용프로그램에서 호출할 수 없으므로 래퍼( wrapper )를 사용해야 할 것입니다.


정리


Vulkan 에서 오브젝트는 디스패쳐블 오브젝트와 논디스패쳐블 오브젝트로 구분됩니다. 이것의 구분이 조금 모호하기는 하지만 디스패쳐블 오브젝트는 디스패치가 가능한 오브젝트이고 논디스패쳐블 오브젝트는 드라이버 관련 오브젝트라고 생각하는 것이 속이 편합니다.


Vulkan 오브젝트의 생성/파괴는 드라이버 구현에 의해서 블랙박스로 가려져 있는 상태이며, Vulkan 이 제공하는 API 를 통해서만 가능합니다. 이것은 메모리 할당자를 통해서 응용프로그램측에서 메모리를 제공하는 경우에도 마찬가지로 적용됩니다.


응용프로램은 오브젝트가 참조되고 있을 때 파괴되지 않도록 관리해야 합니다.


추가 : [ 2 ] 에서는 디스패쳐블 오브젝트에 대해서 다음과 같이 설명하고 있습니다. 위에서의 추측이 맞는 것 같습니다.




참고 자료


[ 1 ] Vulkan 1.1.89 - A Specification (with all registered Vulkan extensions), The Khronos Vulkan Working Group.


[ 2 ] Vulkan Loader DeepDive, Khronos.

+ Recent posts