목차

     

     



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

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

     

     

     

    화면 출력을 위해 필요한 Vulkan Extension


     

     

    화면 출력 기능에 대한 Vulkan의 정책

     

    대개 Graphics API를 사용해서 렌더링 결과를 화면에 출력하는 것이 일반적이다. ex. DirectX12, Metal

     

    하지만 Vulkan 렌더링 결과를 화면에 출력하는 기능은 필수가 기능이 아닌 Extension으로 분류하고 있다. (원하지 않으면 안 그릴 수 있다.)

    • 렌더링 결과를 화면에 직접 출력할 필요가 없는 서버 사이드 렌더링이 대표적인 예다. ex. Stadia
      • 게임이 서버에 렌더링되고 결과가 사용자의 모니터로만 전송된다.

     

    Vulkan에서 렌더링 결과화면출력하기 위해서는 Extension사용해야 한다.

     

    Google은 클라우드 게이밍 서비스인 Stadia에서는 서버 사이드 렌더링을 쓴다. 해당 경우 대개 우측이미지처럼 포트가 없는 GPU를 사용한다. 이 말은 즉슨 화면 출력 기능이 필수가 아니라는 뜻이다.

     

     

    화면 출력을 위해서는 적어도 2개의 Instance Extension과 1개의 Device Extension이 필요하다.

     

    Vulkan Instance Extension

    • VK_KHR_surface
    • 사용하는 OS에 따라 필요한 Instance Extension이 다르다. (아래 중 하나 이상이 필요하다)  
      • VK_KHR_android_surface
      • VK_KHR_win32_surface
      • VK_KHR_wayland_surface
      • VK_MVK_ios_surface
      • 등등

     

    Vulkan Device Extension

    • VK_KHR_swapchain

     

     

    사용가능한 Vulkan Instance Extension 얻기 & 화면 출력을 위한 Vulkan Instance Extension 선택하기

     

    사용가능한 Vulkan Instance Extension 얻기

    VkResult vkEnumerateInstanceExtensionProperties(
         const char* pLayerName,
         uint32_t* pPropertyCount,
         VkExtensionProperties* pProperties);

     

    매개 변수  설명 
     pLayerName   NULL이거나 특정 Layer 이름 변수의 포인터
      특정 Layer에 해당하는 Extension을 조사하고 싶을때 사용
    pPropertyCount   사용할 수 있는 Extension의 개수를 얻는데 사용되거나, 얻으려는 Extension의 개수를 정의한 변수의 포인터
    pProperties   사용할 수 있는 Extension의 개수를 얻기 위해서는 NULL이어야 하고, Extension의 정보를 얻기 위해선 VkExtensionProperties 배열의 포인터여야 한다.

     

     

     

    화면 출력을 위한 Vulkan Instance Extension 선택하기

     

    이번에는 안드로이드에서 동작하는 코드를 작성하므로 VK_KHR_android_surface로 작성함.  


     

     

    Vulkan Instance 정의

     


     

     

    사용가능한 Vulkan Device Extension 얻기 & 화면 출력을 위한 Vulkan Device Extension 선택하기

     

    사용가능한 Vulkan Device Extension 얻기

    VkResult vkEnumerateDeviceExtensionProperties(
         VkPhysicalDevice physicalDevice,
         const char* pLayerName,
         uint32_t* pPropertyCount,
         VkExtensionProperties* pProperties);

     

    매개 변수  설명 
     physicalDevice   VkPhysicalDevice
     pLayerName   NULL이거나 특정 Layer 이름 변수의 포인터
      특정 Layer에 해당하는 Extension을 조사하고 싶을때 사용
      일반적인 경우에 pLayerName은 NULL을 사용
     pPropertyCount   사용할 수 있는 Extension의 개수를 얻는데 사용되거나, 얻으려는 Extension의 개수를 정의한 변수의 포인터
     pProperties   사용할 수 있는 Extension의 개수를 얻기 위해서는 NULL이어야 하고, Extension의 정보를 얻기 위해선 VkExtensionProperties 배열의 포인터여야 한다.

     

     

     

     

    화면 출력을 위한 Vulkan Device Extension 선택하기

     

     

     

    Vulkan Device 정의

     


     

     

    코드

     

    #include ...
    using namespace std;
    
    VkRenderer::VkRenderer() {
        // ================================================================================
        // 1. VkInstance 생성
        // ================================================================================
        // VkApplicationInfo 구조체 정의
        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 &layerProperty : instanceLayerProperties) {
            instanceLayerNames.push_back(layerProperty.layerName);
        }
    
        uint32_t instanceExtensionCount; // 사용 가능한 InstanceExtension 개수
        VK_CHECK_ERROR(vkEnumerateInstanceExtensionProperties(nullptr,
                                                              &instanceExtensionCount,
                                                              nullptr));
    
        vector<VkExtensionProperties> instanceExtensionProperties(instanceExtensionCount);
        VK_CHECK_ERROR(vkEnumerateInstanceExtensionProperties(nullptr,
                                                              &instanceExtensionCount,
                                                              instanceExtensionProperties.data()));
    
        vector<const char *> instanceExtensionNames; // instanceExtensionName을 담는 배열
        for (const auto &properties: instanceExtensionProperties) {
            if (properties.extensionName == string("VK_KHR_surface") ||
                properties.extensionName == string("VK_KHR_android_surface")) {
                instanceExtensionNames.push_back(properties.extensionName);
            }
        }
        assert(instanceExtensionNames.size() == 2); // 반드시 2개의 이름이 필요하기 때문에 확인
    
        // sType: 구조체의 타입, pApplicationInfo: 어플리케이션의 이름
        // enabledLayerCount, ppEnableLayerNames: 사용할 레이어의 정보를 정의
        VkInstanceCreateInfo instanceCreateInfo{
            .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
            .pApplicationInfo = &applicationInfo,
            .enabledLayerCount = static_cast<uint32_t>(instanceLayerNames.size()),
            .ppEnabledLayerNames = instanceLayerNames.data(),
            .enabledExtensionCount = static_cast<uint32_t>(instanceExtensionNames.size()),
            .ppEnabledExtensionNames = instanceExtensionNames.data()
        };
    
        // vkCreateInstance로 인스턴스 생성. 생성된 인스턴스가 mInstance에 쓰여진다.
        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()));
    
        // 간단한 예제를 위해 첫 번째 VkPhysicalDevice를 사용
        mPhysicalDevice = physicalDevices[0];
    
        VkPhysicalDeviceProperties physicalDeviceProperties; // 이 구조체 안에 GPU에 필요한 모든 정보가 있다.
        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;
    
        //---------------------------------------------------------------------------------
        //** 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()  // 큐의 우선순위
        };
    
        uint32_t deviceExtensionCount; // 사용 가능한 deviceExtension 개수
        VK_CHECK_ERROR(vkEnumerateDeviceExtensionProperties(mPhysicalDevice,
                                                            nullptr,
                                                            &deviceExtensionCount,
                                                            nullptr));
    
        vector<VkExtensionProperties> deviceExtensionProperties(deviceExtensionCount);
        VK_CHECK_ERROR(vkEnumerateDeviceExtensionProperties(mPhysicalDevice,
                                                            nullptr,
                                                            &deviceExtensionCount,
                                                            deviceExtensionProperties.data()));
    
        vector<const char *> deviceExtensionNames;
        for (const auto &properties: deviceExtensionProperties) {
            if (properties.extensionName == string("VK_KHR_swapchain")) {
                deviceExtensionNames.push_back(properties.extensionName);
            }
        }
        assert(deviceExtensionNames.size() == 1); // VK_KHR_swapchain이 반드시 필요하기 때문에 확인
    
        // 생성할 Device 정의
        VkDeviceCreateInfo deviceCreateInfo{
                .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
                .queueCreateInfoCount = 1,                   // 큐의 개수
                .pQueueCreateInfos = &deviceQueueCreateInfo, // 생성할 큐의 정보
                .enabledExtensionCount = static_cast<uint32_t>(deviceExtensionNames.size()),
                .ppEnabledExtensionNames = deviceExtensionNames.data() // 활성화하려는 deviceExtension들을 넘겨줌
        };
    
        // 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 파괴. queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다.
        vkDestroyInstance(mInstance, nullptr);
    }

     


     

     

    '⭐ Vulkan & CMake > Vulkan' 카테고리의 다른 글

    [Vulkan] Vulkan Swapchain  (0) 2024.08.05
    [Vulkan] Vulkan Surface  (0) 2024.08.04
    [Vulkan] Vulkan Device  (0) 2024.08.02
    [Vulkan] Vulkan Physical device  (0) 2024.08.02
    [Vulkan] Vulkan Instance  (0) 2024.08.02