[vk] Drawing triangle - Drawing - Command buffers

2021. 8. 11. 15:01그래픽스/vk

1. Command buffers

- Vulkan의 commands (drawing operations and memory transfers)

- 함수호출하여 직접적으로 사용하지 않음

- 수행하고자 하는 모든 operations을 command buffer에 record 해야함. 

- 이후에는 mainloop에서 Vulkan에게 실행해주라고 명령만해주면됨.

 

장점 

- 미리 drawing commands을 설정할 수 있다.

- multiple threads을 사용할 수 있다.

 

 

2. Command pools

- command buffers을 만들기전에 command pool을 생성해야한다.

- Command pools 는 buffers을 저장하는데 사용하는 메모리를 관리한다.

- command buffers은  command pools로 부터 할당된다.

- VkCommandPool 타입인 새 class member를 만들어 주자 

VkCommandPool commandPool;

- 그다음 createCommandPool  함수를 만들고 initVulkan 에서 createFramebuffers 앞에 호출하자

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
    createImageViews();
    createRenderPass();
    createGraphicsPipeline();
    createFramebuffers();
    createCommandPool();
}

...

void createCommandPool() {

}

2.1 Create info

VkCommandPoolCreateInfo

- create info에서는 기본 두개(pNext, sType)를 제외하고, 두개의 파라미터를 더 설정해야함.

QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);

VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
poolInfo.flags = 0; // Optional

- Command buffers은 devices queue중 하나에 제출함으로써 실행된다.

  (검색을 통해 얻은 graphics and presentation queues중 하나)

- 각각의 command pool은 오직 단일 타입 queue에 제출된 command buffers만 할당할 수 있다

  (Each command pool can only allocate command buffers that are submitted on a single type of queue.)

- 여기서 graphicsFamily로 설정한 이유는, 오직 그리기 명령만 기록하려고 하기 때문이다.

 

- Flags은 두가지 종류가 있다.

  • VK_COMMAND_POOL_CREATE_TRANSIENT_BIT: 명령 버퍼가 새 명령으로 매우 자주 재기록된다는 힌트(메모리 할당 동작이 변경될 수 있음)
  • VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT: 명령 버퍼가 개별적으로 재기록되도록 허용. 이 플래그가 없으면 모두 함께 재설정해야함.

- 튜토리얼에선 프로그램시작 부분에서 the command buffers 를 기록한 다음

- 메인 루프에서 여러번 그것들을 실행할것임.

- 그러므로 이러한 flag를 무시해도됨.

 

 

2.2 Create & Destroy

vkCreateCommandPool

if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
    throw std::runtime_error("failed to create command pool!");
}

- screen에 무언가를 그리는 명령어들은 프로그램 전체에서 사용될것임.

- 따라서 destroy는 프로그램 끝에서 수행

void cleanup() {
    vkDestroyCommandPool(device, commandPool, nullptr);

    ...
}

 

 

 

 

3. Command buffer allocation

 

- 이제 command buffers을 할당 받을 수 있다.

- 이제 여기에 drawing commands을 기록할 수 있다.

- drawing commands중 하나는 적절한 VkFramebuffer 과의 바인딩을 포함하고있기 때문에

- 실제로 모든 swapchain의 images에 대한 commandbuffer를 다시 기록해야함.

- 이를 위해, VkCommandBuffer객체의 list를 class member로 생성해야함.

- command buffers은 자동적으로 command pool이 destroy될 때 같이 destroy됨.

  (따로 cleanup에 명시할 필요없음)

https://www.youtube.com/watch?v=0IIqvi3Z0ng&list=PL8327DO66nu9qYVKLDmdLW_84-yE4auCR&index=10

std::vector<VkCommandBuffer> commandBuffers;
void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
    createImageViews();
    createRenderPass();
    createGraphicsPipeline();
    createFramebuffers();
    createCommandPool();
    createCommandBuffers();
}

...

void createCommandBuffers() {
    commandBuffers.resize(swapChainFramebuffers.size());
}

 

 

3.1 createCommandBuffers

 

createCommandBuffers

- 각각의 swapchain image를 위한 commands를 할당하고 기록

 

command buffers

- vkAllocateCommandBuffers 함수로 할당됨

- - - - 이 함수는 할당할 버퍼, command pool의 수를 지정하는

- - - - VkCommandBufferAllocateInfo 구조체를 매개변수로 받음

VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t) commandBuffers.size();

if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
    throw std::runtime_error("failed to allocate command buffers!");
}

level  : 할당된 command buffers 가 "primary" 인지 "secondary" 인지를 명시  

 

  • VK_COMMAND_BUFFER_LEVEL_PRIMARY: Can be submitted to a queue for execution, but cannot be called from other command buffers.
  • VK_COMMAND_BUFFER_LEVEL_SECONDARY: Cannot be submitted directly, but can be called from primary command buffers.

- 여기선 secondary 기능을 사용하지 않지만,

- primary command buffer에서 일반적인 작업을 재사용할 수 있는것이 도움이 됨.?

 

 

4. Starting command buffer recording

 - vkBeginCommandBuffer  함수를 호출하여 기록을 시작할 수 있음

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

 

4.1 BeginInfo

- 파라미터인 VkCommandBufferBeginInfo 를 기술해야함.

for (size_t i = 0; i < commandBuffers.size(); i++) {
    VkCommandBufferBeginInfo beginInfo{};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = 0; // Optional
    beginInfo.pInheritanceInfo = nullptr; // Optional

    if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
        throw std::runtime_error("failed to begin recording command buffer!");
    }
}

- flags : 어떻게 명령 버퍼를 사용하는지를 나타냄

  • VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT: The command buffer will be rerecorded right after executing it once.
  • VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT: This is a secondary command buffer that will be entirely within a single render pass.
  • VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT: The command buffer can be resubmitted while it is also already pending execution.

- 이 플래그들은 지금당장 사용하지 않음.

 

- pInheritanceInfo : secondary(보조) 명령 버퍼에만 적용됨.

- - - - 호출하는 primary command buffers에서 상속할 상태를 지정

 

- 만일 이 command buffer 가 이미 기록되어졌다면.

- vkBeginCommandBuffer 는 암시적으로 재설정함.

- 나중에 버퍼에 명령을 추가할 수 없음. (not possible append command)

 

 

5. Starting a render pass

- 드로잉는 vkCmdBeginRenderPass로  render pass를 시작함으로써 시작한다.

- render pass 는 VkRenderPassBeginInfo 안의 파라미터들을 사용하여 구성됨.

 

5.1 Begin info

VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];

- renderPass : 인스턴스를 시작하기 위한 renderpass (framebuffer 의 renderpass와 호환이되는)

  ( and the attachments to bind )

- framebuffer : renderpass와 함께 사용되는 attachments을 포함하고 있는 framebuffer.

- - - - 각 swapchain image의 color attachment에 대한 framebuffer를 만든것으로 지정

renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = swapChainExtent;

- renderArea 의 size를 정의 

- renderArea는 shader 에서 loads 와 store 이 발생하는 영역을 정의함

- 이 영역 밖의 픽셀은 정의되지 않음.

- attachment의 사이즈와 크기가 일치해야 좋은 성능을 보여줌.

 

VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;

- VK_ATTACHMENT_LOAD_OP_CLEAR 에서 사용할 값을 정의.

- 이 값은 color attachment 에 대한 로드 연산으로 사용됨.

- 명확한 색상을 위해 black + 100% opacity로 설정함

 

5.2 BeginRenderPass

vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

- render pass 를 이제 시작할 수 있음.

- 명령어를 기록할 수 있는 모든 명령어(함수) 들은 전부 vkCmd  접두사가 붙음

- 그 함수들은 전부 void를 반환하는 함수임

- 그래서 기록을 하는동안에는 오류처리가 없을 것임. 

 

- 파라미터 1 : 기록하려는 모든 명령어의 첫번째 파라미터는 항상 command buffer임.

- 파라미터 2 : 방금 생성한 render pass의 세부정보를 지정

- 파라미터 3 : 어떻게 commands을 컨트롤할 것인지에 대해, (render pass에서 제공될 범위안에서),

- - - - 다음 두가지 값을 가질 수 있음.

  • VK_SUBPASS_CONTENTS_INLINE: The render pass commands will be embedded in the primary command buffer itself and no secondary command buffers will be executed.
  • VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS: The render pass commands will be executed from secondary command buffers.

- 지금은 secondary command buffers을 사용하지 않으므로 첫번째 옵션을 설정한것.

6. Basic drawing commands

- 이제 graphics pipeline을 바인딩할 수 있음.

vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

- 파라미터 2 : graphics or compute pipeline, 용도를 명시.

- 이제 Vulkan에게 그래픽 파이프 라인에서 수행할 작업과 fragment shader에서 사용할 attachment를 알려주었음.

- 이제 남은것은 삼각형을 그려달라고 말하는것만 남음

 

6.1 vkCmdDraw

- vkCmdDraw- anticlimactic

- 실제 이 함수는 약간 반항적이지만

- 미리 지정한 모든 정보 때문에 간단하다.

- 이것은 다음과 같은 파라미터를 가짐 (commandbuffer를 제외하고)

  • vertexCount: Even though we don't have a vertex buffer, we technically still have 3 vertices to draw.
  • instanceCount: Used for instanced rendering, use 1 if you're not doing that.
  • firstVertex: Used as an offset into the vertex buffer, defines the lowest value of gl_VertexIndex.
  • firstInstance: Used as an offset for instanced rendering, defines the lowest value of gl_InstanceIndex.
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);

 

7. Finishing up

- EndRenderPass 함수로 rendpass를 종료ㅅ킴.

vkCmdEndRenderPass(commandBuffers[i]);

- 그다음에,  command buffer 기록을 종료한다.

if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
    throw std::runtime_error("failed to record command buffer!");
}

 

- 다음 챕터는 image를 swap chain으로부터 얻어와 적절한 command buffer를 실행하고, swapchain 에 finished image를 반환하는 main loop를 작성할것임.

 

 

 

 

이전글  다음글

    void createCommandBuffers() {
        commandBuffers.resize(swapChainFramebuffers.size());

        VkCommandBufferAllocateInfo allocInfo{};
        allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        allocInfo.commandPool = commandPool;
        allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        allocInfo.commandBufferCount = (uint32_t) commandBuffers.size();

        if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
            throw std::runtime_error("failed to allocate command buffers!");
        }

        for (size_t i = 0; i < commandBuffers.size(); i++) {
            VkCommandBufferBeginInfo beginInfo{};
            beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

            if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
                throw std::runtime_error("failed to begin recording command buffer!");
            }

            VkRenderPassBeginInfo renderPassInfo{};
            renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
            renderPassInfo.renderPass = renderPass;
            renderPassInfo.framebuffer = swapChainFramebuffers[i];
            renderPassInfo.renderArea.offset = {0, 0};
            renderPassInfo.renderArea.extent = swapChainExtent;

            VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
            renderPassInfo.clearValueCount = 1;
            renderPassInfo.pClearValues = &clearColor;

            vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

                vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

                vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);

            vkCmdEndRenderPass(commandBuffers[i]);

            if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
                throw std::runtime_error("failed to record command buffer!");
            }
        }

 

 

C++ code / Vertex shader / Fragment shader

 

https://vulkan-tutorial.com/en/Drawing_a_triangle/Drawing/Command_buffers