softbuffer/
kms.rs

1//! Backend for DRM/KMS for raw rendering directly to the screen.
2//!
3//! This strategy uses dumb buffers for rendering.
4
5use drm::buffer::{Buffer, DrmFourcc};
6use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
7use drm::control::{
8    connector, crtc, framebuffer, plane, ClipRect, Device as CtrlDevice, PageFlipFlags,
9};
10use drm::Device;
11
12use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};
13
14use std::collections::HashSet;
15use std::marker::PhantomData;
16use std::num::NonZeroU32;
17use std::os::unix::io::{AsFd, BorrowedFd};
18use std::rc::Rc;
19
20use crate::error::{InitError, SoftBufferError, SwResultExt};
21
22#[derive(Debug)]
23pub(crate) struct KmsDisplayImpl<D: ?Sized> {
24    /// The underlying raw device file descriptor.
25    fd: BorrowedFd<'static>,
26
27    /// Holds a reference to the display.
28    _display: D,
29}
30
31impl<D: ?Sized> AsFd for KmsDisplayImpl<D> {
32    fn as_fd(&self) -> BorrowedFd<'_> {
33        self.fd
34    }
35}
36
37impl<D: ?Sized> Device for KmsDisplayImpl<D> {}
38impl<D: ?Sized> CtrlDevice for KmsDisplayImpl<D> {}
39
40impl<D: HasDisplayHandle> KmsDisplayImpl<D> {
41    pub(crate) fn new(display: D) -> Result<Self, InitError<D>> {
42        let fd = match display.display_handle()?.as_raw() {
43            RawDisplayHandle::Drm(drm) => drm.fd,
44            _ => return Err(InitError::Unsupported(display)),
45        };
46        if fd == -1 {
47            return Err(SoftBufferError::IncompleteDisplayHandle.into());
48        }
49
50        // SAFETY: Invariants guaranteed by the user.
51        let fd = unsafe { BorrowedFd::borrow_raw(fd) };
52
53        Ok(KmsDisplayImpl {
54            fd,
55            _display: display,
56        })
57    }
58}
59
60/// All the necessary types for the Drm/Kms backend.
61#[derive(Debug)]
62pub(crate) struct KmsImpl<D: ?Sized, W: ?Sized> {
63    /// The display implementation.
64    display: Rc<KmsDisplayImpl<D>>,
65
66    /// The connectors to use.
67    connectors: Vec<connector::Handle>,
68
69    /// The CRTC to render to.
70    crtc: crtc::Info,
71
72    /// The dumb buffer we're using as a buffer.
73    buffer: Option<Buffers>,
74
75    /// Window handle that we are keeping around.
76    window_handle: W,
77}
78
79#[derive(Debug)]
80struct Buffers {
81    /// The involved set of buffers.
82    buffers: [SharedBuffer; 2],
83
84    /// Whether to use the first buffer or the second buffer as the front buffer.
85    first_is_front: bool,
86
87    /// A buffer full of zeroes.
88    zeroes: Box<[u32]>,
89}
90
91/// The buffer implementation.
92pub(crate) struct BufferImpl<'a, D: ?Sized, W: ?Sized> {
93    /// The mapping of the dump buffer.
94    mapping: DumbMapping<'a>,
95
96    /// The framebuffer object of the current front buffer.
97    front_fb: framebuffer::Handle,
98
99    /// The CRTC handle.
100    crtc_handle: crtc::Handle,
101
102    /// This is used to change the front buffer.
103    first_is_front: &'a mut bool,
104
105    /// Buffer full of zeroes.
106    zeroes: &'a [u32],
107
108    /// The current size.
109    size: (NonZeroU32, NonZeroU32),
110
111    /// The display implementation.
112    display: &'a KmsDisplayImpl<D>,
113
114    /// Age of the front buffer.
115    front_age: &'a mut u8,
116
117    /// Age of the back buffer.
118    back_age: &'a mut u8,
119
120    /// Window reference.
121    _window: PhantomData<&'a mut W>,
122}
123
124/// The combined frame buffer and dumb buffer.
125#[derive(Debug)]
126struct SharedBuffer {
127    /// The frame buffer.
128    fb: framebuffer::Handle,
129
130    /// The dumb buffer.
131    db: DumbBuffer,
132
133    /// The age of this buffer.
134    age: u8,
135}
136
137impl<D: ?Sized, W: HasWindowHandle> KmsImpl<D, W> {
138    /// Create a new KMS backend.
139    pub(crate) fn new(window: W, display: Rc<KmsDisplayImpl<D>>) -> Result<Self, InitError<W>> {
140        // Make sure that the window handle is valid.
141        let plane_handle = match window.window_handle()?.as_raw() {
142            RawWindowHandle::Drm(drm) => match NonZeroU32::new(drm.plane) {
143                Some(handle) => plane::Handle::from(handle),
144                None => return Err(SoftBufferError::IncompleteWindowHandle.into()),
145            },
146            _ => return Err(InitError::Unsupported(window)),
147        };
148
149        let plane_info = display
150            .get_plane(plane_handle)
151            .swbuf_err("failed to get plane info")?;
152        let handles = display
153            .resource_handles()
154            .swbuf_err("failed to get resource handles")?;
155
156        // Use either the attached CRTC or the primary CRTC.
157        let crtc = {
158            let handle = match plane_info.crtc() {
159                Some(crtc) => crtc,
160                None => {
161                    log::warn!("no CRTC attached to plane, falling back to primary CRTC");
162                    handles
163                        .filter_crtcs(plane_info.possible_crtcs())
164                        .first()
165                        .copied()
166                        .swbuf_err("failed to find a primary CRTC")?
167                }
168            };
169
170            // Get info about the CRTC.
171            display
172                .get_crtc(handle)
173                .swbuf_err("failed to get CRTC info")?
174        };
175
176        // Figure out all of the encoders that are attached to this CRTC.
177        let encoders = handles
178            .encoders
179            .iter()
180            .flat_map(|handle| display.get_encoder(*handle))
181            .filter(|encoder| encoder.crtc() == Some(crtc.handle()))
182            .map(|encoder| encoder.handle())
183            .collect::<HashSet<_>>();
184
185        // Get a list of every connector that the CRTC is connected to via encoders.
186        let connectors = handles
187            .connectors
188            .iter()
189            .flat_map(|handle| display.get_connector(*handle, false))
190            .filter(|connector| {
191                connector
192                    .current_encoder()
193                    .map_or(false, |encoder| encoders.contains(&encoder))
194            })
195            .map(|info| info.handle())
196            .collect::<Vec<_>>();
197
198        Ok(Self {
199            crtc,
200            connectors,
201            display,
202            buffer: None,
203            window_handle: window,
204        })
205    }
206
207    /// Get the inner window handle.
208    #[inline]
209    pub fn window(&self) -> &W {
210        &self.window_handle
211    }
212
213    /// Resize the internal buffer to the given size.
214    pub(crate) fn resize(
215        &mut self,
216        width: NonZeroU32,
217        height: NonZeroU32,
218    ) -> Result<(), SoftBufferError> {
219        // Don't resize if we don't have to.
220        if let Some(buffer) = &self.buffer {
221            let (buffer_width, buffer_height) = buffer.size();
222            if buffer_width == width && buffer_height == height {
223                return Ok(());
224            }
225        }
226
227        // Create a new buffer set.
228        let front_buffer = SharedBuffer::new(&self.display, width, height)?;
229        let back_buffer = SharedBuffer::new(&self.display, width, height)?;
230
231        self.buffer = Some(Buffers {
232            first_is_front: true,
233            buffers: [front_buffer, back_buffer],
234            zeroes: vec![0; width.get() as usize * height.get() as usize].into_boxed_slice(),
235        });
236
237        Ok(())
238    }
239
240    /// Fetch the buffer from the window.
241    pub(crate) fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
242        // TODO: Implement this!
243        Err(SoftBufferError::Unimplemented)
244    }
245
246    /// Get a mutable reference to the buffer.
247    pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {
248        // Map the dumb buffer.
249        let set = self
250            .buffer
251            .as_mut()
252            .expect("Must set size of surface before calling `buffer_mut()`");
253
254        let size = set.size();
255
256        let [first_buffer, second_buffer] = &mut set.buffers;
257        let (front_buffer, back_buffer) = if set.first_is_front {
258            (first_buffer, second_buffer)
259        } else {
260            (second_buffer, first_buffer)
261        };
262
263        let front_fb = front_buffer.fb;
264        let front_age = &mut front_buffer.age;
265        let back_age = &mut back_buffer.age;
266
267        let mapping = self
268            .display
269            .map_dumb_buffer(&mut front_buffer.db)
270            .swbuf_err("failed to map dumb buffer")?;
271
272        Ok(BufferImpl {
273            mapping,
274            size,
275            first_is_front: &mut set.first_is_front,
276            front_fb,
277            crtc_handle: self.crtc.handle(),
278            display: &self.display,
279            zeroes: &set.zeroes,
280            front_age,
281            back_age,
282            _window: PhantomData,
283        })
284    }
285}
286
287impl<D: ?Sized, W: ?Sized> Drop for KmsImpl<D, W> {
288    fn drop(&mut self) {
289        // Map the CRTC to the information that was there before.
290        self.display
291            .set_crtc(
292                self.crtc.handle(),
293                self.crtc.framebuffer(),
294                self.crtc.position(),
295                &self.connectors,
296                self.crtc.mode(),
297            )
298            .ok();
299    }
300}
301
302impl<D: ?Sized, W: ?Sized> BufferImpl<'_, D, W> {
303    #[inline]
304    pub fn pixels(&self) -> &[u32] {
305        // drm-rs doesn't let us have the immutable reference... so just use a bunch of zeroes.
306        // TODO: There has to be a better way of doing this!
307        self.zeroes
308    }
309
310    #[inline]
311    pub fn pixels_mut(&mut self) -> &mut [u32] {
312        bytemuck::cast_slice_mut(self.mapping.as_mut())
313    }
314
315    #[inline]
316    pub fn age(&self) -> u8 {
317        *self.front_age
318    }
319
320    #[inline]
321    pub fn present_with_damage(self, damage: &[crate::Rect]) -> Result<(), SoftBufferError> {
322        let rectangles = damage
323            .iter()
324            .map(|&rect| {
325                let err = || SoftBufferError::DamageOutOfRange { rect };
326                Ok::<_, SoftBufferError>(ClipRect::new(
327                    rect.x.try_into().map_err(|_| err())?,
328                    rect.y.try_into().map_err(|_| err())?,
329                    rect.x
330                        .checked_add(rect.width.get())
331                        .and_then(|x| x.try_into().ok())
332                        .ok_or_else(err)?,
333                    rect.y
334                        .checked_add(rect.height.get())
335                        .and_then(|y| y.try_into().ok())
336                        .ok_or_else(err)?,
337                ))
338            })
339            .collect::<Result<Vec<_>, _>>()?;
340
341        // Dirty the framebuffer with out damage rectangles.
342        //
343        // Some drivers don't support this, so we just ignore the `ENOSYS` error.
344        // TODO: It would be nice to not have to heap-allocate the above rectangles if we know that
345        // this is going to fail. Low hanging fruit PR: add a flag that's set to false if this
346        // returns `ENOSYS` and check that before allocating the above and running this.
347        match self.display.dirty_framebuffer(self.front_fb, &rectangles) {
348            Ok(()) => {}
349            Err(e) if e.raw_os_error() == Some(rustix::io::Errno::NOSYS.raw_os_error()) => {}
350            Err(e) => {
351                return Err(SoftBufferError::PlatformError(
352                    Some("failed to dirty framebuffer".into()),
353                    Some(e.into()),
354                ));
355            }
356        }
357
358        // Swap the buffers.
359        // TODO: Use atomic commits here!
360        self.display
361            .page_flip(self.crtc_handle, self.front_fb, PageFlipFlags::EVENT, None)
362            .swbuf_err("failed to page flip")?;
363
364        // Flip the front and back buffers.
365        *self.first_is_front = !*self.first_is_front;
366
367        // Set the ages.
368        *self.front_age = 1;
369        if *self.back_age != 0 {
370            *self.back_age += 1;
371        }
372
373        Ok(())
374    }
375
376    #[inline]
377    pub fn present(self) -> Result<(), SoftBufferError> {
378        let (width, height) = self.size;
379        self.present_with_damage(&[crate::Rect {
380            x: 0,
381            y: 0,
382            width,
383            height,
384        }])
385    }
386}
387
388impl SharedBuffer {
389    /// Create a new buffer set.
390    pub(crate) fn new<D: ?Sized>(
391        display: &KmsDisplayImpl<D>,
392        width: NonZeroU32,
393        height: NonZeroU32,
394    ) -> Result<Self, SoftBufferError> {
395        let db = display
396            .create_dumb_buffer((width.get(), height.get()), DrmFourcc::Xrgb8888, 32)
397            .swbuf_err("failed to create dumb buffer")?;
398        let fb = display
399            .add_framebuffer(&db, 24, 32)
400            .swbuf_err("failed to add framebuffer")?;
401
402        Ok(SharedBuffer { fb, db, age: 0 })
403    }
404
405    /// Get the size of this buffer.
406    pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) {
407        let (width, height) = self.db.size();
408
409        NonZeroU32::new(width)
410            .and_then(|width| NonZeroU32::new(height).map(|height| (width, height)))
411            .expect("buffer size is zero")
412    }
413}
414
415impl Buffers {
416    /// Get the size of this buffer.
417    pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) {
418        self.buffers[0].size()
419    }
420}