Once the Render Pass instance is prepared and recorded successfully in the drawing object's command buffer, we can reuse it each time to draw the object.
Drawing an object comprises three steps. First, we will need to acquire the index to the next available swapchain image onto which the primitive will be drawn or rasterized. Second, we will need to submit the command buffer to the graphics queue to execute the recorded command on the GPU; the GPU executes these commands and paints the available swapchain drawing images with the draw commands. Finally, the drawn image is handed over to the presentation engine, whichrenders the output onto the attached display window. The following subsections will describe each of these three steps in detail.
Before executing the Render Pass instance-recorded commands, we will need to acquire a swapchain image onto which the drawings will be performed. For this, we will need to query the index of the swapchain image that will be available from the system using the WSI extension vkAcquireNextImageKHR()
; this will return the index of the swapchain image that your application will render to. This extension is available in the form of a function pointer of type PFN_vkAcquireNextImageKHR
.
Upon the API call, it acquires the presentable image onto which the current command buffer will be used to paint and notifies the application that the target presentable image has changed.
Multiple factors influence the availability of presentable images when the API is called. This includes presentation engine implementation, how the VkPresentModeKHR
is being used, the total number of images in the swapchain, the number of images that the application owns at any given time, and of course the application's performance.
VkResult vkAcquireNextImageKHR( VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex);
The fields and a description of each parameter follow:
Parameters |
Description |
|
This indicates the logical |
|
This indicates the swapchain object ( |
|
This parameter indicates whether the API is blocking or nonblocking. When a time-out (in nanosecond) is specified, it indicates for how long the function waits or blocks, if no image is available. If the time-out is 0, then this API will not block and return the success or failure error. This field guarantees that the |
|
This is the |
|
This is a |
|
This retrieves the index of the next presentable image. This index belongs to the index into the array of image handles returned by the |
The following table specifies the return value from the vkAcquireNextImageKHR()
API, which depends on the timeout field:
Return value |
Description |
|
This means that it has successfully acquired the presentable image. |
|
This appears when the presentable surface is no longer available. |
|
This indicates that no image is available and when |
|
When |
|
In this case, the returned presentable image no longer matches with swapchain surface properties, but it can still be used. |
|
Here, the returned presentable image is no longer compatible with the swapchain and thus cannot be further used for presentation with swapchain. In this case, the application must query the compatible surface properties and recreate the swapchain with the required surface properties in order to continue with presentation service. |
The drawing command buffer object is executed by submitting it into the graphics queue using the CommandBufferMgr::submitCommandBuffer()
function, which internally calls the vkQueueSubmit()
API to submit the command buffer.
For more information on the submitCommandBuffer()
API and its usage, please refer to Chapter 5, Command Buffer and Memory Management in Vulkan. In this chapter, you can refer to the subsection Submitting the command to queue under Implementing the wrapper class for command buffer.
When the drawing object's command buffer is executed, the target presentation image is painted with the recorded commands. This image is then queued to the presentation engine using the vkQueuePresentKHR()
API, which renders the output presentation image onto the display output.
VkResult vkQueuePresentKHR( VkQueue queue, const VkPresentInfoKHR* pPresentInfo);
The following parameters are used inside the vkQueuePresentKHR()
API:
Parameters |
Description |
|
This is a |
|
This is a pointer to a |
The following is the syntax and description of VkPresentInfoKHR()
:
typedef struct VkPresentInfoKHR { VkStructureType sType; const void* pNext; uint32_t waitSemaphoreCount; const VkSemaphore* pWaitSemaphores; uint32_t swapchainCount; const VkSwapchainKHR* pSwapchains; const uint32_t* pImageIndices; VkResult* pResults; } VkPresentInfoKHR;
The fields and a description of each parameter follow:
Parameters |
Description |
|
This specifies the type of this structure; this must be |
|
This could be a valid pointer to an extension-specific structure or could be |
|
This field indicates the count of semaphores the presentation engine should wait on before displaying the image. |
|
This specifies the semaphores to wait on before issuing the present request. This is a |
|
There can be more than one swapchain presented; this field indicates the number of swapchains that will be presented using this API command. |
|
This indicates an array of the |
|
This is an array of presentable image indices of each swapchain's presentable images, with total entries specified by |
|
This field, if |
The vkQueuePresentKHR
is capable of presenting multiple presentable images from corresponding swapchains. It releases the ownership of the images (indicated by pImageIndices
; refer to the following VkPresentInfoKHR
) to the presentation engine. These presented images must not be used again until the application regains control of them using the vkAcquireNextImageKHR()
API (and must wait until the returned semaphore is signaled, or fence is completed).
The presentation images sent to the queue are always processed in order; the transfer of the ownership of a presentation image to the presentation engine happens with the submission in the queue. These presentable images are only performed if the submitted semaphore is signaled, indicating that no prior rendering operation is pending. The presentation time is very implementation-specific; it may be affected by the semantics of the presentation engine and the native platform in use.
The following table specifies the return value from the vkQueuePresentKHR()
API:
Return value |
Description |
|
This means that it has successfully acquired the presentable image. |
|
This appears when the presentable surface is no longer available. |
|
In this case, the returned presentable image no longer matches with swapchain surface properties, but it can still be used for paint purposes successfully. |
|
Here, the returned presentable image is no longer compatible with the swapchain and thus cannot be further used for presentation with the swapchain. In this case, the application must query the compatible surface properties and recreate the swapchain with the required surface properties in order to continue with the presentation service. |
When a presentable image is given to the presentation engine, the presentation does not change the contents of this image. If this image is again reacquired using vkAcquireNextImageKHR()
and the transitioning is taken away from the VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
layout, even then the contents remain the same as they were before transitioning. In contrast, if some other mechanism modifies the platform window other than Vulkan, then the contents of all the presentable images in the swapchain become undefined.
Let's understand the implemented rendering code. First, acquire the presentable image index using the vkAcquireNextImageKHR()
API extension; the index is returned in the currentColorImage
variable. This swapchain extension is stored as a function pointer (fpAcquireNextImageKHR
) in the VulkanRender
class.
For more information on querying swapchain extensions, please refer to the subsection Querying swapchain extensions in Chapter 6, Allocating Image Resources and Building a Swapchain with WSI.
You must provide at least one of the sync objects (semaphore or fence); otherwise, you will have no idea when you can use the image. For example, your image will still be read by the presentation engine when you try to acquire it. The vkAcquireNextImageKHR()
is permitted to return as soon as it has identified the image that it has to give you next, not when that image is actually usable. For this reason, synchronization is very important at this step. Vulkan provides two ways to synchronize the swapchain image using semaphore and fences. When semaphore and fences are used, they ensure that when the image is acquired it has no previous pending operation (such as the presentation engine reading it).
In this example, we used a semaphore object (presentCompleteSemaphore
) for synchronization purposes; this object is passed into the vkAcquireNextImageKHR()
to be associated with the image, and this semaphore is signaled when the image can be rendered.
Use the retrieved image index (currentColorImage
) and get the corresponding command buffer from the vecCmdDraw
vector. Create the VkSubmitInfo
control structure and specify the create semaphore object (presentCompleteSemaphore
) in order to submit the command buffer. Upon submission, the commands will only begin execution when a semaphore is signaled; in other words, the image is ready to paint with drawing commands.
As a final approach, the painted image is then queued in the presentation engine for display purposes using the fpQueuePresentKHR
API (vkQueuePresentKHR
), transferring the ownership to the presentation engine. It is very important to ensure that when an image is used by the presentation engine it is not being painted or has any pending operations since the last command buffer submission. This can be simply checked using another semaphore object called drawingCompleteSemaphore
; this object is passed into the VkSubmitInfo
class pSignalSemaphores
field before the command buffer is submitted into the queue. This semaphore is signaled when the command buffer is successfully processed, removing any chance to overlap with the presentation engine's ownership. Once the presentable image is displayed, the presentation engine relinquishes the ownership. vkAcquireNextImageKHR()
can query the same image again and get the ownership.
The following is the implementation code that demonstrates the rendering of the object in Vulkan:
void VulkanDrawable::render() { VulkanDevice* deviceObj = rendererObj->getDevice(); VulkanSwapChain* swapChainObj= rendererObj->getSwapChain(); uint32_t¤tColorImage = swapChainObj-> scPublicVars.currentColorBuffer; VkSwapchainKHR& swapChain = swapChainObj-> scPublicVars.swapChain; VkFence nullFence = VK_NULL_HANDLE; // Get the index of the next available swapchain image: VkResult result = swapChainObj->fpAcquireNextImageKHR( deviceObj->device, swapChain,UINT64_MAX, presentCompleteSemaphore, VK_NULL_HANDLE, ¤tColorImage); VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // Prepare the submit into control structure VkSubmitInfo submitInfo = {}; submitInfo.sType= VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pNext= NULL; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores= &presentCompleteSemaphore; submitInfo.pWaitDstStageMask= &submitPipelineStages; submitInfo.commandBufferCount= (uint32_t)sizeof(&vecCmdDraw [currentColorImage]) / sizeof(VkCommandBuffer); submitInfo.pCommandBuffers = &vecCmdDraw[currentColorImage]; submitInfo.signalSemaphoreCount= 1; submitInfo.pSignalSemaphores = &drawingCompleteSemaphore; // Queue the command buffer for execution CommandBufferMgr::submitCommandBuffer(deviceObj->queue, &cmdDraw[currentColorImage],&submitInfo); // Present the image in the window VkPresentInfoKHR present; present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; present.pNext = NULL; present.swapchainCount= 1; present.pSwapchains= &swapChain; present.pImageIndices= ¤tColorImage; present.pWaitSemaphores= &drawingCompleteSemaphore; present.waitSemaphoreCount= 1; present.pResults= NULL; // Queue the image for presentation, result = swapChainObj->fpQueuePresentKHR (deviceObj->queue, &present); assert(result == VK_SUCCESS); }
The semaphore objects are created in the constructor of the drawable
class and reused throughout the application as shown in the following.
VulkanDrawable::VulkanDrawable(VulkanRenderer* parent) { memset(&VertexBuffer, 0, sizeof(VertexBuffer)); rendererObj = parent; // Prepare the semaphore create info data structure VkSemaphoreCreateInfo presentCompleteSemaphoreCreateInfo; presentCompleteSemaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; presentCompleteSemaphoreCreateInfo.pNext = NULL; presentCompleteSemaphoreCreateInfo.flags = 0; VkSemaphoreCreateInfo drawingCompleteSemaphoreCreateInfo; drawingCompleteSemaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; drawingCompleteSemaphoreCreateInfo.pNext = NULL; drawingCompleteSemaphoreCreateInfo.flags = 0; VulkanDevice* deviceObj = VulkanApplication::GetInstance()-> deviceObj; vkCreateSemaphore(deviceObj->device, &presentCompleteSemaphoreCreateInfo, NULL, &presentCompleteSemaphore); vkCreateSemaphore(deviceObj->device, &drawingCompleteSemaphoreCreateInfo, NULL, &drawingCompleteSemaphore); }
These semaphore objects can be destroyed using the user-defined destroySynchronizationObjects()
function during the de-initialization process:
void VulkanDrawable::destroySynchronizationObjects() { VulkanApplication* appObj = VulkanApplication::GetInstance(); VulkanDevice* deviceObj = appObj->deviceObj; vkDestroySemaphore(deviceObj->device, presentCompleteSemaphore, NULL); vkDestroySemaphore(deviceObj->device, drawingCompleteSemaphore, NULL); }
The following is the output of the program:
3.145.71.115