This section implements the wrapper class for a command buffer called CommandBufferMgr
. This class contains static functions that can be directly used like utility functions without requiring a class object. The class is implemented in a new file called wrapper.h/.cpp
; this file will contain multiple utility methods.
Most of the implemented functions in this class are provided with a default implementation. This means that each function provides the user with the flexibility to change the control structure parameters from outside the function call and send them as parametric arguments. The function arguments are inherent defaults, so if you are not specifying anything custom, the function will do all the jobs for you.
The following is the header implementation of the CommandBufferMgr
class. It declares four static functions that are responsible for allocating memory, recording the command buffer, and submitting the command buffer to the command queue:
/**************** Wrapper.h ******************/ class CommandBufferMgr{ public: // Allocate memory for command buffers from the command pool static void allocCommandBuffer(const VkDevice* device, const VkCommandPool cmdPool, VkCommandBuffer* cmdBuf, const VkCommandBufferAllocateInfo* commandBufferInfo); // Start the command buffer recording static void beginCommandBuffer(VkCommandBuffer cmdBuf, VkCommandBufferBeginInfo* inCmdBufInfo = NULL); // End the command buffer recording static void endCommandBuffer(VkCommandBuffer cmdBuf); // Submit the command buffer for execution static void submitCommandBuffer(const VkQueue& queue, const VkCommandBuffer* cmdBufList, const VkSubmitInfo* submit- Info = NULL, const VkFence& fence = VK_NULL_HANDLE); };
The allocCommandBuffer()
function allocates a command buffer (cmdBuf
) from the specified command pool (cmdPool
). The allocation behavior can be controlled using the VkCommandBufferAllocateInfo
pointer argument. When the last parameter of this function has its default parameter value (NULL
), then VkCommandBufferAllocateInfo
is implemented inside, as shown in the following code, and used for command buffer allocation:
void CommandBufferMgr::allocCommandBuffer(const VkDevice* device, const VkCommandPool cmdPool, VkCommandBuffer* cmdBuf, const VkCommandBufferAllocateInfo* commandBufferInfo){ VkResult result; // If command information is available use it as it is. if (commandBufferInfo) { result = vkAllocateCommandBuffers (*device, commandBufferInfo, cmdBuf); assert(!result); return; } // Default implementation, create the command buffer // allocation info and use the supplied parameter into it VkCommandBufferAllocateInfo cmdInfo = {}; cmdInfo.sType = VK_STRUCTRE_TYPE_COMMAND_BUFFER_ALOCATE_INFO; cmdInfo.pNext = NULL; cmdInfo.commandPool = cmdPool; cmdInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdInfo.commandBufferCount = (uint32_t) sizeof(cmdBuf)/ sizeof(VkCommandBuffer); // Allocate the memory result = vkAllocateCommandBuffers(*device, &cmdInfo, cmdBuf); assert(!result); }
API calls can be easily recorded with the simple beginCommandBuffer()
and endCommandBuffer()
wrapper functions. Refer to the following code to understand the implementation:
void CommandBufferMgr::beginCommandBuffer(VkCommandBuffer cmdBuf, VkCommandBufferBeginInfo* inCmdBufInfo){ VkResult result; // If the user has specified the custom command buffer use it if (inCmdBufInfo) { result = vkBeginCommandBuffer(cmdBuf, inCmdBufInfo); assert(result == VK_SUCCESS); return; } // otherwise, use the default implementation. VkCommandBufferInheritanceInfo cmdBufInheritInfo = {}; cmdBufInheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND- _BUFFER_INHERITANCE_INFO; cmdBufInheritInfo.pNext = NULL; cmdBufInheritInfo.renderPass = VK_NULL_HANDLE; cmdBufInheritInfo.subpass = 0; cmdBufInheritInfo.framebuffer = VK_NULL_HANDLE; cmdBufInheritInfo.occlusionQueryEnable = VK_FALSE; cmdBufInheritInfo.queryFlags = 0; cmdBufInheritInfo.pipelineStatistics = 0; VkCommandBufferBeginInfo cmdBufInfo = {}; cmdBufInfo.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; cmdBufInfo.flags = 0; cmdBufInfo.pInheritanceInfo = &cmdBufInheritInfo; result = vkBeginCommandBuffer(cmdBuf, &cmdBufInfo); assert(result == VK_SUCCESS); }
The following code describes the end of command buffer recording:
void CommandBufferMgr::endCommandBuffer(VkCommandBuffer commandBuffer){ VkResult result; result = vkEndCommandBuffer(commandBuffer); assert(result == VK_SUCCESS); }
In this section, we will use the code snippet from Chapter 7, Buffer Resource, Render Pass, Framebuffer, and Shaders with SPIR-V, and demonstrate the use of the implemented command buffer management utility functions. These functions grately simplify the code for the command buffer creation and submission processes.
In the following code, the Render Pass instance is created using command buffers. First, the command pool (cmdPool
) is created and used to allocate a command buffer (vecCmdDraw
) using allocCommandBuffer()
. The command buffer recording scope is managed between the beginCommandBuffer()
and endCommandBuffer()
functions. Under these scope-defining functions, a series of commands is recorded (vkCmdBeginRenderPass
, vkCmdBindPipeline
, vkCmdDraw
, and more). Ignore the working of the commands used for Render Pass instance creation for now; we will understand them in detail in Chapter 7, Buffer Resource, Render Pass, Framebuffer, and Shaders with SPIR-V. Here, our purpose is only er recording.
Finally, the command buffer is submitted with submitCommandBuffer()
; this function is described in detail in the next section:
vkCreateCommandPool(device, &cmdPoolInfo, NULL, &cmdPool); CommandBufferMgr::allocCommandBuffer(&device, cmdPool,vecCmdDraw); // Start recording the command buffer CommandBufferMgr::beginCommandBuffer(vecCmdDraw); // Render pass instance vkCmdBeginRenderPass( . . . ); vkCmdBindPipeline(. . .); vkCmdBindDescriptorSets(. . .); vkCmdBindVertexBuffers(. . .); vkCmdSetViewport(. . .); vkCmdSetScissor(. . .); vkCmdDraw(. . .); vkCmdEndRenderPass(. . .); // End recording the command buffer CommandBufferMgr::endCommandBuffer(vecCmdDraw); CommandBufferMgr::submitCommandBuffer(queue, &vecCmdDraw);
Submission of the prepared command buffer is done with the help of the submitCommandBuffer()
function. It takes four parameters. The first parameter specifies the submission queue (queue
) to which the second parameter, namely the command buffer (cmdBuffer
), is to be submitted for execution. The third parameter (inSubmitInfo
) specifies the behavior involved in controlling the submission process. The last parameter signals the completion of the submitted command buffers (fence
):
void CommandBufferMgr::submitCommandBuffer(const VkQueue& queue, const VkCommandBuffer cmdBuffer, const VkSubmitInfo* inSubmitInfo, const VkFence& fence){ VkResult result; // If Submit information is available use it as it is. // This assumes that the commands are already specified // in the structure, hence ignore command buffer if (inSubmitInfo){ vkQueueSubmit(queue, 1, inSubmitInfo, fence); result = vkQueueWaitIdle(queue); return; } // Else, create the submit info with specified buffer commands VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pNext = NULL; submitInfo.waitSemaphoreCount = 0; submitInfo.pWaitSemaphores = NULL; submitInfo.pWaitDstStageMask = NULL; submitInfo.commandBufferCount = (uint32_t) sizeof(commandBuffer)/sizeof(VkCommandBuffer); submitInfo.pCommandBuffers = commandBuffer; submitInfo.signalSemaphoreCount = 0; submitInfo.pSignalSemaphores = NULL; result = vkQueueSubmit(queue, 1, &submitInfo, fence); assert(!result); result = vkQueueWaitIdle(queue); assert(!result); }
18.220.245.233