From e4891b0c9e784ba0674ef973144166a6411c7ce1 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Wed, 3 Nov 2021 01:15:21 -0500 Subject: [PATCH] egl: Introduce EGLDevice More specifically, this introduces a way to query the available EGL devices on a system, `EGLDevices::enumerate`. Also this introduces a way to get the `EGLDevice` used by an `EGLDisplay`, `EGLDevice::device_for_display` Co-authored-by: Drakulix --- Cargo.toml | 2 +- anvil/src/x11.rs | 19 +++- build.rs | 7 ++ src/backend/egl/device.rs | 207 ++++++++++++++++++++++++++++++++++++++ src/backend/egl/error.rs | 9 +- src/backend/egl/ffi.rs | 2 + src/backend/egl/mod.rs | 2 + src/backend/egl/native.rs | 34 ++++++- src/backend/x11/error.rs | 4 + src/backend/x11/mod.rs | 171 +++++++++++++++---------------- 10 files changed, 357 insertions(+), 100 deletions(-) create mode 100644 src/backend/egl/device.rs diff --git a/Cargo.toml b/Cargo.toml index 1ac994b..b2c1ef3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ pkg-config = { version = "0.3.17", optional = true } [features] default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"] backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"] -backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm"] +backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"] backend_drm = ["drm", "drm-ffi"] backend_gbm = ["gbm"] backend_egl = ["gl_generator", "libloading"] diff --git a/anvil/src/x11.rs b/anvil/src/x11.rs index d06ae83..e3882f6 100644 --- a/anvil/src/x11.rs +++ b/anvil/src/x11.rs @@ -12,6 +12,7 @@ use smithay::{ }, reexports::{ calloop::EventLoop, + gbm, wayland_server::{protocol::wl_output, Display}, }, wayland::{ @@ -53,9 +54,17 @@ pub fn run_x11(log: Logger) { let mut event_loop = EventLoop::try_new().unwrap(); let display = Rc::new(RefCell::new(Display::new())); - let (backend, surface) = - X11Backend::with_title("Anvil", log.clone()).expect("Failed to initialize X11 backend"); - let window = backend.window(); + let mut backend = X11Backend::with_title("Anvil", log.clone()).expect("Failed to initialize X11 backend"); + // Obtain the DRM node the X server uses for direct rendering. + let drm_node = backend + .drm_node() + .expect("Could not get DRM node used by X server"); + + // Create the gbm device for buffer allocation and the X11 surface which presents to the window. + let device = gbm::Device::new(drm_node).expect("Failed to create gbm device"); + let format = backend.format(); + let surface = + X11Surface::new(&mut backend, device, format).expect("Failed to create X11 surface"); // Initialize EGL using the GBM device setup earlier. let egl = EGLDisplay::new(&surface, log.clone()).expect("Failed to create EGLDisplay"); @@ -83,8 +92,10 @@ pub fn run_x11(log: Logger) { } } + let window = backend.window(); + let size = { - let s = backend.window().size(); + let s = window.size(); (s.w as i32, s.h as i32).into() }; diff --git a/build.rs b/build.rs index 58e9097..5ebcb19 100644 --- a/build.rs +++ b/build.rs @@ -17,6 +17,12 @@ fn gl_generate() { "EGL_EXT_create_context_robustness", "EGL_KHR_create_context_no_error", "EGL_KHR_no_config_context", + "EGL_EXT_device_base", + "EGL_EXT_device_enumeration", + "EGL_EXT_device_query", + "EGL_EXT_device_drm", + "EGL_KHR_stream", + "EGL_KHR_stream_producer_eglsurface", "EGL_EXT_platform_base", "EGL_KHR_platform_x11", "EGL_EXT_platform_x11", @@ -24,6 +30,7 @@ fn gl_generate() { "EGL_EXT_platform_wayland", "EGL_KHR_platform_gbm", "EGL_MESA_platform_gbm", + "EGL_EXT_platform_device", "EGL_WL_bind_wayland_display", "EGL_KHR_image_base", "EGL_EXT_image_dma_buf_import", diff --git a/src/backend/egl/device.rs b/src/backend/egl/device.rs new file mode 100644 index 0000000..bce5926 --- /dev/null +++ b/src/backend/egl/device.rs @@ -0,0 +1,207 @@ +use std::{ffi::CStr, mem::MaybeUninit, os::raw::c_void, path::PathBuf, ptr}; + +use super::{ + ffi::{self, egl::types::EGLDeviceEXT}, + wrap_egl_call, EGLDisplay, EGLError, Error, +}; + +/// safe EGLDevice wrapper +#[derive(Debug)] +pub struct EGLDevice { + pub(super) inner: EGLDeviceEXT, + device_extensions: Vec, +} + +unsafe impl Send for EGLDevice {} + +impl EGLDevice { + /// Returns an iterator which enumerates over the available [`EGLDevices`](EGLDevice) on the system. + /// + /// This function will return an error if the following extensions are not available: + /// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt) + /// - [`EGL_EXT_device_enumeration`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_enumeration.txt) + /// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt) + pub fn enumerate() -> Result, Error> { + // Check the required extensions are present: + let extensions = ffi::make_sure_egl_is_loaded()?; + + if !extensions.iter().any(|s| s == "EGL_EXT_device_base") { + return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"])); + } + + if !extensions.iter().any(|s| s == "EGL_EXT_device_enumeration") { + return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_enumeration"])); + } + + if !extensions.iter().any(|s| s == "EGL_EXT_device_query") { + return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"])); + } + + // Yes, this is marked as `mut` even though the value is never mutated. EGL expects a mutable pointer + // for num_devices and will not modify the value if we are asking for pointers to some EGLDeviceEXT. + let mut device_amount = match wrap_egl_call(|| { + let mut amount: MaybeUninit = MaybeUninit::uninit(); + + // Passing 0 for max devices and a null-pointer for devices is safe because we indicate we only + // want the number of devices. + if unsafe { ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), amount.as_mut_ptr()) } == ffi::egl::FALSE + { + 0 + } else { + // SAFETY: Success + unsafe { amount.assume_init() } + } + }) { + Ok(number) => number, + Err(err) => return Err(Error::QueryDevices(err)), + }; + + let mut devices = Vec::with_capacity(device_amount as usize); + + wrap_egl_call(|| unsafe { + // SAFETY: + // - Vector used as pointer is correct size. + // - Device amount will accommodate all available devices because we have checked the size earlier. + ffi::egl::QueryDevicesEXT(device_amount, devices.as_mut_ptr(), &mut device_amount) + }).map_err(Error::QueryDevices)?; + + // Set the length of the vec so that rust does not think it is still empty. + + // SAFETY: + // 1) the vector is pre-allocated to the same size as the amount of returned devices. + // 2) EGL has initialized every value in the vector. + unsafe { devices.set_len(device_amount as usize) }; + + Ok(devices + .into_iter() + .map(|device| { + // SAFETY: We have queried that the extensions are valid and the device pointer is valid. + let device_extensions = unsafe { device_extensions(device) }?; + Ok(EGLDevice { + inner: device, + device_extensions, + }) + }) + .collect::, EGLError>>() + .map_err(Error::QueryDevices)? + .into_iter()) + } + + /// Returns the [`EGLDevices`](EGLDevice) related to the given `EGLDisplay`. + /// + /// This function will return an error if the following extensions are not available: + /// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt) + /// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt) + pub fn device_for_display(display: &EGLDisplay) -> Result { + // Check the required extensions are present: + let extensions = ffi::make_sure_egl_is_loaded()?; + + if !extensions.iter().any(|s| s == "EGL_EXT_device_base") { + return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"])); + } + + if !extensions.iter().any(|s| s == "EGL_EXT_device_query") { + return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"])); + } + + let mut device: ffi::egl::types::EGLAttrib = 0; + if unsafe { + ffi::egl::QueryDisplayAttribEXT( + display.get_display_handle().handle, + ffi::egl::DEVICE_EXT as i32, + &mut device as *mut _, + ) + } != ffi::egl::TRUE + { + return Err(Error::DisplayNotSupported); + } + + let device = device as EGLDeviceEXT; + + // Per the EGL specification: + // + // > Functions with a return type of EGLDeviceEXT will return this value on failure: EGL_NO_DEVICE_EXT + if device == ffi::egl::NO_DEVICE_EXT { + return Err(Error::DisplayNotSupported); + } + + // SAFETY: We have queried that the extensions are valid and the device pointer is valid. + let device_extensions = unsafe { device_extensions(device) }.map_err(Error::QueryDevices)?; + Ok(EGLDevice { + inner: device, + device_extensions, + }) + } + + /// Returns a list of extensions the device supports. + pub fn extensions(&self) -> Vec { + self.device_extensions.clone() + } + + /// Returns the path to the drm node of this EGLDevice. + /// + /// This function will return an error if the following extensions are not available: + /// - [`EGL_EXT_device_drm`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm.txt) + pub fn drm_device_path(&self) -> Result { + if !self.extensions().contains(&"EGL_EXT_device_drm".to_owned()) { + Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_drm"])) + } else { + let raw_path = wrap_egl_call(|| unsafe { + ffi::egl::QueryDeviceStringEXT( + self.inner, + ffi::egl::DRM_DEVICE_FILE_EXT as ffi::egl::types::EGLint, + ) + }) + .expect("TODO: Add error variant"); + + // FIXME: Ensure EGL_FALSE is not returned. + + // This is safe because of the following: + // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long + // as the EGLDisplay is valid. Since the pointer is only used in this function, the + // lifetime of the pointer will fulfil Rust's CStr requirements on lifetime. + // 2) The string returned by EGL is null terminated. + let device_path = unsafe { CStr::from_ptr(raw_path) } + .to_str() + // EGL ensures the string is valid UTF-8 + .expect("Non-UTF8 device path name"); + + Ok(PathBuf::from(device_path)) + } + } + + /// Returns the pointer to the raw [`EGLDevice`]. + pub fn inner(&self) -> *const c_void { + self.inner + } +} + +/// Returns all device extensions a device supports. +/// +/// # Safety +/// +/// - The `device` must be a valid pointer to an `EGLDeviceEXT`. +/// - The following extensions must be supported by the display which provides the device: +/// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt) +/// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt) +unsafe fn device_extensions(device: EGLDeviceEXT) -> Result, EGLError> { + let raw_extensions = wrap_egl_call(|| { + ffi::egl::QueryDeviceStringEXT(device, ffi::egl::EXTENSIONS as ffi::egl::types::EGLint) + })?; + + // SAFETY: + // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long + // as the EGLDisplay is valid. Safety requirements for the function ensure this. + // 2) The string returned by EGL is null terminated. + let c_extensions = CStr::from_ptr(raw_extensions); + + Ok(c_extensions + .to_str() + // EGL ensures the string is valid UTF-8 + .expect("Non-UTF8 device extension name") + // Each extension is space separated (0x20) in the pointer, so strlen cannot return an improper length. + .split_whitespace() + // Take an owned copy so we do not point to garbage if EGL somehow vanishes. + .map(ToOwned::to_owned) + .collect::>()) +} diff --git a/src/backend/egl/error.rs b/src/backend/egl/error.rs index d389182..9f5a348 100644 --- a/src/backend/egl/error.rs +++ b/src/backend/egl/error.rs @@ -45,6 +45,9 @@ pub enum Error { /// Failed to create `EGLBuffer` from the buffer #[error("Failed to create `EGLBuffer` from the buffer")] EGLImageCreationFailed, + /// Failed to query the available `EGLDevice`s + #[error("Failed to query the available `EGLDevice`s")] + QueryDevices(#[source] EGLError), } /// Raw EGL error @@ -73,6 +76,9 @@ pub enum EGLError { /// The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid. #[error("The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid.")] BadCurrentSurface, + /// An EGLDevice argument is not valid for this display. + #[error("An EGLDevice argument is not valid for this display.")] + BadDevice, /// An EGLDisplay argument does not name a valid EGL display connection. #[error("An EGLDisplay argument does not name a valid EGL display connection.")] BadDisplay, @@ -91,7 +97,6 @@ pub enum EGLError { /// A NativeWindowType argument does not refer to a valid native window. #[error("A NativeWindowType argument does not refer to a valid native window.")] BadNativeWindow, - #[cfg(feature = "backend_drm_eglstream")] /// The EGL operation failed due to temporary unavailability of a requested resource, but the arguments were otherwise valid, and a subsequent attempt may succeed. #[error("The EGL operation failed due to temporary unavailability of a requested resource, but the arguments were otherwise valid, and a subsequent attempt may succeed.")] ResourceBusy, @@ -112,13 +117,13 @@ impl From for EGLError { ffi::egl::BAD_ATTRIBUTE => EGLError::BadAttribute, ffi::egl::BAD_CONTEXT => EGLError::BadContext, ffi::egl::BAD_CURRENT_SURFACE => EGLError::BadCurrentSurface, + ffi::egl::BAD_DEVICE_EXT => EGLError::BadDevice, ffi::egl::BAD_DISPLAY => EGLError::BadDisplay, ffi::egl::BAD_SURFACE => EGLError::BadSurface, ffi::egl::BAD_MATCH => EGLError::BadMatch, ffi::egl::BAD_PARAMETER => EGLError::BadParameter, ffi::egl::BAD_NATIVE_PIXMAP => EGLError::BadNativePixmap, ffi::egl::BAD_NATIVE_WINDOW => EGLError::BadNativeWindow, - #[cfg(feature = "backend_drm_eglstream")] ffi::egl::RESOURCE_BUSY_EXT => EGLError::ResourceBusy, ffi::egl::CONTEXT_LOST => EGLError::ContextLost, x => EGLError::Unknown(x), diff --git a/src/backend/egl/ffi.rs b/src/backend/egl/ffi.rs index 7272438..6fc7c69 100644 --- a/src/backend/egl/ffi.rs +++ b/src/backend/egl/ffi.rs @@ -129,6 +129,8 @@ pub mod egl { include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs")); + pub const RESOURCE_BUSY_EXT: u32 = 0x3353; + type EGLDEBUGPROCKHR = Option< extern "system" fn( _error: egl::types::EGLenum, diff --git a/src/backend/egl/mod.rs b/src/backend/egl/mod.rs index f4e7069..a6a4b0f 100644 --- a/src/backend/egl/mod.rs +++ b/src/backend/egl/mod.rs @@ -31,6 +31,7 @@ use std::fmt; pub mod context; pub use self::context::EGLContext; +mod device; mod error; pub use self::error::*; use crate::backend::SwapBuffersError as GraphicsSwapBuffersError; @@ -47,6 +48,7 @@ use self::{display::EGLDisplayHandle, ffi::egl::types::EGLImage}; pub mod display; pub mod native; pub mod surface; +pub use self::device::EGLDevice; pub use self::display::EGLDisplay; pub use self::surface::EGLSurface; diff --git a/src/backend/egl/native.rs b/src/backend/egl/native.rs index 6c6dad9..c2441c4 100644 --- a/src/backend/egl/native.rs +++ b/src/backend/egl/native.rs @@ -1,6 +1,6 @@ //! Type safe native types for safe context/surface creation -use super::{display::EGLDisplayHandle, ffi, wrap_egl_call, SwapBuffersError}; +use super::{display::EGLDisplayHandle, ffi, wrap_egl_call, EGLDevice, SwapBuffersError}; #[cfg(feature = "backend_winit")] use std::os::raw::c_int; use std::os::raw::c_void; @@ -189,6 +189,38 @@ impl EGLNativeDisplay for X11Surface { } } +/// Shallow type for EGL_PLATFORM_X11_EXT with the default X11 display +#[derive(Debug)] +pub struct X11DefaultDisplay; + +impl EGLNativeDisplay for X11DefaultDisplay { + fn supported_platforms(&self) -> Vec> { + vec![egl_platform!( + PLATFORM_X11_EXT, + // We pass DEFAULT_DISPLAY (null pointer) because the driver should open a connection to the X server. + ffi::egl::DEFAULT_DISPLAY, + &["EGL_EXT_platform_x11"] + )] + } +} + +impl EGLNativeDisplay for EGLDevice { + fn supported_platforms(&self) -> Vec> { + // see: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_device.txt + vec![egl_platform!( + PLATFORM_DEVICE_EXT, + self.inner, + &["EGL_EXT_platform_device"] + )] + } + + fn surface_type(&self) -> ffi::EGLint { + // EGLDisplays based on EGLDevices do not support normal windowed surfaces. + // But they may support streams, so lets allow users to create them themselves. + ffi::egl::STREAM_BIT_KHR as ffi::EGLint + } +} + /// Trait for types returning valid surface pointers for initializing egl /// /// ## Unsafety diff --git a/src/backend/x11/error.rs b/src/backend/x11/error.rs index b1c2e42..8b10db1 100644 --- a/src/backend/x11/error.rs +++ b/src/backend/x11/error.rs @@ -24,6 +24,10 @@ pub enum X11Error { #[error("Creating the window failed")] CreateWindow(CreateWindowError), + /// An X11 surface already exists for this backend. + #[error("An X11 surface already exists for this backend")] + SurfaceExists, + /// The X server is not capable of direct rendering. #[error("The X server is not capable of direct rendering")] CannotDirectRender, diff --git a/src/backend/x11/mod.rs b/src/backend/x11/mod.rs index 98af5f0..dce2656 100644 --- a/src/backend/x11/mod.rs +++ b/src/backend/x11/mod.rs @@ -13,17 +13,34 @@ //! //! ```rust,no_run //! # use std::error::Error; -//! # use smithay::backend::x11::X11Backend; +//! # use smithay::backend::x11::{X11Backend, X11Surface}; +//! use smithay::backend::allocator::Fourcc; +//! use smithay::reexports::gbm; +//! //! # struct CompositorState; //! fn init_x11_backend( //! handle: calloop::LoopHandle, //! logger: slog::Logger //! ) -> Result<(), Box> { //! // Create the backend, also yielding a surface that may be used to render to the window. -//! let (backend, surface) = X11Backend::new(logger)?; +//! let mut backend = X11Backend::new(logger)?; //! // You can get a handle to the window the backend has created for later use. //! let window = backend.window(); //! +//! // To render to the window the X11 backend creates, we need to create an X11 surface. +//! +//! // Get the DRM node used by the X server for direct rendering. +//! let drm_node = backend.drm_node()?; +//! // Create the gbm device for allocating buffers +//! let device = gbm::Device::new(drm_node)?; +//! +//! // Finally create the X11 surface, you will use this to obtain buffers that will be presented to the +//! // window. +//! +//! // It is more than likely you will want more robust format detection rather than forcing `Argb8888`, +//! // but that is outside of the scope of the example. +//! let surface = X11Surface::new(&mut backend, device, Fourcc::Argb8888); +//! //! // Insert the backend into the event loop to receive events. //! handle.insert_source(backend, |event, _window, state| { //! // Process events from the X server that apply to the window. @@ -125,8 +142,12 @@ pub struct X11Backend { source: X11Source, screen_number: usize, window: Arc, - resize: Sender>, + /// Channel used to send resize notifications to the surface. + /// + /// This value will be [`None`] if no surface is bound to the window managed by the backend. + resize: Option>>, key_counter: Arc, + window_format: DrmFourcc, } atom_manager! { @@ -143,7 +164,7 @@ impl X11Backend { /// Initializes the X11 backend. /// /// This connects to the X server and configures the window using the default options. - pub fn new(logger: L) -> Result<(X11Backend, X11Surface), X11Error> + pub fn new(logger: L) -> Result where L: Into>, { @@ -154,7 +175,7 @@ impl X11Backend { /// /// This connects to the X server and configures the window using the default size and the /// specified window title. - pub fn with_title(title: &str, logger: L) -> Result<(X11Backend, X11Surface), X11Error> + pub fn with_title(title: &str, logger: L) -> Result where L: Into>, { @@ -165,7 +186,7 @@ impl X11Backend { /// /// This connects to the X server and configures the window using the default window title /// and the specified window size. - pub fn with_size(size: Size, logger: L) -> Result<(X11Backend, X11Surface), X11Error> + pub fn with_size(size: Size, logger: L) -> Result where L: Into>, { @@ -179,7 +200,7 @@ impl X11Backend { size: Size, title: &str, logger: L, - ) -> Result<(X11Backend, X11Surface), X11Error> + ) -> Result where L: Into>, { @@ -247,21 +268,16 @@ impl X11Backend { info!(logger, "Window created"); - let (resize_send, resize_recv) = mpsc::channel(); - - let backend = X11Backend { + Ok(X11Backend { log: logger, source, connection, window, key_counter: Arc::new(AtomicU32::new(0)), screen_number, - resize: resize_send, - }; - - let surface = X11Surface::new(&backend, format, resize_recv)?; - - Ok((backend, surface)) + resize: None, + window_format: format, + }) } /// Returns the default screen number of the X server. @@ -278,6 +294,36 @@ impl X11Backend { pub fn window(&self) -> Window { self.window.clone().into() } + + /// Returns the format of the window. + pub fn format(&self) -> DrmFourcc { + self.window_format + } + + /// Returns the DRM node the X server uses for direct rendering. + /// + /// The DRM node may be used to create a [`gbm::Device`] to allocate buffers. + pub fn drm_node(&self) -> Result { + // Kernel documentation explains why we should prefer the node to be a render node: + // https://kernel.readthedocs.io/en/latest/gpu/drm-uapi.html + // + // > Render nodes solely serve render clients, that is, no modesetting or privileged ioctls + // > can be issued on render nodes. Only non-global rendering commands are allowed. If a + // > driver supports render nodes, it must advertise it via the DRIVER_RENDER DRM driver + // > capability. If not supported, the primary node must be used for render clients together + // > with the legacy drmAuth authentication procedure. + // + // Since giving the X11 backend the ability to do modesetting is a big nono, we try to only + // ever create a gbm device from a render node. + // + // Of course if the DRM device does not support render nodes, no DRIVER_RENDER capability, then + // fall back to the primary node. + + // We cannot fallback on the egl_init method, because there is no way for us to authenticate a primary node. + // dri3 does not work for closed-source drivers, but *may* give us a authenticated fd as a fallback. + // As a result we try to use egl for a cleaner, better supported approach at first and only if that fails use dri3. + todo!() + } } /// An X11 surface which uses GBM to allocate and present buffers. @@ -295,84 +341,18 @@ pub struct X11Surface { } impl X11Surface { - fn new( - backend: &X11Backend, + /// Creates a surface that allocates and presents buffers to the window managed by the backend. + /// + /// This will fail if the backend has already been used to create a surface. + pub fn new( + backend: &mut X11Backend, + device: gbm::Device, format: DrmFourcc, - resize: Receiver>, ) -> Result { - let connection = &backend.connection; - - // Determine which drm-device the Display is using. - let screen = &connection.setup().roots[backend.screen()]; - // 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 mut drm_node = DrmNode::from_fd(drm_device_fd).map_err(Into::::into)?; - - if drm_node.ty() != NodeType::Render { - if drm_node.has_render() { - // Try to get the render node. - if let Some(path) = drm_node.dev_path_with_type(NodeType::Render) { - if let Ok(node) = fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) - .map_err(Into::::into) - .map_err(CreateDrmNodeError::Io) - .and_then(DrmNode::from_fd) - { - drm_node = node; - } else { - slog::warn!(&backend.log, "Could not create render node from existing DRM node, falling back to primary node"); - } - } - } + if backend.resize.is_some() { + return Err(X11Error::SurfaceExists); } - // Kernel documentation explains why we should prefer the node to be a render node: - // https://kernel.readthedocs.io/en/latest/gpu/drm-uapi.html - // - // > Render nodes solely serve render clients, that is, no modesetting or privileged ioctls - // > can be issued on render nodes. Only non-global rendering commands are allowed. If a - // > driver supports render nodes, it must advertise it via the DRIVER_RENDER DRM driver - // > capability. If not supported, the primary node must be used for render clients together - // > with the legacy drmAuth authentication procedure. - // - // Since giving the X11 backend the ability to do modesetting is a big nono, we try to only - // ever create a gbm device from a render node. - // - // Of course if the DRM device does not support render nodes, no DRIVER_RENDER capability, then - // fall back to the primary node. - - // Finally create a GBMDevice to manage the buffers. - let device = gbm::Device::new(drm_node).map_err(Into::::into)?; - let size = backend.window().size(); let current = device .create_buffer_object::<()>(size.w as u32, size.h as u32, format, BufferObjectFlags::empty()) @@ -386,6 +366,10 @@ impl X11Surface { .export() .map_err(Into::::into)?; + let (sender, recv) = mpsc::channel(); + + backend.resize = Some(sender); + Ok(X11Surface { connection: Arc::downgrade(&backend.connection), window: backend.window(), @@ -395,7 +379,7 @@ impl X11Surface { height: size.h, current, next, - resize, + resize: recv, }) } @@ -770,7 +754,10 @@ impl EventSource for X11Backend { } (callback)(X11Event::Resized(configure_notify_size), &mut event_window); - let _ = resize.send(configure_notify_size); + + if let Some(resize_sender) = resize { + let _ = resize_sender.send(configure_notify_size); + } } } }