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.

The vertical blanking interval (vblank) is the brief period between the last scanline of one frame and the first scanline of the next, during which the display is not actively being refreshed. Synchronising buffer swaps to this window eliminates visible tearing and keeps frame pacing smooth. Device::wait_vblank() is the primitive that lets userspace block until a specific vblank occurs, or schedule an asynchronous notification for one. The vblank API predates atomic modesetting. It is still useful for simple, single-CRTC scenarios or when working with the legacy KMS API. For atomic workflows, prefer page flip events (see the note at the bottom of this page).

wait_vblank

fn wait_vblank(
    &self,
    target_sequence: VblankWaitTarget,
    flags: VblankWaitFlags,
    high_crtc: u32,
    user_data: usize,
) -> io::Result<VblankWaitReply>
Waits for a vblank event on the device. Depending on flags, this either blocks until the target vblank sequence occurs, or returns immediately and delivers a DRM event to the file descriptor when the vblank fires.

Parameters

ParameterTypeDescription
target_sequenceVblankWaitTargetThe vblank sequence to target — either an absolute frame number or a relative offset from the current frame.
flagsVblankWaitFlagsModifier flags controlling blocking vs. event mode and missed-target behaviour.
high_crtcu32CRTC index used for hardware with more than two CRTCs. Requires DriverCapability::VBlankHighCRTC. Pass 0 for the first CRTC.
user_datausizeAn arbitrary value that is echoed back unchanged in the VblankWaitReply. Useful for correlating async events.

Returns

io::Result<VblankWaitReply> — the frame number and timestamp of the vblank that was waited for. If VblankWaitFlags::EVENT is set, the reply is returned immediately (with a zeroed timestamp) and the real event is delivered later via the DRM fd.

VblankWaitTarget

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum VblankWaitTarget {
    /// Wait until this specific frame number is reached.
    Absolute(u32),
    /// Wait for this many vblanks from the current frame.
    Relative(u32),
}
VblankWaitTarget::Relative(1) is the most common value — it means “wait for exactly the next vblank.” Relative(0) returns immediately (current frame). Absolute(n) is useful when you have computed an exact presentation deadline in terms of frame sequence numbers.
The frame sequence counter is a 32-bit value and will wrap around after approximately 497 days at 100 Hz. If you are using Absolute targets across long sessions, handle wrapping carefully.

VblankWaitFlags

bitflags::bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    pub struct VblankWaitFlags: u32 {
        /// Send a DRM event instead of blocking the call.
        const EVENT = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_EVENT;
        /// If the target vblank was already missed, wait for the next one.
        const NEXT_ON_MISS = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_NEXTONMISS;
    }
}

Flag descriptions

VblankWaitFlags::EVENT

When set, wait_vblank returns immediately rather than blocking. The kernel queues an event that will be readable from the DRM file descriptor when the vblank fires. The event payload includes the frame number, timestamp, and the user_data value you passed in. Use this flag when you are running an event loop (e.g. poll/epoll on the DRM fd) and cannot afford to block a thread.
use drm::{VblankWaitTarget, VblankWaitFlags};

// Schedule an async event — returns immediately
card.wait_vblank(
    VblankWaitTarget::Relative(1),
    VblankWaitFlags::EVENT,
    0,
    my_tag as usize,
).unwrap();

// Later, in the event loop: read the DRM fd to get the event

VblankWaitFlags::NEXT_ON_MISS

When set, if the target vblank sequence number is already in the past (i.e. was missed), the kernel waits for the next upcoming vblank rather than returning an error. This makes the call more robust when there is scheduling jitter between preparing work and submitting the wait.
// If we missed our target frame, just wait for the next one
card.wait_vblank(
    VblankWaitTarget::Absolute(target_frame),
    VblankWaitFlags::NEXT_ON_MISS,
    0,
    0,
).unwrap();
You can combine both flags:
// Async event, and tolerate a missed target
let flags = VblankWaitFlags::EVENT | VblankWaitFlags::NEXT_ON_MISS;

VblankWaitReply

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct VblankWaitReply {
    frame: u32,
    time: Option<Duration>,
}
The reply returned by wait_vblank describes the vblank event that was waited for.

frame() -> u32

pub fn frame(&self) -> u32
The sequence number of the vblank that was actually waited for. When NEXT_ON_MISS is used and the original target was missed, this may be greater than the requested sequence number.

time() -> Option<Duration>

pub fn time(&self) -> Option<Duration>
The timestamp of the vblank, as a Duration since the Unix epoch (or since system boot, if DriverCapability::MonotonicTimestamp returns non-zero). Returns None when VblankWaitFlags::EVENT was set, because the actual event has not yet fired at the time wait_vblank returns.
Check DriverCapability::MonotonicTimestamp to know whether the timestamp is relative to CLOCK_MONOTONIC or CLOCK_REALTIME. Most modern drivers use CLOCK_MONOTONIC.

Usage Examples

Blocking wait for the next vblank

The simplest case: block the calling thread until the very next vblank on CRTC 0.
use drm::{Device, VblankWaitTarget, VblankWaitFlags};

let reply = card.wait_vblank(
    VblankWaitTarget::Relative(1),  // next frame
    VblankWaitFlags::empty(),        // blocking
    0,                               // first CRTC
    0,                               // no user data
).unwrap();

println!("Vblank at frame {}, time {:?}", reply.frame(), reply.time());

Async vblank event with NEXT_ON_MISS

Schedule a vblank notification without blocking. The event arrives later on the DRM fd.
use drm::{Device, VblankWaitTarget, VblankWaitFlags};

let flags = VblankWaitFlags::EVENT | VblankWaitFlags::NEXT_ON_MISS;

card.wait_vblank(
    VblankWaitTarget::Relative(1),
    flags,
    0,
    42, // user_data echoed in the event
).unwrap();

// poll/read the DRM fd to receive the vblank event...

Checking the frame counter

Use Relative(0) to query the current frame number without waiting.
let current = card.wait_vblank(
    VblankWaitTarget::Relative(0),
    VblankWaitFlags::empty(),
    0,
    0,
).unwrap();

println!("Current frame: {}", current.frame());

For modern compositor workflows, page flip events are preferred over direct vblank waits. Call page_flip() on a CRTC with PageFlipFlags::EVENT set, and the DRM subsystem delivers a flip-complete event that is implicitly synchronised to the vblank. This approach is simpler, integrates naturally with the atomic API, and avoids the race between submitting a flip and arming a separate vblank wait.

Build docs developers (and LLMs) love