Drawing the Frame

The operations that need to be performed every frame

This section will demonstrate how the example application finally produces some visible output on the screen. As with recording to the command buffer, this stage of the application is relatively short and straightforward due to the setup work done during the first three stages.

An example of what is visible on screen is shown below.

One of the most important elements of drawing the frame is ensuring proper synchronisation. The previously created semaphores and fences are put to use here to ensure acquiring, rendering and presenting images occurs in the correct order.

Many of the commands used below have some combination of a wait semaphore field, a signal semaphore field and a signal fence field.
  • The wait semaphore field instructs a command to wait for a semaphore to be signaled before beginning execution.
  • The signal semaphore field and signal fence field instruct a command to signal a semaphore or fence after it has finished executing .

Example: drawFrame()

In the example code, the drawFrame() function is called in a loop in order to update the triangle on screen as it rotates.

This is the high level overview of what this function does:

  1. Wait for the fence to be signaled before rendering
    • This ensures the command buffer for the previous frame has finished executing before the application starts using resources that otherwise might still be in use by the GPU. This also has the side effect that it controls the speed of the CPU side of the application.
  2. Acquire the next image in the swapchain
    • The vkAcquireNextImageKHR() function blocks until an image is available from the swapchain or it returns an error.
    • This command has a signal semaphore which signals when an image has been acquired successfully. This ensures that the image is available for rendering before the application submits a command buffer to begin that render.
  3. Rotate the triangle
    • The vertices of the triangle are calculated in 3 dimensions but this is mainly for illustrative purposes, as the triangle is just a 2D shape shown on a 2D surface. A transformation matrix is calculated to transform the triangle's vertices and apply a rotation. This matrix is the product of a rotation and a projection matrix.
    • A new rotation matrix is calculated before a frame is rendered using an angle value which is incremented on each frame.
    • The transformation that the projection matrix defines is fixed so it only needs to be calculated once, during initialisation.
    • The new transformation matrix is copied into a dynamic uniform buffer, where it can be accessed from the vertex shader.
  4. Submit the command buffer to the graphics queue which begins the render
    • At this point the wait and signal semaphores are set. As mentioned before these semaphores are used to control the synchronisation of GPU operations.
    • The wait semaphore instructs the submission process to wait until the image has been successfully acquired.
    • The rendering semaphore is signaled by this command and will be waited on by the presentation stage.
    • A fence is supplied when the command buffer is submitted. This signals back to step 1 when the execution of this command buffer is finished..
  5. Present the final rendered image to screen
    • Presenting the image returns ownership of this image back to the swapchain, ready for presenting.
    • The order in which these images are presented to the surface is determined by the swapchain's presentation mode which was set during swapchain creation. The preferred presentation mode in this application is VK_PRESENT_MODE_IMMEDIATE_KHR, where the images are presented to the surface as soon as they have finished being rendered.
    • The wait semaphore of this command is set to the rendering semaphore which is the signal semaphore of the previous step. This ensures rendering has been completed before the image can be presented.
  6. Update the frameId
    • This tracks which image/command buffer associated with the frame is being rendered in the current loop.
    • This is incremented at the end of each call to drawFrame() and also wraps when it exceeds the total number of images in the swapchain.