From cc1225fe98f17d8d2f14925764ed64642131a283 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 14 Jul 2023 20:17:18 -0700 Subject: [PATCH 01/15] Add a DRM/KMS backend This backend uses DUMB buffers to render to the CRTC. cc #42 --- .github/CODEOWNERS | 3 + .github/workflows/ci.yml | 1 + Cargo.toml | 4 +- build.rs | 1 + src/kms.rs | 286 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 14 ++ 6 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 src/kms.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d138a228..2644dce6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,9 @@ # Apple platforms /src/cg.rs @madsmtm +# DRM/KMS (no maintainer) +/src/kms.rs + # Redox /src/orbital.rs @jackpot51 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8250ceac..51964998 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,7 @@ jobs: - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen" } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms" } - { target: x86_64-unknown-redox, os: ubuntu-latest, } - { target: x86_64-unknown-freebsd, os: ubuntu-latest, } - { target: x86_64-unknown-netbsd, os: ubuntu-latest, } diff --git a/Cargo.toml b/Cargo.toml index cf9d8093..ae923782 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,8 @@ name = "buffer_mut" harness = false [features] -default = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] +default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] +kms = ["drm"] wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"] wayland-dlopen = ["wayland-sys/dlopen"] x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"] @@ -30,6 +31,7 @@ raw-window-handle = "0.5.0" [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies] as-raw-xcb-connection = { version = "1.0.0", optional = true } bytemuck = { version = "1.12.3", optional = true } +drm = { version = "0.9.0", default-features = false, optional = true } memmap2 = { version = "0.7.1", optional = true } nix = { version = "0.26.1", optional = true } tiny-xlib = { version = "0.2.1", optional = true } diff --git a/build.rs b/build.rs index 5113eeb6..63c5f02d 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,7 @@ fn main() { cfg_aliases::cfg_aliases! { free_unix: { all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))) }, + kms_platform: { all(feature = "kms", free_unix, not(target_arch = "wasm32")) }, x11_platform: { all(feature = "x11", free_unix, not(target_arch = "wasm32")) }, wayland_platform: { all(feature = "wayland", free_unix, not(target_arch = "wasm32")) }, } diff --git a/src/kms.rs b/src/kms.rs new file mode 100644 index 00000000..82e97b90 --- /dev/null +++ b/src/kms.rs @@ -0,0 +1,286 @@ +//! Backend for DRM/KMS for raw rendering directly to the screen. +//! +//! This strategy uses dumb buffers for rendering. + +use drm::buffer::{Buffer, DrmFourcc}; +use drm::control::dumbbuffer::{DumbBuffer, DumbMapping}; +use drm::control::{connector, crtc, framebuffer, plane, Device as CtrlDevice}; +use drm::Device; + +use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle}; + +use std::num::NonZeroU32; +use std::os::unix::io::{AsFd, BorrowedFd}; +use std::rc::Rc; + +use crate::error::{SoftBufferError, SwResultExt}; + +#[derive(Debug)] +pub(crate) struct KmsDisplayImpl { + /// The underlying raw device file descriptor. + /// + /// Once rwh v0.6 support is merged, this an be made safe. Until then, + /// we use this hacky workaround, since this FD's validity is guaranteed by + /// the unsafe constructor. + fd: BorrowedFd<'static>, +} + +impl AsFd for KmsDisplayImpl { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd + } +} + +impl Device for KmsDisplayImpl {} +impl CtrlDevice for KmsDisplayImpl {} + +impl KmsDisplayImpl { + /// SAFETY: The underlying fd must not outlive the display. + pub(crate) unsafe fn new(handle: DrmDisplayHandle) -> Result { + let fd = handle.fd; + if fd == -1 { + return Err(SoftBufferError::IncompleteDisplayHandle); + } + + // SAFETY: Invariants guaranteed by the user. + let fd = unsafe { BorrowedFd::borrow_raw(fd) }; + + Ok(KmsDisplayImpl { fd }) + } +} + +/// All the necessary types for the Drm/Kms backend. +#[derive(Debug)] +pub(crate) struct KmsImpl { + /// The display implementation. + display: Rc, + + /// The connector to use. + connector: connector::Handle, + + /// The CRTC to render to. + crtc: crtc::Info, + + /// The dumb buffer we're using as a buffer. + buffer: Option, +} + +/// The buffer implementation. +pub(crate) struct BufferImpl<'a> { + /// The mapping of the dump buffer. + mapping: DumbMapping<'a>, + + /// The framebuffer. + fb: framebuffer::Handle, + + /// The connector. + connector: connector::Handle, + + /// The CRTC info. + crtc_info: &'a crtc::Info, + + /// The display implementation. + display: &'a KmsDisplayImpl, + + /// The zero buffer. + zeroes: &'a [u32], +} + +/// The combined frame buffer and dumb buffer. +#[derive(Debug)] +struct BufferSet { + /// The frame buffer. + fb: framebuffer::Handle, + + /// The dumb buffer. + db: DumbBuffer, + + /// Equivalent mapping for reading. + zeroes: Box<[u32]>, +} + +impl KmsImpl { + /// Create a new KMS backend. + /// + /// # Safety + /// + /// The plane must be valid for the lifetime of the backend. + pub(crate) unsafe fn new( + window_handle: DrmWindowHandle, + display: Rc, + ) -> Result { + log::trace!("new: window_handle={:X}", window_handle.plane); + + // Make sure that the window handle is valid. + let plane_handle = match NonZeroU32::new(window_handle.plane) { + Some(handle) => plane::Handle::from(handle), + None => return Err(SoftBufferError::IncompleteWindowHandle), + }; + + let plane_info = display + .get_plane(plane_handle) + .swbuf_err("failed to get plane info")?; + let handles = display + .resource_handles() + .swbuf_err("failed to get resource handles")?; + + // Use either the attached CRTC or the primary CRTC. + let crtc = match plane_info.crtc() { + Some(crtc) => crtc, + None => { + log::warn!("no CRTC attached to plane, falling back to primary CRTC"); + handles + .filter_crtcs(plane_info.possible_crtcs()) + .first() + .copied() + .swbuf_err("failed to find a primary CRTC")? + } + }; + + // Use a preferred connector or just select the first one. + let connector = handles + .connectors + .iter() + .flat_map(|handle| display.get_connector(*handle, false)) + .find_map(|conn| { + if conn.state() == connector::State::Connected { + Some(conn.handle()) + } else { + None + } + }) + .or_else(|| handles.connectors.first().copied()) + .swbuf_err("failed to find a valid connector")?; + + Ok(Self { + crtc: display + .get_crtc(crtc) + .swbuf_err("failed to get CRTC info")?, + connector, + display, + buffer: None, + }) + } + + /// Resize the internal buffer to the given size. + pub(crate) fn resize( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + ) -> Result<(), SoftBufferError> { + // Don't resize if we don't have to. + if let Some(buffer) = &self.buffer { + let (buffer_width, buffer_height) = buffer.size(); + if buffer_width == width && buffer_height == height { + return Ok(()); + } + } + + // Create a new buffer. + let buffer = BufferSet::new(&self.display, width, height)?; + self.buffer = Some(buffer); + + Ok(()) + } + + /// Fetch the buffer from the window. + pub(crate) fn fetch(&mut self) -> Result, SoftBufferError> { + // TODO: Implement this! + Err(SoftBufferError::Unimplemented) + } + + /// Get a mutable reference to the buffer. + pub(crate) fn buffer_mut(&mut self) -> Result, SoftBufferError> { + // Map the dumb buffer. + let set = self + .buffer + .as_mut() + .expect("Must set size of surface before calling `buffer_mut()`"); + let mapping = self + .display + .map_dumb_buffer(&mut set.db) + .swbuf_err("failed to map dumb buffer")?; + + Ok(BufferImpl { + mapping, + fb: set.fb, + connector: self.connector, + crtc_info: &self.crtc, + display: &self.display, + zeroes: &set.zeroes, + }) + } +} + +impl BufferImpl<'_> { + #[inline] + pub fn pixels(&self) -> &[u32] { + // drm-rs doesn't let us have the immutable reference... so just use a bunch of zeroes. + // TODO: There has to be a better way of doing this! + self.zeroes + } + + #[inline] + pub fn pixels_mut(&mut self) -> &mut [u32] { + bytemuck::cast_slice_mut(self.mapping.as_mut()) + } + + #[inline] + pub fn age(&self) -> u8 { + todo!() + } + + #[inline] + pub fn present_with_damage(self, _damage: &[crate::Rect]) -> Result<(), SoftBufferError> { + // TODO: Is there a way of doing this? + self.present() + } + + #[inline] + pub fn present(self) -> Result<(), SoftBufferError> { + // Set the CRTC mode. + // TODO: This requires root access, find a way that doesn't! + self.display + .set_crtc( + self.crtc_info.handle(), + Some(self.fb), + (0, 0), + &[self.connector], + self.crtc_info.mode(), + ) + .swbuf_err("failed to set CRTC")?; + + Ok(()) + } +} + +impl BufferSet { + /// Create a new buffer set. + pub(crate) fn new( + display: &KmsDisplayImpl, + width: NonZeroU32, + height: NonZeroU32, + ) -> Result { + let db = display + .create_dumb_buffer((width.get(), height.get()), DrmFourcc::Argb8888, 32) + .swbuf_err("failed to create dumb buffer")?; + let fb = display + .add_framebuffer(&db, 32, 32) + .swbuf_err("failed to add framebuffer")?; + + Ok(BufferSet { + fb, + db, + zeroes: vec![0; width.get() as usize * height.get() as usize].into_boxed_slice(), + }) + } + + /// Get the size of this buffer. + pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) { + let (width, height) = self.db.size(); + + NonZeroU32::new(width) + .and_then(|width| NonZeroU32::new(height).map(|height| (width, height))) + .expect("buffer size is zero") + } +} diff --git a/src/lib.rs b/src/lib.rs index 0c39dd1a..b02ccfe2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,8 @@ extern crate core; #[cfg(target_os = "macos")] mod cg; +#[cfg(kms_platform)] +mod kms; #[cfg(target_os = "redox")] mod orbital; #[cfg(wayland_platform)] @@ -174,6 +176,8 @@ make_dispatch! { X11(Rc, x11::X11Impl, x11::BufferImpl<'a>), #[cfg(wayland_platform)] Wayland(Rc, wayland::WaylandImpl, wayland::BufferImpl<'a>), + #[cfg(kms_platform)] + Kms(Rc, kms::KmsImpl, kms::BufferImpl<'a>), #[cfg(target_os = "windows")] Win32((), win32::Win32Impl, win32::BufferImpl<'a>), #[cfg(target_os = "macos")] @@ -213,6 +217,10 @@ impl Context { RawDisplayHandle::Wayland(wayland_handle) => unsafe { ContextDispatch::Wayland(Rc::new(wayland::WaylandDisplayImpl::new(wayland_handle)?)) }, + #[cfg(kms_platform)] + RawDisplayHandle::Drm(drm_handle) => unsafe { + ContextDispatch::Kms(Rc::new(kms::KmsDisplayImpl::new(drm_handle)?)) + }, #[cfg(target_os = "windows")] RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()), #[cfg(target_os = "macos")] @@ -303,6 +311,12 @@ impl Surface { ) => SurfaceDispatch::Wayland(unsafe { wayland::WaylandImpl::new(wayland_window_handle, wayland_display_impl.clone())? }), + #[cfg(kms_platform)] + (ContextDispatch::Kms(kms_display_impl), RawWindowHandle::Drm(drm_window_handle)) => { + SurfaceDispatch::Kms(unsafe { + kms::KmsImpl::new(drm_window_handle, kms_display_impl.clone())? + }) + } #[cfg(target_os = "windows")] (ContextDispatch::Win32(()), RawWindowHandle::Win32(win32_handle)) => { SurfaceDispatch::Win32(unsafe { win32::Win32Impl::new(&win32_handle)? }) From 7765e6efdd71399def4853fe89efc89eba15fc41 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 14 Jul 2023 20:28:43 -0700 Subject: [PATCH 02/15] Fix basic compile errors --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae923782..c7c02d94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ harness = false [features] default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] -kms = ["drm"] +kms = ["bytemuck", "drm"] wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"] wayland-dlopen = ["wayland-sys/dlopen"] x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"] diff --git a/src/lib.rs b/src/lib.rs index b02ccfe2..4408c220 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ mod util; use std::marker::PhantomData; use std::num::NonZeroU32; use std::ops; -#[cfg(any(wayland_platform, x11_platform))] +#[cfg(any(wayland_platform, x11_platform, kms_platform))] use std::rc::Rc; pub use error::SoftBufferError; From e6449e2caa9ad4a4f87f7b8df03a1bb4098c1345 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 15 Jul 2023 13:10:50 -0700 Subject: [PATCH 03/15] Don't test KMS on netbsd --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51964998..6939832c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms" } - { target: x86_64-unknown-redox, os: ubuntu-latest, } - { target: x86_64-unknown-freebsd, os: ubuntu-latest, } - - { target: x86_64-unknown-netbsd, os: ubuntu-latest, } + - { target: x86_64-unknown-netbsd, os: ubuntu-latest, options: --no-defaut-features, features: "x11,x11-dlopen,wayland,wayland-dlopen" } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: wasm32-unknown-unknown, os: ubuntu-latest, } include: From da94cde4b1329cd8ad546caf3d5c6df0da8db167 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Tue, 8 Aug 2023 13:04:57 -0700 Subject: [PATCH 04/15] Add damage support Signed-off-by: John Nunley --- .github/workflows/ci.yml | 2 +- Cargo.toml | 3 +- src/kms.rs | 74 +++++++++++++++++++++++++++------------- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6939832c..1be96a25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms" } - { target: x86_64-unknown-redox, os: ubuntu-latest, } - { target: x86_64-unknown-freebsd, os: ubuntu-latest, } - - { target: x86_64-unknown-netbsd, os: ubuntu-latest, options: --no-defaut-features, features: "x11,x11-dlopen,wayland,wayland-dlopen" } + - { target: x86_64-unknown-netbsd, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen,wayland,wayland-dlopen" } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: wasm32-unknown-unknown, os: ubuntu-latest, } include: diff --git a/Cargo.toml b/Cargo.toml index c7c02d94..9a3efdec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ harness = false [features] default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] -kms = ["bytemuck", "drm"] +kms = ["bytemuck", "drm", "drm-sys"] wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"] wayland-dlopen = ["wayland-sys/dlopen"] x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"] @@ -32,6 +32,7 @@ raw-window-handle = "0.5.0" as-raw-xcb-connection = { version = "1.0.0", optional = true } bytemuck = { version = "1.12.3", optional = true } drm = { version = "0.9.0", default-features = false, optional = true } +drm-sys = { version = "0.4.0", default-features = false, optional = true } memmap2 = { version = "0.7.1", optional = true } nix = { version = "0.26.1", optional = true } tiny-xlib = { version = "0.2.1", optional = true } diff --git a/src/kms.rs b/src/kms.rs index 82e97b90..ede6f460 100644 --- a/src/kms.rs +++ b/src/kms.rs @@ -73,11 +73,8 @@ pub(crate) struct BufferImpl<'a> { /// The framebuffer. fb: framebuffer::Handle, - /// The connector. - connector: connector::Handle, - - /// The CRTC info. - crtc_info: &'a crtc::Info, + /// The current size. + size: (NonZeroU32, NonZeroU32), /// The display implementation. display: &'a KmsDisplayImpl, @@ -178,6 +175,19 @@ impl KmsImpl { // Create a new buffer. let buffer = BufferSet::new(&self.display, width, height)?; + + // Set the framebuffer in the CRTC info. + // TODO: This requires root access, find a way that doesn't! + self.display + .set_crtc( + self.crtc.handle(), + Some(buffer.fb), + self.crtc.position(), + &[self.connector], + self.crtc.mode(), + ) + .swbuf_err("failed to set CRTC")?; + self.buffer = Some(buffer); Ok(()) @@ -196,6 +206,7 @@ impl KmsImpl { .buffer .as_mut() .expect("Must set size of surface before calling `buffer_mut()`"); + let size = set.size(); let mapping = self .display .map_dumb_buffer(&mut set.db) @@ -203,9 +214,8 @@ impl KmsImpl { Ok(BufferImpl { mapping, + size, fb: set.fb, - connector: self.connector, - crtc_info: &self.crtc, display: &self.display, zeroes: &set.zeroes, }) @@ -231,27 +241,45 @@ impl BufferImpl<'_> { } #[inline] - pub fn present_with_damage(self, _damage: &[crate::Rect]) -> Result<(), SoftBufferError> { - // TODO: Is there a way of doing this? - self.present() - } + pub fn present_with_damage(self, damage: &[crate::Rect]) -> Result<(), SoftBufferError> { + let rectangles = damage + .iter() + .map(|&rect| { + let err = || SoftBufferError::DamageOutOfRange { rect }; + Ok(drm_sys::drm_clip_rect { + x1: rect.x.try_into().map_err(|_| err())?, + y1: rect.y.try_into().map_err(|_| err())?, + x2: rect + .x + .checked_add(rect.width.get()) + .and_then(|x| x.try_into().ok()) + .ok_or_else(err)?, + y2: rect + .y + .checked_add(rect.height.get()) + .and_then(|y| y.try_into().ok()) + .ok_or_else(err)?, + }) + }) + .collect::, _>>()?; - #[inline] - pub fn present(self) -> Result<(), SoftBufferError> { - // Set the CRTC mode. - // TODO: This requires root access, find a way that doesn't! self.display - .set_crtc( - self.crtc_info.handle(), - Some(self.fb), - (0, 0), - &[self.connector], - self.crtc_info.mode(), - ) - .swbuf_err("failed to set CRTC")?; + .dirty_framebuffer(self.fb, &rectangles) + .swbuf_err("failed to dirty framebuffer")?; Ok(()) } + + #[inline] + pub fn present(self) -> Result<(), SoftBufferError> { + let (width, height) = self.size; + self.present_with_damage(&[crate::Rect { + x: 0, + y: 0, + width, + height, + }]) + } } impl BufferSet { From 26f776bf36f5cdc79fc9dcd37f11506056ae5a9d Mon Sep 17 00:00:00 2001 From: John Nunley Date: Tue, 8 Aug 2023 14:40:17 -0700 Subject: [PATCH 05/15] Add example and fix KMS Signed-off-by: John Nunley --- Cargo.toml | 1 + examples/drm.rs | 172 ++++++++++++++++++++++++++++++++++++++++++++++++ src/kms.rs | 19 +++++- 3 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 examples/drm.rs diff --git a/Cargo.toml b/Cargo.toml index 9a3efdec..3d4c46ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ redox_syscall = "0.3" cfg_aliases = "0.1.1" [dev-dependencies] +colorous = "1.0.12" criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } instant = "0.1.12" winit = "0.28.1" diff --git a/examples/drm.rs b/examples/drm.rs new file mode 100644 index 00000000..1e98c0ce --- /dev/null +++ b/examples/drm.rs @@ -0,0 +1,172 @@ +//! Example of using softbuffer with drm-rs. + +#[cfg(kms_platform)] +mod imple { + use drm::control::{connector, Device as CtrlDevice, ModeTypeFlags, PlaneType}; + use drm::Device; + + use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle}; + use softbuffer::{Context, Surface}; + + use std::num::NonZeroU32; + use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; + use std::time::{Duration, Instant}; + + pub(super) fn entry() -> Result<(), Box> { + // Open a new device. + let device = Card::open()?; + + // Create the softbuffer context. + let context = unsafe { + Context::from_raw({ + let mut handle = DrmDisplayHandle::empty(); + handle.fd = device.as_fd().as_raw_fd(); + handle.into() + }) + }?; + + // Get the DRM handles. + let handles = device.resource_handles()?; + + // Get the list of connectors and CRTCs. + let connectors = handles + .connectors() + .iter() + .map(|&con| device.get_connector(con, true)) + .collect::, _>>()?; + let crtcs = handles + .crtcs() + .iter() + .map(|&crtc| device.get_crtc(crtc)) + .collect::, _>>()?; + + // Find a connected crtc. + let con = connectors + .iter() + .find(|con| con.state() == connector::State::Connected) + .ok_or("No connected connectors")?; + + // Get the first CRTC. + let crtc = crtcs.first().ok_or("No CRTCs")?; + + // Find a mode to use. + let mode = con + .modes() + .iter() + .find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) + .or_else(|| con.modes().first()) + .ok_or("No modes")?; + + // Look for a primary plane compatible with our CRTC. + let planes = device.plane_handles()?; + let planes = planes + .iter() + .filter(|&&plane| { + device.get_plane(plane).map_or(false, |plane| { + let crtcs = handles.filter_crtcs(plane.possible_crtcs()); + crtcs.contains(&crtc.handle()) + }) + }) + .collect::>(); + + // Find the first primary plane or take the first one period. + let plane = planes + .iter() + .find(|&&&plane| { + if let Ok(props) = device.get_properties(plane) { + let (ids, vals) = props.as_props_and_values(); + for (&id, &val) in ids.iter().zip(vals.iter()) { + if let Ok(info) = device.get_property(id) { + if info.name().to_str().map_or(false, |x| x == "type") { + return val == PlaneType::Primary as u32 as u64; + } + } + } + } + + false + }) + .or(planes.first()) + .ok_or("No planes")?; + + // Create the surface on top of this plane. + // Note: This requires root on DRM/KMS. + let mut surface = unsafe { + Surface::from_raw(&context, { + let mut handle = DrmWindowHandle::empty(); + handle.plane = (**plane).into(); + handle.into() + }) + }?; + + // Resize the surface. + let (width, height) = mode.size(); + surface.resize( + NonZeroU32::new(width as u32).unwrap(), + NonZeroU32::new(height as u32).unwrap(), + )?; + + // Start drawing to it. + let start = Instant::now(); + let mut tick = 0; + while Instant::now().duration_since(start) < Duration::from_secs(2) { + tick += 1; + println!("Drawing tick {tick}"); + + // Start drawing. + let mut buffer = surface.buffer_mut()?; + draw_to_buffer(&mut buffer, tick); + buffer.present()?; + + // Sleep for a little. + std::thread::sleep(Duration::from_millis(5)); + } + + Ok(()) + } + + fn draw_to_buffer(buf: &mut [u32], tick: usize) { + let scale = colorous::VIRIDIS; + let mut i = (tick as f64) / 10.0; + while i > 1.0 { + i -= 1.0; + } + + let color = scale.eval_continuous(i); + let pixel = (color.r as u32) << 16 | (color.g as u32) << 8 | (color.b as u32); + buf.fill(pixel); + } + + struct Card(std::fs::File); + + impl Card { + fn open() -> Result> { + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/dri/card0")?; + Ok(Card(file)) + } + } + + impl AsFd for Card { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + } + + impl Device for Card {} + impl CtrlDevice for Card {} +} + +#[cfg(not(kms_platform))] +mod imple { + pub(super) fn entry() -> Result<(), Box> { + eprintln!("This example requires the `kms` feature."); + Ok(()) + } +} + +fn main() -> Result<(), Box> { + imple::entry() +} diff --git a/src/kms.rs b/src/kms.rs index ede6f460..a902d6ff 100644 --- a/src/kms.rs +++ b/src/kms.rs @@ -222,6 +222,21 @@ impl KmsImpl { } } +impl Drop for KmsImpl { + fn drop(&mut self) { + // Map the CRTC to the information that was there before. + self.display + .set_crtc( + self.crtc.handle(), + self.crtc.framebuffer(), + self.crtc.position(), + &[self.connector], + self.crtc.mode(), + ) + .ok(); + } +} + impl BufferImpl<'_> { #[inline] pub fn pixels(&self) -> &[u32] { @@ -290,10 +305,10 @@ impl BufferSet { height: NonZeroU32, ) -> Result { let db = display - .create_dumb_buffer((width.get(), height.get()), DrmFourcc::Argb8888, 32) + .create_dumb_buffer((width.get(), height.get()), DrmFourcc::Xrgb8888, 32) .swbuf_err("failed to create dumb buffer")?; let fb = display - .add_framebuffer(&db, 32, 32) + .add_framebuffer(&db, 24, 32) .swbuf_err("failed to add framebuffer")?; Ok(BufferSet { From c10ae7a9abc018d33ff7cc9cbab4dca2e07f4555 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Wed, 9 Aug 2023 21:31:26 -0700 Subject: [PATCH 06/15] Add double buffering Signed-off-by: John Nunley --- src/kms.rs | 95 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 26 deletions(-) diff --git a/src/kms.rs b/src/kms.rs index a902d6ff..5fdaa8c0 100644 --- a/src/kms.rs +++ b/src/kms.rs @@ -4,7 +4,7 @@ use drm::buffer::{Buffer, DrmFourcc}; use drm::control::dumbbuffer::{DumbBuffer, DumbMapping}; -use drm::control::{connector, crtc, framebuffer, plane, Device as CtrlDevice}; +use drm::control::{connector, crtc, framebuffer, plane, Device as CtrlDevice, PageFlipFlags}; use drm::Device; use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle}; @@ -62,7 +62,19 @@ pub(crate) struct KmsImpl { crtc: crtc::Info, /// The dumb buffer we're using as a buffer. - buffer: Option, + buffer: Option, +} + +#[derive(Debug)] +struct Buffers { + /// The involved set of buffers. + buffers: [SharedBuffer; 2], + + /// Whether to use the first buffer or the second buffer as the front buffer. + first_is_front: bool, + + /// A buffer full of zeroes. + zeroes: Box<[u32]>, } /// The buffer implementation. @@ -70,30 +82,36 @@ pub(crate) struct BufferImpl<'a> { /// The mapping of the dump buffer. mapping: DumbMapping<'a>, - /// The framebuffer. - fb: framebuffer::Handle, + /// The framebuffer object of the current front buffer. + front_fb: framebuffer::Handle, + + /// The framebuffer object of the current back buffer. + back_fb: framebuffer::Handle, + + /// The CRTC handle. + crtc_handle: crtc::Handle, + + /// This is used to change the front buffer. + first_is_front: &'a mut bool, + + /// Buffer full of zeroes. + zeroes: &'a [u32], /// The current size. size: (NonZeroU32, NonZeroU32), /// The display implementation. display: &'a KmsDisplayImpl, - - /// The zero buffer. - zeroes: &'a [u32], } /// The combined frame buffer and dumb buffer. #[derive(Debug)] -struct BufferSet { +struct SharedBuffer { /// The frame buffer. fb: framebuffer::Handle, /// The dumb buffer. db: DumbBuffer, - - /// Equivalent mapping for reading. - zeroes: Box<[u32]>, } impl KmsImpl { @@ -173,22 +191,27 @@ impl KmsImpl { } } - // Create a new buffer. - let buffer = BufferSet::new(&self.display, width, height)?; + // Create a new buffer set. + let front_buffer = SharedBuffer::new(&self.display, width, height)?; + let back_buffer = SharedBuffer::new(&self.display, width, height)?; // Set the framebuffer in the CRTC info. - // TODO: This requires root access, find a way that doesn't! + // This requires root access without libseat. self.display .set_crtc( self.crtc.handle(), - Some(buffer.fb), + Some(front_buffer.fb), self.crtc.position(), &[self.connector], self.crtc.mode(), ) .swbuf_err("failed to set CRTC")?; - self.buffer = Some(buffer); + self.buffer = Some(Buffers { + first_is_front: true, + buffers: [front_buffer, back_buffer], + zeroes: vec![0; width.get() as usize * height.get() as usize].into_boxed_slice(), + }); Ok(()) } @@ -206,16 +229,25 @@ impl KmsImpl { .buffer .as_mut() .expect("Must set size of surface before calling `buffer_mut()`"); + let size = set.size(); + let (front_index, back_index) = if set.first_is_front { (0, 1) } else { (1, 0) }; + + let front_fb = set.buffers[front_index].fb; + let back_fb = set.buffers[back_index].fb; + let mapping = self .display - .map_dumb_buffer(&mut set.db) + .map_dumb_buffer(&mut set.buffers[front_index].db) .swbuf_err("failed to map dumb buffer")?; Ok(BufferImpl { mapping, size, - fb: set.fb, + first_is_front: &mut set.first_is_front, + front_fb, + back_fb, + crtc_handle: self.crtc.handle(), display: &self.display, zeroes: &set.zeroes, }) @@ -252,7 +284,7 @@ impl BufferImpl<'_> { #[inline] pub fn age(&self) -> u8 { - todo!() + 0 // TODO: Implement this! } #[inline] @@ -279,9 +311,17 @@ impl BufferImpl<'_> { .collect::, _>>()?; self.display - .dirty_framebuffer(self.fb, &rectangles) + .dirty_framebuffer(self.front_fb, &rectangles) .swbuf_err("failed to dirty framebuffer")?; + // Swap the buffers. + self.display + .page_flip(self.crtc_handle, self.back_fb, PageFlipFlags::EVENT, None) + .swbuf_err("failed to page flip")?; + + // Flip the front and back buffers. + *self.first_is_front = !*self.first_is_front; + Ok(()) } @@ -297,7 +337,7 @@ impl BufferImpl<'_> { } } -impl BufferSet { +impl SharedBuffer { /// Create a new buffer set. pub(crate) fn new( display: &KmsDisplayImpl, @@ -311,11 +351,7 @@ impl BufferSet { .add_framebuffer(&db, 24, 32) .swbuf_err("failed to add framebuffer")?; - Ok(BufferSet { - fb, - db, - zeroes: vec![0; width.get() as usize * height.get() as usize].into_boxed_slice(), - }) + Ok(SharedBuffer { fb, db }) } /// Get the size of this buffer. @@ -327,3 +363,10 @@ impl BufferSet { .expect("buffer size is zero") } } + +impl Buffers { + /// Get the size of this buffer. + pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) { + self.buffers[0].size() + } +} From a276f6738a74fd5a3ef8ce44c951f2e0ef1006f0 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Thu, 10 Aug 2023 08:52:28 -0700 Subject: [PATCH 07/15] Fix implementation of double buffering I was previously using the wrong framebuffer to page flip. This also adds an implementation of buffer aging. Signed-off-by: John Nunley --- examples/drm.rs | 24 +++++++++++++++++++----- src/kms.rs | 42 +++++++++++++++++++++++++++++++----------- src/lib.rs | 1 + 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/examples/drm.rs b/examples/drm.rs index 1e98c0ce..9e8bda90 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -2,7 +2,7 @@ #[cfg(kms_platform)] mod imple { - use drm::control::{connector, Device as CtrlDevice, ModeTypeFlags, PlaneType}; + use drm::control::{connector, Device as CtrlDevice, Event, ModeTypeFlags, PlaneType}; use drm::Device; use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle}; @@ -118,16 +118,30 @@ mod imple { draw_to_buffer(&mut buffer, tick); buffer.present()?; - // Sleep for a little. - std::thread::sleep(Duration::from_millis(5)); + // Wait for the page flip to happen. + let events = device.receive_events()?; + println!("Got some events..."); + for event in events { + match event { + Event::PageFlip(_) => { + println!("Page flip event."); + } + Event::Vblank(_) => { + println!("Vblank event."); + } + _ => { + println!("Unknown event."); + } + } + } } Ok(()) } fn draw_to_buffer(buf: &mut [u32], tick: usize) { - let scale = colorous::VIRIDIS; - let mut i = (tick as f64) / 10.0; + let scale = colorous::SINEBOW; + let mut i = (tick as f64) / 20.0; while i > 1.0 { i -= 1.0; } diff --git a/src/kms.rs b/src/kms.rs index 5fdaa8c0..27348d40 100644 --- a/src/kms.rs +++ b/src/kms.rs @@ -85,9 +85,6 @@ pub(crate) struct BufferImpl<'a> { /// The framebuffer object of the current front buffer. front_fb: framebuffer::Handle, - /// The framebuffer object of the current back buffer. - back_fb: framebuffer::Handle, - /// The CRTC handle. crtc_handle: crtc::Handle, @@ -102,6 +99,12 @@ pub(crate) struct BufferImpl<'a> { /// The display implementation. display: &'a KmsDisplayImpl, + + /// Age of the front buffer. + front_age: &'a mut u8, + + /// Age of the back buffer. + back_age: &'a mut u8, } /// The combined frame buffer and dumb buffer. @@ -112,6 +115,9 @@ struct SharedBuffer { /// The dumb buffer. db: DumbBuffer, + + /// The age of this buffer. + age: u8, } impl KmsImpl { @@ -231,14 +237,21 @@ impl KmsImpl { .expect("Must set size of surface before calling `buffer_mut()`"); let size = set.size(); - let (front_index, back_index) = if set.first_is_front { (0, 1) } else { (1, 0) }; - let front_fb = set.buffers[front_index].fb; - let back_fb = set.buffers[back_index].fb; + let [first_buffer, second_buffer] = &mut set.buffers; + let (front_buffer, back_buffer) = if set.first_is_front { + (first_buffer, second_buffer) + } else { + (second_buffer, first_buffer) + }; + + let front_fb = front_buffer.fb; + let front_age = &mut front_buffer.age; + let back_age = &mut back_buffer.age; let mapping = self .display - .map_dumb_buffer(&mut set.buffers[front_index].db) + .map_dumb_buffer(&mut front_buffer.db) .swbuf_err("failed to map dumb buffer")?; Ok(BufferImpl { @@ -246,10 +259,11 @@ impl KmsImpl { size, first_is_front: &mut set.first_is_front, front_fb, - back_fb, crtc_handle: self.crtc.handle(), display: &self.display, zeroes: &set.zeroes, + front_age, + back_age, }) } } @@ -284,7 +298,7 @@ impl BufferImpl<'_> { #[inline] pub fn age(&self) -> u8 { - 0 // TODO: Implement this! + *self.front_age } #[inline] @@ -316,12 +330,18 @@ impl BufferImpl<'_> { // Swap the buffers. self.display - .page_flip(self.crtc_handle, self.back_fb, PageFlipFlags::EVENT, None) + .page_flip(self.crtc_handle, self.front_fb, PageFlipFlags::EVENT, None) .swbuf_err("failed to page flip")?; // Flip the front and back buffers. *self.first_is_front = !*self.first_is_front; + // Set the ages. + *self.front_age = 1; + if *self.back_age != 0 { + *self.back_age += 1; + } + Ok(()) } @@ -351,7 +371,7 @@ impl SharedBuffer { .add_framebuffer(&db, 24, 32) .swbuf_err("failed to add framebuffer")?; - Ok(SharedBuffer { fb, db }) + Ok(SharedBuffer { fb, db, age: 0 }) } /// Get the size of this buffer. diff --git a/src/lib.rs b/src/lib.rs index 4408c220..ff49863a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,7 @@ macro_rules! make_dispatch { } } + #[allow(clippy::large_enum_variant)] // it's boxed anyways enum SurfaceDispatch { $( $(#[$attr])* From 1716692a5bffcc123b5ab2761b6184e5618795ae Mon Sep 17 00:00:00 2001 From: John Nunley Date: Thu, 10 Aug 2023 18:21:03 -0700 Subject: [PATCH 08/15] Fix usage of connectors Apparently I was setting up the connectors wrong. Signed-off-by: John Nunley --- src/kms.rs | 80 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/kms.rs b/src/kms.rs index 27348d40..7fe063a4 100644 --- a/src/kms.rs +++ b/src/kms.rs @@ -9,6 +9,7 @@ use drm::Device; use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle}; +use std::collections::HashSet; use std::num::NonZeroU32; use std::os::unix::io::{AsFd, BorrowedFd}; use std::rc::Rc; @@ -55,8 +56,8 @@ pub(crate) struct KmsImpl { /// The display implementation. display: Rc, - /// The connector to use. - connector: connector::Handle, + /// The connectors to use. + connectors: Vec, /// The CRTC to render to. crtc: crtc::Info, @@ -146,38 +147,50 @@ impl KmsImpl { .swbuf_err("failed to get resource handles")?; // Use either the attached CRTC or the primary CRTC. - let crtc = match plane_info.crtc() { - Some(crtc) => crtc, - None => { - log::warn!("no CRTC attached to plane, falling back to primary CRTC"); - handles - .filter_crtcs(plane_info.possible_crtcs()) - .first() - .copied() - .swbuf_err("failed to find a primary CRTC")? - } + let crtc = { + let handle = match plane_info.crtc() { + Some(crtc) => crtc, + None => { + log::warn!("no CRTC attached to plane, falling back to primary CRTC"); + handles + .filter_crtcs(plane_info.possible_crtcs()) + .first() + .copied() + .swbuf_err("failed to find a primary CRTC")? + } + }; + + // Get info about the CRTC. + display + .get_crtc(handle) + .swbuf_err("failed to get CRTC info")? }; - // Use a preferred connector or just select the first one. - let connector = handles + // Figure out all of the encoders that are attached to this CRTC. + let encoders = handles + .encoders + .iter() + .flat_map(|handle| display.get_encoder(*handle)) + .filter(|encoder| encoder.crtc() == Some(crtc.handle())) + .map(|encoder| encoder.handle()) + .collect::>(); + + // Get a list of every connector. + let connectors = handles .connectors .iter() .flat_map(|handle| display.get_connector(*handle, false)) - .find_map(|conn| { - if conn.state() == connector::State::Connected { - Some(conn.handle()) - } else { - None - } + .filter(|connector| { + connector + .current_encoder() + .map_or(false, |encoder| encoders.contains(&encoder)) }) - .or_else(|| handles.connectors.first().copied()) - .swbuf_err("failed to find a valid connector")?; + .map(|info| info.handle()) + .collect::>(); Ok(Self { - crtc: display - .get_crtc(crtc) - .swbuf_err("failed to get CRTC info")?, - connector, + crtc, + connectors, display, buffer: None, }) @@ -201,18 +214,6 @@ impl KmsImpl { let front_buffer = SharedBuffer::new(&self.display, width, height)?; let back_buffer = SharedBuffer::new(&self.display, width, height)?; - // Set the framebuffer in the CRTC info. - // This requires root access without libseat. - self.display - .set_crtc( - self.crtc.handle(), - Some(front_buffer.fb), - self.crtc.position(), - &[self.connector], - self.crtc.mode(), - ) - .swbuf_err("failed to set CRTC")?; - self.buffer = Some(Buffers { first_is_front: true, buffers: [front_buffer, back_buffer], @@ -276,7 +277,7 @@ impl Drop for KmsImpl { self.crtc.handle(), self.crtc.framebuffer(), self.crtc.position(), - &[self.connector], + &self.connectors, self.crtc.mode(), ) .ok(); @@ -329,6 +330,7 @@ impl BufferImpl<'_> { .swbuf_err("failed to dirty framebuffer")?; // Swap the buffers. + // TODO: Use atomic commits here! self.display .page_flip(self.crtc_handle, self.front_fb, PageFlipFlags::EVENT, None) .swbuf_err("failed to page flip")?; From 3e160a6b454c39ae02d721796c70fc3ab33e6177 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 12 Aug 2023 09:20:58 -0700 Subject: [PATCH 09/15] Address example review comments - Add a mechanism that uses udev to find a card - Poll the fd before receiving events Signed-off-by: John Nunley --- Cargo.toml | 4 ++++ examples/drm.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3d4c46ed..d5b243ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,10 @@ rayon = "1.5.1" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3" +[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dev-dependencies] +rustix = { version = "0.38.8", features = ["event"] } +udev = "0.7.0" + [workspace] members = [ "run-wasm", diff --git a/examples/drm.rs b/examples/drm.rs index 9e8bda90..9890569f 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -10,6 +10,7 @@ mod imple { use std::num::NonZeroU32; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; + use std::path::{Path, PathBuf}; use std::time::{Duration, Instant}; pub(super) fn entry() -> Result<(), Box> { @@ -119,6 +120,15 @@ mod imple { buffer.present()?; // Wait for the page flip to happen. + rustix::event::poll( + &mut [rustix::event::PollFd::new( + &device, + rustix::event::PollFlags::IN, + )], + -1, + )?; + + // Receive the events. let events = device.receive_events()?; println!("Got some events..."); for event in events { @@ -155,10 +165,57 @@ mod imple { impl Card { fn open() -> Result> { + let mut enumerator = udev::Enumerator::new()?; + + // Look for a drm system with name card[0-9] + enumerator.match_subsystem("drm")?; + enumerator.match_sysname("card[0-9]*")?; + + // Get the first device. + let path = enumerator + .scan_devices()? + .filter(|device| { + let seat_name = device + .property_value("ID_SEAT") + .map(|x| x.to_os_string()) + .unwrap_or_else(|| "seat0".into()); + + seat_name == "seat0" + }) + .filter(|device| { + let pci = match device.parent_with_subsystem(Path::new("pci")) { + Ok(Some(pci)) => pci, + _ => return false, + }; + + let id = match pci.attribute_value("boot_vga") { + Some(id) => id, + _ => return false, + }; + + id.to_str() == Some("1") + }) + .find_map(|dev| dev.devnode().map(PathBuf::from)) + .or_else(|| { + enumerator + .scan_devices() + .expect("Failed to scan devices") + .filter(|device| { + let seat_name = device + .property_value("ID_SEAT") + .map(|x| x.to_os_string()) + .unwrap_or_else(|| "seat0".into()); + + seat_name == "seat0" + }) + .find_map(|dev| dev.devnode().map(PathBuf::from)) + }) + .ok_or("No drm device found")?; + let file = std::fs::OpenOptions::new() .read(true) .write(true) - .open("/dev/dri/card0")?; + .open(path)?; Ok(Card(file)) } } From 526eabd429afbe31e741c67d5fd6801bbcf77baa Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 12 Aug 2023 10:10:47 -0700 Subject: [PATCH 10/15] Add a strategy for finding the card without udev Signed-off-by: John Nunley --- Cargo.toml | 1 - examples/drm.rs | 70 +++++++++++++++---------------------------------- 2 files changed, 21 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5b243ca..fd874d77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,6 @@ wasm-bindgen-test = "0.3" [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dev-dependencies] rustix = { version = "0.38.8", features = ["event"] } -udev = "0.7.0" [workspace] members = [ diff --git a/examples/drm.rs b/examples/drm.rs index 9890569f..b0c38a72 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -10,12 +10,12 @@ mod imple { use std::num::NonZeroU32; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; - use std::path::{Path, PathBuf}; + use std::path::Path; use std::time::{Duration, Instant}; pub(super) fn entry() -> Result<(), Box> { // Open a new device. - let device = Card::open()?; + let device = Card::find()?; // Create the softbuffer context. let context = unsafe { @@ -164,54 +164,26 @@ mod imple { struct Card(std::fs::File); impl Card { - fn open() -> Result> { - let mut enumerator = udev::Enumerator::new()?; - - // Look for a drm system with name card[0-9] - enumerator.match_subsystem("drm")?; - enumerator.match_sysname("card[0-9]*")?; - - // Get the first device. - let path = enumerator - .scan_devices()? - .filter(|device| { - let seat_name = device - .property_value("ID_SEAT") - .map(|x| x.to_os_string()) - .unwrap_or_else(|| "seat0".into()); - - seat_name == "seat0" - }) - .filter(|device| { - let pci = match device.parent_with_subsystem(Path::new("pci")) { - Ok(Some(pci)) => pci, - _ => return false, - }; - - let id = match pci.attribute_value("boot_vga") { - Some(id) => id, - _ => return false, - }; - - id.to_str() == Some("1") - }) - .find_map(|dev| dev.devnode().map(PathBuf::from)) - .or_else(|| { - enumerator - .scan_devices() - .expect("Failed to scan devices") - .filter(|device| { - let seat_name = device - .property_value("ID_SEAT") - .map(|x| x.to_os_string()) - .unwrap_or_else(|| "seat0".into()); - - seat_name == "seat0" - }) - .find_map(|dev| dev.devnode().map(PathBuf::from)) - }) - .ok_or("No drm device found")?; + fn find() -> Result> { + for i in 0..10 { + let path = format!("/dev/dri/card{i}"); + let device = Card::open(path)?; + + // Only use it if it has connectors. + let handles = match device.resource_handles() { + Ok(handles) => handles, + Err(_) => continue, + }; + + if !handles.connectors.is_empty() { + return Ok(device); + } + } + + Err("No DRM device found".into()) + } + fn open(path: impl AsRef) -> Result> { let file = std::fs::OpenOptions::new() .read(true) .write(true) From 83e3d97c05692472c394ae3c6e51da06f4720641 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 12 Aug 2023 10:21:34 -0700 Subject: [PATCH 11/15] fmt Signed-off-by: John Nunley --- examples/drm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/drm.rs b/examples/drm.rs index b0c38a72..9c51a720 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -174,7 +174,7 @@ mod imple { Ok(handles) => handles, Err(_) => continue, }; - + if !handles.connectors.is_empty() { return Ok(device); } From 09e80ad65352040bd7c67d34aab48bb163cf88a0 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 12 Aug 2023 10:24:52 -0700 Subject: [PATCH 12/15] Add comment to buffer_mut for DRM page flip Signed-off-by: John Nunley --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ff49863a..e904f6aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -376,6 +376,12 @@ impl Surface { /// Return a [`Buffer`] that the next frame should be rendered into. The size must /// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or /// may contain a previous frame. Call [`Buffer::age`] to determine this. + /// + /// ## Platform Dependent Behavior + /// + /// - On DRM/KMS, there is no reliable and sound way to wait for the page flip to happen from within + /// `softbuffer`. Therefore it is the responsibility of the user to wait for the page flip before + /// sending another frame. pub fn buffer_mut(&mut self) -> Result { Ok(Buffer { buffer_impl: self.surface_impl.buffer_mut()?, From 205657b600c540ec75ad3c6b730cb0ee43adb344 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 12 Aug 2023 10:26:34 -0700 Subject: [PATCH 13/15] fmt Signed-off-by: John Nunley --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e904f6aa..3f9b919c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -376,9 +376,9 @@ impl Surface { /// Return a [`Buffer`] that the next frame should be rendered into. The size must /// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or /// may contain a previous frame. Call [`Buffer::age`] to determine this. - /// + /// /// ## Platform Dependent Behavior - /// + /// /// - On DRM/KMS, there is no reliable and sound way to wait for the page flip to happen from within /// `softbuffer`. Therefore it is the responsibility of the user to wait for the page flip before /// sending another frame. From a85b569b484e3b7361e1789e7017056dc0d56770 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 12 Aug 2023 10:32:27 -0700 Subject: [PATCH 14/15] Test for connected connectors instead of amount Signed-off-by: John Nunley --- examples/drm.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/drm.rs b/examples/drm.rs index 9c51a720..caf6d617 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -175,7 +175,12 @@ mod imple { Err(_) => continue, }; - if !handles.connectors.is_empty() { + if handles + .connectors + .iter() + .filter_map(|c| device.get_connector(*c, false).ok()) + .any(|c| c.state() == connector::State::Connected) + { return Ok(device); } } From c8ba5d4aecbc850622d5024609cd08fc8df3232d Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 12 Aug 2023 11:34:09 -0700 Subject: [PATCH 15/15] Ignore ENOSYS for dirty_framebuffer Signed-off-by: John Nunley --- Cargo.toml | 2 +- src/kms.rs | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fd874d77..ac7c7330 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ harness = false [features] default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] -kms = ["bytemuck", "drm", "drm-sys"] +kms = ["bytemuck", "drm", "drm-sys", "nix"] wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"] wayland-dlopen = ["wayland-sys/dlopen"] x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"] diff --git a/src/kms.rs b/src/kms.rs index 7fe063a4..bc1638c5 100644 --- a/src/kms.rs +++ b/src/kms.rs @@ -175,7 +175,7 @@ impl KmsImpl { .map(|encoder| encoder.handle()) .collect::>(); - // Get a list of every connector. + // Get a list of every connector that the CRTC is connected to via encoders. let connectors = handles .connectors .iter() @@ -325,9 +325,24 @@ impl BufferImpl<'_> { }) .collect::, _>>()?; - self.display - .dirty_framebuffer(self.front_fb, &rectangles) - .swbuf_err("failed to dirty framebuffer")?; + // Dirty the framebuffer with out damage rectangles. + // + // Some drivers don't support this, so we just ignore the `ENOSYS` error. + // TODO: It would be nice to not have to heap-allocate the above rectangles if we know that + // this is going to fail. Low hanging fruit PR: add a flag that's set to false if this + // returns `ENOSYS` and check that before allocating the above and running this. + match self.display.dirty_framebuffer(self.front_fb, &rectangles) { + Ok(()) + | Err(drm::SystemError::Unknown { + errno: nix::errno::Errno::ENOSYS, + }) => {} + Err(e) => { + return Err(SoftBufferError::PlatformError( + Some("failed to dirty framebuffer".into()), + Some(e.into()), + )); + } + } // Swap the buffers. // TODO: Use atomic commits here!