본문 바로가기

Vulkan

Drawing - Command buffers

이전 글 : Drawing - Framebuffers

다음 글 : Drawing - Rendering and presentation




Drawing - Command buffers
커멘드 버퍼, 명령 버퍼

Vulkan의 명령은 그리기 작업 및 메모리 전송과 마찬 가지로 함수 호출을 사용하여 직접 실행하지 않습니다. 명령 버퍼 객체에서 수행 하려는 모든 작업을 기록 해야합니다. 이것의 장점은 미리 그리기 명령을 설정하는 모든 작업이 사전에 그리고 여러 스레드에서 수행 될 수 있다는 것입니다. 그 후에 Vulkan에게 메인 루프에서 명령을 실행하도록 명령하면 됩니다.



Command pools


명령 버퍼를 만들기 전에 명령 풀을 만들어야합니다. 명령 풀은 버퍼를 저장하는 데 사용되는 메모리를 관리하며 각 명령 버퍼는 자신의 버퍼를 할당합니다. VkCommandPool 을 저장할 새 클래스 멤버를 추가합니다.


VkCommandPool commandPool;


그런 다음 createCommandPool이라는 새 함수를 만들고 프레임 버퍼를 만든 후 initVulkan에서 호출합니다.


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

...

void createCommandPool() {

}


명령 풀 생성에는 두 개의 매개 변수 만 필요합니다.


QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);

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


명령 버퍼는 우리가 검색 한 그래픽 및 프리젠 테이션 대기열과 같은 장치 대기열 중 하나에 이를 제출하여 실행됩니다. 각 명령 풀은 단일 유형의 큐에 제출 된 명령 버퍼 만 할당 할 수 있습니다. 우리는 그림 그리기 명령을 기록 할 것이기 때문에 그래픽 대기열 패밀리를 선택해야 합니다.


명령 풀에는 두 가지 플래그가 있습니다.


  • VK_COMMAND_POOL_CREATE_TRANSIENT_BIT : 새로운 커맨드로 커맨드 버퍼가 자주 다시 기록된다는 것을 암시합니다 (메모리 할당 동작을 변경시킬 수 있습니다)

  • VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT : 명령 버퍼를 개별적으로 재기록 할 수 있습니다.이 플래그를 사용하지 않으면 모두 다시 설정해야합니다.


프로그램 시작 부분에 명령 버퍼를 기록한 다음 주 루프에서 명령 버퍼를 여러 번 실행하므로 이 플래그들은 사용하지 않을 것입니다.


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


vkCreateCommandPool 함수를 사용하여 명령 풀 만들기를 마칩니다. 특별한 매개 변수가 없습니다. 프로그램은 프로그램 전체에서 명령을 사용하여 화면에 그려야 하므로 마지막에 풀만 제거 해야 합니다.

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

   ...
}





Command buffer allocation

명령 버퍼 할당


이제 명령 버퍼를 할당하고 명령 버퍼를 기록 할 수 있습니다. 드로잉 명령 중 하나는 올바른 VkFramebuffer 를 바인딩하기 때문에 실제로 스왑 체인의 모든 이미지에 대한 명령 버퍼를 다시 기록해야 합니다. 이를 위해 VkCommandBuffer 객체 목록을 클래스 멤버로 만듭니다. 명령 버퍼는 명령 풀이 삭제되면 자동으로 해제되므로 명시적인 정리가 필요하지 않습니다.


std::vector<VkCommandBuffer> commandBuffers;


이제 각 스왑 체인 이미지에 대한 명령을 할당하고 기록하는 createCommandBuffers 함수에 대한 작업을 시작합니다.


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

...

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


명령 버퍼는 vkAllocateCommandBuffers 함수로 할당 됩니다.이 함수는 할당 할 버퍼와 명령 풀을 지정하는 매개 변수로 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 매개 변수는 할당 된 명령 버퍼가 기본 또는 보조 명령 버퍼인지 여부를 지정합니다.


  • VK_COMMAND_BUFFER_LEVEL_PRIMARY : 실행을 위해 대기열에 제출할 수 있지만 다른 명령 버퍼에서는 호출 할 수 없습니다.

  • VK_COMMAND_BUFFER_LEVEL_SECONDARY : 직접 제출할 수는 없지만 기본 명령 버퍼에서 호출 할 수 있습니다.


여기서 보조 명령 버퍼 기능을 사용하지는 않지만 기본 명령 버퍼에서 일반적인 작업을 재사용 할 수 있다는 것이 도움이 된다고 생각할 수 있습니다.


Starting command buffer recording

명령 버퍼 기록 시작


드로잉은 vkCmdBeginRenderPass 로 렌더 패스를 시작하여 시작 됩니다. 렌더 패스는 VkRenderPassBeginInfo 구조체의 일부 매개 변수를 사용하여 구성됩니다.


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


첫 번째 매개 변수는 렌더 패스 자체와 바인딩 할 어테치먼트 입니다. 각 스왑 체인 이미지에 대해 color attachment 로 지정하는 프레임 버퍼를 만들었습니다.


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;


마지막 두 매개 변수는 VK_ATTACHMENT_LOAD_OP_CLEAR에 사용할 명확한 값을 정의합니다.이 값은 color attachement 에 대한 로드 연산으로 사용됩니다. 명확한 색상을 100 % 불투명도의 검은 색으로 정의했습니다.



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


이제 렌더링 패스를 시작할 수 있습니다. 명령을 기록하는 모든 기능은 vkCmd 접두어로 알아 볼  수 있습니다. 그것들은 모두 void 를 리턴 합니다.우리가 기록을 마칠 때까지 오류 처리가 없을 것입니다.


모든 명령의 첫 번째 매개 변수는 항상 명령을 기록하는 명령 버퍼입니다. 두 번째 매개 변수는 방금 제공 한 렌더링 패스의 세부 정보를 지정합니다. 마지막 매개 변수는 렌더 패스 내에서 드로잉 명령을 제공하는 방법을 제어합니다. 두 값 중 하나를 가질 수 있습니다.


  • VK_SUBPASS_CONTENTS_INLINE : 렌더링 패스 명령은 기본 명령 버퍼 자체에 포함되며 보조 명령 버퍼는 실행되지 않습니다.

  • VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS : 렌더 패스 명령은 보조 명령 버퍼에서 실행됩니다.


보조 명령 버퍼를 사용하지 않으므로 첫 번째 옵션을 사용합니다.




Basic drawing commands

그리기 기초 명령들


이제 그래픽 파이프 라인을 바인딩 할 수 있습니다.


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


두 번째 매개 변수는 파이프 라인 개체가 그래픽 또는 계산 파이프 라인인지 여부를 지정합니다. 이제 Vulkan에게 그래픽 파이프 라인에서 수행 할 작업과 프레그먼트 쉐이더에서 사용할 어테치먼트를 알려주었습니다. 이제 남은것은 삼각형을 그리라고 말하는 것입니다.


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


실제 vkCmdDraw 함수는 약간의 정반대 이지만, 우리가 미리 지정한 모든 정보 때문에 너무 간단합니다. 명령 버퍼와 별도로 다음 매개 변수가 있습니다.


  • vertexCount : 우리는 꼭지점 버퍼를 가지고 있지 않지만 기술적으로 3 개의 꼭지점을 가지고 있습니다.

  • instanceCount : 인스턴스 렌더링에 사용되며, 수행하지 않는 경우 1을 사용합니다.

  • firstVertex : 정점 버퍼에 대한 오프셋으로 사용되며 gl_VertexIndex의 최소값을 정의합니다.

  • firstInstance : 인스턴스 렌더링의 오프셋으로 사용되며 gl_InstanceIndex의 가장 낮은 값을 정의합니다.



Finishing up

마무리


이제 렌더링 패스를 종료 할 수 있습니다.


vkCmdEndRenderPass(commandBuffers[i]);


그리고 명령 버퍼 기록을 마쳤습니다.


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


다음 장에서는 스왑 체인에서 이미지를 수집하고 올바른 명령 버퍼를 실행 한 다음 스왑 체인에 완성 된 이미지를 반환하는 기본 루프 코드를 작성하겠습니다.


C++ code / Vertex shader / Fragment shader


이전 글 : Drawing - Framebuffers

다음 글 : Drawing - Rendering and presentation



'Vulkan' 카테고리의 다른 글

Swap chain recreation  (0) 2018.01.25
Drawing - Rendering and presentation  (0) 2018.01.25
Drawing - Framebuffers  (1) 2018.01.24
Graphics pipeline basics- Conclusion  (0) 2018.01.24
Graphics Pipeline basics - Render passes  (0) 2018.01.24