Creating the Synchronisation Objects

Synchronisation objects ensure CPU and GPU operations occur in the correct order

Vulkan makes very few guarantees about the order in which command buffers will be executed, in order to get as much parallelism as possible in an application. Acquiring an image from the swapchain, rendering that image, and then presenting that image to the surface need to occur in a very strict order, but otherwise a lot of operations are allowed to overlap.

Three types of synchronisation objects, called fences, semaphores and events are used to ensure CPU and GPU operations occur in the correct order.

What are fences, semaphores, and events?

Fences and semaphores are used to synchronise work on the CPU and GPU that share the same resources.

Fences are GPU to CPU syncs, meaning they ensure certain CPU operations only occur after GPU operations have finished. They are signaled by the GPU and can only be waited on by the CPU. This is done in code by calling vkWaitForFences(…). They also need to be reset manually by the application.

Semaphores are GPU to GPU syncs, specifically used to sync queue submissions (on the same or different queues). Again they are signaled by the GPU but are waited on by the GPU. They are reset after they are waited on.

Events are advanced, extremely fine grained, flexible versions of both, that can synchronise specific points during command buffer execution with either other points in GPU command buffer execution or the CPU. However they are much more detailed and verbose than fences and semaphores so they will not be used in this example.

Example: initSemaphoreAndFences()

In the example code, two semaphores and a fence are created for each swapchain. The semaphores are used to sync acquiring and presenting operations between different images.

  • The acquire semaphore is going to be used to signal that the image has been retrieved from the swapchain and is ready to be rendered to.
  • The present semaphore will signal when rendering has been completed and the image is ready to be presented to the surface.

These semaphores ensure the application does not attempt to acquire an image from the swapchain while it is still being rendered or attempt to render to an image before it has been acquired.

The fence is going to be used to signal when the command buffer of the previous frame has finished executing. The application will wait for the fence to be signalled before continuing with the rest of the drawFrame() function.