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 inDocumentation 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::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
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.
The buffer::Name Type (Flink)
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.
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:
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:
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
Mapping and Writing Pixels
Once created, a dumb buffer can be memory-mapped usingmap_dumb_buffer(). The returned DumbMapping<'_> borrows from the buffer for its lifetime and dereferences as a &mut [u8] slice.
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 withdestroy_dumb_buffer(). Note that this consumes the DumbBuffer value:
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 likeSCM_RIGHTS socket control messages.
Exporting a Buffer to a File Descriptor
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:Why PRIME Over Flink
| Property | PRIME | Flink (buffer::Name) |
|---|---|---|
| Security | Capability-based (you send the fd explicitly) | Name-guessable by any process |
| DRM Master required? | No | Yes (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. Thegbm 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.