diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7a992..682fe98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ - EGLBufferReader now checks if buffers are alive before using them. - LibSeat no longer panics on seat disable event. +- X11 backend will report an error when trying to present a dmabuf fails. ### Anvil diff --git a/src/backend/x11/buffer.rs b/src/backend/x11/buffer.rs index 8389124..387145a 100644 --- a/src/backend/x11/buffer.rs +++ b/src/backend/x11/buffer.rs @@ -29,54 +29,23 @@ use std::sync::atomic::Ordering; -use super::{Window, X11Error}; +use super::{PresentError, Window, X11Error}; use drm_fourcc::DrmFourcc; use nix::fcntl; -use x11rb::connection::Connection; -use x11rb::protocol::dri3::ConnectionExt as _; -use x11rb::protocol::present::{self, ConnectionExt}; -use x11rb::protocol::xproto::PixmapWrapper; -use x11rb::rust_connection::{ConnectionError, ReplyOrIdError}; -use x11rb::utils::RawFdContainer; +use x11rb::{ + connection::Connection, + protocol::{ + dri3::ConnectionExt as _, + present::{self, ConnectionExt}, + xproto::PixmapWrapper, + }, + utils::RawFdContainer, +}; -use crate::backend::allocator::dmabuf::Dmabuf; -use crate::backend::allocator::Buffer; +use crate::backend::allocator::{dmabuf::Dmabuf, Buffer}; // Shm can be easily supported in the future using, xcb_shm_create_pixmap. -#[derive(Debug, thiserror::Error)] -pub enum CreatePixmapError { - #[error("An x11 protocol error occured")] - Protocol(X11Error), - - #[error("The Dmabuf had too many planes")] - TooManyPlanes, - - #[error("Duplicating the file descriptors for the dmabuf handles failed")] - DupFailed(String), - - #[error("Buffer had incorrect format, expected: {0}")] - IncorrectFormat(DrmFourcc), -} - -impl From for CreatePixmapError { - fn from(e: X11Error) -> Self { - CreatePixmapError::Protocol(e) - } -} - -impl From for CreatePixmapError { - fn from(e: ReplyOrIdError) -> Self { - X11Error::from(e).into() - } -} - -impl From for CreatePixmapError { - fn from(e: ConnectionError) -> Self { - X11Error::from(e).into() - } -} - pub trait PixmapWrapperExt<'c, C> where C: Connection, @@ -88,7 +57,7 @@ where connection: &'c C, window: &Window, dmabuf: &Dmabuf, - ) -> Result, CreatePixmapError>; + ) -> Result, X11Error>; /// Presents the pixmap to the window. /// @@ -108,9 +77,9 @@ where connection: &'c C, window: &Window, dmabuf: &Dmabuf, - ) -> Result, CreatePixmapError> { + ) -> Result, X11Error> { if dmabuf.format().code != window.format() { - return Err(CreatePixmapError::IncorrectFormat(window.format())); + return Err(PresentError::IncorrectFormat(window.format()).into()); } let mut fds = Vec::new(); @@ -121,7 +90,7 @@ where handle, fcntl::FcntlArg::F_DUPFD_CLOEXEC(3), // Set to 3 so the fd cannot become stdin, stdout or stderr ) - .map_err(|e| CreatePixmapError::DupFailed(e.to_string()))?; + .map_err(|e| PresentError::DupFailed(e.to_string()))?; fds.push(RawFdContainer::new(fd)) } @@ -129,7 +98,7 @@ where // We need dri3 >= 1.2 in order to use the enhanced dri3_pixmap_from_buffers function. let xid = if window.0.extensions.dri3 >= Some((1, 2)) { if dmabuf.num_planes() > 4 { - return Err(CreatePixmapError::TooManyPlanes); + return Err(PresentError::TooManyPlanes.into()); } let xid = connection.generate_id()?; @@ -165,7 +134,7 @@ where } else { // Old codepath can only create a pixmap using one plane from a dmabuf. if dmabuf.num_planes() != 1 { - return Err(CreatePixmapError::TooManyPlanes); + return Err(PresentError::TooManyPlanes.into()); } let xid = connection.generate_id()?; diff --git a/src/backend/x11/error.rs b/src/backend/x11/error.rs index 90c4d32..53d3826 100644 --- a/src/backend/x11/error.rs +++ b/src/backend/x11/error.rs @@ -6,24 +6,26 @@ use x11rb::rust_connection::{ConnectError, ConnectionError, ReplyError, ReplyOrI use crate::backend::{allocator::gbm::GbmConvertError, drm::CreateDrmNodeError}; +use super::PresentError; + /// An error emitted by the X11 backend during setup. #[derive(Debug, thiserror::Error)] pub enum X11Error { /// Connecting to the X server failed. #[error("Connecting to the X server failed")] - ConnectionFailed(ConnectError), + ConnectionFailed(#[from] ConnectError), /// A required X11 extension was not present or has the right version. #[error("{0}")] - MissingExtension(MissingExtensionError), + MissingExtension(#[from] MissingExtensionError), /// Some protocol error occurred during setup. #[error("Some protocol error occurred during setup")] - Protocol(ReplyOrIdError), + Protocol(#[from] ReplyOrIdError), /// Creating the window failed. #[error("Creating the window failed")] - CreateWindow(CreateWindowError), + CreateWindow(#[from] CreateWindowError), /// An X11 surface already exists for this window. #[error("An X11 surface already exists for this window")] @@ -42,13 +44,11 @@ pub enum X11Error { /// Failed to allocate buffers needed to present to the window. #[error("Failed to allocate buffers needed to present to the window")] - Allocation(AllocateBuffersError), -} + Allocation(#[from] AllocateBuffersError), -impl From for X11Error { - fn from(err: ConnectError) -> Self { - Self::ConnectionFailed(err) - } + /// Error while presenting to a window. + #[error(transparent)] + Present(#[from] PresentError), } impl From for X11Error { @@ -63,12 +63,6 @@ impl From for X11Error { } } -impl From for X11Error { - fn from(err: ReplyOrIdError) -> Self { - Self::Protocol(err) - } -} - /// An error that occurs when a required X11 extension is not present. #[derive(Debug, thiserror::Error)] pub enum MissingExtensionError { @@ -99,12 +93,6 @@ pub enum MissingExtensionError { }, } -impl From for X11Error { - fn from(err: MissingExtensionError) -> Self { - Self::MissingExtension(err) - } -} - /// An error which may occur when creating an X11 window. #[derive(Debug, thiserror::Error)] pub enum CreateWindowError { @@ -117,12 +105,6 @@ pub enum CreateWindowError { NoVisual, } -impl From for X11Error { - fn from(err: CreateWindowError) -> Self { - Self::CreateWindow(err) - } -} - /// An error which may occur when allocating buffers for presentation to the window. #[derive(Debug, thiserror::Error)] pub enum AllocateBuffersError { @@ -165,9 +147,3 @@ impl From for AllocateBuffersError { } } } - -impl From for X11Error { - fn from(err: AllocateBuffersError) -> Self { - Self::Allocation(err) - } -} diff --git a/src/backend/x11/mod.rs b/src/backend/x11/mod.rs index c71d305..f165d29 100644 --- a/src/backend/x11/mod.rs +++ b/src/backend/x11/mod.rs @@ -79,15 +79,12 @@ mod error; #[macro_use] mod extension; mod input; +mod surface; mod window_inner; -use self::{buffer::PixmapWrapperExt, window_inner::WindowInner}; use crate::{ backend::{ - allocator::{ - dmabuf::{AsDmabuf, Dmabuf}, - Slot, Swapchain, - }, + allocator::Swapchain, drm::{node::path_to_type, CreateDrmNodeError, DrmNode, NodeType}, egl::{native::X11DefaultDisplay, EGLDevice, EGLDisplay, Error as EGLError}, input::{Axis, ButtonState, InputEvent, KeyState}, @@ -96,7 +93,6 @@ use crate::{ }; use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory}; use drm_fourcc::{DrmFourcc, DrmModifier}; -use gbm::BufferObject; use nix::{ fcntl::{self, OFlag}, sys::stat::Mode, @@ -104,12 +100,11 @@ use nix::{ use slog::{error, info, o, Logger}; use std::{ collections::HashMap, - io, mem, + io, os::unix::prelude::AsRawFd, sync::{ atomic::{AtomicU32, Ordering}, - mpsc::{self, Receiver}, - Arc, Mutex, MutexGuard, Weak, + mpsc, Arc, Mutex, Weak, }, }; use x11rb::{ @@ -118,18 +113,17 @@ use x11rb::{ protocol::{ self as x11, dri3::ConnectionExt as _, - xproto::{ - ColormapAlloc, ConnectionExt, CreateWindowAux, PixmapWrapper, VisualClass, WindowClass, - WindowWrapper, - }, + xproto::{ColormapAlloc, ConnectionExt, CreateWindowAux, VisualClass, WindowClass, WindowWrapper}, ErrorKind, }, rust_connection::{ReplyError, RustConnection}, }; +use self::{extension::Extensions, window_inner::WindowInner}; + pub use self::error::*; -use self::extension::Extensions; pub use self::input::*; +pub use self::surface::*; /// An event emitted by the X11 backend. #[derive(Debug)] @@ -161,16 +155,6 @@ pub struct X11Backend { inner: Arc>, } -atom_manager! { - pub(crate) Atoms: AtomCollectionCookie { - WM_PROTOCOLS, - WM_DELETE_WINDOW, - _NET_WM_NAME, - UTF8_STRING, - _SMITHAY_X11_BACKEND_CLOSE, - } -} - impl X11Backend { /// Initializes the X11 backend by connecting to the X server. pub fn new(logger: L) -> Result @@ -277,19 +261,6 @@ impl X11Backend { } } -/// An X11 surface which uses GBM to allocate and present buffers. -#[derive(Debug)] -pub struct X11Surface { - connection: Weak, - window: Weak, - resize: Receiver>, - swapchain: Swapchain>>, BufferObject<()>>, - format: DrmFourcc, - width: u16, - height: u16, - buffer: Option>>, -} - #[derive(Debug, thiserror::Error)] enum EGLInitError { #[error(transparent)] @@ -298,81 +269,6 @@ enum EGLInitError { IO(#[from] io::Error), } -fn egl_init(_: &X11Inner) -> Result { - let display = EGLDisplay::new(&X11DefaultDisplay, None)?; - let device = EGLDevice::device_for_display(&display)?; - let path = path_to_type(device.drm_device_path()?, NodeType::Render)?; - fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) - .map_err(Into::::into) - .and_then(|fd| { - DrmNode::from_fd(fd).map_err(|err| match err { - CreateDrmNodeError::Io(err) => err, - _ => unreachable!(), - }) - }) - .map_err(EGLInitError::IO) -} - -fn dri3_init(x11: &X11Inner) -> Result { - let connection = &x11.connection; - - // Determine which drm-device the Display is using. - let screen = &connection.setup().roots[x11.screen_number]; - // provider being NONE tells the X server to use the RandR provider. - let dri3 = match connection.dri3_open(screen.root, x11rb::NONE)?.reply() { - Ok(reply) => reply, - Err(err) => { - return Err(if let ReplyError::X11Error(ref protocol_error) = err { - match protocol_error.error_kind { - // Implementation is risen when the renderer is not capable of X server is not capable - // of rendering at all. - ErrorKind::Implementation => X11Error::CannotDirectRender, - // Match may occur when the node cannot be authenticated for the client. - ErrorKind::Match => X11Error::CannotDirectRender, - _ => err.into(), - } - } else { - err.into() - }); - } - }; - - // Take ownership of the container's inner value so we do not need to duplicate the fd. - // This is fine because the X server will always open a new file descriptor. - let drm_device_fd = dri3.device_fd.into_raw_fd(); - - let fd_flags = - fcntl::fcntl(drm_device_fd.as_raw_fd(), fcntl::F_GETFD).map_err(AllocateBuffersError::from)?; - - // Enable the close-on-exec flag. - fcntl::fcntl( - drm_device_fd, - fcntl::F_SETFD(fcntl::FdFlag::from_bits_truncate(fd_flags) | fcntl::FdFlag::FD_CLOEXEC), - ) - .map_err(AllocateBuffersError::from)?; - let drm_node = DrmNode::from_fd(drm_device_fd).map_err(Into::::into)?; - - if drm_node.ty() != NodeType::Render && drm_node.has_render() { - // Try to get the render node. - if let Some(path) = drm_node.dev_path_with_type(NodeType::Render) { - return Ok(fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) - .map_err(Into::::into) - .map_err(CreateDrmNodeError::Io) - .and_then(DrmNode::from_fd) - .unwrap_or_else(|err| { - slog::warn!(&x11.log, "Could not create render node from existing DRM node ({}), falling back to primary node", err); - drm_node - })); - } - } - - slog::warn!( - &x11.log, - "DRM Device does not have a render node, falling back to primary node" - ); - Ok(drm_node) -} - /// A handle to the X11 backend. /// /// This is the primary object used to interface with the backend. @@ -482,115 +378,6 @@ impl X11Handle { } } -impl X11Surface { - /// Returns the window the surface presents to. - /// - /// This will return [`None`] if the window has been destroyed. - pub fn window(&self) -> Option + '_> { - let window = self.window.upgrade().map(Window).map(WindowTemporary); - - struct WindowTemporary(Window); - - impl AsRef for WindowTemporary { - fn as_ref(&self) -> &Window { - &self.0 - } - } - - window - } - - /// Returns a handle to the GBM device used to allocate buffers. - pub fn device(&self) -> MutexGuard<'_, gbm::Device> { - self.swapchain.allocator.lock().unwrap() - } - - /// Returns the format of the buffers the surface accepts. - pub fn format(&self) -> DrmFourcc { - self.format - } - - /// Returns the next buffer that will be presented to the Window and its age. - /// - /// You may bind this buffer to a renderer to render. - /// This function will return the same buffer until [`submit`](Self::submit) is called - /// or [`reset_buffers`](Self::reset_buffer) is used to reset the buffers. - pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> { - if let Some(new_size) = self.resize.try_iter().last() { - self.resize(new_size)?; - } - - if self.buffer.is_none() { - self.buffer = Some( - self.swapchain - .acquire() - .map_err(Into::::into)? - .ok_or(AllocateBuffersError::NoFreeSlots)?, - ); - } - - let slot = self.buffer.as_ref().unwrap(); - let age = slot.age(); - match slot.userdata().get::() { - Some(dmabuf) => Ok((dmabuf.clone(), age)), - None => { - let dmabuf = slot.export().map_err(Into::::into)?; - slot.userdata().insert_if_missing(|| dmabuf.clone()); - Ok((dmabuf, age)) - } - } - } - - /// Consume and submit the buffer to the window. - pub fn submit(&mut self) -> Result<(), AllocateBuffersError> { - if let Some(connection) = self.connection.upgrade() { - // Get a new buffer - let mut next = self - .swapchain - .acquire() - .map_err(Into::::into)? - .ok_or(AllocateBuffersError::NoFreeSlots)?; - - // Swap the buffers - if let Some(current) = self.buffer.as_mut() { - mem::swap(&mut next, current); - } - - let window = self.window().ok_or(AllocateBuffersError::WindowDestroyed)?; - - if let Ok(pixmap) = PixmapWrapper::with_dmabuf( - &*connection, - window.as_ref(), - next.userdata().get::().unwrap(), - ) { - // Now present the current buffer - let _ = pixmap.present(&*connection, window.as_ref()); - } - self.swapchain.submitted(next); - - // Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection. - let _ = connection.flush(); - } - Ok(()) - } - - /// Resets the internal buffers, e.g. to reset age values - pub fn reset_buffers(&mut self) { - self.swapchain.reset_buffers(); - self.buffer = None; - } - - fn resize(&mut self, size: Size) -> Result<(), AllocateBuffersError> { - self.swapchain.resize(size.w as u32, size.h as u32); - self.buffer = None; - - self.width = size.w; - self.height = size.h; - - Ok(()) - } -} - /// Builder used to construct a window. #[derive(Debug)] pub struct WindowBuilder<'a> { @@ -755,6 +542,16 @@ impl EventSource for X11Backend { } } +atom_manager! { + pub(crate) Atoms: AtomCollectionCookie { + WM_PROTOCOLS, + WM_DELETE_WINDOW, + _NET_WM_NAME, + UTF8_STRING, + _SMITHAY_X11_BACKEND_CLOSE, + } +} + #[derive(Debug)] pub(crate) struct X11Inner { log: Logger, @@ -1032,3 +829,78 @@ impl X11Inner { } } } + +fn egl_init(_: &X11Inner) -> Result { + let display = EGLDisplay::new(&X11DefaultDisplay, None)?; + let device = EGLDevice::device_for_display(&display)?; + let path = path_to_type(device.drm_device_path()?, NodeType::Render)?; + fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) + .map_err(Into::::into) + .and_then(|fd| { + DrmNode::from_fd(fd).map_err(|err| match err { + CreateDrmNodeError::Io(err) => err, + _ => unreachable!(), + }) + }) + .map_err(EGLInitError::IO) +} + +fn dri3_init(x11: &X11Inner) -> Result { + let connection = &x11.connection; + + // Determine which drm-device the Display is using. + let screen = &connection.setup().roots[x11.screen_number]; + // provider being NONE tells the X server to use the RandR provider. + let dri3 = match connection.dri3_open(screen.root, x11rb::NONE)?.reply() { + Ok(reply) => reply, + Err(err) => { + return Err(if let ReplyError::X11Error(ref protocol_error) = err { + match protocol_error.error_kind { + // Implementation is risen when the renderer is not capable of X server is not capable + // of rendering at all. + ErrorKind::Implementation => X11Error::CannotDirectRender, + // Match may occur when the node cannot be authenticated for the client. + ErrorKind::Match => X11Error::CannotDirectRender, + _ => err.into(), + } + } else { + err.into() + }); + } + }; + + // Take ownership of the container's inner value so we do not need to duplicate the fd. + // This is fine because the X server will always open a new file descriptor. + let drm_device_fd = dri3.device_fd.into_raw_fd(); + + let fd_flags = + fcntl::fcntl(drm_device_fd.as_raw_fd(), fcntl::F_GETFD).map_err(AllocateBuffersError::from)?; + + // Enable the close-on-exec flag. + fcntl::fcntl( + drm_device_fd, + fcntl::F_SETFD(fcntl::FdFlag::from_bits_truncate(fd_flags) | fcntl::FdFlag::FD_CLOEXEC), + ) + .map_err(AllocateBuffersError::from)?; + let drm_node = DrmNode::from_fd(drm_device_fd).map_err(Into::::into)?; + + if drm_node.ty() != NodeType::Render && drm_node.has_render() { + // Try to get the render node. + if let Some(path) = drm_node.dev_path_with_type(NodeType::Render) { + return Ok(fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) + .map_err(Into::::into) + .map_err(CreateDrmNodeError::Io) + .and_then(DrmNode::from_fd) + .unwrap_or_else(|err| { + slog::warn!(&x11.log, "Could not create render node from existing DRM node ({}), falling back to primary node", err); + drm_node + })); + } + } + + slog::warn!( + &x11.log, + "DRM Device does not have a render node, falling back to primary node" + ); + Ok(drm_node) +} diff --git a/src/backend/x11/surface.rs b/src/backend/x11/surface.rs new file mode 100644 index 0000000..7c79d33 --- /dev/null +++ b/src/backend/x11/surface.rs @@ -0,0 +1,157 @@ +use std::{ + mem, + sync::{mpsc::Receiver, Arc, Mutex, MutexGuard, Weak}, +}; + +use drm_fourcc::DrmFourcc; +use gbm::BufferObject; +use x11rb::{connection::Connection, protocol::xproto::PixmapWrapper, rust_connection::RustConnection}; + +use crate::{ + backend::{ + allocator::{ + dmabuf::{AsDmabuf, Dmabuf}, + Slot, Swapchain, + }, + drm::DrmNode, + x11::{buffer::PixmapWrapperExt, window_inner::WindowInner, AllocateBuffersError, Window}, + }, + utils::{Logical, Size}, +}; + +use super::X11Error; + +/// An error that may occur when presenting. +#[derive(Debug, thiserror::Error)] +pub enum PresentError { + /// The dmabuf being presented has too many planes. + #[error("The Dmabuf had too many planes")] + TooManyPlanes, + + /// Duplicating the dmabuf handles failed. + #[error("Duplicating the file descriptors for the dmabuf handles failed")] + DupFailed(String), + + /// The format dmabuf presented does not match the format of the window. + #[error("Buffer had incorrect format, expected: {0}")] + IncorrectFormat(DrmFourcc), +} + +/// An X11 surface which uses GBM to allocate and present buffers. +#[derive(Debug)] +pub struct X11Surface { + pub(crate) connection: Weak, + pub(crate) window: Weak, + pub(crate) resize: Receiver>, + pub(crate) swapchain: Swapchain>>, BufferObject<()>>, + pub(crate) format: DrmFourcc, + pub(crate) width: u16, + pub(crate) height: u16, + pub(crate) buffer: Option>>, +} + +impl X11Surface { + /// Returns the window the surface presents to. + /// + /// This will return [`None`] if the window has been destroyed. + pub fn window(&self) -> Option + '_> { + let window = self.window.upgrade().map(Window).map(WindowTemporary); + + struct WindowTemporary(Window); + + impl AsRef for WindowTemporary { + fn as_ref(&self) -> &Window { + &self.0 + } + } + + window + } + + /// Returns a handle to the GBM device used to allocate buffers. + pub fn device(&self) -> MutexGuard<'_, gbm::Device> { + self.swapchain.allocator.lock().unwrap() + } + + /// Returns the format of the buffers the surface accepts. + pub fn format(&self) -> DrmFourcc { + self.format + } + + /// Returns the next buffer that will be presented to the Window and its age. + /// + /// You may bind this buffer to a renderer to render. + /// This function will return the same buffer until [`submit`](Self::submit) is called + /// or [`reset_buffers`](Self::reset_buffer) is used to reset the buffers. + pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> { + if let Some(new_size) = self.resize.try_iter().last() { + self.resize(new_size); + } + + if self.buffer.is_none() { + self.buffer = Some( + self.swapchain + .acquire() + .map_err(Into::::into)? + .ok_or(AllocateBuffersError::NoFreeSlots)?, + ); + } + + let slot = self.buffer.as_ref().unwrap(); + let age = slot.age(); + match slot.userdata().get::() { + Some(dmabuf) => Ok((dmabuf.clone(), age)), + None => { + let dmabuf = slot.export().map_err(Into::::into)?; + slot.userdata().insert_if_missing(|| dmabuf.clone()); + Ok((dmabuf, age)) + } + } + } + + /// Consume and submit the buffer to the window. + pub fn submit(&mut self) -> Result<(), X11Error> { + if let Some(connection) = self.connection.upgrade() { + // Get a new buffer + let mut next = self + .swapchain + .acquire() + .map_err(Into::::into)? + .ok_or(AllocateBuffersError::NoFreeSlots)?; + + // Swap the buffers + if let Some(current) = self.buffer.as_mut() { + mem::swap(&mut next, current); + } + + let window = self.window().ok_or(AllocateBuffersError::WindowDestroyed)?; + let pixmap = PixmapWrapper::with_dmabuf( + &*connection, + window.as_ref(), + next.userdata().get::().unwrap(), + )?; + + // Now present the current buffer + let _ = pixmap.present(&*connection, window.as_ref())?; + self.swapchain.submitted(next); + + // Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection. + let _ = connection.flush(); + } + Ok(()) + } + + /// Resets the internal buffers, e.g. to reset age values + pub fn reset_buffers(&mut self) { + self.swapchain.reset_buffers(); + self.buffer = None; + } + + fn resize(&mut self, size: Size) { + self.swapchain.resize(size.w as u32, size.h as u32); + self.buffer = None; + + self.width = size.w; + self.height = size.h; + } +}