목차

     

     



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

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

     

     

     

    화면에 색 출력하기


     

     

    화면에 단색 출력하기

     

    Vulkan Swapchain의 Vulkan Image를 단색으로 초기화한다.

    1. Command를 기록하기 위해 Vulkan Command buffer를 Recording 상태로 변경한다.
    2. Vulkan Swapchain의 Vulkan Image를 단색으로 클리어하는 Command를 기록한다.
    3. Vulkan Command buffer의 기록을 끝마친다. 상태는 Executable다.
    4. Vulkan Command buffer를 Vulkan Queue에 제출한다.
    5. 제출된 Vulkan Command buffer가 처리될 때까지 기다린다.


     

     

    VkCommandBufferBeginInfo 구조체

     

    typedef struct VkCommandBufferBeginInfo {
         VkStructureType sType;
         const void* pNext;
         VkCommandBufferUsageFlags flags;
         const VkCommandBufferInheritanceInfo* pInheritanceInfo;
        } VkCommandBufferBeginInfo;

     

    매개 변수  설명 
     sType   구조체 타입
     pNext   NULL 또는 확장 기능 구조체의 포인터
     flags   VkCommandBuffer의 동작을 정의
    pInheritanceInfo   

     

    typedef enum VkCommandBufferUsageFlagBits {
         VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT = 0x00000001,
         VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT = 0x00000002,
         VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT = 0x00000004,
    } VkCommandBufferUsageFlagBits;

     

    VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT은 기록된 Vulkan Command buffer가 한번제출되고 리셋될 것이라고 Vulkan 드라이버에 알려준다. 이 정보를 통해 Vulkan 드라이버는 최적화를 할 수 있다.

     


     

     

    Vulkan Command buffer 기록 시작

     

    VkResult vkBeginCommandBuffer(
         VkCommandBuffer commandBuffer,
         const VkCommandBufferBeginInfo* pBeginInfo);

     

    매개 변수  설명 
    commandBuffer   VkCommandBuffer
    pBeginInfo   VkCommandBufferBeginInfo 변수의 포인터

     

    mCommandBuffer를 Recording 상태로 전환

     


     

     

    Vulkan Image 단색 초기화

     

    void vkCmdClearColorImage(
         VkCommandBuffer commandBuffer,
         VkImage image,
         VkImageLayout imageLayout,
         const VkClearColorValue* pColor,
         uint32_t rangeCount,
         const VkImageSubresourceRange* pRanges);

     

    매개 변수  설명 
     commandBuffer   VkCommandBuffer
     image   VkImage. 클리어할 이미지.
     imageLayout   VkImageLayout입니다.
    VkImage는
    VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR,
    VK_IMAGE_LAYOUT_GENERAL,
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 중 하나의 VkImageLayout를 가지고 있어야 한다.
     pColor   VkClearColorValue 변수의 포인터
     rangeCount   클리어할 VkImageSubresourceRange의 개수
     pRanges   VkImageSubresourceRange 배열의 포인터

     


     

     

    VkClearColorValue 구조체

     

    typedef union VkClearColorValue {
         float float32[4];
         int32_t int32[4];
         uint32_t uint32[4];
    } VkClearColorValue;

     

    매개 변수  설명 
     float32  클리어 색상. 0.0 ~ 1.0 사이의 값으로 정의
     int32  
     uint32  

     

    초록색으로 설정


     

     

    VkImageSubresourceRange 구조체

     

    VkImageSubresourceRange 구조체를 설정하여 클리어할 이미지 영역 정의.

    typedef struct VkImageSubresourceRange {
         VkImageAspectFlags aspectMask;
         uint32_t baseMipLevel;
         uint32_t levelCount;
         uint32_t baseArrayLayer;
         uint32_t layerCount;
    } VkImageSubresourceRange;

     

    매개 변수  설명 
    aspectMask  VkImageSubresourceRange에 정의된 영역의 Aspect
    baseMipLevel   Mipmap 레벨의 오프셋
    levelCount   Mipmap 레벨의 개수
    baseArrayLayer   Array layer의 오프셋
    layerCount   Array layer의 개수

     


     

     

    VkImageAspectFlagBits 열거형

     

    typedef enum VkImageAspectFlagBits {
         VK_IMAGE_ASPECT_COLOR_BIT = 0x00000001,	// RGBA
         VK_IMAGE_ASPECT_DEPTH_BIT = 0x00000002,
         VK_IMAGE_ASPECT_STENCIL_BIT = 0x00000004,
         VK_IMAGE_ASPECT_METADATA_BIT = 0x00000008,
         VK_IMAGE_ASPECT_PLANE_0_BIT = 0x00000010,
         VK_IMAGE_ASPECT_PLANE_1_BIT = 0x00000020,
         VK_IMAGE_ASPECT_PLANE_2_BIT = 0x00000040,
    } VkImageAspectFlagBits;

     


     

     

    Mipmap과 Array layer란?

     

    Minmap

    Minmap은 렌더링 속도를 향상시키기 위해 기본 텍스처와 이를 연속적으로 미리 축소시킨 텍스처로 이루어진 이미지 집합이다. ex. 우측 인공위성 이미지

     

    Array layer

    Array layer는 이미지를 구성하는 개별적인 공간이다.

    이는 3D Texture, Cubemap, Texture Array에서 중요한 개념이다.

     

     

    각 Array layer는 동일한 이미지 포맷과 Minmap을 가지지만 독립적인 이미지 데이터를 포함할 수 있다.

    ex. Cubemap의 경우, 각 면은 별도의 Array layer로 처리될 수 있으며 이를 통해 하나의 이미지 내에서 Cubemap에 필요한 6개의 다른 면을 표현하고 관리할 수 있다.

     

     


     

     

    Array layer 응용 - VR

     

     

    위의 그림과 같이 여러 관점에서 동시에 장면을 렌더링할 수 있다.
    Image Array를 사용한다면 이미지 한 개의 각각의 관점에서 렌더링된 결과를 쉽게 관리할 수 있다.


     

     

    VkImageSubresourceRange 구조체

     


     

     

    Vulkan Image 단색 클리어

     


     

     

    Vulkan Command buffer 기록 종료

     

    VkResult vkEndCommandBuffer(
         VkCommandBuffer commandBuffer);

     

    매개 변수  설명 
    commandBuffer VkCommandBuffer. 상태가 반드시 Recording이어야 한다.

     

    mCommandBuffer의 기록을 종료한다.


     

     

    VkSubmitInfo 구조체

     

    typedef struct VkSubmitInfo {
         VkStructureType sType;
         const void* pNext;
         uint32_t waitSemaphoreCount;
         const VkSemaphore* pWaitSemaphores;
         const VkPipelineStageFlags* pWaitDstStageMask;
         uint32_t commandBufferCount;
         const VkCommandBuffer* pCommandBuffers;
         uint32_t signalSemaphoreCount;
         const VkSemaphore* pSignalSemaphores;
    } VkSubmitInfo;

     

    매개 변수  설명 
     sType   구조체 타입
     pNext   NULL 또는 확장 기능 구조체의 포인터
     waitSemaphoreCount   기다려야 하는 VkSemaphore의 개수
     pWaitSemaphores   VkSemaphore 배열의 포인터
     pWaitDstStageMask   VkSemaphore들이 기다려야 하는 파이프라인 스테이지
     commandBufferCount   제출할 VkCommandBuffer의 개수
     pCommandBuffers   VkCommandBuffer 배열의 포인터
     signalSemaphoreCount   시그널 해야하는 VkSemaphore의 개수
     pSignalSemaphores   VkSemaphore 배열의 포인터

     

    해당 구조체는 queue에 제출할 mCommandBuffer를 정의하고 있다.

     

    제출할 Command buffer를 지정하는 것은 VkSubmitInfo 구조체를 통해 이뤄진다.


     

     

    Vulkan Command buffer를 Vulkan Queue에 제출

     

    VkResult vkQueueSubmit(
         VkQueue queue,
         uint32_t submitCount,
         const VkSubmitInfo* pSubmits,
         VkFence fence);

     

    매개 변수  설명 
     queue   VkQueue
     submitCount   제출할 VkSubmitInfo의 개수
     pSubmits   VkSubmitInfo 배열의 포인터
     fence   VkFence 또는 VK_NULL_HANDLE

     

    Command buffer를 mQueue에 제출


     

     

    모든 Command가 처리될 때까지 기다리기

     

    Command buffer를 queue에 제출했지만 디바이스가 아직 Command buffer를 처리하지 않았다. 따라서 디바이스가 커맨드 버퍼의 처리를 완료할 때까지 기다려야 한다. 기다리지 않으면 처음에는 이상한 색깔이 출력될 것이다.

     

    VkResult vkQueueWaitIdle(
         VkQueue queue);

     

    매개 변수  설명 
     queue   VkQueue

     

     

    vkQueueWaitIdle을 호출함으로써 제출한 Command buffer의 실행이 끝날때까지 대기한다.  


     

     

    VK_PRESENT_MODE_FIFO_KHR 모드

     

    Vulkan Swapchain이 관리하고 있는 Vulkan Image는 총 7개입니다.

     

    1번, 5번, 7번

    • 어플리케이션에서 사용하고 있다.

     

    2번, 3번

    • 대기중이며 어플리케이션이 요청하면 Vulkan Swapchain에서 어플리케이션으로 반환된다.

     

    4번

    • 화면에 출력중이고 6번은 출력 대기중이다.

     

    6번

    • 다음 VSync가 맞춰서 화면에 출력된다.

     


     

     

    화면에 단색을 출력해 봅시다!

     

    화면 갱신이 필요할때마다 단색으로 초기화된 Vulkan Image를 화면에 출력합니다.

     

    1. Vulkan Swapchain으로부터 출력 가능한 Image index를 가져옵니다.

    2. Vulkan Swapchain에서 Image index에 해당하는 Vulkan Image를 화면에 출력합니다.


     

     

    Vulkan Swapchain으로부터 출력 가능한 Image index VkResult 가져오기

     

    VkResult vkAcquireNextImageKHR(
         VkDevice device,
         VkSwapchainKHR swapchain,
         uint64_t timeout,
         VkSemaphore semaphore,
         VkFence fence,
         uint32_t* pImageIndex);

     

    매개 변수  설명 
    device   VkDevice
    swapchain   VkSwapchainKHR
    timeout   사용 가능한 VkImage가 없을 때 기다릴 시간
    semaphore   사용 가능한 VkImage를 얻어 왔을때 시그널이 되는 VkSemaphore
    fence   사용 가능한 VkImage를 얻어 왔을때 시그널이 되는 VkFence
    pImageIndex   uint32_t 변수의 포인터

     

     

    &swapchainImageIndex으로부터 사용 가능한 이미지 인덱스를 가져온다. 


     

     

    VkPresentInfoKHR 구조체

     

    어떤 이미지를 출력할지는 VkPresentInfoKHR을 사용하여 정의할 수 있다.

     

    typedef struct VkPresentInfoKHR {
         VkStructureType sType;
         const void* pNext;
         uint32_t waitSemaphoreCount;
         const VkSemaphore* pWaitSemaphores;
         uint32_t swapchainCount;
         const VkSwapchainKHR* pSwapchains;
         const uint32_t* pImageIndices;
         VkResult* pResults;
    } VkPresentInfoKHR;

     

    매개 변수  설명 
     sType   구조체의 타입
     pNext   NULL 또는 확장 기능 구조체의 포인터
     waitSemaphoreCount   기다려야 하는 VkSemaphore의 개수
     pWaitSemaphores   VkSemaphore 배열의 포인터
     swapchainCount   화면 출력이 되야하는 VkSwapchainKHR의 개수
     pSwapchains   VkSwapchainKHR 배열의 포인터
     pImageIndices   각 VkSwapchainKHR이 화면에 출력할 Image index를 담고 있는 배열의 포인터
     pResults   NULL 또는 VkBool 배열의 포인터. 각 VkSwapchainKHR의 출력 결과가 반환된다.

     


     

     

    Vulkan Image를 화면에 출력하기

     

    VkResult vkQueuePresentKHR(
         VkQueue queue,
         const VkPresentInfoKHR* pPresentInfo);

     

    매개 변수  설명 
     queue   VkQueue
     pPresentInfo   VkPresentInfoKHR 변수의 포인터

     


     

     

    코드

     

    #include ...
    
    VkRenderer::VkRenderer() {
        // 1. VkInstance 생성
        // 2. VkPhysicalDevice 선택
        // 3. VkDevice 생성
        // 4. VkSurface 생성
        // 5. VkSwapchain 생성
        // 6. VkCommandPool 생성
        // 6. VkCommandBuffer 할당
       
        // ================================================================================
        // 7. VkCommandBuffer 기록 시작
        // ================================================================================
        VkCommandBufferBeginInfo commandBufferBeginInfo{
                .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
                .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT // 한 번만 기록되고 다시 리셋 될 것이라는 의미
        };
    
        // mCommandBuffer를 기록중인 상태로 변경.
        VK_CHECK_ERROR(vkBeginCommandBuffer(mCommandBuffer, &commandBufferBeginInfo));
    
        for (auto swapchainImage : mSwapchainImages) { // 스왑체인 이미지만큼 for문을 돈다.
            // ================================================================================
            // 8. VkImage 색상 초기화
            // ================================================================================
            VkClearColorValue clearColorValue{
                    .float32 = {0.6431, 0.7765, 0.2235, 1.0} // 초록색으로 clearColor 지정.
            };
    
            VkImageSubresourceRange imageSubresourceRange { // clear할 영역 지정.
                    .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                    .baseMipLevel = 0,
                    .levelCount = 1,
                    .baseArrayLayer = 0,
                    .layerCount = 1
            };
    
            // 해당 swapchainImage를 초록색으로 clear해준다.
            vkCmdClearColorImage(mCommandBuffer,
                                 swapchainImage,
                                 VK_IMAGE_LAYOUT_UNDEFINED,
                                 &clearColorValue,
                                 1,
                                 &imageSubresourceRange);
        }
    
        // ================================================================================
        // 9. VkCommandBuffer 기록 종료
        // ================================================================================
        VK_CHECK_ERROR(vkEndCommandBuffer(mCommandBuffer)); // mCommandBuffer는 Executable 상태가 된다.
    
        // ================================================================================
        // 10. VkCommandBuffer 제출
        // ================================================================================
        VkSubmitInfo submitInfo{
                .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                .commandBufferCount = 1,
                .pCommandBuffers = &mCommandBuffer
        };
    
        // submitInfo 구조체를 넘김으로써 commandBuffer 정보를 queue에 제출
        VK_CHECK_ERROR(vkQueueSubmit(mQueue, 1, &submitInfo, VK_NULL_HANDLE));
        // commandBuffer를 vkQueueSubmit에 제출했지만 해당 Command buffer가 실행이 됐을지 안 됐을지 알 수 없다. CPU와 GPU는 따로따로 돌기 때문에 항상 실행이 됐다는 보장을 할 수 없다. 그래서 이를 보장하기 위해 vkQueueWaitIdle를 호출하여 이 queue에 제출한 Command buffer가 모두 다 실행되는 것을 보장한다.
        VK_CHECK_ERROR(vkQueueWaitIdle(mQueue));
    }
    
    VkRenderer::~VkRenderer() {
        ...
    }
    
    void VkRenderer::render() {
        // ================================================================================
        // 1. 화면에 출력할 수 있는 VkImage 얻기
        // ================================================================================
        uint32_t swapchainImageIndex;
        VK_CHECK_ERROR(vkAcquireNextImageKHR(mDevice,
                                             mSwapchain,
                                             UINT64_MAX,
                                             VK_NULL_HANDLE,
                                             VK_NULL_HANDLE,
                                             &swapchainImageIndex)); // 사용 가능한 이미지 변수에 담기
    
        // ================================================================================
        // 2. VkImage 화면에 출력
        // ================================================================================
        VkPresentInfoKHR presentInfo{
                .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
                .swapchainCount = 1,
                .pSwapchains = &mSwapchain,
                .pImageIndices = &swapchainImageIndex
        };
    
        VK_CHECK_ERROR(vkQueuePresentKHR(mQueue, &presentInfo)); // 화면에 출력.
        VK_CHECK_ERROR(vkQueueWaitIdle(mQueue));
    }

     

     

    전체 코드

    더보기
    // 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()));
    
        // ================================================================================
        // 6. VkCommandPool 생성
        // ================================================================================
        VkCommandPoolCreateInfo commandPoolCreateInfo{
                .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
                .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // command buffer를 개별적으로 초기화 가능하게 설정
                .queueFamilyIndex = mQueueFamilyIndex
        };
    
        VK_CHECK_ERROR(vkCreateCommandPool(mDevice, &commandPoolCreateInfo, nullptr, &mCommandPool)); // mCommandPool 생성
    
        // ================================================================================
        // 6. VkCommandBuffer 할당
        // ================================================================================
        VkCommandBufferAllocateInfo commandBufferAllocateInfo{ // 할당하려는 command buffer 정의
                .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
                .commandPool = mCommandPool,
                .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
                .commandBufferCount = 1
        };
    
        VK_CHECK_ERROR(vkAllocateCommandBuffers(mDevice, &commandBufferAllocateInfo, &mCommandBuffer));
    
        // ================================================================================
        // 7. VkCommandBuffer 기록 시작
        // ================================================================================
        VkCommandBufferBeginInfo commandBufferBeginInfo{
                .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
                .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT // 한 번만 기록되고 다시 리셋 될 것이라는 의미
        };
    
        // mCommandBuffer를 기록중인 상태로 변경.
        VK_CHECK_ERROR(vkBeginCommandBuffer(mCommandBuffer, &commandBufferBeginInfo));
    
        for (auto swapchainImage : mSwapchainImages) { // 스왑체인 이미지만큼 for문을 돈다.
            // ================================================================================
            // 8. VkImage 색상 초기화
            // ================================================================================
            VkClearColorValue clearColorValue{
                    .float32 = {0.6431, 0.7765, 0.2235, 1.0} // 초록색으로 clearColor 지정.
            };
    
            VkImageSubresourceRange imageSubresourceRange { // clear할 영역 지정.
                    .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                    .baseMipLevel = 0,
                    .levelCount = 1,
                    .baseArrayLayer = 0,
                    .layerCount = 1
            };
    
            // 해당 swapchainImage를 초록색으로 clear해준다.
            vkCmdClearColorImage(mCommandBuffer,
                                 swapchainImage,
                                 VK_IMAGE_LAYOUT_UNDEFINED,
                                 &clearColorValue,
                                 1,
                                 &imageSubresourceRange);
        }
    
        // ================================================================================
        // 9. VkCommandBuffer 기록 종료
        // ================================================================================
        VK_CHECK_ERROR(vkEndCommandBuffer(mCommandBuffer)); // mCommandBuffer는 Executable 상태가 된다.
    
        // ================================================================================
        // 10. VkCommandBuffer 제출
        // ================================================================================
        VkSubmitInfo submitInfo{
                .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                .commandBufferCount = 1,
                .pCommandBuffers = &mCommandBuffer
        };
    
        // submitInfo 구조체를 넘김으로써 commandBuffer 정보를 queue에 제출
        VK_CHECK_ERROR(vkQueueSubmit(mQueue, 1, &submitInfo, VK_NULL_HANDLE));
        // commandBuffer를 vkQueueSubmit에 제출했지만 해당 Command buffer가 실행이 됐을지 안 됐을지 알 수 없다. CPU와 GPU는 따로따로 돌기 때문에 항상 실행이 됐다는 보장을 할 수 없다. 그래서 이를 보장하기 위해 vkQueueWaitIdle를 호출하여 이 queue에 제출한 Command buffer가 모두 다 실행되는 것을 보장한다.
        VK_CHECK_ERROR(vkQueueWaitIdle(mQueue));
    }
    
    VkRenderer::~VkRenderer() {
        vkFreeCommandBuffers(mDevice, mCommandPool, 1, &mCommandBuffer);
        vkDestroyCommandPool(mDevice, mCommandPool, nullptr);
        vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr);
        vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
        vkDestroyDevice(mDevice, nullptr); // Device 파괴. queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다.
        vkDestroyInstance(mInstance, nullptr);
    }
    
    void VkRenderer::render() {
        // ================================================================================
        // 1. 화면에 출력할 수 있는 VkImage 얻기
        // ================================================================================
        uint32_t swapchainImageIndex;
        VK_CHECK_ERROR(vkAcquireNextImageKHR(mDevice,
                                             mSwapchain,
                                             UINT64_MAX,
                                             VK_NULL_HANDLE,
                                             VK_NULL_HANDLE,
                                             &swapchainImageIndex)); // 사용 가능한 이미지 변수에 담기
    
        // ================================================================================
        // 2. VkImage 화면에 출력
        // ================================================================================
        VkPresentInfoKHR presentInfo{
                .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
                .swapchainCount = 1,
                .pSwapchains = &mSwapchain,
                .pImageIndices = &swapchainImageIndex
        };
    
        VK_CHECK_ERROR(vkQueuePresentKHR(mQueue, &presentInfo)); // 화면에 출력.
        VK_CHECK_ERROR(vkQueueWaitIdle(mQueue));
    }

     

     

    실행 화면

     


     

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

    [Vulkan] Vulkan Image Layer  (0) 2024.08.08
    [Vulkan] Vulkan Fence  (0) 2024.08.07
    [Vulkan] Command buffer  (0) 2024.08.06
    [Vulkan] Command Pool  (0) 2024.08.06
    [Vulkan] Command  (0) 2024.08.05