Vulkan Samples Tutorial-03-Create a (Logical) Device

in #vulkan8 years ago

이제 VkDevice object를 만들 차례입니다.

  • VkDevice obj는 시스템상의 실제GPU와 대응되며
  • GPU HW에 직접 gpu command를 보낼 수 있게하는 역할을 합니다.

Device 선택하기

웬만한 PC상에는 GPU가 하나 이상은 있죠. 물론 sample에선 device 리스팅시 나오는 첫 GPU를 선택하게 되어 있습니다. 선택했다고 바로 device object를 만드는 것은 아니고 queue 관련설정을 해줘야 합니다.

Device Queue와 Queue Families

"Unlike other graphics APIs,~" 라는표현이 나오는데 DirectX, OpenGL, OpengGLES 등등 수 많은 API들이 있겠지만 OpenGLES를 주로 사용하다보니 OpenGLES와 비교를 하겠습니다. 웬만한건 다 driver 안에서 처리되기 때문에 queue?가 뭐지 하고 생각할 수 있습니다. 핵심은 Vulkan에선 세밀한 코딩을 user단 개발에 넘긴다는 것이죠.

아무튼, queue가 뭐냐면

  • 추상화된 메커니즘으로 GPU에 command를 날리는데 사용된다.
  • 나중에 command buffer에 command들을 어떻게 채우고 queue로 어떻게 보내는지 알게 된답니다. -_-
    이렇게 command들을 buffer에 채우고 queue로 보내주면서 GPU가 async하게 동작할 수 있게 해주는게 포인트네요.

Vulkan에서는 queue를 종류에 따라 queue family로 정렬을 하고, queue의 종류와 속성을 찾고 싶으면 device로 부터 QueueFamilyProperties 쿼리를 날려야 합니다.

아.. 꼬리에 꼬리를 뭅니다만, 계속 반복하다보면 외워질겁니다 :)

##복습

  1. per-application에 VkInstance obj가 있고 obj는 application state를 관리한다.
  2. VkInstance 만으론 할 수 있는게 없고 GPU를 찾아 application이 사용할 수 있게끔 해줘야 한다.
  3. VkPhysicalDevice list를 받아서 어느 device를 쓸 건지 정하고 쓰기 위해서 VkDevice obj를 만들어야 한다.
  4. 그런데 VkDevice obj를 만들기 전에 queue 설정을 해야한다. :)
typedef struct VkQueueFamilyProperties {
    VkQueueFlags    queueFlags;
    uint32_t        queueCount;
    uint32_t        timestampValidBits;
    VkExtent3D      minImageTransferGranularity;
} VkQueueFamilyProperties;

typedef enum VkQueueFlagBits {
    VK_QUEUE_GRAPHICS_BIT = 0x00000001,
    VK_QUEUE_COMPUTE_BIT = 0x00000002,
    VK_QUEUE_TRANSFER_BIT = 0x00000004,
    VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
} VkQueueFlagBits;

sample 코드를 보면서 순차적으로 처음 보는 type과 function들을 살펴보죠.

 vkGetPhysicalDeviceQueueFamilyProperties(info.gpus[0], &info.queue_family_count, NULL);
    assert(info.queue_family_count >= 1);

    info.queue_props.resize(info.queue_family_count);
    vkGetPhysicalDeviceQueueFamilyProperties(info.gpus[0], &info.queue_family_count, info.queue_props.data());
    assert(info.queue_family_count >= 1);

모든 properties 함수를 사용하는 첫 단계로 속성의 count를 얻어오고
그 개 수만큼 vector를 resize 해줍니다. (지난번 post에도 적었으나 resize하지 않으면 이후 sample에서 다음 step에 해당하는 vk function 호출시 error가 발생합니다...)
참고로 info.queue_props도

std::vector<VkQueueFamilyProperties> queue_props;

이며 위 struct type을 family라고 표합니다. 이유는 아래 설명할 특정 queueFlags가 있는 사용가능한 qeueue들이 많이 모여 있기 때문입니다. (표현이 좀 약하네요 ㅠㅠ)

코드는 우선 속성 정보를 얻어온 다음 loop를 돌며 queueFlags에 VK_QUEUE_GRAPHICS_BIT flag가 있는 queue를 찾습니다.

    bool found = false;
    for (unsigned int i = 0; i < info.queue_family_count; i++) {
        if (info.queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
            queue_info.queueFamilyIndex = i;
            found = true;
            break;
        }
    }


이 그림부터 까딱하면 헷갈릴 수가 있습니다만 쉽게 보면

  • queueFlags로 queue의 속성, 이게 3D 렌더링을 위한건지, 컴퓨팅 계산을 위한건지, 아니면 걍 block copy를 위한건지 볼 수 있고,
  • 이 속성은 여러분이 사용하고자 하는 GPU HW에 따라 queue family 별로 다를 수도 있고 합쳐질 수 도 있다는 겁니다.
  • 일반적으로 GPU는 3D 렌더링을 해야하니 queue family index 0에 VK_QUEUE_GRAPHCIS_BIT이 설정되어 있는데, pixel block copy를 지원하는 특수 HW가 달려있다면 쿼리를 날렸을 때 1번 queue family index에는 VK_QUEUE_TRANSFER_BIT이 설정되어 있을 수 있겠죠.

아니면 아래 그림 처럼 어떤 GPU는 복수의 flag가 설정된 경우도 있을 것이고요.

다시 돌아와서 -_- sample에서는 뭐.. 보통 삼각형이라도 그리듯 뭔가를 그리는게 목적입니다. 그래서 VK_QUEUE_GRAPHICS_BIT flag를 찾아서 해당 index를 기억해 둡니다. 여기서는 handle로 안하고 index로 하는게 좀 의아하긴 하나 우선 넘어가시죠.

자 이제 맨 처음 VkInstance obj를 만들 때 모습과 흡사하게 VkDevice obj를 만듭니다.

     VkDeviceQueueCreateInfo queue_info = {}; // 이게 샘플 초반에 선언됩니다.
    float queue_priorities[1] = {0.0};
    queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queue_info.pNext = NULL;
    queue_info.queueCount = 1;
    queue_info.pQueuePriorities = queue_priorities;

    VkDeviceCreateInfo device_info = {};
    device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    device_info.pNext = NULL;
    device_info.queueCreateInfoCount = 1;
    device_info.pQueueCreateInfos = &queue_info;
    device_info.enabledExtensionCount = 0;
    device_info.ppEnabledExtensionNames = NULL;
    device_info.enabledLayerCount = 0;
    device_info.ppEnabledLayerNames = NULL;
    device_info.pEnabledFeatures = NULL;

    VkDevice device;
    VkResult U_ASSERT_ONLY res = vkCreateDevice(info.gpus[0], &device_info, NULL, &device);

설명에선 sample은 queue 하나를 쓰니 queue_info struct의 field에 굳이 세세히 설정하지 않아도 된다 -_-
라고 하네요 허허

우선 웬만한 설정은 NULL이나 0입니다만, 차차 쓰임새가 있을테니 지금까지의 과정만 헷갈리지 않으면 됩니다.
Vulkan에서 device layer는 곧 안쓰일 예정이라 시원시원하게 넘어가서 VkDevice object를 생성하였습니다!

와 역시 OpenGLES 에서 삼각형 만드는건 몇 줄로 끝났는데 여긴 아직 shader는 시작도 못했네요 :)
그래도 하나씩 밟아 나가면 cube를 곧 볼 수 있겠죠!
다음에는 Command buffer를 생성하는 예제랍니다.