Creating the Command Buffers#

The command buffers are the most important part of the Vulkan setup, as these are how the application executes tasks on the GPU.

The command buffer is an object that stores a sequence of commands such as draw calls and memory transfer operations. Vulkan, unlike earlier APIs, separates the tasks of command buffer recording and command buffer submission. Thus, the command buffers can be recorded in advance and/or in multiple threads, while the submission may happen immediately or at a later time, one-off, or repeatedly. Vulkan makes very few guarantees about the order in which the GPU executes commands – it can be assumed that commands in the same command buffer will begin executing in order, but sequentiality is not guaranteed. If dependencies need to be expressed, Barriers need to be used.

The approach of splitting the recording of GPU commands from their submission to the queue helps to improve performance. The driver knows in advance how much work will be coming, and so can schedule and execute this work optimally.

Command buffers are allocated from a memory pool called a command pool. This object allows the application to spread the cost of resource creation and command recording. The amount of memory allocated is determined dynamically when the command buffer is recorded.

In this application’s initCommandPoolAndBuffer, the pool is created and linked to the graphics queue family found earlier. The usage flag is set to VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT during creation, which allows the command buffers to be reset individually rather than all at once. A number of primary command buffers equal to the number of swapchain images are then allocated. It is possible to create secondary command buffers, but this is not required here. View in context.

void VulkanHelloAPI::initCommandPoolAndBuffer()
{
    // Populate a command pool info struct with the queue family that will be used and the intended usage behaviour of command buffers
    // that can be allocated out of it.
    VkCommandPoolCreateInfo commandPoolInfo = {};
    commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    commandPoolInfo.pNext = nullptr;
    commandPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    commandPoolInfo.queueFamilyIndex = appManager.graphicsQueueFamilyIndex;

    // Create the actual command pool.
    debugAssertFunctionResult(vk::CreateCommandPool(appManager.device, &commandPoolInfo, nullptr, &appManager.commandPool), "Command Pool Creation");

    // Resize the vector to have a number of elements equal to the number of swapchain images.
    appManager.cmdBuffers.resize(appManager.swapChainImages.size());

    // Populate a command buffer info struct with a reference to the command pool from which the memory for the command buffer is taken.
    // Notice the "level" parameter which ensures these will be primary command buffers.
    VkCommandBufferAllocateInfo commandBufferAllocateInfo = {};
    commandBufferAllocateInfo.pNext = nullptr;
    commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    commandBufferAllocateInfo.commandPool = appManager.commandPool;
    commandBufferAllocateInfo.commandBufferCount = static_cast<uint32_t>(appManager.cmdBuffers.size());
    commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;

    // Allocate the command buffers from the command pool.
    debugAssertFunctionResult(vk::AllocateCommandBuffers(appManager.device, &commandBufferAllocateInfo, appManager.cmdBuffers.data()), "Command Buffer Creation");
}