Legacy modesetting uses theDocumentation 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.
set_crtc() ioctl to bind a connector, a CRTC, and a framebuffer together in a single call. It is the original KMS API and is the simplest way to put pixels on screen: pick a mode, allocate a buffer, call set_crtc, done. The trade-off is that each call is applied immediately without validation, can cause brief flicker, and only configures one CRTC at a time.
Legacy modesetting is effectively deprecated for new display-server code. The kernel still supports it, but the atomic API (see Atomic Modesetting) is preferred for any new work because it provides validation, atomicity across multiple outputs, and better plane management.
Follow the steps in Opening a Device to create your
Card wrapper, implement AsFd, drm::Device, and drm::control::Device:use std::fs::{File, OpenOptions};
use std::os::unix::io::{AsFd, BorrowedFd};
use drm::control::Device as ControlDevice;
pub struct Card(File);
impl AsFd for Card {
fn as_fd(&self) -> BorrowedFd<'_> { self.0.as_fd() }
}
impl drm::Device for Card {}
impl ControlDevice for Card {}
impl Card {
pub fn open(path: &str) -> Self {
let mut opts = OpenOptions::new();
opts.read(true).write(true);
Card(opts.open(path).unwrap())
}
}
resource_handles() returns handles for every framebuffer, CRTC, connector, and encoder the device controls:Fetch detailed info for each connector handle. Pass
force_probe: true on startup so the kernel performs a fresh hardware probe and updates EDID, modes, and connection state:use drm::control::connector;
let coninfo: Vec<connector::Info> = res
.connectors()
.iter()
.flat_map(|&handle| card.get_connector(handle, true))
.collect();
get_connector() blocks while the kernel probes the hardware when force_probe is true. Only request forced probes at startup or after a hotplug event — repeated forced probes can cause brief display disruption.let con = coninfo
.iter()
.find(|info| info.state() == connector::State::Connected)
.expect("No connected connector found");
The mode list returned by the connector is already sorted with the preferred (native) resolution first:
let &mode = con
.modes()
.first()
.expect("No modes available on connector");
let (width, height) = mode.size();
println!("Using mode: {}x{}", width, height);
Retrieve the first available CRTC from the resource list. In a more complete implementation you would match the CRTC to the connector via the encoder’s
possible_crtcs bitmask:use drm::control::crtc;
let crtcinfo: Vec<crtc::Info> = res
.crtcs()
.iter()
.flat_map(|&handle| card.get_crtc(handle))
.collect();
let crtc = crtcinfo.first().expect("No CRTCs found");
Allocate a CPU-writable dumb buffer at the connector’s native resolution, fill it with a solid colour, then wrap it in a framebuffer object. See Dumb Buffers for a full explanation of this step:
use drm::buffer::DrmFourcc;
// Allocate the dumb buffer.
let mut db = card
.create_dumb_buffer(
(width.into(), height.into()),
DrmFourcc::Xrgb8888,
32, // bits per pixel
)
.expect("Could not create dumb buffer");
// Map it and fill with mid-grey.
{
let mut map = card
.map_dumb_buffer(&mut db)
.expect("Could not map dumb buffer");
for b in map.as_mut() {
*b = 128;
}
} // mapping is dropped here, unmapping the buffer
// Wrap in a framebuffer object (depth=24, bpp=32 for XRGB8888).
let fb = card
.add_framebuffer(&db, 24, 32)
.expect("Could not create framebuffer");
set_crtc takes the CRTC handle, an optional framebuffer, a scanout offset within the framebuffer, a slice of connector handles to drive, and the mode to apply:card.set_crtc(
crtc.handle(),
Some(fb),
(0, 0), // (x, y) offset into the framebuffer
&[con.handle()], // connectors driven by this CRTC
Some(mode), // mode to set; None clears the display
)
.expect("Could not set CRTC");
The call blocks until the kernel applies the new mode. When it returns, the display should show the framebuffer contents.
Page flipping for double-buffering
Callingset_crtc repeatedly is fine for static images but causes tearing for animation because it updates the scanout pointer mid-frame. Page flipping schedules the buffer swap to occur exactly at the next vertical blanking interval.
Allocate a second framebuffer, render into it, then queue the flip:
receive_events() to learn when the flip completed and it is safe to render into the old buffer again:
receive_events() reads from the DRM device file descriptor. To integrate it with an event loop (epoll, mio, Tokio, etc.), monitor the device fd for readability and call receive_events() when data is available.