Vulkan Timeline Semaphores

Vulkan Timeline Semaphores are a synchronization primitive accessible both from the device and the host. A timeline semaphore represents a monotonically increasing 64-bit unsigned value. Whereas binary Vulkan semaphores are waited on just to become signaled, timeline semaphores are waited on to reach a specific value. Once a timeline semaphore reaches a certain value, it is considered signaled for every value less than or equal to that value. vkWaitSemaphores is used to wait for semaphores on the host. It can operate in one of two modes: “wait for all” and “wait for any”.

In SwiftShader, Vulkan Timeline Semaphores are implemented as an unsigned 64-bit integer protected by a mutex with changes signaled by a condition variable. Waiting for all timeline semaphores in a set is implemented by simply waiting for each of the semaphores in turn. Waiting for any semaphore in a set is a bit more complex.

Wait for any semaphore

A “wait for any” of a set of semaphores is represented by a TimelineSemaphore::WaitForAny object. Additionally, TimelineSemaphore contains an internal list of all WaitForAny objects that wait for it, as well as for which values they wait. When signaled, the timeline semaphore looks through this list and, in turn, signals any WaitForAny objects that are waiting for a value less than or equal to the timeline semaphore's new value.

A WaitForAny object is created from a VkSemaphoreWaitInfo. During construction, it checks the value of each timeline semaphore provided against the value for which it is waiting. If it has not yet been reached, the wait object registers itself with the timeline semaphore. If it has been reached, the wait object is immediately signaled and no further timeline semaphores are checked.

Once a WaitForAny object is signaled, it remains signaled. There is no way to change what semaphores or values to wait for after construction. Any subsequent calls to wait() will return VK_SUCCESS immediately.

When a WaitForAny object is destroyed, it unregisters itself from every TimelineSemaphore it was waiting for. It is expected that the number of concurrent waits are few, and that the wait objects are short-lived, so there should not be a build-up of wait objects in any timeline semaphore.