목차

     

     



    인프런 삼각형님의 '삼각형의 실전! Vulkan 중급' 강의를 참고하였습니다. 

    😎 [삼각형의 실전! Vulkan 중급] 강의 들으러 가기!

     

     

     

    Vulkan Device


     

     

    Vulkan Device란?

     

    렌더링이나 컴퓨팅을 하기 위해서는 Vulkan Device가 반드시 필요함.

    Vulkan Device는 Logical한 자원임.

    Vulkan Physical DeviceVulkan Device를 통해 추상화함으로써 하드웨어 의존성벗어날 수 있음.

    • NVIDIA GPU와 AMD GPU가 Logical하게 같다면 두 Vulkan Device를 동일하다고 생각할 수 있다.
    • Logical하게 같은 두 Vulkan Device를 하나로 묶어서 mGPU로 사용할 수 있습니다.

     

     

    Vulkan Device 생성

     

     

     


     

    Vulkan Queue

     

    Vulkan Queue는 생성되는 자원이 아니라 Vulkan Device요소입니다.

     

    RDNA 아키텍처를 살펴보면 다양한 Queue가 있는 것을 확인할 수 있다. Queue는 아래에 적힌 Graphics Command Processor, AC, DMA 중 한 개에 포함된다. 이러한 분류를 Queue Family라고 한다.

    • Graphics Command Processor
    • ACE
    • DMA

     

    개발자는 다양한 Queue 중에서 어플리케이션에 적합한 Queue를 선택하여 Vulkan Device를 생성한다.

     


     

     

    Vulkan Queue family properties 얻기

     

    void vkGetPhysicalDeviceQueueFamilyProperties(
         VkPhysicalDevice physicalDevice,
         uint32_t* pQueueFamilyPropertyCount,
         VkQueueFamilyProperties* pQueueFamilyProperties);

     

    매개 변수  설명 
     physicalDevice    VkPhysicalDevice
     pQueueFamilyPropertyCount   사용할 수 있는 Queue family의 개수를 얻는데 사용되거나, 얻으려는 Queue family의 개수를 정의한 변수의 포인터입니다.
     pQueueFamilyProperties   사용할 수 있는 Queue family의 개수를 얻기 위해서는 NULL이어야 하고, Queue family의 정보를 얻기 위해선 VkQueueFamilyProperties 배열의 포인터이어야 합니다.

     

     

     

     

    VkQueueFamilyProperties 구조체

    typedef struct VkQueueFamilyProperties {
         VkQueueFlags queueFlags;
         uint32_t queueCount;
         uint32_t timestampValidBits;
         VkExtent3D minImageTransferGranularity;
    } VkQueueFamilyProperties;

     

    매개 변수  설명 
     queueFlags   VkQueue의 특징
    -  Graphics Command Processor, AC, DMA에 대한 정보가 VkQueueFlags에 담겨 있다. 
     queueCount  Queue family에 해당하는 VkQueue의 개수
     timestampValidBits  
     minImageTransferGranularity  

     


     

     

    VkQueueFlagBits 열거형

     

    typedef enum VkQueueFlagBits {
         VK_QUEUE_GRAPHICS_BIT = 0x00000001, // 랜더링 작업을 수행하기 위해서는 켜져 있어야 함.
         VK_QUEUE_COMPUTE_BIT = 0x00000002,
         VK_QUEUE_TRANSFER_BIT = 0x00000004,
         VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
         VK_QUEUE_PROTECTED_BIT = 0x00000010,
    } VkQueueFlagBits;

     

    Vulkan Queue가 어떤 기능을 제공하는지 VkQueueFlagsBits 열거형을 사용하여 알 수 있다.

     

    Vulkan을 사용해서 렌더링을 할 것이기 때문에 VK_QUEUE_GRAPHICS_BIT활성화되어 있어야 한다.

    Vulkan을 통해 랜더링 작업을 수행하기 위해서는 VK_QUEUE_GRAPHICS_BIT이 활성화된 queue-family를 선택해야 한다. 만약 이 플래그가 활성화되지 않으면 해당 queue에서 렌더링 작업을 할 수 없다.

     


     

     

    Graphics가 지원되는 Vulkan Queue family 찾기

     

     

    mQueueFamilyIndex는 VKGapPhysicalDeviceQueueFamilyProperty 함수를 통해 얻어온 배열의 인덱스를 의미한다.


     

     

    VkDeviceQueueCreateInfo 구조체

     

    typedef struct VkDeviceQueueCreateInfo {
         VkStructureType sType;
         const void* pNext;
         VkDeviceQueueCreateFlags flags;
         uint32_t queueFamilyIndex;
         uint32_t queueCount;
         const float* pQueuePriorities;
    } VkDeviceQueueCreateInfo;

     

    매개 변수  설명 
     sType 구조체의 타입을 정의
     pNext  NULL 또는 확장 기능 구조체의 포인터
     flags  Queue family에 해당하는 VkQueue의 개수
     queueFamilyIndex  Queue family index
     Queue family index는 VkQueueFamilyProperties 배열의 Index
     queueCount  VkQueue의 개수
     pQueuePriorities VkQueue간의 우선순위를 정의한 배열의 포인터
    0.0 ~ 1.0 사이의 값

     


     

     

    VkDeviceCreateInfo 구조체

     

     

    만약 queue를 2개 생성한다면 const vector<float> queuePriorities 배열의 크기도 2가 되어야 한다.


     

     

    VkDeviceCreateInfo 구조체

     

    typedef struct VkDeviceCreateInfo {
         VkStructureType sType;
         const void* pNext;
         VkDeviceCreateFlags flags;
         uint32_t queueCreateInfoCount;
         const VkDeviceQueueCreateInfo* pQueueCreateInfos;
         uint32_t enabledLayerCount;
         const char* const* ppEnabledLayerNames;
         uint32_t enabledExtensionCount;
         const char* const* ppEnabledExtensionNames;
         const VkPhysicalDeviceFeatures* pEnabledFeatures;
    } VkDeviceCreateInfo;

     

    매개 변수  설명 
     sType  구조체의 타입
     pNext  NULL 또는 확장 기능 구조체의 포인터
     flags  지금은 사용되지 않는다.
     queueCreateInfoCount  VkQueueCreateInfo 배열의 크기
     pQueueCreateInfos  VkQueueCreateInfo 배열의 포인터
     enabledLayerCount  더 이상 사용되지 않는다. 하위 호환성을 위해 존재한다.
     ppEnabledLayerNames  더 이상 사용되지 않는다. 하위 호환성을 위해 존재한다.
     enabledExtensionCount  활성화할 Extension의 개수
     ppEnabledExtensionNames  활성화할 Extension 이름 배열의 포인터
     pEnabledFeatures  

     


     

     

    Vulkan Device 정의

     


     

     

    Vulkan Device 생성

     

    VkResult vkCreateDevice(
         VkPhysicalDevice physicalDevice,
         const VkDeviceCreateInfo* pCreateInfo,
         const VkAllocationCallbacks* pAllocator,
         VkDevice* pDevice);

     

    매개 변수  설명 
     physicalDevice  VkPhysicalDevice
     pCreateInfo  VkDeviceCreateInfo 변수의 포인터
     pAllocator  
     pDevice  VkDevice 변수의 포인터

     


     

     

    Vulkan Queue 얻기

     

    void vkGetDeviceQueue(
         VkDevice device,
         uint32_t queueFamilyIndex,
         uint32_t queueIndex,
         VkQueue* pQueue);

     

    매개 변수  설명 
    device VkDevice
    queueFamilyIndex Queue family index
    queueIndex Queue family에 해당하는 VkQueue의 Index
    pQueue VkQueue 변수의 포인터

     


     

     

    Vulkan Device 파괴

     

    void vkDestroyDevice(
         VkDevice device,
         const VkAllocationCallbacks* pAllocator);

     

    매개 변수  설명 
     device VkDevice
     pAllocator Queue family index

     


     

     

    코드

     

    #include ...
    using namespace std;
    
    VkRenderer::VkRenderer() {
        // 1. VkInstance 생성
        // 2. VkPhysicalDevice 선택
    
        // ================================================================================
        // 3. VkDevice 생성
        
        uint32_t queueFamilyPropertiesCount;
    
        //---------------------------------------------------------------------------------
        //** queueFamily 속성을 조회
        // 사용 가능한 queueFamily의 수(=queueFamilyPropertiesCount)를 얻어온다.
        vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, nullptr);
    
        vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyPropertiesCount);
        // 해당 queueFamily들의 속성을 배열에 얻어온다.
        vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, queueFamilyProperties.data());
        //---------------------------------------------------------------------------------
    
        // 특정 queueFamilyProperties가 VK_QUEUE_GRAPHICS_BIT를 지원하는지 확인.
        // 지원하는 queueFamilyProperties를 찾으면 break. queueFamily에 대한 정보는 mQueueFamilyIndex에 저장.
        for (mQueueFamilyIndex = 0;
             mQueueFamilyIndex != queueFamilyPropertiesCount; ++mQueueFamilyIndex) {
            if (queueFamilyProperties[mQueueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                break;
            }
        }
    
        // 생성할 큐를 정의
        const vector<float> queuePriorities{1.0};
        VkDeviceQueueCreateInfo deviceQueueCreateInfo{
            .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
            .queueFamilyIndex = mQueueFamilyIndex,      // queueFamilyIndex
            .queueCount = 1,                            // 생성할 큐의 개수
            .pQueuePriorities = queuePriorities.data()  // 큐의 우선순위
        };
    
        // 생성할 Device 정의
        VkDeviceCreateInfo deviceCreateInfo{
            .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
            .queueCreateInfoCount = 1,                  // 큐의 개수
            .pQueueCreateInfos = &deviceQueueCreateInfo // 생성할 큐의 정보
        };
    
        // vkCreateDevice를 호출하여 Device 생성(= mDevice 생성)
        VK_CHECK_ERROR(vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice));
        // 생성된 Device(= mDevice)로부터 큐를 vkGetDeviceQueue를 호출하여 얻어온다.
        vkGetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue);
    }
    
    VkRenderer::~VkRenderer() {
        vkDestroyDevice(mDevice, nullptr);      // Device 파괴
        vkDestroyInstance(mInstance, nullptr);
        
        // queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다.
    }

     

     

    전체 코드

    더보기
    #include <cassert>
    #include <array>
    #include <vector>
    #include <iomanip>
    #include "VkRenderer.h"
    #include "VkUtil.h"
    #include "AndroidOut.h"
    using namespace std;
    
    VkRenderer::VkRenderer() {
        // ================================================================================
        // 1. VkInstance 생성
        // ================================================================================
        VkApplicationInfo applicationInfo{
            .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
            .pApplicationName = "Practice Vulkan",
            .applicationVersion = VK_MAKE_API_VERSION(0, 0, 1, 0),
            .apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0)
        };
    
        uint32_t instanceLayerCount;
        VK_CHECK_ERROR(vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr));
    
        vector<VkLayerProperties> instanceLayerProperties(instanceLayerCount);
        VK_CHECK_ERROR(vkEnumerateInstanceLayerProperties(&instanceLayerCount,
                                                          instanceLayerProperties.data()));
    
        vector<const char *> instanceLayerNames;
        for (const auto &properties: instanceLayerProperties) {
            instanceLayerNames.push_back(properties.layerName);
        }
    
        VkInstanceCreateInfo instanceCreateInfo{
            .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
            .pApplicationInfo = &applicationInfo,
            .enabledLayerCount = static_cast<uint32_t>(instanceLayerNames.size()),
            .ppEnabledLayerNames = instanceLayerNames.data()
        };
    
        VK_CHECK_ERROR(vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance));
    
        // ================================================================================
        // 2. VkPhysicalDevice 선택
        // ================================================================================
        uint32_t physicalDeviceCount;
        VK_CHECK_ERROR(vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr));
    
        vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
        VK_CHECK_ERROR(vkEnumeratePhysicalDevices(
            mInstance, &physicalDeviceCount, physicalDevices.data()));
    
        mPhysicalDevice = physicalDevices[0];
    
        VkPhysicalDeviceProperties physicalDeviceProperties;
        vkGetPhysicalDeviceProperties(mPhysicalDevice, &physicalDeviceProperties);
    
        aout << "Selected Physical Device Information ↓" << endl;
        aout << setw(16) << left << " - Device Name: "
             << string_view(physicalDeviceProperties.deviceName) << endl;
        aout << setw(16) << left << " - Device Type: "
             << vkToString(physicalDeviceProperties.deviceType) << endl;
        aout << std::hex;
        aout << setw(16) << left << " - Device ID: " << physicalDeviceProperties.deviceID << endl;
        aout << setw(16) << left << " - Vendor ID: " << physicalDeviceProperties.vendorID << endl;
        aout << std::dec;
        aout << setw(16) << left << " - API Version: "
             << VK_API_VERSION_MAJOR(physicalDeviceProperties.apiVersion) << "."
             << VK_API_VERSION_MINOR(physicalDeviceProperties.apiVersion);
        aout << setw(16) << left << " - Driver Version: "
             << VK_API_VERSION_MAJOR(physicalDeviceProperties.driverVersion) << "."
             << VK_API_VERSION_MINOR(physicalDeviceProperties.driverVersion);
    
        // ================================================================================
        // 3. VkDevice 생성
        // ================================================================================
        uint32_t queueFamilyPropertiesCount;
        vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, nullptr);
    
        vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyPropertiesCount);
        vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount,
                                                 queueFamilyProperties.data());
    
        for (mQueueFamilyIndex = 0;
             mQueueFamilyIndex != queueFamilyPropertiesCount; ++mQueueFamilyIndex) {
            if (queueFamilyProperties[mQueueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                break;
            }
        }
    
        const vector<float> queuePriorities{1.0};
        VkDeviceQueueCreateInfo deviceQueueCreateInfo{
            .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
            .queueFamilyIndex = mQueueFamilyIndex,
            .queueCount = 1,
            .pQueuePriorities = queuePriorities.data()
        };
    
        VkDeviceCreateInfo deviceCreateInfo{
            .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
            .queueCreateInfoCount = 1,
            .pQueueCreateInfos = &deviceQueueCreateInfo
        };
    
        VK_CHECK_ERROR(vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice));
        vkGetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue);
    }
    
    VkRenderer::~VkRenderer() {
        vkDestroyDevice(mDevice, nullptr);
        vkDestroyInstance(mInstance, nullptr);
    }