⭐ Vulkan & CMake/Vulkan

[Vulkan] Hello Triangle

Designerd 2024. 9. 14. 09:55

 

 

목차

     

     


     

     


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

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

     

     

     

    Hello Triangle


     

     

    Vulkan Buffer와 Vulkan Memory 바인드하기

     

    VkResult vkBindBufferMemory(
         VkDevice device,
         VkBuffer buffer,
         VkDeviceMemory memory,
         VkDeviceSize memoryOffset);

     

    파라미터 설명 
     device   VkDevice
     buffer   VkBuffer
     memory   VkDeviceMemory
     memoryOffset   Memory 오프셋

     

     

    vkBindBufferMemory 함수로 mVertexBuffer와 mVertexMemory를 연결한다.


     

     

    Vertex 데이터 복사하기

     

    HOST가 메모리에 데이터를 복사하기 위해서는 HOST가 접근 가능한 가상 주소가 필요하다. 이 주소는 vkMapMemory를 호출하여 얻는다.

    (※참고:  HOST_VISIBLE_BIT 속성을 가진 메모리만 이 함수를 사용할 수 있다. 왜냐하면 HOST 가상 주소 공간에 맵핑이 되어있어야지만 HOST가 접근할 수 있기 때문이다. )

     

    VkResult vkMapMemory(
         VkDevice device,
         VkDeviceMemory memory,
         VkDeviceSize offset,
         VkDeviceSize size,
         VkMemoryMapFlags flags,
         void** ppData);

     

    파라미터 설명 
     device   VkDevice
     memory   VkDeviceMemory
     offset   Map 오프셋
     size   Map 크기. 전체를 Map하는 경우 VK_WHOLE_SIZE를 사용한다.
     flags   일단 0을 사용
     ppData   void* 변수의 포인터

     

     vkMapMemory를 호출하여 HOST가 접근 가능한 가상 주소를 얻어온다. 그리고 memcpy를 사용해서 vertex 데이터를 복사한다. 

     

     

    HOST에서 메모리에 대한 접근이 더 이상 필요하지 않을 때는 vkUnmapMemory를 호출해서 메모리를 Unmap할 수 있다. 

    ( ※참고: GPU 벤더사에 ' 메모리를 다 사용하고 나서 vkUnmapMemory를 꼭 호출해야 하는가? '라고 물었다. 

    대답:  할 필요 없다. 이 작업이 성능에 영향을 주지 않는다. )

    vkmapMemory, vkUnmapMemory를 반복해서 호출해야 하는 경우에 vkUnmapMemory를 호출하지 않고 vkMapMemory로 얻은 가상 주소 공간을 계속 유지하는게 성능상으로 조금 더 좋다.

    void vkUnmapMemory(
         VkDevice device,
         VkDeviceMemory memory);

     

    파라미터 설명 
     device   VkDevice
     memory   VkDeviceMemory

     

    mVertexMemory를 Unmap한다.


     

     

    VkPipelineVertexInputStateCreateInfo 구조체

     

    VkPipelineVertexInputStateCreateInfo 구조체를 사용해 Graphic pipeline이 Buffer로부터 vertex 정보를 읽도록 변경한다. 

     

    typedef struct VkPipelineVertexInputStateCreateInfo {
         VkStructureType sType;
         const void* pNext;
         VkPipelineVertexInputStateCreateFlags flags;
         uint32_t vertexBindingDescriptionCount;
         const VkVertexInputBindingDescription* pVertexBindingDescriptions;
         uint32_t vertexAttributeDescriptionCount;
         const VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
    } VkPipelineVertexInputStateCreateInfo;

     

    멤버 변수 설명 
     sType   구조체의 타입
     pNext   NULL 또는 확장 기능 구조체의 포인터
     flags   일단 0을 사용
     vertexBindingDescriptionCount   VkVertexInputBindingDescription의 개수
     pVertexBindingDescriptions   VkVertexInputBindingDescription 배열의 포인터
     vertexAttributeDescriptionCount   VkVertexInputAttributeDescription의 개수
     pVertexAttributeDescriptions   VkVertexInputAttributeDescription 배열의 포인터

     

     

    VkVertexInputBindingDescription과 VkVertexInputAttributeDescription의 관계

     

    VkVertexInputBindingDescription는 바인딩 될 Buffer에 대해서 기술한다.

    VkVertexInputAttributeDescription는 바인딩된 Buffer에서 어떻게읽을지 기술한다.  


     

     

     

    VkVertexInputBindingDescription 구조체

     

    typedef struct VkVertexInputBindingDescription {
         uint32_t binding;
         uint32_t stride;
         VkVertexInputRate inputRate;
    } VkVertexInputBindingDescription;

     

    멤버 변수 설명 
     binding   바인딩 인덱스
     stride   스트라이드
     inputRate   일단 VK_VERTEX_INPUT_RATE_VERTEX을 사용

     

     

    Graphics pipeline에서 0번 Buffer에서 정점마다 Vertex 구조체의 크기만큼 데이터를 읽게 된다.


     

     

    VkVertexInputAttributeDescription 구조체

     

    typedef struct VkVertexInputAttributeDescription {
         uint32_t location;
         uint32_t binding;
         VkFormat format;
         uint32_t offset;
    } VkVertexInputAttributeDescription;

     

    멤버 변수 설명 
     location   어트리뷰트의 위치
     binding   바인딩 인덱스
     format   어트리뷰트의 포맷
     offset   오프셋

     

    Attribute가 2개가 정의되었다. 

    • 첫번째 Attribute는 Vertex Shader의 location 0에 연결된다. 이 Attribute는 0번 바인딩된 Buffer로부터 읽어오고, Offset은 Vertex 구조체의 포지션만큼 되어 있고, format은 VK_FORMAT_R32G32B32_SFLOAT인 103 타입이다.
    • 두번째 Attribute는 Vertex Shader의 location 1에 연결된다. format은 VK_FORMAT_R32G32B32_SFLOAT인 103 타입이다.

     

     

    VkPipelineVertexInputStateCreateInfo 구조체

     

    한 개의 Buffer 바인딩과 두 개의 Attribute를 정의한 VkPipelineVertexInputStateCreateInfo 구조체

     

     


     

     

    Vertex buffer 바인드하기

     

    void vkCmdBindVertexBuffers(
         VkCommandBuffer commandBuffer,
         uint32_t firstBinding,
         uint32_t bindingCount,
         const VkBuffer* pBuffers,
         const VkDeviceSize* pOffsets);

     

    멤버 변수 설명 
    commandBuffer   VkCommandBuffer
    firstBinding   첫 번째 바인딩 위치
    bindingCount   VkBuffer의 개수
    pBuffers   VkBuffer 배열의 포인터
    pOffset   VkDeviceSize 배열의 포인터

     

     

    mVertexBuffer를 0번에 바인딩한다.


     

     

     

    Vertex 쉐이더 변경

     

    Vertex Shader는 이제 위치색상 정보를 입력으로 받기 때문에 Shader 코드도 해당 입력에 맞추어 변경되어야 한다.

     

    데이터 타입103위치 정보가 location 0으로 들어온다. 그리고 같은 타입인 색상 정보가 locaiton 1로 들어온다.

    또한 이 두 정보는 Shader의 입력이기 때문에 in 키워드가 사용된다.

     

    색상 정보는 Vertex Shader에서 직접적으로 필요하지 않지만 Fragment Shader에서 사용된다. 따라서 Vertex Shader는 Out 키워드를 사용해서 이 색상 정보를 Fragment Shader로 전달해야 한다. (모든 컬러에 대한 결정은 Fragment Shader에서 결정되기 때문에 값을 Fragment Shader로 넘겨준다.)

     

    "#version 310 es 							\n"
    " 											\n"
    "layout(location = 0) in vec3 inPosition; 	\n"
    "layout(location = 1) in vec3 inColor; 		\n"
    " 											\n"
    "layout(location = 0) out vec3 outColor; 	\n"
    " 											\n"
    "void main() { 								\n"

     

     

    Fragment 쉐이더 변경

     

    변경된 Fragment Shader는 Vertex Shader로부터 색상 정보를 location을 통해 입력받는다. 이 location 번호는 Vertex Shader의 Out 변수의 location과 일치해야 한다.

     

    "#version 310 es 							\n"
    "precision mediump float; 					\n"
    " 											\n"
    "layout(location = 0) in vec3 inColor; 		\n"
    " 											\n"
    "layout(location = 0) out vec4 outColor; 	\n"
    " 											\n"
    "void main() { 								\n"

     

     

    코드

     

    #include ...
    using namespace std;
    
    VkRenderer::VkRenderer(ANativeWindow *window) {
        // 1. VkInstance 생성
        // 2. VkPhysicalDevice 선택
        // 3. VkPhysicalDeviceMemoryProperties 얻기
        // 4. VkDevice 생성
        // 5. VkSurface 생성
        // 6. VkSwapchain 생성
        
        mSwapchainImageViews.resize(swapchainImageCount); // ImageView를 Swapchain의 개수만큼 생성
        for (auto i = 0; i != swapchainImageCount; ++i) {
            // 7. VkImageView 생성 
        }
        // 8. VkCommandPool 생성
        // 9. VkCommandBuffer 할당
        // 10. VkFence 생성
        // 11. VkSemaphore 생성
        // 12. VkRenderPass 생성
        
        mFramebuffers.resize(swapchainImageCount);
        for (auto i = 0; i != swapchainImageCount; ++i) {
            // 13. VkFramebuffer 생성
        }
       
        // 14. Vertex VkShaderModule 생성
        // 아래 부분 수정됨. VertexShaderCode를 수정함.
         string_view vertexShaderCode = {
            "#version 310 es                                        \n"
            "                                                       \n"
            "layout(location = 0) in vec3 inPosition;               \n"
            "layout(location = 1) in vec3 inColor;                  \n"
            "                                                       \n"
            "layout(location = 0) out vec3 outColor;                \n"
            "                                                       \n"
            "void main() {                                          \n"
            "    gl_Position = vec4(inPosition, 1.0);               \n"
            "    outColor = inColor;                                \n"
            "}                                                      \n"
        };
        
        // 15. Fragment VkShaderModule 생성
         // 아래 부분 수정됨. fragmentShaderCode 수정함.
        string_view fragmentShaderCode = {
                "#version 310 es                                        \n"
                "precision mediump float;                               \n"
                "                                                       \n"
                "layout(location = 0) in vec3 inColor;                  \n" // vertexShaderCode에서 location이 0번 이었기때문에 똑같이 맞춰준다.
                "                                                       \n"
                "layout(location = 0) out vec4 outColor;                \n"
                "                                                       \n"
                "void main() {                                          \n"
                "    outColor = vec4(inColor, 1.0);                     \n" // 얻어온 색상 값을 outColor에 저장
                "}                                                      \n"
        };
        
        // 16. VkPipelineLayout 생성
        
        // 17. Graphics VkPipeline 생성
        // 아래 부분 수정 및 추가됨.
         VkVertexInputBindingDescription vertexInputBindingDescription{
                .binding = 0,
                .stride = sizeof(Vertex),
                .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
        };
    
        array<VkVertexInputAttributeDescription, 2> vertexInputAttributeDescriptions{
                VkVertexInputAttributeDescription{
                        .location = 0,
                        .binding = 0,
                        .format = VK_FORMAT_R32G32B32_SFLOAT,
                        .offset = offsetof(Vertex, position)
                },
                VkVertexInputAttributeDescription{
                        .location = 1,
                        .binding = 0,
                        .format = VK_FORMAT_R32G32B32_SFLOAT,
                        .offset = offsetof(Vertex, color)
                }
        };
        
        VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
                .vertexBindingDescriptionCount = 1,
                .pVertexBindingDescriptions = &vertexInputBindingDescription,
                .vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributeDescriptions.size()),
                .pVertexAttributeDescriptions = vertexInputAttributeDescriptions.data()
        };
        
        // 18. Vertex VkBuffer 생성
        // 19. Vertex VkBuffer의 VkMemoryRequirements 얻기
        // 20. Vertex VkDeviceMemory를 할당 할 수 있는 메모리 타입 인덱스 얻기
        // 21. Vertex VkDeviceMemory 할당
        
        // ================================================================================
        // 22. Vertex VkBuffer와 Vertex VkDeviceMemory 바인드
        // ================================================================================
        VK_CHECK_ERROR(vkBindBufferMemory(mDevice, mVertexBuffer, mVertexMemory, 0));
    
        // ================================================================================
        // 23. Vertex 데이터 복사
        // ================================================================================
        void* vertexData;
        VK_CHECK_ERROR(vkMapMemory(mDevice, mVertexMemory, 0, vertexDataSize, 0, &vertexData));
        memcpy(vertexData, vertices.data(), vertexDataSize);
        vkUnmapMemory(mDevice, mVertexMemory);
    }
    
    VkRenderer::~VkRenderer() { 
        ...
    }
    
    void VkRenderer::render() {
        // 1. 화면에 출력할 수 있는 VkImage 얻기
        // 2. VkFence 기다린 후 초기화
        // 3. VkCommandBuffer 초기화
        // 4. VkCommandBuffer 기록 시작
        // 5. VkRenderPass 시작
        // 6. Graphics VkPipeline 바인드
        
        // ================================================================================
        // 7. Vertex VkBuffer 바인드
        // ================================================================================
        VkDeviceSize vertexBufferOffset{0};
        vkCmdBindVertexBuffers(mCommandBuffer, 0, 1, &mVertexBuffer, &vertexBufferOffset);
        
        // 8. 삼각형 그리기
        // 9. VkRenderPass 종료
        // -------------9. Clear 색상 갱신--------------삭제됨.
        // 10. VkCommandBuffer 기록 종료
        // 11. VkCommandBuffer 제출
        // 12. VkImage 화면에 출력
    }

     


    전체코드

    더보기
    // 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 <cstddef>
    #include <array>
    #include <vector>
    #include <iomanip>
    
    #include "VkRenderer.h"
    #include "VkUtil.h"
    #include "AndroidOut.h"
    
    using namespace std;
    
    struct Vector3 {
        union {
            float x;
            float r;
        };
    
        union {
            float y;
            float g;
        };
    
        union {
            float z;
            float b;
        };
    };
    
    struct Vertex {
        Vector3 position;
        Vector3 color;
    };
    
    VkRenderer::VkRenderer(ANativeWindow *window) {
        // ================================================================================
        // 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. VkPhysicalDeviceMemoryProperties 얻기
        // ================================================================================
        vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &mPhysicalDeviceMemoryProperties);
    
        // ================================================================================
        // 4. 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);
    
    
        // ================================================================================
        // 5. 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);
    
    
        // ================================================================================
        // 6. 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 swapchainImageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
        assert(surfaceCapabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
    
        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 = mSwapchainImageExtent,
                .imageArrayLayers = 1,
                .imageUsage = swapchainImageUsage,
                .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()));
    
    
        mSwapchainImageViews.resize(swapchainImageCount); // ImageView를 Swapchain의 개수만큼 생성
        for (auto i = 0; i != swapchainImageCount; ++i) {
            // ================================================================================
            // 7. VkImageView 생성
            // ================================================================================
            VkImageViewCreateInfo imageViewCreateInfo{ // 생성할 ImageView를 정의
                    .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
                    .image = mSwapchainImages[i],
                    .viewType = VK_IMAGE_VIEW_TYPE_2D,
                    .format = surfaceFormats[surfaceFormatIndex].format, // Swapchain 이미지 포맷과 동일한 포맷으로 설정
                    .components = {
                            .r = VK_COMPONENT_SWIZZLE_R,
                            .g = VK_COMPONENT_SWIZZLE_G,
                            .b = VK_COMPONENT_SWIZZLE_B,
                            .a = VK_COMPONENT_SWIZZLE_A,
                    },
                    .subresourceRange = { // 모든 이미지에 대해서 이 이미지 뷰가 접근할 수 있도록 설정
                            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                            .baseMipLevel = 0,
                            .levelCount = 1,
                            .baseArrayLayer = 0,
                            .layerCount = 1
                    }
            };
    
            VK_CHECK_ERROR(vkCreateImageView(mDevice,
                                             &imageViewCreateInfo,
                                             nullptr,
                                             &mSwapchainImageViews[i])); // mSwapchainImageViews[i] 생성
        }
    
        // ================================================================================
        // 8. VkCommandPool 생성
        // ================================================================================
        VkCommandPoolCreateInfo commandPoolCreateInfo{
                .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
                .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |           // command buffer가 자주 변경될 것임을 알려줌
                         VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // command buffer를 개별적으로 초기화 가능하게 설정
                .queueFamilyIndex = mQueueFamilyIndex
        };
    
        VK_CHECK_ERROR(vkCreateCommandPool(mDevice, &commandPoolCreateInfo, nullptr, &mCommandPool)); // mCommandPool 생성
    
        // ================================================================================
        // 9. 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));
    
    
        // ================================================================================
        // 10. VkFence 생성
        // ================================================================================
        VkFenceCreateInfo fenceCreateInfo{
            .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
        }; // 생성할 Fence의 정보를 해당 구조체에서 정의
    
        VK_CHECK_ERROR(vkCreateFence(mDevice, &fenceCreateInfo, nullptr, &mFence)); // mFence 생성. flag에 아무것도 넣어주지 않았기 때문에 생성된 Fence의 초기 상태는 Unsignal 상태다.
    
    
        // ================================================================================
        // 11. VkSemaphore 생성
        // ================================================================================
        VkSemaphoreCreateInfo semaphoreCreateInfo{
                .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        };
    
        VK_CHECK_ERROR(vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, &mSemaphore));
    
    
        // ================================================================================
        // 12. VkRenderPass 생성
        // ================================================================================
        VkAttachmentDescription attachmentDescription{
                .format = surfaceFormats[surfaceFormatIndex].format,
                .samples = VK_SAMPLE_COUNT_1_BIT,
                .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
                .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
                .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
                .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
        };
    
        VkAttachmentReference attachmentReference{
                .attachment = 0,
                .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
        };
    
        VkSubpassDescription subpassDescription{
                .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
                .colorAttachmentCount = 1,
                .pColorAttachments = &attachmentReference
        };
    
        VkRenderPassCreateInfo renderPassCreateInfo{
                .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
                .attachmentCount = 1,
                .pAttachments = &attachmentDescription,
                .subpassCount = 1,
                .pSubpasses = &subpassDescription
        };
    
        VK_CHECK_ERROR(vkCreateRenderPass(mDevice, &renderPassCreateInfo, nullptr, &mRenderPass)); // mRenderPass 생성.
    
        mFramebuffers.resize(swapchainImageCount);
        for (auto i = 0; i != swapchainImageCount; ++i) {
            // ================================================================================
            // 13. VkFramebuffer 생성
            // ================================================================================
            VkFramebufferCreateInfo framebufferCreateInfo{
                    .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
                    .renderPass = mRenderPass,
                    .attachmentCount = 1,
                    .pAttachments = &mSwapchainImageViews[i], // ImageView
                    .width = mSwapchainImageExtent.width,
                    .height = mSwapchainImageExtent.height,
                    .layers = 1
            };
    
            VK_CHECK_ERROR(vkCreateFramebuffer(mDevice, &framebufferCreateInfo, nullptr, &mFramebuffers[i]));// mFramebuffers[i] 생성
        }
    
    
        // ================================================================================
        // 14. Vertex VkShaderModule 생성
        // ================================================================================
        string_view vertexShaderCode = {
                "#version 310 es                                        \n"
                "                                                       \n"
                "layout(location = 0) in vec3 inPosition;               \n"
                "layout(location = 1) in vec3 inColor;                  \n"
                "                                                       \n"
                "layout(location = 0) out vec3 outColor;                \n"
                "                                                       \n"
                "void main() {                                          \n"
                "    gl_Position = vec4(inPosition, 1.0);               \n"
                "    outColor = inColor;                                \n"
                "}                                                      \n"
        };
    
        std::vector<uint32_t> vertexShaderBinary;
        // VKSL을 SPIR-V로 변환.
        VK_CHECK_ERROR(vkCompileShader(vertexShaderCode,
                                       VK_SHADER_TYPE_VERTEX,
                                       &vertexShaderBinary));
    
        VkShaderModuleCreateInfo vertexShaderModuleCreateInfo{
                .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
                .codeSize = vertexShaderBinary.size() * sizeof(uint32_t), // 바이트 단위.
                .pCode = vertexShaderBinary.data()
        };
    
        VK_CHECK_ERROR(vkCreateShaderModule(mDevice,
                                            &vertexShaderModuleCreateInfo,
                                            nullptr,
                                            &mVertexShaderModule)); // mVertexShaderModule 생성.
    
        // ================================================================================
        // 15. Fragment VkShaderModule 생성
        // ================================================================================
        string_view fragmentShaderCode = {
                "#version 310 es                                        \n"
                "precision mediump float;                               \n"
                "                                                       \n"
                "layout(location = 0) in vec3 inColor;                  \n"
                "                                                       \n"
                "layout(location = 0) out vec4 outColor;                \n"
                "                                                       \n"
                "void main() {                                          \n"
                "    outColor = vec4(inColor, 1.0);                     \n"
                "}                                                      \n"
        };
    
        std::vector<uint32_t> fragmentShaderBinary;
        VK_CHECK_ERROR(vkCompileShader(fragmentShaderCode,
                                       VK_SHADER_TYPE_FRAGMENT,
                                       &fragmentShaderBinary));
    
        VkShaderModuleCreateInfo fragmentShaderModuleCreateInfo{
                .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
                .codeSize = fragmentShaderBinary.size() * sizeof(uint32_t),
                .pCode = fragmentShaderBinary.data()
        };
    
        VK_CHECK_ERROR(vkCreateShaderModule(mDevice,
                                            &fragmentShaderModuleCreateInfo,
                                            nullptr,
                                            &mFragmentShaderModule));
    
        // ================================================================================
        // 16. VkPipelineLayout 생성
        // ================================================================================
        VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
        };
    
        VK_CHECK_ERROR(vkCreatePipelineLayout(mDevice,
                                              &pipelineLayoutCreateInfo,
                                              nullptr,
                                              &mPipelineLayout));
    
        // ================================================================================
        // 17. Graphics VkPipeline 생성
        // ================================================================================
        array<VkPipelineShaderStageCreateInfo, 2> pipelineShaderStageCreateInfos{
                VkPipelineShaderStageCreateInfo{
                        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                        .stage = VK_SHADER_STAGE_VERTEX_BIT,
                        .module = mVertexShaderModule,
                        .pName = "main"
                },
                VkPipelineShaderStageCreateInfo{
                        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                        .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
                        .module = mFragmentShaderModule,
                        .pName = "main"
                }
        };
    
        VkVertexInputBindingDescription vertexInputBindingDescription{
                .binding = 0,
                .stride = sizeof(Vertex),
                .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
        };
    
        array<VkVertexInputAttributeDescription, 2> vertexInputAttributeDescriptions{
                VkVertexInputAttributeDescription{
                        .location = 0,
                        .binding = 0,
                        .format = VK_FORMAT_R32G32B32_SFLOAT,
                        .offset = offsetof(Vertex, position)
                },
                VkVertexInputAttributeDescription{
                        .location = 1,
                        .binding = 0,
                        .format = VK_FORMAT_R32G32B32_SFLOAT,
                        .offset = offsetof(Vertex, color)
                }
        };
    
        VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
                .vertexBindingDescriptionCount = 1,
                .pVertexBindingDescriptions = &vertexInputBindingDescription,
                .vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributeDescriptions.size()),
                .pVertexAttributeDescriptions = vertexInputAttributeDescriptions.data()
        };
    
        VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
                .topology =VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
        };
    
        VkViewport viewport{
                .width = static_cast<float>(mSwapchainImageExtent.width),
                .height = static_cast<float>(mSwapchainImageExtent.height),
                .maxDepth = 1.0f
        };
    
        VkRect2D scissor{
                .extent = mSwapchainImageExtent
        };
    
        VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
                .viewportCount = 1,
                .pViewports = &viewport,
                .scissorCount = 1,
                .pScissors = &scissor
        };
    
        VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
                .polygonMode = VK_POLYGON_MODE_FILL,
                .cullMode = VK_CULL_MODE_NONE,
                .lineWidth = 1.0f
        };
    
        VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
                .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
        };
    
        VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
        };
    
        VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState{
                .colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
                                  VK_COLOR_COMPONENT_G_BIT |
                                  VK_COLOR_COMPONENT_B_BIT |
                                  VK_COLOR_COMPONENT_A_BIT
        };
    
        VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo{
                .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
                .attachmentCount = 1,
                .pAttachments = &pipelineColorBlendAttachmentState
        };
    
        VkGraphicsPipelineCreateInfo graphicsPipelineCreateInfo{
                .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
                .stageCount = pipelineShaderStageCreateInfos.size(),
                .pStages = pipelineShaderStageCreateInfos.data(),
                .pVertexInputState = &pipelineVertexInputStateCreateInfo,
                .pInputAssemblyState = &pipelineInputAssemblyStateCreateInfo,
                .pViewportState = &pipelineViewportStateCreateInfo,
                .pRasterizationState = &pipelineRasterizationStateCreateInfo,
                .pMultisampleState = &pipelineMultisampleStateCreateInfo,
                .pDepthStencilState = &pipelineDepthStencilStateCreateInfo,
                .pColorBlendState = &pipelineColorBlendStateCreateInfo,
                .layout = mPipelineLayout,
                .renderPass = mRenderPass
        };
    
        VK_CHECK_ERROR(vkCreateGraphicsPipelines(mDevice,
                                                 VK_NULL_HANDLE,
                                                 1,
                                                 &graphicsPipelineCreateInfo,
                                                 nullptr,
                                                 &mPipeline));
    
        // ================================================================================
        // 18. Vertex VkBuffer 생성
        // ================================================================================
        constexpr array<Vertex, 3> vertices{
                Vertex{
                        .position{0.0, -0.5, 0.0},
                        .color{1.0, 0.0, 0.0}
                },
                Vertex{
                        .position{0.5, 0.5, 0.0},
                        .color{0.0, 1.0, 0.0}
                },
                Vertex{
                        .position{-0.5, 0.5, 0.0},
                        .color{0.0, 0.0, 1.0}
                },
        };
        constexpr VkDeviceSize verticesSize{vertices.size() * sizeof(Vertex)};
    
        VkBufferCreateInfo bufferCreateInfo{
                .sType =VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
                .size = verticesSize,
                .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
        };
    
        VK_CHECK_ERROR(vkCreateBuffer(mDevice, &bufferCreateInfo, nullptr, &mVertexBuffer));
    
        // ================================================================================
        // 19. Vertex VkBuffer의 VkMemoryRequirements 얻기
        // ================================================================================
        VkMemoryRequirements vertexMemoryRequirements;
        vkGetBufferMemoryRequirements(mDevice, mVertexBuffer, &vertexMemoryRequirements);
    
        // ================================================================================
        // 20. Vertex VkDeviceMemory를 할당 할 수 있는 메모리 타입 인덱스 얻기
        // ================================================================================
        uint32_t vertexMemoryTypeIndex;
        VK_CHECK_ERROR(vkGetMemoryTypeIndex(mPhysicalDeviceMemoryProperties,
                                            vertexMemoryRequirements,
                                            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
                                            VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                                            &vertexMemoryTypeIndex));
    
        // ================================================================================
        // 21. Vertex VkDeviceMemory 할당
        // ================================================================================
        VkMemoryAllocateInfo vertexMemoryAllocateInfo{
                .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
                .allocationSize = vertexMemoryRequirements.size,
                .memoryTypeIndex = vertexMemoryTypeIndex
        };
    
        VK_CHECK_ERROR(vkAllocateMemory(mDevice, &vertexMemoryAllocateInfo, nullptr, &mVertexMemory));
    
        // ================================================================================
        // 22. Vertex VkBuffer와 Vertex VkDeviceMemory 바인드
        // ================================================================================
        VK_CHECK_ERROR(vkBindBufferMemory(mDevice, mVertexBuffer, mVertexMemory, 0));
    
        // ================================================================================
        // 23. Vertex 데이터 복사
        // ================================================================================
        void* vertexData;
        VK_CHECK_ERROR(vkMapMemory(mDevice, mVertexMemory, 0, vertexDataSize, 0, &vertexData));
        memcpy(vertexData, vertices.data(), vertexDataSize);
        vkUnmapMemory(mDevice, mVertexMemory);
    }
    
    VkRenderer::~VkRenderer() {
        vkFreeMemory(mDevice, mVertexMemory, nullptr);
        vkDestroyBuffer(mDevice, mVertexBuffer, nullptr);
        vkDestroyPipelineLayout(mDevice, mPipelineLayout, nullptr);
        vkDestroyPipeline(mDevice, mPipeline, nullptr);
        vkDestroyShaderModule(mDevice, mVertexShaderModule, nullptr);
        vkDestroyShaderModule(mDevice, mFragmentShaderModule, nullptr);
        for (auto framebuffer : mFramebuffers) {
            vkDestroyFramebuffer(mDevice, framebuffer, nullptr);
        }
        mFramebuffers.clear();
        vkDestroyRenderPass(mDevice, mRenderPass, nullptr);
        for (auto imageView : mSwapchainImageViews) {
            vkDestroyImageView(mDevice, imageView, nullptr);
        }
        mSwapchainImageViews.clear();
        vkDestroySemaphore(mDevice, mSemaphore, nullptr);
        vkDestroyFence(mDevice, mFence, nullptr);
        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,
                                             mFence,                 // Fence 설정
                                             &swapchainImageIndex)); // 사용 가능한 이미지 변수에 담기
        //auto swapchainImage = mSwapchainImages[swapchainImageIndex]; // swapchainImage에 더 이상 직접 접근하지 않으므로 이제 사용X
        auto framebuffer = mFramebuffers[swapchainImageIndex];
    
        // ================================================================================
        // 2. VkFence 기다린 후 초기화
        // ================================================================================
        // mFence가 Signal 될 때까지 기다린다.
        VK_CHECK_ERROR(vkWaitForFences(mDevice, 1, &mFence, VK_TRUE, UINT64_MAX));
        // mFence가 Siganl이 되면 vkResetFences를 호출해서 Fence의 상태를 다시 초기화한다.
        // 초기화하는 이유: vkAcquireNextImageKHR을 호출할 때 이 Fence의 상태는 항상 Unsignal 상태여야 하기 때문이다.
        VK_CHECK_ERROR(vkResetFences(mDevice, 1, &mFence));
    
        // ================================================================================
        // 3. VkCommandBuffer 초기화
        // ================================================================================
        vkResetCommandBuffer(mCommandBuffer, 0);
    
        // ================================================================================
        // 4. 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));
    
    
        // ================================================================================
        // 5. VkRenderPass 시작
        // ================================================================================
        VkRenderPassBeginInfo renderPassBeginInfo{
                .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
                .renderPass = mRenderPass,
                .framebuffer = framebuffer,
                .renderArea{
                        .extent = mSwapchainImageExtent
                },
                .clearValueCount = 1,
                .pClearValues = &mClearValue
        };
    
        vkCmdBeginRenderPass(mCommandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
    
        // ================================================================================
        // 6. Graphics VkPipeline 바인드
        // ================================================================================
        vkCmdBindPipeline(mCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mPipeline);
    
        // ================================================================================
        // 7. Vertex VkBuffer 바인드
        // ================================================================================
        VkDeviceSize vertexBufferOffset{0};
        vkCmdBindVertexBuffers(mCommandBuffer, 0, 1, &mVertexBuffer, &vertexBufferOffset);
    
        // ================================================================================
        // 8. 삼각형 그리기
        // ================================================================================
        vkCmdDraw(mCommandBuffer, 3, 1, 0, 0);
    
        // ================================================================================
        // 9. VkRenderPass 종료
        // ================================================================================
        vkCmdEndRenderPass(mCommandBuffer);
    
        // ================================================================================
        // 10. VkCommandBuffer 기록 종료
        // ================================================================================
        VK_CHECK_ERROR(vkEndCommandBuffer(mCommandBuffer)); // mCommandBuffer는 Executable 상태가 된다.
    
        // ================================================================================
        // 11. VkCommandBuffer 제출
        // ================================================================================
        VkSubmitInfo submitInfo{
                .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                .commandBufferCount = 1,
                .pCommandBuffers = &mCommandBuffer,
                .signalSemaphoreCount = 1,
                .pSignalSemaphores = &mSemaphore
        };
    
        // submitInfo 구조체를 넘김으로써 commandBuffer 정보를 queue에 제출
        VK_CHECK_ERROR(vkQueueSubmit(mQueue, 1, &submitInfo, VK_NULL_HANDLE));
    
        // ================================================================================
        // 12. VkImage 화면에 출력
        // ================================================================================
        VkPresentInfoKHR presentInfo{
                .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
                .waitSemaphoreCount = 1,
                .pWaitSemaphores = &mSemaphore,
                .swapchainCount = 1,
                .pSwapchains = &mSwapchain,
                .pImageIndices = &swapchainImageIndex
        };
    
        VK_CHECK_ERROR(vkQueuePresentKHR(mQueue, &presentInfo)); // 화면에 출력.
        VK_CHECK_ERROR(vkQueueWaitIdle(mQueue));
    }

     


     

     

    실행화면