Binary semaphores are the acquire→signal→wait one-shot primitive —
each is born, fires once, dies. Frame-in-flight tracking becomes a
ring of them. VK_KHR_timeline_semaphore replaces the ring with a
single monotonic counter: each submit signals a value, each wait
targets a value — “wait until frame 42’s work is done” is
vkWaitSemaphores(sem, 42).
Binary vs. timeline
compare.txt
binary: sem signaled → consumed by the wait → must be recreated
next frame — N in-flight frames = N semaphores + N fences
timeline: one semaphore, uint64 counter — "wait for value ≥ N" is
a query against the counter — one object per queue, foreverThe primitives
timeline.cpp
// creation — just a flag + initial value:
vk::SemaphoreTypeCreateInfo tci{vk::SemaphoreType::eTimeline, 0};
auto sem = dev.createSemaphore({{}, &tci});
// a submit signals a value:
vk::TimelineSemaphoreSubmitInfo tssi{};
uint64_t next = ++frameCounter;
tssi.signalSemaphoreValueCount = 1;
tssi.pSignalSemaphoreValues = &next;
si.signalSemaphoreCount = 1;
si.pSignalSemaphores = &sem;
si.pNext = &tssi;
queue.submit(si, fence); // fence still covers CPU-side wait
// host-side wait for a specific value:
dev.waitSemaphores({{}, sem, frameN}, UINT64_MAX);
// GPU-side wait — another submit waits on a specific value:
tssi.waitSemaphoreValueCount = 1;
tssi.pWaitSemaphoreValues = &frameN;Why a camera app cares
uses.txt
in-flight frame tracking — "is frame N's DMA-Buf free to reuse?"
= timeline ≥ N — no per-frame fence ring
decoder → render — decode worker signals value N, renderer
waits on N — the frame number IS the
sync token
multi-slot — one timeline per slot; each camera's
stream is its own counter
screenshot/capture — "wait until the GPU-JPEG dispatch for
frame N landed" = waitSemaphores(N)The convenience calls
calls.cpp
// CPU wait — blocks until the counter reaches value:
dev.waitSemaphores({{}, sem, value}, timeout);
// query without waiting:
uint64_t cur = dev.getSemaphoreCounterValue(sem);
// signal from the host (rare — usually GPU signals):
dev.signalSemaphore({sem, value});Practical rules
timelineSemaphoredevice feature +eTimelinetype at creation — a binary semaphore can’t be promoted.- Values must be monotonic — signal N, then N+1, never N-1; the counter only goes forward.
- A wait on value N means “wait until at least N” — a submit signaling N+5 satisfies a wait on N.
- Still use fences for “the submit is fully retired” — a timeline wait says the semaphore reached N, not that the command buffer’s side effects are CPU-visible; the fence is the memory-flush.
vkWaitSemaphoreson a timeline is the frame-pacing primitive — wait onN-2to allow 2 frames in flight, no fence ring needed.
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow