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 <github@drakulix.de>
This commit is contained in:
i509VCB 2021-11-03 01:15:21 -05:00
parent 8bf7d91f98
commit e4891b0c9e
No known key found for this signature in database
GPG Key ID: EE47F5EFC5EE8CDC
10 changed files with 357 additions and 100 deletions

View File

@ -60,7 +60,7 @@ pkg-config = { version = "0.3.17", optional = true }
[features] [features]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"] 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_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_drm = ["drm", "drm-ffi"]
backend_gbm = ["gbm"] backend_gbm = ["gbm"]
backend_egl = ["gl_generator", "libloading"] backend_egl = ["gl_generator", "libloading"]

View File

@ -12,6 +12,7 @@ use smithay::{
}, },
reexports::{ reexports::{
calloop::EventLoop, calloop::EventLoop,
gbm,
wayland_server::{protocol::wl_output, Display}, wayland_server::{protocol::wl_output, Display},
}, },
wayland::{ wayland::{
@ -53,9 +54,17 @@ pub fn run_x11(log: Logger) {
let mut event_loop = EventLoop::try_new().unwrap(); let mut event_loop = EventLoop::try_new().unwrap();
let display = Rc::new(RefCell::new(Display::new())); let display = Rc::new(RefCell::new(Display::new()));
let (backend, surface) = let mut backend = X11Backend::with_title("Anvil", log.clone()).expect("Failed to initialize X11 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 window = backend.window(); 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. // Initialize EGL using the GBM device setup earlier.
let egl = EGLDisplay::new(&surface, log.clone()).expect("Failed to create EGLDisplay"); 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 size = {
let s = backend.window().size(); let s = window.size();
(s.w as i32, s.h as i32).into() (s.w as i32, s.h as i32).into()
}; };

View File

@ -17,6 +17,12 @@ fn gl_generate() {
"EGL_EXT_create_context_robustness", "EGL_EXT_create_context_robustness",
"EGL_KHR_create_context_no_error", "EGL_KHR_create_context_no_error",
"EGL_KHR_no_config_context", "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_EXT_platform_base",
"EGL_KHR_platform_x11", "EGL_KHR_platform_x11",
"EGL_EXT_platform_x11", "EGL_EXT_platform_x11",
@ -24,6 +30,7 @@ fn gl_generate() {
"EGL_EXT_platform_wayland", "EGL_EXT_platform_wayland",
"EGL_KHR_platform_gbm", "EGL_KHR_platform_gbm",
"EGL_MESA_platform_gbm", "EGL_MESA_platform_gbm",
"EGL_EXT_platform_device",
"EGL_WL_bind_wayland_display", "EGL_WL_bind_wayland_display",
"EGL_KHR_image_base", "EGL_KHR_image_base",
"EGL_EXT_image_dma_buf_import", "EGL_EXT_image_dma_buf_import",

207
src/backend/egl/device.rs Normal file
View File

@ -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<String>,
}
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<impl Iterator<Item = EGLDevice>, 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<ffi::egl::types::EGLint> = 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::<Result<Vec<_>, 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<EGLDevice, 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_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<String> {
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<PathBuf, Error> {
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<Vec<String>, 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::<Vec<_>>())
}

View File

@ -45,6 +45,9 @@ pub enum Error {
/// Failed to create `EGLBuffer` from the buffer /// Failed to create `EGLBuffer` from the buffer
#[error("Failed to create `EGLBuffer` from the buffer")] #[error("Failed to create `EGLBuffer` from the buffer")]
EGLImageCreationFailed, EGLImageCreationFailed,
/// Failed to query the available `EGLDevice`s
#[error("Failed to query the available `EGLDevice`s")]
QueryDevices(#[source] EGLError),
} }
/// Raw EGL error /// 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. /// 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.")] #[error("The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid.")]
BadCurrentSurface, 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. /// An EGLDisplay argument does not name a valid EGL display connection.
#[error("An EGLDisplay argument does not name a valid EGL display connection.")] #[error("An EGLDisplay argument does not name a valid EGL display connection.")]
BadDisplay, BadDisplay,
@ -91,7 +97,6 @@ pub enum EGLError {
/// A NativeWindowType argument does not refer to a valid native window. /// A NativeWindowType argument does not refer to a valid native window.
#[error("A NativeWindowType argument does not refer to a valid native window.")] #[error("A NativeWindowType argument does not refer to a valid native window.")]
BadNativeWindow, 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. /// 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.")] #[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, ResourceBusy,
@ -112,13 +117,13 @@ impl From<u32> for EGLError {
ffi::egl::BAD_ATTRIBUTE => EGLError::BadAttribute, ffi::egl::BAD_ATTRIBUTE => EGLError::BadAttribute,
ffi::egl::BAD_CONTEXT => EGLError::BadContext, ffi::egl::BAD_CONTEXT => EGLError::BadContext,
ffi::egl::BAD_CURRENT_SURFACE => EGLError::BadCurrentSurface, ffi::egl::BAD_CURRENT_SURFACE => EGLError::BadCurrentSurface,
ffi::egl::BAD_DEVICE_EXT => EGLError::BadDevice,
ffi::egl::BAD_DISPLAY => EGLError::BadDisplay, ffi::egl::BAD_DISPLAY => EGLError::BadDisplay,
ffi::egl::BAD_SURFACE => EGLError::BadSurface, ffi::egl::BAD_SURFACE => EGLError::BadSurface,
ffi::egl::BAD_MATCH => EGLError::BadMatch, ffi::egl::BAD_MATCH => EGLError::BadMatch,
ffi::egl::BAD_PARAMETER => EGLError::BadParameter, ffi::egl::BAD_PARAMETER => EGLError::BadParameter,
ffi::egl::BAD_NATIVE_PIXMAP => EGLError::BadNativePixmap, ffi::egl::BAD_NATIVE_PIXMAP => EGLError::BadNativePixmap,
ffi::egl::BAD_NATIVE_WINDOW => EGLError::BadNativeWindow, ffi::egl::BAD_NATIVE_WINDOW => EGLError::BadNativeWindow,
#[cfg(feature = "backend_drm_eglstream")]
ffi::egl::RESOURCE_BUSY_EXT => EGLError::ResourceBusy, ffi::egl::RESOURCE_BUSY_EXT => EGLError::ResourceBusy,
ffi::egl::CONTEXT_LOST => EGLError::ContextLost, ffi::egl::CONTEXT_LOST => EGLError::ContextLost,
x => EGLError::Unknown(x), x => EGLError::Unknown(x),

View File

@ -129,6 +129,8 @@ pub mod egl {
include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs")); include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs"));
pub const RESOURCE_BUSY_EXT: u32 = 0x3353;
type EGLDEBUGPROCKHR = Option< type EGLDEBUGPROCKHR = Option<
extern "system" fn( extern "system" fn(
_error: egl::types::EGLenum, _error: egl::types::EGLenum,

View File

@ -31,6 +31,7 @@ use std::fmt;
pub mod context; pub mod context;
pub use self::context::EGLContext; pub use self::context::EGLContext;
mod device;
mod error; mod error;
pub use self::error::*; pub use self::error::*;
use crate::backend::SwapBuffersError as GraphicsSwapBuffersError; use crate::backend::SwapBuffersError as GraphicsSwapBuffersError;
@ -47,6 +48,7 @@ use self::{display::EGLDisplayHandle, ffi::egl::types::EGLImage};
pub mod display; pub mod display;
pub mod native; pub mod native;
pub mod surface; pub mod surface;
pub use self::device::EGLDevice;
pub use self::display::EGLDisplay; pub use self::display::EGLDisplay;
pub use self::surface::EGLSurface; pub use self::surface::EGLSurface;

View File

@ -1,6 +1,6 @@
//! Type safe native types for safe context/surface creation //! 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")] #[cfg(feature = "backend_winit")]
use std::os::raw::c_int; use std::os::raw::c_int;
use std::os::raw::c_void; 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<EGLPlatform<'_>> {
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<EGLPlatform<'_>> {
// 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 /// Trait for types returning valid surface pointers for initializing egl
/// ///
/// ## Unsafety /// ## Unsafety

View File

@ -24,6 +24,10 @@ pub enum X11Error {
#[error("Creating the window failed")] #[error("Creating the window failed")]
CreateWindow(CreateWindowError), 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. /// The X server is not capable of direct rendering.
#[error("The X server is not capable of direct rendering")] #[error("The X server is not capable of direct rendering")]
CannotDirectRender, CannotDirectRender,

View File

@ -13,17 +13,34 @@
//! //!
//! ```rust,no_run //! ```rust,no_run
//! # use std::error::Error; //! # 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; //! # struct CompositorState;
//! fn init_x11_backend( //! fn init_x11_backend(
//! handle: calloop::LoopHandle<CompositorState>, //! handle: calloop::LoopHandle<CompositorState>,
//! logger: slog::Logger //! logger: slog::Logger
//! ) -> Result<(), Box<dyn Error>> { //! ) -> Result<(), Box<dyn Error>> {
//! // Create the backend, also yielding a surface that may be used to render to the window. //! // 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. //! // You can get a handle to the window the backend has created for later use.
//! let window = backend.window(); //! 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. //! // Insert the backend into the event loop to receive events.
//! handle.insert_source(backend, |event, _window, state| { //! handle.insert_source(backend, |event, _window, state| {
//! // Process events from the X server that apply to the window. //! // Process events from the X server that apply to the window.
@ -125,8 +142,12 @@ pub struct X11Backend {
source: X11Source, source: X11Source,
screen_number: usize, screen_number: usize,
window: Arc<WindowInner>, window: Arc<WindowInner>,
resize: Sender<Size<u16, Logical>>, /// 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<Sender<Size<u16, Logical>>>,
key_counter: Arc<AtomicU32>, key_counter: Arc<AtomicU32>,
window_format: DrmFourcc,
} }
atom_manager! { atom_manager! {
@ -143,7 +164,7 @@ impl X11Backend {
/// Initializes the X11 backend. /// Initializes the X11 backend.
/// ///
/// This connects to the X server and configures the window using the default options. /// This connects to the X server and configures the window using the default options.
pub fn new<L>(logger: L) -> Result<(X11Backend, X11Surface), X11Error> pub fn new<L>(logger: L) -> Result<X11Backend, X11Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -154,7 +175,7 @@ impl X11Backend {
/// ///
/// This connects to the X server and configures the window using the default size and the /// This connects to the X server and configures the window using the default size and the
/// specified window title. /// specified window title.
pub fn with_title<L>(title: &str, logger: L) -> Result<(X11Backend, X11Surface), X11Error> pub fn with_title<L>(title: &str, logger: L) -> Result<X11Backend, X11Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -165,7 +186,7 @@ impl X11Backend {
/// ///
/// This connects to the X server and configures the window using the default window title /// This connects to the X server and configures the window using the default window title
/// and the specified window size. /// and the specified window size.
pub fn with_size<L>(size: Size<u16, Logical>, logger: L) -> Result<(X11Backend, X11Surface), X11Error> pub fn with_size<L>(size: Size<u16, Logical>, logger: L) -> Result<X11Backend, X11Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -179,7 +200,7 @@ impl X11Backend {
size: Size<u16, Logical>, size: Size<u16, Logical>,
title: &str, title: &str,
logger: L, logger: L,
) -> Result<(X11Backend, X11Surface), X11Error> ) -> Result<X11Backend, X11Error>
where where
L: Into<Option<slog::Logger>>, L: Into<Option<slog::Logger>>,
{ {
@ -247,21 +268,16 @@ impl X11Backend {
info!(logger, "Window created"); info!(logger, "Window created");
let (resize_send, resize_recv) = mpsc::channel(); Ok(X11Backend {
let backend = X11Backend {
log: logger, log: logger,
source, source,
connection, connection,
window, window,
key_counter: Arc::new(AtomicU32::new(0)), key_counter: Arc::new(AtomicU32::new(0)),
screen_number, screen_number,
resize: resize_send, resize: None,
}; window_format: format,
})
let surface = X11Surface::new(&backend, format, resize_recv)?;
Ok((backend, surface))
} }
/// Returns the default screen number of the X server. /// Returns the default screen number of the X server.
@ -278,6 +294,36 @@ impl X11Backend {
pub fn window(&self) -> Window { pub fn window(&self) -> Window {
self.window.clone().into() 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<DrmNode, X11Error> {
// 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. /// An X11 surface which uses GBM to allocate and present buffers.
@ -295,83 +341,17 @@ pub struct X11Surface {
} }
impl X11Surface { impl X11Surface {
fn new( /// Creates a surface that allocates and presents buffers to the window managed by the backend.
backend: &X11Backend, ///
/// This will fail if the backend has already been used to create a surface.
pub fn new(
backend: &mut X11Backend,
device: gbm::Device<DrmNode>,
format: DrmFourcc, format: DrmFourcc,
resize: Receiver<Size<u16, Logical>>,
) -> Result<X11Surface, X11Error> { ) -> Result<X11Surface, X11Error> {
let connection = &backend.connection; if backend.resize.is_some() {
return Err(X11Error::SurfaceExists);
// 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::<AllocateBuffersError>::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::<std::io::Error>::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");
}
}
}
}
// 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::<AllocateBuffersError>::into)?;
let size = backend.window().size(); let size = backend.window().size();
let current = device let current = device
@ -386,6 +366,10 @@ impl X11Surface {
.export() .export()
.map_err(Into::<AllocateBuffersError>::into)?; .map_err(Into::<AllocateBuffersError>::into)?;
let (sender, recv) = mpsc::channel();
backend.resize = Some(sender);
Ok(X11Surface { Ok(X11Surface {
connection: Arc::downgrade(&backend.connection), connection: Arc::downgrade(&backend.connection),
window: backend.window(), window: backend.window(),
@ -395,7 +379,7 @@ impl X11Surface {
height: size.h, height: size.h,
current, current,
next, next,
resize, resize: recv,
}) })
} }
@ -770,7 +754,10 @@ impl EventSource for X11Backend {
} }
(callback)(X11Event::Resized(configure_notify_size), &mut event_window); (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);
}
} }
} }
} }