The preparation of the drawing object is implemented in the prepare()
function; this function has already been covered in Chapter 7, Buffer Resource, Render Pass, Framebuffer, and Shaders with SPIR-V. For more information, please refer to the subsection Setting the background color in Render Pass instance under Clearing the background color.
The Render Pass instance records the command's single subpass at a time. A Render Pass may contain one or more subpasses. For each subpass, the commands are recorded using the vkCmdBeginRenderPass()
and vkCmdEndRenderPass()
APIs. These two APIs together define a scope under which iterating through different subpasses will record the commands for that particular subpass.
The vkCmdBeginRenderPass()
API begins Render Pass instance command recording for a given subpass. The following is the specification:
void vkCmdBeginRenderPass( VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents);
The vkCmdBeginRenderPass
API accepts three parameters. The first parameter—commandBuffer
of the VkCommandBuffer
type—indicates the command buffer into which the commands are recorded. The second parameter—pRenderPassBegin
—is a VkRenderPassBeginInfo
type control structure into which Render Pass metadata is passed (more information is provided in the following section). The last parameter is of the VkSubpassContents
type and indicates where and how the contents will be recorded in the subpass execution.
The following are the two types of VkSubpassContents
:
VK_SUBPASS_CONTENTS_INLINE
: In this type, the primary command buffer directly records the subpass contents and doesn't permit the secondary command buffer to execute within this subpassVK_SUBPASS_CONTENTS_SECONDARY_COMMAND
: Here, the secondary command buffer is invoked through primary command buffers and is responsible for recording the subpass contentsWhat are primary and secondary command buffers?
The primary command buffer does not have any parent command buffer. However, the secondary command buffers are always executed from the primary command buffers behaving as its parent. The secondary command buffers are not directly submitted into the device queue; these are recorded into the primary command buffer and executed using the vkCmdExecuteCommands()
API. void vkCmdExecuteCommands(
VkCommandBuffer commandBuffer,
uint32_t commandBufferCount,
const VkCommandBuffer* pCommandBuffers);
This API takes three arguments. The first argument—commandBuffer
(of type Vk
CommandBuffer
)—is the primary command buffer object handle. The second parameter—commandBufferCount
(of typeuint32_t
)—indicates the total number of secondary command buffers that needs to be invoked under the primary command buffer. The last parameter—pCommandBuffers
(of type VkCommandBuffer*
)—specifies a complete list of the secondary command buffer objects to be passed in.
How are secondary command buffers useful?
Secondary command buffers are useful in recording common operations into modular units. These modular capsules can be attached to any of your desired primary buffer as required. In the absence of the secondary command buffer, such common operations will be recorded as part of the primary buffer, making them bulky and resulting in redundancy pollution.
Let's take a look at the VkRenderPassBeginInfo
structure syntax and its parameters:
typedef struct VkRenderPassBeginInfo { VkStructureType sType; const void* pNext; VkRenderPass renderPass; VkFramebuffer framebuffer; VkRect2D renderArea; uint32_t clearValueCount; const VkClearValue* pClearValues; } VkRenderPassBeginInfo;
The following are the various parameters of the VkRenderPassBeginInfo
structure:
Parameters |
Description |
|
This is the type information of this control structure. It must be specified as |
|
This could be a valid pointer to an extension-specific structure or could be |
|
The Render Pass instance consumes the Render Pass object to begin the recording. |
|
The Render Pass is also associated with a |
|
This field indicates the rectangular region affected as a result of the Render Pass execution. |
|
This indicates the number of clear values for color or depth. |
|
This field contains the clear values associated with attachments specified in the |
While defining the renderArea
in the Render Pass instance, if the rendering region is smaller than the framebuffer, then it may cause a performance penalty. In such case, it's advisable either to keep the render area equal to the framebuffer region or to qualify the granularity for the Render Pass. Render area granularity can be checked using the vkGetRenderAreaGranularity()
API.
void vkGetRenderAreaGranularity( VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity);
This API accepts three parameters: the first parameter—device
—is the VkDevice
that is associated with renderPass
; the second parameter is the renderPass
object; the last parameter retrieves the granularity size in pGranularity
.
In a given Render Pass instance, when a subpass recording is finished, the application can switch or transit to the next subpass using the vkCmdNextSubpass()
API.
void vkCmdNextSubpass( VkCommandBuffer commandBuffer, VkSubpassContents contents);
This API accepts two parameters as shown in the following table:
Parameters |
Description |
|
This indicates the command buffer into which the commands are recorded. |
|
This indicates where and how the contents will be provided in the next subpass execution. For more information on |
The vkCmdEndRenderPass()
API ends the Render Pass instance command buffer recording for the subpass that is currently being executed. This API takes one parameter specifying the handle of the command buffer on which the recording must be stopped.
void vkCmdEndRenderPass( VkCommandBuffer commandBuffer);
The current subpass is specified with a clear black color, which will paint the swapchain image with this value, making the background appear black. In addition, other parameters, such as the Render Pass object, framebuffer, and dimension, are also specified. There are many other commands that get executed in the Render Pass instance; this will be discussed in the next section, the following implementation shows the Render Pass instance recording using the vkCmdBeginRenderPass()
and vkCmdEndRenderPass()
APIs:
void VulkanDrawable::recordCommandBuffer(int currentImage, VkCommandBuffer* cmdDraw) { VulkanDevice* deviceObj= rendererObj->getDevice(); // Specify the clear color value VkClearValue clearValues[2]; clearValues[0].color.float32[0]= 0.0f; clearValues[0].color.float32[1]= 0.0f; clearValues[0].color.float32[2]= 0.0f; clearValues[0].color.float32[3]= 0.0f; // Specify the depth/stencil clear value clearValues[1].depthStencil.depth = 1.0f; clearValues[1].depthStencil.stencil = 0; // Define the VkRenderPassBeginInfo control structure VkRenderPassBeginInfo renderPassBegin; renderPassBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassBegin.pNext = NULL; renderPassBegin.renderPass = rendererObj->renderPass; renderPassBegin.framebuffer = rendererObj-> framebuffers[currentImage]; renderPassBegin.renderArea.offset.x = 0; renderPassBegin.renderArea.offset.y = 0; renderPassBegin.renderArea.extent.width = rendererObj->width; renderPassBegin.renderArea.extent.height= rendererObj->height; renderPassBegin.clearValueCount = 2; renderPassBegin.pClearValues = clearValues; // Start recording the render pass instance vkCmdBeginRenderPass(*cmdDraw, &renderPassBegin, VK_SUBPASS_CONTENTS_INLINE); // Execute the commands as per requirement . . . . // pipeline bind, geometry, viewport, scissoring // End of render pass instance recording vkCmdEndRenderPass(*cmdDraw); . . . . }
In the Render Pass instance, the first thing we will need to do is bind the pipeline using the vkCmdBindPipeline()
API. This API binds a specific pipeline (either Graphics or Compute) with the current command buffer that is using this command.
void vkCmdBindPipeline( VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline);
Parameters |
Description |
|
This indicates the command buffer object (used in Render Pass recording) that will be bound to the |
|
This field indicates the type of the pipeline binding point to which the pipeline object will be bounded. This field is of type typedef enum VkPipelineBindPoint { VK_PIPELINE_BIND_POINT_GRAPHICS = 0, VK_PIPELINE_BIND_POINT_COMPUTE = 1, } VkPipelineBindPoint;
The first value indicates the bind point for graphics pipeline and the second for the compute pipeline.
|
|
This indicates the pipeline object to which the command buffer will be bounded. |
Each pipeline—graphics or compute—is very specific to the commands that it affect once bounded:
VK_PIPELINE_BIND_POINT_COMPUTE
, only the vkCmdDispatch
and vkCmdDispatchIndirect
commands behavior can be controlled. Any other command under this pipeline state will remain unaffected. For more information on these commands, please refer to the official Vulkan specification documentation at https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html.VK_PIPELINE_BIND_POINT_GRAPHICS,
the vkCmdDraw
, vkCmdDrawIndexed
, vkCmdDrawIndirect
, and vkCmdDrawIndexedIndirect
commands can be controlled. Any other command under this pipeline state will remain unaffected.The following code shows the binding of the graphics pipeline with the existing command buffer object cmdDraw
, The pipeline
object is connected with the graphics bind point (VK_PIPELINE_BIND_POINT_GRAPHICS
):
void VulkanDrawable::recordCommandBuffer(int currentImage, VkCommandBuffer* cmdDraw){
. . . .
// Bound the command buffer with the graphics pipeline
vkCmdBindPipeline(*cmdDraw, VK_PIPELINE_BIND_POINT_GRAPHICS,
*pipeline);
. . . .
}
For more information on pipeline creation and the pipeline object specified in the implementation, please refer to Chapter 8, Pipelines and Pipeline State Management.
The geometry information for the drawing object can be specified using the vertex buffer. Remember, we built the buffer resource in Chapter 7, Buffer Resource, Render Pass, Framebuffer, and Shaders with SPIR-V and stored the vertex information in the VertexBuffer.buf
of the VulkanDrawble
class.
For more information on , and to recap, vertex buffer building, please refer to the Understanding the buffer resource and Creating geometry with buffer resource section in Chapter 7, Buffer Resource, Render Pass, Framebuffer, and Shaders with SPIR-V.
Vertex data contains vertex position and color information together in an interleaved form. Bind this vertex data to the command buffer under the graphics pipeline state using the vkCmdBindVertexBuffers()
API. This command bounds a specific vertex buffer to a command buffer on a per-draw basis.
void vkCmdBindVertexBuffers( VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets);
The following table lists the fields and indicates the description of each parameter:
Parameters |
Description |
|
This is the command buffer object into which the |
|
This field indicates the index of the vertex input binding, which will be updated by the command. |
|
This indicates the number of vertex input bindings whose state will be updated by the command. |
|
This field is an array of the |
|
This field is an array of vertex buffer offsets. |
Bind the command buffer object with the necessary information to pick the geometry data using the vkCmdBindVertexBuffer()
API; this API takes the vertex buffer information of the drawing object that we are interested in to draw on the display:
void VulkanDrawable::recordCommandBuffer(int currentImage, VkCommandBuffer* cmdDraw){
. . . .
// Bound the vertex buffer with the command buffer
vkCmdBindVertexBuffers(*cmdDraw, 0, 1, &VertexBuffer.buf,
offsets);
. . . .
}
A viewport determines the portion of the drawing surface region on which the drawing object primitives will be rendered. In Chapter 8, Pipelines and Pipeline State Management, we learned to manage viewport state under the graphics pipeline in the Viewport management subsection under Understanding the Pipeline State Objects (PSO) and created a pipeline state object. Viewport parameters can be controlled statically or dynamically:
VK_DYNAMIC_STATE_VIEWPORT
is disabled, then the viewport parameters are not supposed to be changed and specified once the viewport pipeline state object is created using the VkPipelineViewportStateCreateInfo
class' member variable pViewport
.VK_DYNAMIC_STATE_VIEWPORT
is enabled while creating the pipeline state object, then the viewport transformation parameters are allowed to be changed at runtime. These parameters can be controlled dynamically at runtime using the vkCmdSetViewport()
API. The following is the syntax and description of this API:void vkCmdSetViewport( VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport* pViewports);
The fields and a description of each parameter follow:
Parameters |
Description |
|
This field specifies the command buffer object that will be used to record this command. |
|
The |
|
These are the total number of viewports in the |
|
This is a pointer array of the |
typedef struct VkViewport { float x; float y; float width; float height; float minDepth; float maxDepth; } VkViewport;
The fields and a description of each parameter follow:
Parameters |
Description |
|
This is the upper-left corner of the viewport (x, y). |
|
This indicates the width and height of the viewport. |
|
This is the depth range for the viewport. |
The viewport is initialized using the initViewport()
function by passing the current command buffer object into which the vkCmdSetViewport()
command will be recorded. The vkCmdSetViewport()
API sets the viewport parameters, which are dynamically set in the viewport region, specifying the upper-left dimension and depth information of the viewable region:
void VulkanDrawable::recordCommandBuffer(int currentImage, VkCommandBuffer* cmdDraw){
. . . .
// Define the dynamic viewport here.
initViewports(cmdDraw);
. . . .
}
void VulkanDrawable::initViewports(VkCommandBuffer* cmd)
{
viewport.height = (float)rendererObj->height;
viewport.width = (float)rendererObj->width;
viewport.minDepth = (float) 0.0f;
viewport.maxDepth = (float) 1.0f;
viewport.x = 0;
viewport.y = 0;
vkCmdSetViewport(*cmd, 0, NUMBER_OF_VIEWPORTS, &viewport);
}
A scissor defines a rectangular region; any framebuffer fragment's location (x, y) falling outside this rectangular region is discarded.
If this pipeline state object is not created with the dynamic state VK_DYNAMIC_STATE_VIEWPORT
enabled, then scissor rectangles are controlled using the VkPipelineViewportStateCreateInfo
class's member variable pScissors
. On the other hand, if the pipeline state object is created with the dynamic state VK_DYNAMIC_STATE_VIEWPORT
enabled, then the scissor rectangle parameter can be specified and controlled dynamically using the vkCmdSetScissor()
API.
Similar to viewport parameters, scissor parameters can be controlled statically or dynamically:
VK_DYNAMIC_STATE_VIEWPORT
. Such a case informs the pipeline about the static nature of the viewport, which could be beneficial for decision-making and avoiding any kind of housekeeping that the viewports dynamic factors required. For static viewport configuration, the scissor parameters read from the viewport pipeline state object VkPipelineViewportStateCreateInfo
class's member variable pScissors
.VK_DYNAMIC_STATE_VIEWPORT
is enabled, scissor parameters can be changed dynamically and specified through a special API called vkCmdSetViewport()
. The following is the syntax and description of this API:void vkCmdSetScissor( VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount, const VkRect2D* pScissors);
The fields and a description of each parameter follow:
Parameters |
Description |
|
This field specifies the command buffer object that will be used to record this command. |
|
The |
|
This is the total number of scissors in the |
|
This is a pointer to an array of the |
Scissoring is initialized using the initScissors()
function specifying the non-clipping region. Anything outside this rectangular region will be discarded. The vkCmdSetScissor()
API sets the scissoring parameters, which can be dynamically set, indicating the rectangular dimensions used for single or multiple scissoring:
void VulkanDrawable::recordCommandBuffer(int currentImage, VkCommandBuffer* cmdDraw){ . . . . // Define the scissor here. initScissors(cmdDraw); . . . . } void VulkanDrawable::initScissors(VkCommandBuffer* cmd) { scissor.extent.width = rendererObj->width; scissor.extent.height = rendererObj->height; scissor.offset.x = 0; scissor.offset.y = 0; vkCmdSetScissor(*cmd, 0, NUMBER_OF_SCISSORS, &scissor); }
The draw command helps in assembling the primitives. Vulkan supports index- and nonindex-based draw commands. A draw command can affect the framebuffer by the order in which fragments are ordered. In cases where multiple instances of a draw command are used, the API order is used to process the draw commands. For nonindex-based commands, the rule is to put primitives with lowered number instances earlier in the order. For index-based commands, the primitive with a lower number of vertex index values is placed earlier in the API order.
Vulkan renders drawing objects by recording draw commands in the command buffer. There are four different drawing commands available in Vulkan, which are broadly divided into the following two types of categories:
vkCmdDraw
and vkCmdDrawIndexed
) specifies the drawing parameters in the command buffer object itselfvkCmdDrawIndirect
and vkCmdDrawIndexedIndirect
) uses buffer memory to read the parameters from a drawing API, which is suffixed with the Indirect
keyword and is of the latter type; otherwise, it's the former oneThe vkCmdDraw()
API reads the drawing parameters from the command buffer; the vertex information is accessed in the form of an array in sequential order, starting from the first vertex (firstVertex
) to the total number of vertices specified by the vertexCount
. The vkCmdDraw
API renders primitives specified by the input assembly state under the pipeline state object using vertex array data information.
This API supports instancing, which allows efficient rendering of an object multiple times without calling multiple draw commands. Such drawing features are very helpful in situations such as crowd simulation, tree rendering, clone patterns, and so on. The total number of drawing instances is specified using instanceCount,
starting from the first instance index indicated by firstInstance
.
void vkCmdDraw( VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance);
The fields and a description of each parameter follow:
Parameters |
Description |
|
This field specifies the command buffer object ( |
|
This is the total count of the vertex intended to draw. |
|
This field indicates the total number of instances to be drawn using this command. |
|
This field specifies the very first vertex index from which the vertices are read in order to draw them. |
|
This field specifies the very first instance ID of the instance to draw. |
The present example in this chapter makes use of the following command:
vkCmdDraw(*cmdDraw, 3, 1, 0, 0);
This command consumes the following triangle data, which represents three vertices in the interleaved form, hence the second parameter is specified as 3
; since there is only one instance, the third parameter is 1
. The drawing should start from the first vertex, which is indicated by index 0 as the fourth parameter. The last parameter is 0, pointing to the first instance ID:
struct VertexWithColor { float x, y, z, w; // Vertex Position float r, g, b, a; // Color format Red, Green, Blue, Alpha }; static const VertexWithColor triangleData[] = { { 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0 },/*Vertex0*/ { 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0 },/*Vertex1*/ {-1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0 },/*Vertex2*/ };
Another type of drawing command used in this chapter is the vkCmdDrawIndexed()
API. This API renders indexed geometry. For detailed information, please refer to the Rendering an indexed geometry section at the end of this chapter. But before jumping to this section, you must work through all the remaining sections to understand the rendering of drawing objects in Vulkan.
The drawing object's preparation process is executed inside the recordCommandBuffer
function of the VulkanDrawable
class. This function records the commands associated with the drawing object for each swapchain buffer in a separate command buffer object (cmdDraw
). The process includes the following steps.
Record the Render Pass instance using the vkCmdBeginRenderPass
() API; this accepts the VkRenderPassBeginInfo
class's object (renderPassBegin
) as an input parameter, which contains the Render Pass and framebuffer objects that indicate the attachments, subpasses, and image views associated with this Render Pass instance. For more information on Render Pass and framebuffer objects, please refer to Chapter 7, Buffer Resource, Render Pass, Framebuffer, and Shaders with SPIR-V.
The other information the renderPassBegin
carries is the extent of the drawing region on the framebuffer. In addition, the clear values for color, depth, or stencil image are also set inside. This information clears the buffer attachments associated with the specified clear value. For example, the clear value associated with the color buffer attachment works like a background color.
Use vkCmdBindPipeline()
and bind the command buffer with the graphics pipeline object that we created in Chapter 8, Pipelines and Pipeline State Management.
For dynamically setting the viewport and scissoring region, use initViewports()
and initScissors()
and set the vkCmdSetViewport()
and vkCmdSetScissor()
APIs with the required rectangular region dimensions.
Specify the draw command using the non-indexed drawing API vkCmdDraw()
. The first argument specifies the command buffer object (VkCommandBuffer
) into which the drawing command will be recorded. The second argument 3
specifies the number of vertices the geometry is intended to draw. The third argument 1
specifies that there needs to draw single instance at a time. The fourth argument specifies the first vertex index (0) to draw; the last argument 0 specifies the first index to be used for instance-based drawing responsible for controlling the rate at which data advances from an instanced array.
Finish the Render Pass instance recording using vkCmdEndRenderPass()
and passing the current command buffer object into it.
The recording of the command buffer is implemented as follows:
void VulkanDrawable::recordCommandBuffer(int currentImage, VkCommandBuffer* cmdDraw) { VulkanDevice* deviceObj = rendererObj->getDevice(); VkClearValue clearValues[2]; clearValues[0].color.float32[0] = 0.0f; clearValues[0].color.float32[1] = 0.0f; clearValues[0].color.float32[2] = 0.0f; clearValues[0].color.float32[3] = 0.0f; clearValues[1].depthStencil.depth = 1.0f; clearValues[1].depthStencil.stencil = 0; VkRenderPassBeginInfo renderPassBegin; renderPassBegin.sType = VK_STRUCTURE_TYPE_ RENDER_PASS-_BEGIN_INFO; renderPassBegin.pNext = NULL; renderPassBegin.renderPass = rendererObj->renderPass; renderPassBegin.framebuffer = rendererObj-> framebuffers[currentImage]; renderPassBegin.renderArea.offset.x = 0; renderPassBegin.renderArea.offset.y = 0; renderPassBegin.renderArea.extent.width = rendererObj->width; renderPassBegin.renderArea.extent.height = rendererObj->height; renderPassBegin.clearValueCount = 2; renderPassBegin.pClearValues = clearValues; // Start recording the render pass instance vkCmdBeginRenderPass(*cmdDraw, &renderPassBegin, VK_SUBPASS_CONTENTS_INLINE); // Bound the command buffer with the graphics pipeline vkCmdBindPipeline(*cmdDraw, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); // Bound the command buffer with the graphics pipeline const VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(*cmdDraw, 0, 1, &VertexBuffer.buf, offsets); // Define the dynamic viewport here initViewports(cmdDraw); // Define the scissoring initScissors(cmdDraw); // Issue the draw command with 3 vertex, 1 instance starting // from first vertex vkCmdDraw(*cmdDraw, 3, 1, 0, 0); // End of render pass instance recording vkCmdEndRenderPass(*cmdDraw); }
3.145.179.85