Module vulkano::sync

source ·
Expand description

Synchronization on the GPU.

Just like for CPU code, you have to ensure that buffers and images are not accessed mutably by multiple GPU queues simultaneously and that they are not accessed mutably by the CPU and by the GPU simultaneously.

This safety is enforced at runtime by vulkano but it is not magic and you will require some knowledge if you want to avoid errors.


Whenever you ask the GPU to start an operation by using a function of the vulkano library (for example executing a command buffer), this function will return a future. A future is an object that implements the GpuFuture trait and that represents the point in time when this operation is over.

No function in vulkano immediately sends an operation to the GPU (with the exception of some unsafe low-level functions). Instead they return a future that is in the pending state. Before the GPU actually starts doing anything, you have to flush the future by calling the flush() method or one of its derivatives.

Futures serve several roles:

  • Futures can be used to build dependencies between operations and makes it possible to ask that an operation starts only after a previous operation is finished.
  • Submitting an operation to the GPU is a costly operation. By chaining multiple operations with futures you will submit them all at once instead of one by one, thereby reducing this cost.
  • Futures keep alive the resources and objects used by the GPU so that they don’t get destroyed while they are still in use.

The last point means that you should keep futures alive in your program for as long as their corresponding operation is potentially still being executed by the GPU. Dropping a future earlier will block the current thread (after flushing, if necessary) until the GPU has finished the operation, which is usually not what you want.

If you write a function that submits an operation to the GPU in your program, you are encouraged to let this function return the corresponding future and let the caller handle it. This way the caller will be able to chain multiple futures together and decide when it wants to keep the future alive or drop it.

Executing an operation after a future

Respecting the order of operations on the GPU is important, as it is what proves vulkano that what you are doing is indeed safe. For example if you submit two operations that modify the same buffer, then you need to execute one after the other instead of submitting them independently. Failing to do so would mean that these two operations could potentially execute simultaneously on the GPU, which would be unsafe.

This is done by calling one of the methods of the GpuFuture trait. For example calling prev_future.then_execute(command_buffer) takes ownership of prev_future and will make sure to only start executing command_buffer after the moment corresponding to prev_future happens. The object returned by the then_execute function is itself a future that corresponds to the moment when the execution of command_buffer ends.

Between two different GPU queues

When you want to perform an operation after another operation on two different queues, you must put a semaphore between them. Failure to do so would result in a runtime error. Adding a semaphore is a simple as replacing prev_future.then_execute(...) with prev_future.then_signal_semaphore().then_execute(...).

Note: A common use-case is using a transfer queue (ie. a queue that is only capable of performing transfer operations) to write data to a buffer, then read that data from the rendering queue.

What happens when you do so is that the first queue will execute the first set of operations (represented by prev_future in the example), then put a semaphore in the signalled state. Meanwhile the second queue blocks (if necessary) until that same semaphore gets signalled, and then only will execute the second set of operations.

Since you want to avoid blocking the second queue as much as possible, you probably want to flush the operation to the first queue as soon as possible. This can easily be done by calling then_signal_semaphore_and_flush() instead of then_signal_semaphore().

Between several different GPU queues

The then_signal_semaphore() method is appropriate when you perform an operation in one queue, and want to see the result in another queue. However in some situations you want to start multiple operations on several different queues.

TODO: this is not yet implemented


A Fence is an object that is used to signal the CPU when an operation on the GPU is finished.

Signalling a fence is done by calling then_signal_fence() on a future. Just like semaphores, you are encouraged to use then_signal_fence_and_flush() instead.

Signalling a fence is kind of a “terminator” to a chain of futures.

TODO: lots of problems with how to use fences TODO: talk about fence + semaphore simultaneously TODO: talk about using fences to clean up


A set of memory access types that are included in a memory dependency.
A memory barrier that is applied to a single buffer.
Dependency info for a pipeline barrier.
Used to block the GPU execution until an event on the CPU occurs.
Parameters to create a new Event.
A mask of multiple external fence handle types.
The properties for exporting or importing external handles, when a fence is created with a specific configuration.
A mask of multiple external semaphore handle types.
The properties for exporting or importing external handles, when a semaphore is created with a specific configuration.
A two-state synchronization primitive that is signalled by the device and waited on by the host.
Parameters to create a new Fence.
Additional parameters for a fence payload import.
Represents a fence being signaled after a previous event.
A memory barrier that is applied to a single image.
Two futures joined into one.
A memory barrier that is applied globally.
A dummy future that represents “now”.
The full specification of memory access by the pipeline for a particular resource.
A set of stages in the device’s processing pipeline.
Specifies a queue family ownership transfer for a resource.
Used to provide synchronization between command buffers during their execution.
Parameters to create a new Semaphore.
Additional parameters for a semaphore payload import.
Represents a semaphore being signaled after a previous event.


Error that can happen when checking whether we have access to a resource.
Access to a resource was denied.
The handle type used for Vulkan external fence APIs.
The handle type used for Vulkan external semaphore APIs.
Error that can happen when creating a graphics pipeline.
A single stage in the device’s processing pipeline.
Error that can be returned from operations on a semaphore.
Declares in which queue(s) a resource can be used.
Declares in which queue(s) a resource can be used.
Contains all the possible submission builders.


Represents an event that will happen on the GPU in the future.


Builds a future that represents “now”.