|
| 1 | +//! Example of using softbuffer with drm-rs. |
| 2 | +
|
| 3 | +#[cfg(kms_platform)] |
| 4 | +mod imple { |
| 5 | + use drm::control::{connector, Device as CtrlDevice, Event, ModeTypeFlags, PlaneType}; |
| 6 | + use drm::Device; |
| 7 | + |
| 8 | + use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle}; |
| 9 | + use softbuffer::{Context, Surface}; |
| 10 | + |
| 11 | + use std::num::NonZeroU32; |
| 12 | + use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; |
| 13 | + use std::path::Path; |
| 14 | + use std::time::{Duration, Instant}; |
| 15 | + |
| 16 | + pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> { |
| 17 | + // Open a new device. |
| 18 | + let device = Card::find()?; |
| 19 | + |
| 20 | + // Create the softbuffer context. |
| 21 | + let context = unsafe { |
| 22 | + Context::from_raw({ |
| 23 | + let mut handle = DrmDisplayHandle::empty(); |
| 24 | + handle.fd = device.as_fd().as_raw_fd(); |
| 25 | + handle.into() |
| 26 | + }) |
| 27 | + }?; |
| 28 | + |
| 29 | + // Get the DRM handles. |
| 30 | + let handles = device.resource_handles()?; |
| 31 | + |
| 32 | + // Get the list of connectors and CRTCs. |
| 33 | + let connectors = handles |
| 34 | + .connectors() |
| 35 | + .iter() |
| 36 | + .map(|&con| device.get_connector(con, true)) |
| 37 | + .collect::<Result<Vec<_>, _>>()?; |
| 38 | + let crtcs = handles |
| 39 | + .crtcs() |
| 40 | + .iter() |
| 41 | + .map(|&crtc| device.get_crtc(crtc)) |
| 42 | + .collect::<Result<Vec<_>, _>>()?; |
| 43 | + |
| 44 | + // Find a connected crtc. |
| 45 | + let con = connectors |
| 46 | + .iter() |
| 47 | + .find(|con| con.state() == connector::State::Connected) |
| 48 | + .ok_or("No connected connectors")?; |
| 49 | + |
| 50 | + // Get the first CRTC. |
| 51 | + let crtc = crtcs.first().ok_or("No CRTCs")?; |
| 52 | + |
| 53 | + // Find a mode to use. |
| 54 | + let mode = con |
| 55 | + .modes() |
| 56 | + .iter() |
| 57 | + .find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) |
| 58 | + .or_else(|| con.modes().first()) |
| 59 | + .ok_or("No modes")?; |
| 60 | + |
| 61 | + // Look for a primary plane compatible with our CRTC. |
| 62 | + let planes = device.plane_handles()?; |
| 63 | + let planes = planes |
| 64 | + .iter() |
| 65 | + .filter(|&&plane| { |
| 66 | + device.get_plane(plane).map_or(false, |plane| { |
| 67 | + let crtcs = handles.filter_crtcs(plane.possible_crtcs()); |
| 68 | + crtcs.contains(&crtc.handle()) |
| 69 | + }) |
| 70 | + }) |
| 71 | + .collect::<Vec<_>>(); |
| 72 | + |
| 73 | + // Find the first primary plane or take the first one period. |
| 74 | + let plane = planes |
| 75 | + .iter() |
| 76 | + .find(|&&&plane| { |
| 77 | + if let Ok(props) = device.get_properties(plane) { |
| 78 | + let (ids, vals) = props.as_props_and_values(); |
| 79 | + for (&id, &val) in ids.iter().zip(vals.iter()) { |
| 80 | + if let Ok(info) = device.get_property(id) { |
| 81 | + if info.name().to_str().map_or(false, |x| x == "type") { |
| 82 | + return val == PlaneType::Primary as u32 as u64; |
| 83 | + } |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + false |
| 89 | + }) |
| 90 | + .or(planes.first()) |
| 91 | + .ok_or("No planes")?; |
| 92 | + |
| 93 | + // Create the surface on top of this plane. |
| 94 | + // Note: This requires root on DRM/KMS. |
| 95 | + let mut surface = unsafe { |
| 96 | + Surface::from_raw(&context, { |
| 97 | + let mut handle = DrmWindowHandle::empty(); |
| 98 | + handle.plane = (**plane).into(); |
| 99 | + handle.into() |
| 100 | + }) |
| 101 | + }?; |
| 102 | + |
| 103 | + // Resize the surface. |
| 104 | + let (width, height) = mode.size(); |
| 105 | + surface.resize( |
| 106 | + NonZeroU32::new(width as u32).unwrap(), |
| 107 | + NonZeroU32::new(height as u32).unwrap(), |
| 108 | + )?; |
| 109 | + |
| 110 | + // Start drawing to it. |
| 111 | + let start = Instant::now(); |
| 112 | + let mut tick = 0; |
| 113 | + while Instant::now().duration_since(start) < Duration::from_secs(2) { |
| 114 | + tick += 1; |
| 115 | + println!("Drawing tick {tick}"); |
| 116 | + |
| 117 | + // Start drawing. |
| 118 | + let mut buffer = surface.buffer_mut()?; |
| 119 | + draw_to_buffer(&mut buffer, tick); |
| 120 | + buffer.present()?; |
| 121 | + |
| 122 | + // Wait for the page flip to happen. |
| 123 | + rustix::event::poll( |
| 124 | + &mut [rustix::event::PollFd::new( |
| 125 | + &device, |
| 126 | + rustix::event::PollFlags::IN, |
| 127 | + )], |
| 128 | + -1, |
| 129 | + )?; |
| 130 | + |
| 131 | + // Receive the events. |
| 132 | + let events = device.receive_events()?; |
| 133 | + println!("Got some events..."); |
| 134 | + for event in events { |
| 135 | + match event { |
| 136 | + Event::PageFlip(_) => { |
| 137 | + println!("Page flip event."); |
| 138 | + } |
| 139 | + Event::Vblank(_) => { |
| 140 | + println!("Vblank event."); |
| 141 | + } |
| 142 | + _ => { |
| 143 | + println!("Unknown event."); |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + Ok(()) |
| 150 | + } |
| 151 | + |
| 152 | + fn draw_to_buffer(buf: &mut [u32], tick: usize) { |
| 153 | + let scale = colorous::SINEBOW; |
| 154 | + let mut i = (tick as f64) / 20.0; |
| 155 | + while i > 1.0 { |
| 156 | + i -= 1.0; |
| 157 | + } |
| 158 | + |
| 159 | + let color = scale.eval_continuous(i); |
| 160 | + let pixel = (color.r as u32) << 16 | (color.g as u32) << 8 | (color.b as u32); |
| 161 | + buf.fill(pixel); |
| 162 | + } |
| 163 | + |
| 164 | + struct Card(std::fs::File); |
| 165 | + |
| 166 | + impl Card { |
| 167 | + fn find() -> Result<Card, Box<dyn std::error::Error>> { |
| 168 | + for i in 0..10 { |
| 169 | + let path = format!("/dev/dri/card{i}"); |
| 170 | + let device = Card::open(path)?; |
| 171 | + |
| 172 | + // Only use it if it has connectors. |
| 173 | + let handles = match device.resource_handles() { |
| 174 | + Ok(handles) => handles, |
| 175 | + Err(_) => continue, |
| 176 | + }; |
| 177 | + |
| 178 | + if handles |
| 179 | + .connectors |
| 180 | + .iter() |
| 181 | + .filter_map(|c| device.get_connector(*c, false).ok()) |
| 182 | + .any(|c| c.state() == connector::State::Connected) |
| 183 | + { |
| 184 | + return Ok(device); |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | + Err("No DRM device found".into()) |
| 189 | + } |
| 190 | + |
| 191 | + fn open(path: impl AsRef<Path>) -> Result<Card, Box<dyn std::error::Error>> { |
| 192 | + let file = std::fs::OpenOptions::new() |
| 193 | + .read(true) |
| 194 | + .write(true) |
| 195 | + .open(path)?; |
| 196 | + Ok(Card(file)) |
| 197 | + } |
| 198 | + } |
| 199 | + |
| 200 | + impl AsFd for Card { |
| 201 | + fn as_fd(&self) -> BorrowedFd<'_> { |
| 202 | + self.0.as_fd() |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + impl Device for Card {} |
| 207 | + impl CtrlDevice for Card {} |
| 208 | +} |
| 209 | + |
| 210 | +#[cfg(not(kms_platform))] |
| 211 | +mod imple { |
| 212 | + pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> { |
| 213 | + eprintln!("This example requires the `kms` feature."); |
| 214 | + Ok(()) |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 219 | + imple::entry() |
| 220 | +} |
0 commit comments