목차

     

     


    인프런 삼각형님의 '삼각형의 실전! Vulkan 중급' 강의를 참고하였습니다. 
    😎 [삼각형의 실전! Vulkan 중급] 강의 들으러 가기!

     

     

    Vulkan Swapchain


     

     

    Vulkan Swapchain이란?

     

    Vulkan Swapchain은 화면에 출력되는 버퍼를 관리하는 객체다.

    Vulkan Swapchain을 생성시, 화면 출력을 위한 컬러 스페이스, 이미지 포맷 등 다양한 옵션을 설정할 수 있다.

     

     

     

    일련의 chain된 이미지가 있고 화면에 표시하기 위해 Swap한다.

    ex. GPU가 ImageB를 렌더링 하는 동안 ImageA를 사용자에게 보여준다.


     

     

    Vulkan Swapchain 생성

     


     

     

    VkSwapchainCreateInfoKHR 구조체

     

    typedef struct VkSwapchainCreateInfoKHR {
         VkStructureType sType;
         const void* pNext;
         VkSwapchainCreateFlagsKHR flags;
         VkSurfaceKHR surface;
         uint32_t minImageCount;
         VkFormat imageFormat;
         VkColorSpaceKHR imageColorSpace;
         VkExtent2D imageExtent;
         uint32_t imageArrayLayers;
         VkImageUsageFlags imageUsage;
         VkSharingMode imageSharingMode;
         uint32_t queueFamilyIndexCount;
         const uint32_t* pQueueFamilyIndices;
         VkSurfaceTransformFlagBitsKHR preTransform;
         VkCompositeAlphaFlagBitsKHR compositeAlpha;
         VkPresentModeKHR presentMode;
         VkBool32 clipped;
         VkSwapchainKHR oldSwapchain;
    } VkSwapchainCreateInfoKHR

     

    매개 변수  설명 
     sType   구조체의 타입
     pNext    NULL 또는 확장 기능 구조체의 포인터
     flags  
     surface    VkSurface
     minImageCount   VkSwapchain이 가져야할 최소 이미지 개수.
      더블 버퍼링 2. 트리플 버퍼링은 3.
     imageFormat   VkSwapchain이 가지고 있는 VkImage의 포맷
    imageColorSpace   VkSwapchain이 가지고 있는 VkImage의 색영역
    imageExtent   VkSwapchain이 가지고 있는 VkImage의 크기
    imageArrayLayers   일단 1을 사용. 자세한 사용은 찾아보자.
    imageUsage   VkSwapchain이 가지고 있는 VkImage의 사용 목적
    imageSharingMode   
    queueFamilyIndexCount   
    pQueueFamilyIndices   
    preTransform   VkSurface에 적용할 변환(회전)을 지정
    compositeAlpha   다른 윈도우들과 어떻게 합성될지 정의
    presentMode   VkSwapchain의 출력 방법을 정의
    clipped   화면에 보이지 않는 부분을 Clip할지 결정
    oldSwapchain   VK_NULL_HANDLE이거나 파괴하려는 VkSwapchain

     


     

     

    Vulkan Surface Capabilities 얻기

     

    VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
         VkPhysicalDevice physicalDevice,
         VkSurfaceKHR surface,
         VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);

     

    매개 변수  설명 
     physicalDevice   VkPhysicalDevice
     surface   VkSurface
     pSurfaceCapabilities    VkSurfaceCapabilitiesKHR 변수의 포인터

     


     

     

    VkSurfaceCapabilitiesKHR 구조체

     

    typedef struct VkSurfaceCapabilitiesKHR {
         uint32_t minImageCount;
         uint32_t maxImageCount;
         VkExtent2D currentExtent;
         VkExtent2D minImageExtent;
         VkExtent2D maxImageExtent;
         uint32_t maxImageArrayLayers;
         VkSurfaceTransformFlagsKHR supportedTransforms;
         VkSurfaceTransformFlagBitsKHR currentTransform;
         VkCompositeAlphaFlagsKHR supportedCompositeAlpha;
         VkImageUsageFlags supportedUsageFlags;
    } VkSurfaceCapabilitiesKHR;

     

     

    minImageCount 옵션

     

      • minImageCount는 버퍼링을 의미하며 2인 경우는 더블 버퍼링, 3인 경우는 트리플 버퍼링이라고 한다.
      • minImageCount는 최소 이미지 개수이기 때문에 Vulkan Swapchain이 더 많은 이미지를 가질 수도 있다.
      • minImageCount가 높을수록 FPS올라가고 Latency느려진다.
        • SwapChain의 이미지가 많을수록 더 많은 프레임을 미리 준비할 수 있지만, 최종적으로 화면에 반영되기까지의 시간은 늘어난다.

    위 이미지의 SwapChain은 화면에 출력되는 FrontBuffer 1개와 업데이트가 가능한 BackBuffer 2개를 가지고 있다.


     

     

    presentMode 옵션

     

    화면 출력을 요청할 때, 어떤 방식으로 화면에 출력할지를 결정하는 옵션이다.

     

    typedef enum VkPresentModeKHR {
         VK_PRESENT_MODE_IMMEDIATE_KHR = 0,
         VK_PRESENT_MODE_MAILBOX_KHR = 1,
         VK_PRESENT_MODE_FIFO_KHR = 2,
         VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3,
         VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR = 1000111000,
         VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR = 1000111001,
    } VkPresentModeKHR;

     

     

    VK_PRESENT_MODE_IMMEDIATE_KHR & VK_PRESENT_MODE_FIFO_KHR 모드

     

    VK_PRESENT_MODE_IMMEDIATE_KHR 모드

    • Vulkan Swapchain이 관리하고 있는 Vulkan Image는 총 7개다.
    • 1번, 5번, 7번:
      • 어플리케이션에서 (렌더링을 위해) 사용함.
    • 2번, 3번, 6번:
      • (어플리케이션 요청에) 대기중
      • 어플리케이션이 요청하면 Vulkan Swapchain에서 어플리케이션으로 반환된다.
    • 4번:
      • 화면(ex. 모니터)에 출력중

    IMMIDIATE 모드에서 출력을 요청하면 그 즉시 화면에 출력된다.

     

    렌더링 결과를 가능한 빠르게 보여줄 수 있지만 모니터 갱신 주기와 출력 타이밍이 일치하지 않을 경우 화면에 잘리는 티어링 문제가 발생할 수 있다. (티어링 문제: 아래의 이미지처럼 이미지가 잘리는 현상을 의미한다.)

     

    게임 옵션의 수직 동기화 옵션을 끄면 화면이 잘릴 수 있는데 이러한 이유로 티어링이 발생할 수 있다.

    티어링 문제가 발생한 화면 예시

     

     

     

     

    VK_PRESENT_MODE_FIFO_KHR 모드

    • Vulkan Swapchain이 관리하고 있는 Vulkan Image는 총 7개다.
    • 1번, 5번, 7번:
      • 어플리케이션에서 렌더링을 위해 사용함.
    • 2번, 3번, 6번:
      • (어플리케이션 요청에) 대기중
      • 어플리케이션이 요청하면 Vulkan Swapchain에서 어플리케이션으로 반환된다.
    • 4번:
      • 화면(ex. 모니터)에 출력중
    • 6번:
      • 출력 대기중
      • 다음 VSync가 맞춰서 화면에 출력된다.

     

    FIFO 모드는 티어링 문제가 발생하지 않지만 렌더링 결과가 화면에 표시되기 전까지 queue에서 기다리기 때문에 Laytency가 길어질 수 있다.

     

    FIFO 모드는 OpenGL에서 eglSwapInterval을 1로 설정한 것과 동일한 효과를 갖는다.

    eglSwapInterval(display, 1);

     

     

    사용할 수 있는 Vulkan Present Mode 얻기

     

    VkResult vkGetPhysicalDeviceSurfacePresentModesKHR(
         VkPhysicalDevice physicalDevice,
         VkSurfaceKHR surface,
         uint32_t* pPresentModeCount,
         VkPresentModeKHR* pPresentModes);

     

    매개 변수  설명 
     physicalDevice   VkPhysicalDevice
     surface   VkSurface
     pPresentModeCount   사용할 수 있는 VkPresentMode의 개수를 얻는데 사용되거나, 얻으려는 VkPresentMode의 개수를 정의한 변수의 포인터
     pPresentModes   사용할 수 있는 VkPresentMode의 개수를 얻기 위해서는 NULL이어야 하고, VkPresentMode의 정보를 얻기 위해선 VkPresentModeKHR 배열의 포인터여야 한다.

     

    사용 가능한 PresentMode의 수(=PresentModeCount)를 알아내기 위해 vkGetPhysicalDeviceSurfacePresentModeKHR 함수를 호출한다. 이 때, 마지막 인자로 nullptr를 전달함으로써 사용 가능한 presentModeCount를 얻어온다.

     

    presentModeCount 만큼 배열을 할당한다

    vkGetPhysicalDeviceSurfacePresentModeKHR 함수를 호출하여 실제로 사용 가능한 presentModes.data()를 얻는다.

    마지막 인자는 배열의 포인터임을 명심하자.


     

     

    Vulkan Surface Format이란?

     

    Vulkan Swapchain 생성시 필요한 Format, Color space은 Vulkan Surface가 지원하는 옵션 중에서 선택해야 한다.

    그 이유는 Vulkan Swapchain의 Vulkan Image는 Vulkan Surface로 출력되는 때문이다.

     

    typedef struct VkSurfaceFormatKHR {
         VkFormat format;
         VkColorSpaceKHR colorSpace;
    } VkSurfaceFormatKHR;

     

    매개 변수  설명 
      format   VkFormat
      colorSpace   VkColorSpace

     


    모니터를 구매할 때 색 재현율에 대한 광고들을 볼 수 있다. 

     

    모니터가 지원하는 색 깊이와 색재현율이 Color space다.

     

     

    Color space의 차이는 색상의 표현 범위와 정확도에 영향을 미친다. 렌더링 결과물의 색상이 사용자에게 어떻게 보이는지 결정짓는 요소다.

     

    Vulkan은 Color space를 명시적으로 지정할 수 있으며 이를 통해 개발자는 어플리케이션의 비주얼을 더 정밀히 제어할 수 있다.


     

     

     

    사용할 수 있는 Vulkan Surface Format 얻기

     

    VkResult vkGetPhysicalDeviceSurfaceFormatsKHR(
         VkPhysicalDevice physicalDevice,
         VkSurfaceKHR surface,
         uint32_t* pSurfaceFormatCount,
         VkSurfaceFormatKHR* pSurfaceFormats);

     

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

     

    사용 가능한 surfaceFormat의 수(=surfaceFormatCount)를 알아내기 위해 vkGetPhysicalDeviceSurfaceFormatsKHR 함수를 호출한다. 이 때, 마지막 인자로 nullptr를 전달함으로써 사용 가능한 surfaceFormatCount를 얻어온다.

    surfaceFormatCount 만큼 배열을 할당한다

    vkGetPhysicalDeviceSurfaceFormatsKHR 함수를 호출하여 실제로 사용 가능한 surfaceFormats.data()를 얻는다.

    마지막 인자는 배열의 포인터임을 명심하자.


     

     

    Vulkan Composite Alpha란?

     

    Composite Alpha는 윈도우들이 겹쳐 있을 겨우, 그 윈도우들 어떻게 혼합해서 보여줄지 결정하는 옵션이다.

     

    만약 오버레이가 되는 어플리케이션을 개발한다면 이 옵션을 고려해야 한다. 하지만 일반적으로 개발하는 어플리케이션은 투명하지 않기 때문에 Composite Alphs를 심도 있게 이해하는것은 필요시에 학습하자. 

     

     


     

     

    VkCompositeAlphaFlagBitsKHR 열거형

     

    typedef enum VkCompositeAlphaFlagBitsKHR {
         VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR = 0x00000001,
         VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR = 0x00000002,
         VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR = 0x00000004,
         VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR = 0x00000008,
    } VkCompositeAlphaFlagBitsKHR;

     

     

     

    사용할 수 있는 Vulkan Composite Alpha 찾기


     

     

    VkImageUsageFlagBits 열거형

     

    VkImageUsageFlagBits 는 이미지를 어떻게 사용할지 Vulkan 드라이버에게 알려주어 드라이버 내부에서 최적화를 수행할 수 있게 한다. 반드시 필요한 이미지의 사용 용도를 정확하게 지정해야 한다. 

    typedef enum VkImageUsageFlagBits {
         VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001, // 이미지가 복사 연산에 사용될 때
         VK_IMAGE_USAGE_TRANSFER_DST_BIT = 0x00000002, // 이미지가 복사 연산에 사용될 때
         VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004,	   // 이미자가 샘플러에 있을때 필요. 주로 텍스처로 사용되는 이미지
         VK_IMAGE_USAGE_STORAGE_BIT = 0x00000008,  	   // Storage 이미지로 사용될 때
         VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT = 0x00000010, // 렌더링의 결과가 쓰여지는 이미지에 필요. 프레임 버퍼에 사용되는 이미지에 필요
         VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000020, // Depth나 Stencil에 사용될 이미지에 필요
         VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT = 0x00000040,
         VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT = 0x00000080,
    } VkCompositeAlphaFlagBitsKHR;


    Vulkan 드라이버에서 이 정보를 활용하여 최적화를 수행한다.

     

     

    surfaceCapabilities.supportedUsageFlags와 이미지를 & 연산을 수행해서 VK_IMAGE_COLOR_ATTACHMENT_BIT이 지원되는지 확


     

     

    VkSwapchainCreateInfoKHR 구조체

     


     

     

    Vulkan Swapchain 생성

     

    VkResult vkCreateSwapchainKHR(
         VkDevice device,
         const VkSwapchainCreateInfoKHR* pCreateInfo,
         const VkAllocationCallbacks* pAllocator,
         VkSwapchainKHR* pSwapchain);

     

    매개 변수  설명 
    device   VkDevice
    pCreateInfo   VkSwapchainCreateInfoKHR 변수의 포인터
    pAllocator   일단 NULL을 사용. 자세한 내용은 찾아보기.
    pSwapchain   VkSwapchainKHR 변수의 포인터

     

    mSwapchain 생성

     


     

     

    Vulkan Swapchain Image 얻기

     

    • Vulkan Swapchain을 생성할 때 최소 Vulkan Image의 개수를 정했다. 하지만 실제로는 더 많은 Vulkan Image생성됐을 수 있다.
    • Vulkan Swapchain에 몇개의 Vulkan Image를 가지고 있는지 물어봐야 한다.
    • Vulkan Swapchain의 Vulkan Image는 Window System소유권을 가지고 있기 때문에 파괴하면 안된다.

     

    VkResult vkGetSwapchainImagesKHR(
         VkDevice device,
         VkSwapchainKHR swapchain,
         uint32_t* pSwapchainImageCount,
         VkImage* pSwapchainImages);

     

    매개 변수  설명 
    device   VkDevice
    swapchain   VkSwapchainKHR
    pSwapchainImageCount   VkSwapchain이 가지고 있는 VkImage의 개수를 얻는데 사용되거나, 얻으려는 VkImage의 개수를 정의한 변수의 포인터
    pSwapchainImages   VkSwapchain이 가지고 있는 VkImage를 얻기 위해서는 NULL이어야 하고, VkImage를 얻기 위해선 VkImage 배열의 포인터

     

    마지막 인자를 nullptr로 전달하여 사용 가능한 이미지의 수를 얻어온다.   

     

    이미지의 수만큼 배열을 할당하고 실제 사용 가능한 이미지의 개수를 얻기 위해 vkGetSwapchainImagesKHR를 호출한다.

    마지막 인자는 배열의 포인터다. 


     

     

    Vulkan Swapchain 파괴

     

    void vkDestroySwapchainKHR(
         VkDevice device,
         VkSwapchainKHR swapchain,
         const VkAllocationCallbacks* pAllocator);

     

    매개 변수  설명 
    device   VkDevice
    swapchain   VkSwapchainKHR
    pAllocator   일단 NULL을 사용. 자세한 내용은 찾아보기.

     


     

     

    코드

     

    #include ...
    using namespace std;
    
    VkRenderer::VkRenderer() {
        // 1. VkInstance 생성
        // 2. VkPhysicalDevice 선택
        // 3. VkDevice 생성
        // 4. VkSurface 생성
      
        // 5. VkSwapchain 생성
        VkSurfaceCapabilitiesKHR surfaceCapabilities;
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice,
                                                                 mSurface,
                                                                 &surfaceCapabilities));
    
        VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR;
        for (auto i = 0; i <= 4; ++i) {
            if (auto flag = 0x1u << i; surfaceCapabilities.supportedCompositeAlpha & flag) {
                compositeAlpha = static_cast<VkCompositeAlphaFlagBitsKHR>(flag);
                break;
            }
        }
        assert(compositeAlpha != VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR);
    
        VkImageUsageFlags imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
        assert(surfaceCapabilities.supportedUsageFlags & imageUsage);
    
        uint32_t surfaceFormatCount = 0;
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice,
                                                            mSurface,
                                                            &surfaceFormatCount,
                                                            nullptr));
    
        vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatCount);
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice,
                                                            mSurface,
                                                            &surfaceFormatCount,
                                                            surfaceFormats.data()));
    
        uint32_t surfaceFormatIndex = VK_FORMAT_MAX_ENUM;
        for (auto i = 0; i != surfaceFormatCount; ++i) {
            if (surfaceFormats[i].format == VK_FORMAT_R8G8B8A8_UNORM) {
                surfaceFormatIndex = i;
                break;
            }
        }
        assert(surfaceFormatIndex != VK_FORMAT_MAX_ENUM);
    
        uint32_t presentModeCount;
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice,
                                                                 mSurface,
                                                                 &presentModeCount,
                                                                 nullptr));
    
        vector<VkPresentModeKHR> presentModes(presentModeCount);
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice,
                                                                 mSurface,
                                                                 &presentModeCount,
                                                                 presentModes.data()));
    
        uint32_t presentModeIndex = VK_PRESENT_MODE_MAX_ENUM_KHR;
        for (auto i = 0; i != presentModeCount; ++i) {
            if (presentModes[i] == VK_PRESENT_MODE_FIFO_KHR) {
                presentModeIndex = i;
                break;
            }
        }
        assert(presentModeIndex != VK_PRESENT_MODE_MAX_ENUM_KHR);
    
        VkSwapchainCreateInfoKHR swapchainCreateInfo{
                .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
                .surface = mSurface,
                .minImageCount = surfaceCapabilities.minImageCount,
                .imageFormat = surfaceFormats[surfaceFormatIndex].format,
                .imageColorSpace = surfaceFormats[surfaceFormatIndex].colorSpace,
                .imageExtent = surfaceCapabilities.currentExtent,
                .imageArrayLayers = 1,
                .imageUsage = imageUsage,
                .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
                .preTransform = surfaceCapabilities.currentTransform,
                .compositeAlpha = compositeAlpha,
                .presentMode = presentModes[presentModeIndex]
        };
    
        VK_CHECK_ERROR(vkCreateSwapchainKHR(mDevice, &swapchainCreateInfo, nullptr, &mSwapchain));
    
        uint32_t swapchainImageCount;
        VK_CHECK_ERROR(vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount, nullptr));
    
        mSwapchainImages.resize(swapchainImageCount);
        VK_CHECK_ERROR(vkGetSwapchainImagesKHR(mDevice,
                                               mSwapchain,
                                               &swapchainImageCount,
                                               mSwapchainImages.data()));
    }
    
    VkRenderer::~VkRenderer() {
        vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr);
        ...
    }

     

     

    전체 코드

    더보기
    // MIT License
    //
    // Copyright (c) 2024 Daemyung Jang
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to deal
    // in the Software without restriction, including without limitation the rights
    // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    // copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in all
    // copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    // SOFTWARE.
    
    #include <cassert>
    #include <array>
    #include <vector>
    
    #include "VkRenderer.h"
    #include "VkUtil.h"
    #include "AndroidOut.h"
    
    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);
    
    
        // ================================================================================
        // 4. VkSurface 생성
        // ================================================================================
        VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo{
                .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
                .window = window
        };
    
        // surface 생성.
        VK_CHECK_ERROR(vkCreateAndroidSurfaceKHR(mInstance, &surfaceCreateInfo, nullptr, &mSurface));
    
        VkBool32 supported; // surface 지원 여부
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice,
                                                            mQueueFamilyIndex,
                                                            mSurface,
                                                            &supported)); // 지원 여부를 받아옴.
        assert(supported);
    
    
        // ================================================================================
        // 5. VkSwapchain 생성
        // ================================================================================
        VkSurfaceCapabilitiesKHR surfaceCapabilities;
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice,
                                                                 mSurface,
                                                                 &surfaceCapabilities));
    
        VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR;
        for (auto i = 0; i <= 4; ++i) {
            if (auto flag = 0x1u << i; surfaceCapabilities.supportedCompositeAlpha & flag) {
                compositeAlpha = static_cast<VkCompositeAlphaFlagBitsKHR>(flag);
                break;
            }
        }
        assert(compositeAlpha != VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR);
    
        VkImageUsageFlags imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
        assert(surfaceCapabilities.supportedUsageFlags & imageUsage);
    
        uint32_t surfaceFormatCount = 0;
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice,
                                                            mSurface,
                                                            &surfaceFormatCount,
                                                            nullptr));
    
        vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatCount);
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice,
                                                            mSurface,
                                                            &surfaceFormatCount,
                                                            surfaceFormats.data()));
    
        uint32_t surfaceFormatIndex = VK_FORMAT_MAX_ENUM;
        for (auto i = 0; i != surfaceFormatCount; ++i) {
            if (surfaceFormats[i].format == VK_FORMAT_R8G8B8A8_UNORM) {
                surfaceFormatIndex = i;
                break;
            }
        }
        assert(surfaceFormatIndex != VK_FORMAT_MAX_ENUM);
    
        uint32_t presentModeCount;
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice,
                                                                 mSurface,
                                                                 &presentModeCount,
                                                                 nullptr));
    
        vector<VkPresentModeKHR> presentModes(presentModeCount);
        VK_CHECK_ERROR(vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice,
                                                                 mSurface,
                                                                 &presentModeCount,
                                                                 presentModes.data()));
    
        uint32_t presentModeIndex = VK_PRESENT_MODE_MAX_ENUM_KHR;
        for (auto i = 0; i != presentModeCount; ++i) {
            if (presentModes[i] == VK_PRESENT_MODE_FIFO_KHR) {
                presentModeIndex = i;
                break;
            }
        }
        assert(presentModeIndex != VK_PRESENT_MODE_MAX_ENUM_KHR);
    
        VkSwapchainCreateInfoKHR swapchainCreateInfo{
                .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
                .surface = mSurface,
                .minImageCount = surfaceCapabilities.minImageCount,
                .imageFormat = surfaceFormats[surfaceFormatIndex].format,
                .imageColorSpace = surfaceFormats[surfaceFormatIndex].colorSpace,
                .imageExtent = surfaceCapabilities.currentExtent,
                .imageArrayLayers = 1,
                .imageUsage = imageUsage,
                .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
                .preTransform = surfaceCapabilities.currentTransform,
                .compositeAlpha = compositeAlpha,
                .presentMode = presentModes[presentModeIndex]
        };
    
        VK_CHECK_ERROR(vkCreateSwapchainKHR(mDevice, &swapchainCreateInfo, nullptr, &mSwapchain));
    
        uint32_t swapchainImageCount;
        VK_CHECK_ERROR(vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount, nullptr));
    
        mSwapchainImages.resize(swapchainImageCount);
        VK_CHECK_ERROR(vkGetSwapchainImagesKHR(mDevice,
                                               mSwapchain,
                                               &swapchainImageCount,
                                               mSwapchainImages.data()));
    }
    
    VkRenderer::~VkRenderer() {
        vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr);
        vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
        vkDestroyDevice(mDevice, nullptr); // Device 파괴. queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다.
        vkDestroyInstance(mInstance, nullptr);
    }

     

     

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

    [Vulkan] Command Pool  (0) 2024.08.06
    [Vulkan] Command  (0) 2024.08.05
    [Vulkan] Vulkan Surface  (0) 2024.08.04
    [Vulkan] 화면 출력을 위해 필요한 Vulkan Extension  (0) 2024.08.04
    [Vulkan] Vulkan Device  (0) 2024.08.02