본문 바로가기
Vulkan

3. Command buffer

by SimonLee 2024. 6. 21.

이전 챕터에서 스왑체인을 생성하고 스왑체인에 사용될 이미지와 이미지 뷰를 생성했었다.

이번 시간에는 커맨드 버퍼와 풀에 대해서 알아보자.

 

0. 커맨드 풀과 버퍼 란 ?

커맨드는 Vulkan API 명령어 들이며,

커맨드 버퍼는 커맨드들이 레코딩이 되어 들어가는 공간이다.

커맨드 풀에서 커맨드 버퍼를 관리를 하며, 커맨드 버퍼를 미리 생성해놓고 사용을 한다.

 

커맨드 풀을 통해서 커맨드 버퍼를 할당 받고, 

연속된 커맨드를 커맨드 버퍼에 레코딩을 하며,

커맨드 버퍼에서 꺼낸 커맨드를 실행을 함으로써 이미지에 렌더링 과정이 이루어진다.

 

커맨드 버퍼 라이프사이클

https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html

 

Initial 단계는 커맨더 버퍼를 할당한다.

Begin 단계는 vkCmdBeginCommandBuffer() 실행, Recording 과정이 진행된다.

End 단계는 vkEndCommandBuffer() 실행, Recording 과정이 종료된다.

Submission 단계는 VkQueueSubmit() 실행, 큐에 제출을 한다.

Pending 단계는 큐에 제출한 작업이 완료될때까지 대기하는 단계이며, vkQueueWaitIdle() 사용하여 다음 배치를 준비할때까지 큐를 기다려야 한다.

Invalid 단계는 에러가 발생한 단계이다.

Reset 을 통하여 Initial 단계로 돌아갈 수 있다.

 

 

 

커맨드 버퍼에는 1차와 2차 2가지 유형이 있다.

1차 커맨드 버퍼

- 2차 커맨드 버퍼의 소유자로, 이들을 실행하는 책임이 있고 큐에 직접 제출

2차 커맨드 버퍼

- 1차 커맨드 버퍼를 통해 실행되며, 큐에 직접 제출 불가능.

 

응용 프로그램의 커맨드 버퍼의 수는 수백에서 수천까지 다양하다.

커맨드 버퍼는 커맨드 버퍼 풀에서 할당이 될수 있도록 설정이 되어 있다.

 

다중 스레드 환경에서 여러 개의 커맨드 버퍼가 생성되면

각 스레드의 별도의 커맨드 풀을 도입하여 동기화 도메인을 분리하는 것이 좋다.

 

OpenGL에서는 응용 프로그램은 일괄처리를 하기 때문에 커맨드 버퍼를 제어하지 못한다. 

반면 Vulkan은 커맨드 버퍼를 명시적으로 제어하여 원하는 큐에 제출함으로써 처리 시작하게 할 수 있다.

 

커맨드 버퍼에 들어갈 유형

1) 동작명령

- 그리기, 전송, 지우기, 복사, 쿼리와 타임스탬프, 시작 종료

2) 스테이트 관리 명령

- 디스크립터 세트와 파이프라인 바인딩, 버퍼, 동적 스테이트 , 렌더패스, 서브패스 스테이트 설정

3) 동기화 명령

- 파이프라인 장벽, 이벤트 설정, 대기 이벤트, 렌더패스, 서브패스 종속성

 

커맨드 버퍼는 단일 큐 또는 복수의 큐에 제출될 수 있다.

복수의 큐에 제출하는 경우 커맨드 버퍼들은 특별한 순서 없이 실행된다.

순서 지정은 세마포어나 펜스를 통한 동기화를 통해서만 가능하다.

 

렌더링된 스와프 체인 이미지를 화면에 표시하기 위해서는 커맨드 버퍼를 사용하여 그래픽스 큐에 제출해야 한다.

 

1. 커맨드 버퍼와 풀 생성

커맨드 풀 생성

// Provided by VK_VERSION_1_0
typedef struct VkCommandPoolCreateInfo {
    VkStructureType             sType;
    const void*                 pNext;
    VkCommandPoolCreateFlags    flags;
    uint32_t                    queueFamilyIndex;
} VkCommandPoolCreateInfo;

sType : VK_STRUCTURE_TYPE_COMMAND

pNext : 확장판 지정 구조체를 가리키는 유효한 포인터이거나 NULL

flags : 이 필드는 열거형 비트 플래그로, 커맨드 풀의 사용 동작과 풀로부터 할당된 커맨드 버퍼의 동작을 지정하게 된다.

열거형 데이터는 vkCommandPoolFlag에서 지정된 자료형으로 입력으로는 사용 가능.

- VK_COMMAND_POOL_CREATE_TRANSIENT_BIT

: 이 풀에서 할당된 커맨드 버퍼가 자주 변경되고, 수명이 짧음을 나타냄

- VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT 

: 풀에서 할당된 커맨드 버퍼가 두 가지 방법으로 개별적으로 리셋될 수 있음을 나타냄.

: vkResetCommandBuffer를 명시적으로 호출하거나 vkBeginCommandBuffer를 호출하는 암묵적인 방법이 있음.

: 이 플래그가 설정되어 있지 않으면 할당된 실행 가능 커맨드 버퍼에서 두 API를 호출하면 안된다.

: 이는 vkResetCommandPool을 호출해 전체만 리셋 할수 있음을 나타낸다.

queFamilyIndex : 커맨드 버퍼가 제출될 큐 패밀리 지정.

 

// Provided by VK_VERSION_1_0
VkResult vkCreateCommandPool(
    VkDevice                                    device,
    const VkCommandPoolCreateInfo*              pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkCommandPool*                              pCommandPool);

device : 커맨드 풀을 생성할 장치의 핸들

pCreateInfo : VKCommandPoolCreateInfo 개체를 참조하며 커맨드 풀 내 커맨드 버퍼의 특성을 지정한다.

pAllocator : 호스트 메모리 할당을 제어하는 함수

pCommandPool : API 처리를 통해 만들어진 VkCommandPool이 반환되는 포인터다.

 

커맨드 풀 리셋

// Provided by VK_VERSION_1_0
VkResult vkResetCommandPool(
    VkDevice                                    device,
    VkCommandPool                               commandPool,
    VkCommandPoolResetFlags                     flags);

device : 커맨드 풀을 소유한 장치의 핸들을 참조.

commandPool : 리셋해야 하는 vkCommandPool 핸들을 참조

flags : 커맨드 풀을 리셋하는 동작을 제어.

 

커맨드 풀 삭제

// Provided by VK_VERSION_1_0
void vkDestroyCommandPool(
    VkDevice                                    device,
    VkCommandPool                               commandPool,
    const VkAllocationCallbacks*                pAllocator);

device : 커맨드 풀을 삭제할 장치의 핸들

commandPool : 삭제해야 하는 vkCommandPool 핸들

allocator : 호스트 메모리 할당을 제어한다.

 

커맨드 버퍼 할당

// Provided by VK_VERSION_1_0
typedef struct VkCommandBufferAllocateInfo {
    VkStructureType         sType;
    const void*             pNext;
    VkCommandPool           commandPool;
    VkCommandBufferLevel    level;
    uint32_t                commandBufferCount;
} VkCommandBufferAllocateInfo;

sType : VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO

pNext : null 이거나 확장판 지정 구조체 참조

commandPool : 요청받은 커맨드 버퍼에 메모리를 할당해야 하는 커맨드 풀 핸들

level : 커맨드 버퍼가 1차 커맨드 버퍼인지, 2차 커맨드 버퍼인지를 지정하는 VkCommandBufferLevel의 비트 플래그

VK_COMMAND_BUFFER_LEVEL_PRIMARY = 0 , VK_COMMAND_BUFFER_LEVEL_SECONDARY = 1

commandBufferCount : 할당이 필요한 커맨드 버퍼의 수를 나타낸다.

 

// Provided by VK_VERSION_1_0
VkResult vkAllocateCommandBuffers(
    VkDevice                                    device,
    const VkCommandBufferAllocateInfo*          pAllocateInfo,
    VkCommandBuffer*                            pCommandBuffers);

device : 커맨드 풀을 소유한 논리적 장치 개체의 핸들

pAllocateInfo : VkCommandBufferAllocateInfo 구조체를 지정한다. 이 구조체는 할당에 필요한 파라미터를 지정한다.

pCommandBuffers : 할당된 커맨드 버퍼 개체 배열을 참조한다.

 

커맨드 버퍼 리셋

// Provided by VK_VERSION_1_0
VkResult vkResetCommandBuffer(
    VkCommandBuffer                             commandBuffer,
    VkCommandBufferResetFlags                   flags);

commandBuffer : 리셋할 개체를 지정

flags : 아래 플래그가 설정되어 있으면 버퍼가 보유한 메모리가 부모 커맨드 풀로 반환.

- VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT = 0x00000001,

 

커맨드 버퍼 해제

// Provided by VK_VERSION_1_0
void vkFreeCommandBuffers(
    VkDevice                                    device,
    VkCommandPool                               commandPool,
    uint32_t                                    commandBufferCount,
    const VkCommandBuffer*                      pCommandBuffers);

device : 커맨드 풀을 가진 논리 장치

commandPool : 해제해야 할 메모리를 포함한 커맨드 풀을 참조

commandBufferCount : 해제해야 할 커맨드 버퍼의 개수

pCommandBuffers : 해제해야 할 커맨드 버퍼의 배열

 

 

전체 코드

    VkCommandPoolCreateInfo commandPoolCreateInfo {
        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
        .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
        .queueFamilyIndex = (uint32_t)mQueueFamilyIndices.graphicsFamily
    };
    vkCreateCommandPool(mDevice, &commandPoolCreateInfo, nullptr, &mCommandPool);

    VkCommandBufferAllocateInfo commandBufferAllocateInfo {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
        .commandPool = mCommandPool,
        .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
        .commandBufferCount = 1
    };
    vkAllocateCommandBuffers(mDevice, &commandBufferAllocateInfo, &mCommandBuffer);
    vkResetCommandBuffer(mCommandBuffer, 0);

 

 

2. 커맨드 버퍼 레코딩

2.1  커맨드 버퍼 레코딩 시작 API

// Provided by VK_VERSION_1_0
VkResult vkBeginCommandBuffer(
    VkCommandBuffer                             commandBuffer,
    const VkCommandBufferBeginInfo*             pBeginInfo);

 

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

sType : VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO

pNext : 확장판 지정 구조체를 가리키는 유효한 포인터이거나 NULL

flags : VkCommandBufferUsageFlagBits 자료형으로 커맨드 버퍼의 동작 방식을 지정하는 비트 필드 마스크

pInheritanceInfo : 커맨드 버퍼가 2차 커맨드 버퍼일 때 사용한다. 

 

VkCommandBufferUsageFlags is a bitmask type for setting a mask of zero or more VkCommandBufferUsageFlagBits.

// Provided by VK_VERSION_1_0
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;

 

 

2.2  커맨드 버퍼 레코딩 종료 API

// Provided by VK_VERSION_1_0
VkResult vkEndCommandBuffer(
    VkCommandBuffer                             commandBuffer);

commandBuffer : 레코딩을 중지할 커맨드 버퍼의 개체

 

2.3 커맨드 버퍼 제출 API

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

queue : 커맨드 버퍼가 제출될 큐의 핸들

submitCount : submitInfo 개체 배열의 크기

submitInfo : VkSubmitInfo 구조체 배열의 포인터로, 각각의 작업 제출에 대한 핵심 정보를 갖고 있으며,

제출된 작업의 수는 submitCount로 전달한다

fence :  이 필드는 커맨드 버퍼 실행이 완료됐는지를 알려주는 신호 메커니즘에서 사용한다.

fence가 NULL이 아니고 submitCount가 0이 아니면,

SubmitInfo의 멤버인 VkSubmitInfo::pCommandBuffers에 정의된 모든 커맨드 버퍼의 실행이 끝났을 때, fence는 종료를 알리는 신호를 받게 된다.

fence 값은 NULL이 아닌데 submitCount가 0이면 fence 신호는 이전에 큐에 제출한 모든 작업이 완료됐음을 의미한다.

 

// Provided by VK_VERSION_1_0
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 : VK_STRUCTURE_TYPE_SUBMIT_INFO

pNext : 확장판 지정 구조체를 가리키거나 NULL

waitSemaphoreCount : 세마포어의 개수를 참조하며, 이 세마포어는 커맨드 버퍼가 실행되기 전에 대기하는 데 사용한다.

pWaitSemaphores : 세마포어 배열 포인터, 커맨드 버퍼가 만들어져 배치에서 실행되기 전에 기다려야 하는 세마포어를 가리킨다.

pWaitDstStageMask : 이 필드는 파이프라인 스테이지들의 배열의 대한 포인터로, 스테이지에서 각 해당 세마포어의 대기가 발생.

commandBufferCount : 배치에서 실행할 커맨드 버퍼의 수를 참조

pCommandBuffers : 배치에서 실행할 커맨드 버퍼의 배열을 가리키는 포인터

signalSemaphoreCount : commandBuffers에서 지정된 명령의 실행이 완료됐음을 알리는 신호를 받을 세마포어의 개수

pSignalSemaphores : 세마포어의 배열을 가리키는 포인터로, 세마포어는 배치에 제출된 커맨드 버퍼의 명령 실행이 완료되면 신호를 받는다.

 

2.4 큐 대기

// Provided by VK_VERSION_1_0
VkResult vkQueueWaitIdle(
    VkQueue                                     queue);

queue : 일단 큐에 제출이 되면 다음 배치를 처리할 준비가 될 때까지 큐를 기다려야 한다.

이 API는 큐에 들어 있는 모든 커맨드 버퍼와 희소 바인딩 작업이 완료될 때까지 차단된다.

 

 

2.5 전체 코드.

    VkCommandBufferBeginInfo commandBufferBeginInfo {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
        .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT // 커맨드버퍼는 한번만 기록되고 다시 리셋될거라는 의미
    };
    vkBeginCommandBuffer(mCommandBuffer, &commandBufferBeginInfo);

    for (auto swapchainImage : mSwapChainImages) {
        // 색상 초기화
        VkClearColorValue clearColorValue {
            .float32 = {0.64, 0.7, 0.2, 1.0}
        };

        VkImageSubresourceRange imageSubresourceRange {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .baseMipLevel = 0,
            .levelCount = 1,
            .baseArrayLayer = 0,
            .layerCount = 1
        };

        vkCmdClearColorImage(mCommandBuffer,
                             swapchainImage.image,
                             VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                             &clearColorValue,
                             1,
                             &imageSubresourceRange);

    }

    vkEndCommandBuffer(mCommandBuffer);
    VkSubmitInfo submitInfo {
        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
        .commandBufferCount = 1,
        .pCommandBuffers = &mCommandBuffer
    };
    vkQueueSubmit(mGraphicQueue, 1, &submitInfo, VK_NULL_HANDLE);
    vkQueueWaitIdle(mGraphicQueue);

'Vulkan' 카테고리의 다른 글

5. Presentation And Synchronize ( Fence, Semaphore, Event )  (0) 2024.06.27
4. Pipeline Barrier And Image Layout  (0) 2024.06.24
2. Surface And SwapChain  (1) 2024.06.11
1. Vulkan Instance And device  (0) 2024.06.09
0. Vulkan을 시작하면서.....  (0) 2024.06.07