Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Smithay/drm-rs/llms.txt

Use this file to discover all available pages before exploring further.

DRM sync objects (syncobjs) are kernel-managed binding points for GPU fences. A fence is a single-use signal that transitions from unsignalled to signalled when a GPU task — such as a command buffer submission — completes. Syncobjs hold these fences and expose them to userspace through file descriptors that can be polled, shared between processes, or imported into other APIs. In practice, syncobjs underpin Vulkan fence objects: when you submit work to a Vulkan queue with an associated fence, the GPU driver attaches a DRM fence to a syncobj. Your application can then export that fence as a sync_file fd and use it to synchronise with display page flips, other GPU engines, or CPU-side waits. drm-rs exposes the full syncobj API through the drm::control::Device trait. Before using it, verify the driver supports syncobjs:
let cap = card
    .get_driver_capability(drm::DriverCapability::SyncObj)
    .expect("Could not query SyncObj capability");
assert!(cap != 0, "Driver does not support sync objects");

Binary vs timeline syncobjs

drm-rs supports two syncobj flavours:
  • Binary syncobjs have two states: unsignalled and signalled. Once signalled, they stay signalled until explicitly reset. They are the simplest form and are used with Vulkan binary fences and semaphores.
  • Timeline syncobjs associate a monotonically increasing 64-bit sequence counter with their fence slots. You can wait for, signal, or query arbitrary points on the timeline. They map to Vulkan timeline semaphores and enable fine-grained producer/consumer synchronisation without allocating a new syncobj per event.
The driver must report DriverCapability::TimelineSyncObj support before using the timeline API.

Binary syncobj workflow

1
Check capability
2
let cap = card
    .get_driver_capability(drm::DriverCapability::SyncObj)
    .expect("Could not query SyncObj capability");
assert!(cap != 0, "Driver does not support sync objects");
3
Create a syncobj
4
create_syncobj takes a boolean that controls whether the object starts in the signalled state. Pass false for an object that will be signalled later by a GPU command submission:
5
use drm::control::syncobj;

let syncobj: syncobj::Handle = card
    .create_syncobj(false) // start unsignalled
    .expect("Failed to create syncobj");
6
Signal the syncobj (simulating command submission)
7
In a real application the GPU driver signals the syncobj when submitted commands complete. For testing without an actual GPU submission you can signal it manually:
8
card.syncobj_signal(&[syncobj])
    .expect("Failed to signal syncobj");
9
Both syncobj_signal and syncobj_reset accept a slice of handles so you can operate on multiple objects in a single ioctl.
10
Export as a sync_file fd
11
A syncobj can be exported either as a plain handle (for sharing between processes via SCM_RIGHTS) or as a sync file fd. The sync file form is what you use for polling and for passing to other kernel APIs:
12
use std::os::unix::io::OwnedFd;

let sync_fd: OwnedFd = card
    .syncobj_to_fd(syncobj, true) // true = export as sync_file
    .expect("Failed to export syncobj as sync_file");
13
When export_sync_file is false, the exported fd is a raw syncobj handle fd suitable for inter-process sharing via fd_to_syncobj.
14
Wait for the fence
15
syncobj_wait blocks the calling thread until one or all of the specified syncobjs become signalled, or until the timeout elapses:
16
card.syncobj_wait(
    &[syncobj],
    5_000_000_000i64, // timeout in nanoseconds (5 seconds)
    true,             // wait_all: wait for ALL handles in the slice
    false,            // wait_for_submit: also wait for a fence to be submitted
)
.expect("Syncobj wait failed or timed out");
17
Set wait_for_submit to true if you want to wait even before a fence has been attached to the syncobj — useful when you know a GPU submission is in flight but may not have reached the kernel yet.
18
Reset the syncobj
19
syncobj_reset moves the syncobj back to the unsignalled state so it can be reused for the next GPU submission:
20
card.syncobj_reset(&[syncobj])
    .expect("Failed to reset syncobj");
21
Destroy the syncobj
22
card.destroy_syncobj(syncobj)
    .expect("Failed to destroy syncobj");

Polling a sync_file fd without blocking

The sync file fd exported by syncobj_to_fd is a standard Linux file descriptor that becomes readable when the underlying fence is signalled. This makes it directly compatible with epoll, poll, and Rust async runtimes:
use rustix::event::{poll, PollFd, PollFlags};
use std::os::unix::io::AsFd;

let fd = sync_fd.as_fd();
let mut poll_fds = [PollFd::new(&fd, PollFlags::IN)];

// Block until the fence signals (None = infinite timeout).
poll(&mut poll_fds, None).expect("poll failed");

println!("Fence signalled!");
Sync file fds are compatible with tokio::io::unix::AsyncFd for async/await integration. Register the fd with AsyncFd::with_interest(sync_file, Interest::READABLE) and .await its readability future to integrate GPU fence waits into a Tokio runtime without blocking a thread.

Importing a foreign sync_file fd

If you receive a sync_file fd from another process (via socket ancillary data) or from another kernel API, import it back into a local syncobj handle with fd_to_syncobj:
let imported_syncobj: syncobj::Handle = card
    .fd_to_syncobj(foreign_fd, true) // true = import as sync_file
    .expect("Failed to import sync_file");
When import_sync_file is false, the fd is treated as a raw syncobj handle fd rather than a sync_file.

Timeline syncobj operations

Timeline syncobjs extend the binary model with a 64-bit point counter. All operations take a slice of (handle, point) pairs:
// Check timeline support.
let timeline_cap = card
    .get_driver_capability(drm::DriverCapability::TimelineSyncObj)
    .expect("Could not query TimelineSyncObj capability");
assert!(timeline_cap != 0, "Driver does not support timeline sync objects");

let timeline = card
    .create_syncobj(false)
    .expect("Failed to create timeline syncobj");

syncobj_timeline_signal

Signal a specific point on the timeline. All waiters blocked on this point or any earlier point are unblocked:
let points = [42u64];
card.syncobj_timeline_signal(&[timeline], &points)
    .expect("Failed to signal timeline point");

syncobj_timeline_wait

Wait until one or all specified timeline points are signalled:
let handles = [timeline];
let points = [42u64];

card.syncobj_timeline_wait(
    &handles,
    &points,
    5_000_000_000i64, // 5-second timeout in nanoseconds
    true,             // wait_all
    false,            // wait_for_submit
    false,            // wait_available_signals
)
.expect("Timeline wait failed");

syncobj_timeline_query

Query the current signalled point of one or more timeline syncobjs without blocking:
let handles = [timeline];
let mut points = [0u64];

card.syncobj_timeline_query(&handles, &mut points, false)
    .expect("Failed to query timeline");

println!("Current signalled point: {}", points[0]);

syncobj_timeline_transfer

Copy the fence at a specific point from one timeline syncobj to a specific point on another. Useful for chaining GPU work across different queues or devices:
card.syncobj_timeline_transfer(
    src_timeline,    // source syncobj handle
    dst_timeline,    // destination syncobj handle
    src_point,       // point to copy from
    dst_point,       // point to copy to
)
.expect("Failed to transfer timeline point");

syncobj_eventfd

Register a Linux eventfd to be notified when a specific timeline point is signalled. The eventfd receives a write of 1 when the point is reached, making it suitable for epoll-based event loops:
card.syncobj_eventfd(
    timeline,
    point,
    eventfd_fd,
    false, // wait_available_signals: also fire when the point becomes available
)
.expect("Failed to register eventfd for syncobj");
Syncobjs are the mechanism Vulkan drivers use to implement fence and semaphore objects. When you call vkQueueSubmit with a VkFence, the Vulkan driver internally passes a syncobj handle to the command submission ioctl. The GPU sets the fence in that syncobj on completion. You can export the resulting fence via syncobj_to_fd to create a cross-API synchronisation chain — for example, to ensure a page flip waits for Vulkan rendering to complete.

Build docs developers (and LLMs) love