modesetting_atomic/
modesetting-atomic.rs

1use linux_drm::{
2    event::{DrmEvent, GenericDrmEvent},
3    modeset::{
4        AtomicCommitFlags, CardResources, ConnectionState, ConnectorId, ConnectorState, CrtcId,
5        DumbBuffer, DumbBufferRequest, ModeInfo, ObjectId, PlaneId, PropertyId,
6    },
7    result::Error,
8    Card, ClientCap, DeviceCap,
9};
10use std::{
11    borrow::Cow,
12    env,
13    ffi::{CStr, CString},
14};
15
16fn main() -> std::io::Result<()> {
17    let card_path = card_path();
18    let mut card = Card::open(card_path).map_err(map_init_err)?;
19    card.become_master().map_err(map_err)?;
20
21    {
22        let name = card.driver_name().map_err(map_err)?;
23        let name = String::from_utf8_lossy(&name);
24        println!("Driver name: {name}");
25    }
26
27    if card
28        .get_device_cap(DeviceCap::DumbBuffer)
29        .map_err(map_err)?
30        == 0
31    {
32        return Err(std::io::Error::other(
33            "device does not support 'dumb buffers'",
34        ));
35    } else {
36        println!("Device supports 'dumb buffers'");
37    }
38
39    card.set_client_cap(ClientCap::UniversalPlanes, 1)
40        .map_err(map_err)?;
41    card.set_client_cap(ClientCap::Atomic, 1).map_err(map_err)?;
42
43    display_demo(&mut card).map_err(map_err)
44}
45
46fn card_path<'a>() -> Cow<'a, CStr> {
47    static DEFAULT_PATH: &'static CStr = c"/dev/dri/card0";
48
49    let mut args = env::args();
50    if !args.next().is_some() {
51        // skip the executable name
52        return Cow::Borrowed(DEFAULT_PATH);
53    }
54
55    args.next().map_or(Cow::Borrowed(DEFAULT_PATH), |s| {
56        Cow::Owned(CString::new(s).unwrap())
57    })
58}
59
60fn display_demo(card: &mut Card) -> Result<(), Error> {
61    let mut outputs = prepare_outputs(&card)?;
62    let mut req = linux_drm::modeset::AtomicRequest::new();
63
64    for output in &mut outputs {
65        println!("preparing output {output:#?}");
66        let conn = card.connector_state(output.conn_id)?;
67
68        let mode = &output.mode;
69        let mode_name = String::from_utf8_lossy(&mode.name);
70        println!(
71            "{:?} connector uses {mode_name} ({}x{}@{}Hz)",
72            conn.connector_type, mode.hdisplay, mode.vdisplay, mode.vrefresh,
73        );
74
75        let rows = output.db.height() as usize;
76        let pitch = output.db.pitch() as usize;
77        let data = output.db.buffer_mut();
78        for i in 0..rows {
79            if (i % 8) > 3 {
80                let row = &mut data[(i * pitch)..(i * pitch) + pitch];
81                row.fill(0xff);
82            }
83        }
84
85        println!(
86            "configuring CRTC {:?} for framebuffer {:?} and mode {mode_name} on connector {:?}",
87            output.crtc_id,
88            output.db.framebuffer_id(),
89            conn.id
90        );
91
92        req.set_property(
93            ObjectId::Connector(output.conn_id),
94            output.conn_prop_ids.crtc_id,
95            output.crtc_id,
96        );
97        req.set_property(
98            ObjectId::Crtc(output.crtc_id),
99            output.crtc_prop_ids.active,
100            true,
101        );
102        req.set_property(
103            ObjectId::Plane(output.plane_id),
104            output.plane_prop_ids.fb_id,
105            output.db.framebuffer_id(),
106        );
107        req.set_property(
108            ObjectId::Plane(output.plane_id),
109            output.plane_prop_ids.crtc_id,
110            output.crtc_id,
111        );
112        req.set_property(
113            ObjectId::Plane(output.plane_id),
114            output.plane_prop_ids.crtc_x,
115            0,
116        );
117        req.set_property(
118            ObjectId::Plane(output.plane_id),
119            output.plane_prop_ids.crtc_y,
120            0,
121        );
122        req.set_property(
123            ObjectId::Plane(output.plane_id),
124            output.plane_prop_ids.crtc_w,
125            output.db.width(),
126        );
127        req.set_property(
128            ObjectId::Plane(output.plane_id),
129            output.plane_prop_ids.crtc_h,
130            output.db.height(),
131        );
132        req.set_property(
133            ObjectId::Plane(output.plane_id),
134            output.plane_prop_ids.src_x,
135            0,
136        );
137        req.set_property(
138            ObjectId::Plane(output.plane_id),
139            output.plane_prop_ids.src_y,
140            0,
141        );
142        req.set_property(
143            ObjectId::Plane(output.plane_id),
144            output.plane_prop_ids.src_w,
145            (output.db.width() as u64) << 16,
146        );
147        req.set_property(
148            ObjectId::Plane(output.plane_id),
149            output.plane_prop_ids.src_h,
150            (output.db.height() as u64) << 16,
151        );
152    }
153
154    println!("atomic commit {req:#?}");
155    card.atomic_commit(
156        &req,
157        AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::PAGE_FLIP_EVENT,
158        0,
159    )?;
160
161    let mut evt_buf = vec![0_u8; 1024];
162    loop {
163        println!("waiting for events (send SIGINT to exit)");
164        for evt in card.read_events(&mut evt_buf)? {
165            println!("event {evt:?}");
166            match evt {
167                DrmEvent::Generic(GenericDrmEvent::FlipComplete(_)) => {
168                    // In a real program this would be a time place to draw the next frame
169                    // for the reported crtc.
170                }
171                _ => {
172                    // Ignore any unrecognized event types.
173                }
174            }
175        }
176    }
177}
178
179fn prepare_outputs(card: &Card) -> Result<Vec<Output>, Error> {
180    println!("preparing outputs");
181
182    let resources = card.resources()?;
183    let mut outputs = Vec::<Output>::new();
184
185    for id in resources.connector_ids.iter().copied() {
186        println!("preparing output for connector #{id:?}");
187
188        let conn = card.connector_state(id)?;
189        if conn.connection_state != ConnectionState::Connected {
190            println!("ignoring unconnected connector {id:?}");
191            continue;
192        }
193        if conn.current_encoder_id.0 == 0 {
194            println!("ignoring encoderless connector {id:?}");
195            continue;
196        }
197        if conn.modes.len() == 0 {
198            println!("ignoring modeless connector {id:?}");
199            continue;
200        }
201
202        let output = prepare_output(card, conn, &resources)?;
203        outputs.push(output);
204    }
205
206    Ok(outputs)
207}
208
209fn prepare_output(
210    card: &Card,
211    conn: ConnectorState,
212    resources: &CardResources,
213) -> Result<Output, Error> {
214    if conn.current_encoder_id.0 == 0 {
215        // It could be reasonable to go hunting for a suitable encoder and
216        // CRTC to activate this connector, but for this simple example
217        // we'll just use whatever connectors are already producing some
218        // output and keep using whatever modes they are currently in.
219        return Err(Error::NotSupported);
220    }
221    let _ = resources; // (don't actually need this when we're just using the already-selected encoder/crtc)
222
223    let enc = card.encoder_state(conn.current_encoder_id)?;
224    let crtc_id = enc.current_crtc_id;
225    let crtc = card.crtc_state(crtc_id)?;
226    let mode = crtc.mode;
227    let db = card.create_dumb_buffer(DumbBufferRequest {
228        width: mode.hdisplay as u32,
229        height: mode.vdisplay as u32,
230        depth: 24,
231        bpp: 32,
232    })?;
233
234    // We need to find the primary plane that's currently assigned to this CRTC.
235    // The following is not really a correct way to do it, but it'll work for
236    // now just to test if anything is working here at all. (This makes some
237    // assumptions about how the card is already configured which might not
238    // actually hold in practice.)
239    let mut chosen_plane_id: Option<PlaneId> = None;
240    for plane_id in resources.plane_ids.iter().copied() {
241        let plane = card.plane_state(plane_id)?;
242        if plane.crtc_id == crtc_id {
243            chosen_plane_id = Some(plane_id);
244            break;
245        }
246    }
247    let Some(chosen_plane_id) = chosen_plane_id else {
248        return Err(Error::NonExist);
249    };
250
251    println!("collecting properties");
252    let conn_prop_ids = ConnectorPropIds::new(conn.id, card)?;
253    let crtc_prop_ids = CrtcPropIds::new(crtc_id, card)?;
254    let plane_prop_ids = PlanePropIds::new(chosen_plane_id, card)?;
255
256    println!("collected properties");
257    Ok(Output {
258        conn_id: conn.id,
259        conn_prop_ids,
260        crtc_id,
261        crtc_prop_ids,
262        plane_id: chosen_plane_id,
263        plane_prop_ids,
264        mode,
265        db,
266    })
267}
268
269#[derive(Debug)]
270struct Output {
271    db: DumbBuffer,
272    mode: ModeInfo,
273    conn_id: ConnectorId,
274    conn_prop_ids: ConnectorPropIds,
275    crtc_id: CrtcId,
276    crtc_prop_ids: CrtcPropIds,
277    plane_id: PlaneId,
278    plane_prop_ids: PlanePropIds,
279}
280
281#[derive(Debug)]
282struct ConnectorPropIds {
283    crtc_id: PropertyId,
284}
285
286impl ConnectorPropIds {
287    pub fn new(conn_id: ConnectorId, card: &linux_drm::Card) -> Result<Self, Error> {
288        let mut ret: Self = unsafe { core::mem::zeroed() };
289        card.each_object_property_meta(conn_id, |meta, _| ret.populate_from(meta))?;
290        Ok(ret)
291    }
292
293    pub fn populate_from<'card>(&mut self, from: linux_drm::modeset::ObjectPropMeta<'card>) {
294        match from.name() {
295            "CRTC_ID" => self.crtc_id = from.property_id(),
296            _ => {}
297        }
298    }
299}
300
301#[derive(Debug)]
302struct CrtcPropIds {
303    active: PropertyId,
304}
305
306impl CrtcPropIds {
307    pub fn new(crtc_id: CrtcId, card: &linux_drm::Card) -> Result<Self, Error> {
308        let mut ret: Self = unsafe { core::mem::zeroed() };
309        card.each_object_property_meta(linux_drm::modeset::ObjectId::Crtc(crtc_id), |meta, _| {
310            ret.populate_from(meta)
311        })?;
312        Ok(ret)
313    }
314
315    pub fn populate_from<'card>(&mut self, from: linux_drm::modeset::ObjectPropMeta<'card>) {
316        match from.name() {
317            "ACTIVE" => self.active = from.property_id(),
318            _ => {}
319        }
320    }
321}
322
323#[derive(Debug)]
324struct PlanePropIds {
325    typ: PropertyId,
326    fb_id: PropertyId,
327    crtc_id: PropertyId,
328    crtc_x: PropertyId,
329    crtc_y: PropertyId,
330    crtc_w: PropertyId,
331    crtc_h: PropertyId,
332    src_x: PropertyId,
333    src_y: PropertyId,
334    src_w: PropertyId,
335    src_h: PropertyId,
336}
337
338impl PlanePropIds {
339    pub fn new(plane_id: PlaneId, card: &linux_drm::Card) -> Result<Self, Error> {
340        let mut ret: Self = unsafe { core::mem::zeroed() };
341        card.each_object_property_meta(plane_id, |meta, _| ret.populate_from(meta))?;
342        Ok(ret)
343    }
344
345    pub fn populate_from<'card>(&mut self, from: linux_drm::modeset::ObjectPropMeta<'card>) {
346        let field: &mut PropertyId = match from.name() {
347            "type" => &mut self.typ,
348            "FB_ID" => &mut self.fb_id,
349            "CRTC_ID" => &mut self.crtc_id,
350            "CRTC_X" => &mut self.crtc_x,
351            "CRTC_Y" => &mut self.crtc_y,
352            "CRTC_W" => &mut self.crtc_w,
353            "CRTC_H" => &mut self.crtc_h,
354            "SRC_X" => &mut self.src_x,
355            "SRC_Y" => &mut self.src_y,
356            "SRC_W" => &mut self.src_w,
357            "SRC_H" => &mut self.src_h,
358            _ => return,
359        };
360        *field = from.property_id()
361    }
362}
363
364fn map_init_err(e: linux_drm::result::InitError) -> std::io::Error {
365    let e: linux_io::result::Error = e.into();
366    e.into_std_io_error()
367}
368
369fn map_err(e: linux_drm::result::Error) -> std::io::Error {
370    let e: linux_io::result::Error = e.into();
371    e.into_std_io_error()
372}