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 Graphics Execution Manager (GEM) is the DRM kernel subsystem responsible for GPU memory management. Whenever your display server allocates a buffer to hold pixel data — whether for a framebuffer, a cursor image, or a rendering target — it does so through GEM. GEM gives each buffer a 32-bit handle that is local to the process and the file descriptor that created it. Handles are reference-counted in the kernel: when the last file descriptor holding a reference is closed, the buffer is freed. This makes GEM buffers safe by design, as long as you don’t hold onto handles after the backing memory is gone. drm-rs exposes GEM through a small set of types in drm::buffer and through methods on the control::Device trait. The library deliberately keeps the abstraction thin — there is no heap allocation or automatic lifetime tracking on the Rust side, because GEM handles are fundamentally kernel-managed resources.

The buffer::Handle Type

use drm::buffer::Handle;
buffer::Handle is a newtype over NonZeroU32 (via control::RawResourceHandle). It is Copy, Hash, Eq, and transparent to the kernel’s handle representation. Every GEM buffer you work with in drm-rs is identified by one of these. Because GEM handles are valid only within the process and file descriptor that created them, drm-rs makes no attempt to validate a Handle on the Rust side. The library’s documentation states clearly:
There are no guarantees that this handle is valid. It is up to the user to make sure this handle does not outlive the underlying buffer, and to prevent buffers from leaking by properly closing them after they are done.
If you close a buffer before dropping a Handle that refers to it, the handle becomes stale. Subsequent ioctl calls using it will return kernel errors, not undefined behavior — but it remains your responsibility to keep lifetimes correct.
use drm::buffer::Name;
buffer::Name is a global 32-bit name assigned to a GEM buffer via the DRM Flink mechanism. Unlike a Handle, a Name is not scoped to a particular file descriptor — any process that knows the name can open the buffer using device.open_buffer(name). This global sharing is convenient but insecure: because names are small integers, a malicious process can iterate through the 32-bit space and open buffers it was never intended to see. Using Flink also typically requires either holding the DRM Master lock or having an authenticated token. For these reasons, Flink is considered legacy.
Prefer PRIME file descriptors over Flink (buffer::Name) for all new buffer-sharing code. PRIME gives you explicit control over which processes can access a buffer, integrates with standard Unix permission semantics, and does not require DRM Master status. See the PRIME Buffer Sharing section below.

The Buffer and PlanarBuffer Traits

drm-rs defines two traits that abstract over different buffer layouts.

buffer::Buffer

Buffer describes a single-plane buffer — the common case for RGB/RGBA pixel data:
pub trait Buffer {
    /// Width and height in pixels.
    fn size(&self) -> (u32, u32);
    /// Pixel format as a DRM fourcc code (e.g., DrmFourcc::Xrgb8888).
    fn format(&self) -> DrmFourcc;
    /// Row stride in bytes (pitch).
    fn pitch(&self) -> u32;
    /// The GEM handle for this buffer.
    fn handle(&self) -> Handle;
}
Any type implementing Buffer can be passed to device.add_framebuffer() to register it as a KMS framebuffer.

buffer::PlanarBuffer

PlanarBuffer extends the concept to multi-plane formats, such as YUV (where luminance and chrominance channels are stored in separate planes), or tiled/compressed formats that require a DRM format modifier:
pub trait PlanarBuffer {
    fn size(&self) -> (u32, u32);
    fn format(&self) -> DrmFourcc;
    /// Tiling/compression modifier (e.g., DrmModifier::Linear, or a vendor-specific value).
    fn modifier(&self) -> Option<DrmModifier>;
    /// Per-plane row strides (up to 4 planes).
    fn pitches(&self) -> [u32; 4];
    /// Per-plane GEM handles (None for unused planes).
    fn handles(&self) -> [Option<Handle>; 4];
    /// Per-plane byte offsets within each handle's memory region.
    fn offsets(&self) -> [u32; 4];
}
Types implementing PlanarBuffer can be passed to device.add_planar_framebuffer(), which uses the DRM_MODE_ADDFB2 ioctl and supports format modifiers.

Dumb Buffers

For situations where GPU acceleration is not needed — a simple background fill, a software-rendered cursor, or a basic display test — DRM provides dumb buffers. A dumb buffer is a CPU-accessible memory region allocated by the kernel. It requires no GPU-specific driver support, making it the most portable way to get pixels on screen.

Creating a Dumb Buffer

use drm::control::Device as ControlDevice;
use drm::buffer::DrmFourcc;

// Allocate a 1920×1080 XRGB8888 dumb buffer
let mut dumb = device.create_dumb_buffer(
    (1920, 1080),
    DrmFourcc::Xrgb8888,
    32, // bits per pixel
)?;

println!("Size: {:?}", dumb.size());   // (1920, 1080)
println!("Pitch: {} bytes", dumb.pitch());
println!("Handle: {:?}", dumb.handle());

Mapping and Writing Pixels

Once created, a dumb buffer can be memory-mapped using map_dumb_buffer(). The returned DumbMapping<'_> borrows from the buffer for its lifetime and dereferences as a &mut [u8] slice.
use drm::control::Device as ControlDevice;
use drm::buffer::DrmFourcc;

let mut dumb = device.create_dumb_buffer((1920, 1080), DrmFourcc::Xrgb8888, 32)?;

{
    let mut mapping = device.map_dumb_buffer(&mut dumb)?;

    // Fill with a solid red color (XRGB8888: B, G, R, X layout in memory on LE)
    for pixel in mapping.chunks_exact_mut(4) {
        pixel[0] = 0x00; // B
        pixel[1] = 0x00; // G
        pixel[2] = 0xFF; // R
        pixel[3] = 0x00; // X (ignored)
    }
} // DumbMapping is unmapped here (munmap called in Drop)

// Now register the buffer as a KMS framebuffer and use it
let fb = device.add_framebuffer(&dumb, 24, 32)?;
DumbMapping automatically calls munmap when dropped, so you do not need to unmap manually.

Destroying a Dumb Buffer

When you are done with a dumb buffer, free its kernel resources with destroy_dumb_buffer(). Note that this consumes the DumbBuffer value:
device.destroy_dumb_buffer(dumb)?;
Any framebuffer that references the dumb buffer must be destroyed first (device.destroy_framebuffer(fb)?), otherwise the kernel will return an error.

PRIME Buffer Sharing

PRIME is the modern mechanism for sharing GEM buffers between processes — and between different devices (for example, sharing a buffer allocated on a GPU with a display controller that is a separate DRM device). It works by exporting a GEM handle as a regular Unix file descriptor, which can then be passed to other processes using standard techniques like SCM_RIGHTS socket control messages.

Exporting a Buffer to a File Descriptor

use drm::control::Device as ControlDevice;

// Export a GEM handle as a PRIME fd
let prime_fd = device.buffer_to_prime_fd(gem_handle, 0)?;
// prime_fd is an OwnedFd — it will be closed when dropped
The returned OwnedFd represents an open reference to the buffer in the kernel. You can pass it to another process. As long as at least one file descriptor referencing the buffer is open (either the original handle or a PRIME fd), the kernel keeps the buffer alive.

Importing a File Descriptor as a Local Handle

On the receiving end, the foreign file descriptor is converted back into a local GEM handle:
use drm::control::Device as ControlDevice;
use std::os::unix::io::BorrowedFd;

// Receive prime_fd from the other process (e.g., via Unix socket)
let local_handle = device.prime_fd_to_buffer(prime_fd.as_fd())?;
// local_handle is now a buffer::Handle valid on this device and fd
The local handle behaves identically to any other GEM handle on that device. You can wrap it as a framebuffer, read its metadata, or export it again.
PropertyPRIMEFlink (buffer::Name)
SecurityCapability-based (you send the fd explicitly)Name-guessable by any process
DRM Master required?NoYes (or authenticated token)
Works across devices?Yes (via kernel import/export)No
Recommended for new code?✅ Yes❌ No

Hardware-Accelerated Buffers

Dumb buffers are CPU-only. If you need hardware-accelerated rendering — textures, shader output, or tiled/compressed formats — you need to allocate buffers through a GPU driver. The gbm crate (Generic Buffer Management, a Rust binding to libgbm) provides a driver-agnostic API for this purpose. Buffers allocated via gbm implement buffer::PlanarBuffer and can be registered as KMS framebuffers with device.add_planar_framebuffer(), bridging hardware rendering and display directly.

Build docs developers (and LLMs) love