Synchronization for Vulkan: Fences, Semaphores, Events, Barriers

The synchronization is well known for CPUs that one has multiple threads for writing aimed to updating a buffer, then he needs to synchronize to make sure all writes have finished, then he can continue to process the data with multiple threads. However, for GPUs, the synchronization isn’t exposed to developers which was previously hidden inside the driver. But now, synchronization of access to resources in Vulkan is primarily the responsibility of the application, which named “fences”, “Semaphores”, “Events” and “barriers”.

In Vulkan, there are three forms of concurrency during execution: between the host and device, between the queues, and between commands within a command buffer. Vulkan provides the application with a set of synchronization primitives for these purposes. Further, memory caches and other optimizations mean that the normal flow of command execution does not guarantee that all memory transactions from a command are immediately visible to other agents with views into a given block of memory. Vulkan also provides barrier operations to ensure this type of synchronization.

Four synchronization primitive types are exposed by Vulkan. These are:

  1. Fences, being used to communicate completion of execution of command buffer submissions to queues back to the application.
  2. Semaphores, being generally associated with resources or groups of resources and can be used to marshal ownership of shared data. Their status is not visible to the host.
  3. Events, providing a finer-grained synchronization primitive which can be signaled at command level granularity by both device and host, and can be waited upon by either.
  4. Barriers, providing execution and memory synchronization between sets of commands.

Fences

Fences can be used by the host to determine completion of execution of submissions to queues performed with vkQueueSubmit and vkQueueBindSparse. A fence’s status is always either signaled or unsignaled. The host can poll the status of a single fence, or wait for any or all of a group of fences to become signaled. To create a new fence object, use the command vkCreateFence. Following codes illustrate how to create fence.

Fence must be unsignaled before submitting queue. Following codes illustrate the usage:

Besides, user could call method vkWaitForFences to wait for completion. A fence is a very heavyweight synchronization primitive as it requires the GPU to flush all caches at least, and potentially some additional synchronization. Due to those costs, fences should be used sparingly. In particular, try to group per-frame resources and track them together with a single fence instead of fine-grained per-resource tracking. For instance, all commands buffers used in one frame should be protected by one fence, instead of one fence per command buffer. Fences should be also used sparingly to synchronize the compute, copy and graphics queues per frame. Ideally, try to submit large batches of asynchronous compute jobs with a single fence at the end which denotes that all jobs have finished. Same goes for copies, you should have the end of all copies signaled with a single fence to get the best possible performance.

Semaphores

Semaphores are used to coordinate operations between queues and between queue submissions within a single queue. An application might associate semaphores with resources or groups of resources to marshal ownership of shared data. A semaphore’s status is always either signaled or unsignaled. Semaphores are signaled by queues and can also be waited on in the same or different queues until they are signaled. To create a new semaphore object, use the command vkCreateSemaphore. Following codes illustrate how to create semaphore and usage of it.

When a queue signals or waits upon a semaphore, certain implicit ordering guarantees are provided. Semaphore operations may not make the side effects of commands visible to the host.

Events

Events represent a fine-grained synchronization primitive that can be used to gauge progress through a sequence of commands executed on a queue by Vulkan. An event is initially in the unsignaled state. It can be signaled by a device, using commands inserted into the command buffer, or by the host. It can also be reset to the unsignaled state by a device or the host. The host can query the state of an event. A device can wait for one or more events to become signaled. To create an event, use the command vkCreateEvent. Following codes illustrate how to create semaphore and usage of it.

Applications should be careful to avoid race conditions when using events. For example, an event should only be reset if no vkCmdWaitEvents command is executing that waits upon that event. An act of setting or resetting an event in one queue may not affect or be visible to other queues. For cross-queue synchronization, semaphores can be used.

Barriers

A barrier is a new concept exposed to developers which was previously hidden inside the driver. A pipeline barrier inserts an execution dependency and a set of memory dependencies between a set of commands earlier in the command buffer and a set of commands later in the command buffer. A pipeline barrier is recorded by calling vkCmdPipelineBarrier:

The method vkCmdPipelineBarrier could be called inside render pass or outside render pass. If it is called inside render pass, then the first set of commands is all prior commands in the same subpass and the second set of commands is all subsequent commands in the same subpass. Otherwise, the first set of commands is all prior commands submitted to the queue and recorded in the command buffer and the second set of commands is all subsequent commands recorded in the command buffer and submitted to the queue.

Barrier has three functions in total:

  1. Synchronization, ensuring that previous dependent work has completed before new work starts.
  2. Reformatting, ensuring that data to be read by a unit is in a format that unit understands.
  3. Visibility, ensuring that data that might be cached on a particular unit is visible to other units that might want to see it.

Besides, Vulkan provides three types of memory barriers: global memory, buffer memory, and image memory.

  1. Global memory, specifying with an instance of the VkMemoryBarrier structure. This type of barrier applies to memory accesses involving all memory objects that exist at the time of its execution.
  2. Buffer memory, specifying with an instance of the VkBufferMemoryBarrier structure. This type of barrier only applies to memory accesses involving a specific range of the specified buffer object. That is, a memory dependency formed from a buffer memory barrier is scoped to the specified range of the buffer. It is also used to transfer ownership of a buffer range from one queue family to another.
  3. Image memory, specifying with an instance of the VkImageMemoryBarrier structure. This type of barrier only applies to memory accesses involving a specific subresource range of the specified image object. That is, a memory dependency formed from an image memory barrier is scoped to the specified subresources of the image. It is also used to perform a layout transition for an image subresource range, or to transfer ownership of an image subresource range from one queue family to another.

Following illustrate some cases to show how to use barrier:

Besides, there’re many other situations to need barrier for synchronization.

Conclusion

The synchronization isn’t exposed to developers which was previously hidden inside the driver. But now, synchronization of access to resources in Vulkan is primarily the responsibility of the application which is intended to be designed to allow advanced applications to drive modern GPUs to their fullest capacity.

赞 (3)

4 thoughts on “Synchronization for Vulkan: Fences, Semaphores, Events, Barriers

  1. Great help to vulkan newbies! Thanks a lot.

    Just wanted to point out that there is possibly a typo, and the “vkCreateFence” call in the Events example should be a call to “vkCreateEvent” instead.

    Thanks again!

发表评论

电子邮件地址不会被公开。 必填项已用*标注