Merge pull request #420 from i509VCB/x11/egl-drm
Rework X11 backend to use EGL for finding gbm device and associated commits
This commit is contained in:
commit
6b3a00fc06
|
@ -39,7 +39,7 @@ rand = "0.8.4"
|
|||
slog = "2"
|
||||
slog-stdlog = { version = "4", optional = true }
|
||||
tempfile = { version = "3.0", optional = true }
|
||||
thiserror = "1.0.2"
|
||||
thiserror = "1.0.7"
|
||||
udev = { version = "0.6", optional = true }
|
||||
wayland-commons = { version = "0.29.0", optional = true }
|
||||
wayland-egl = { version = "0.29.0", optional = true }
|
||||
|
@ -61,7 +61,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"]
|
||||
|
|
|
@ -12,6 +12,7 @@ use smithay::{
|
|||
},
|
||||
reexports::{
|
||||
calloop::EventLoop,
|
||||
gbm,
|
||||
wayland_server::{protocol::wl_output, Display},
|
||||
},
|
||||
wayland::{
|
||||
|
@ -53,13 +54,27 @@ 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");
|
||||
// Initialize EGL using the GBM device setup earlier.
|
||||
let egl = EGLDisplay::new(&surface, log.clone()).expect("Failed to create EGLDisplay");
|
||||
let egl = EGLDisplay::new(&device, log.clone()).expect("Failed to create EGLDisplay");
|
||||
let context = EGLContext::new(&egl, log.clone()).expect("Failed to create EGLContext");
|
||||
let surface = X11Surface::new(
|
||||
&mut backend,
|
||||
device,
|
||||
context
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.map(|format| format.modifier),
|
||||
)
|
||||
.expect("Failed to create X11 surface");
|
||||
|
||||
let renderer =
|
||||
unsafe { Gles2Renderer::new(context, log.clone()) }.expect("Failed to initialize renderer");
|
||||
let renderer = Rc::new(RefCell::new(renderer));
|
||||
|
@ -83,8 +98,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()
|
||||
};
|
||||
|
@ -202,7 +219,7 @@ pub fn run_x11(log: Logger) {
|
|||
#[cfg(feature = "debug")]
|
||||
let fps_texture = &backend_data.fps_texture;
|
||||
|
||||
if let Err(err) = renderer.bind(present.buffer()) {
|
||||
if let Err(err) = renderer.bind(present.buffer().expect("gbm device was destroyed")) {
|
||||
error!(log, "Error while binding buffer: {}", err);
|
||||
}
|
||||
|
||||
|
|
7
build.rs
7
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",
|
||||
|
|
|
@ -71,14 +71,14 @@
|
|||
|
||||
pub(crate) mod device;
|
||||
pub(self) mod error;
|
||||
pub(self) mod node;
|
||||
pub mod node;
|
||||
#[cfg(feature = "backend_session")]
|
||||
pub(self) mod session;
|
||||
pub(self) mod surface;
|
||||
|
||||
pub use device::{DevPath, DrmDevice, DrmEvent};
|
||||
pub use error::Error as DrmError;
|
||||
pub use node::{ConvertErrorKind, ConvertNodeError, CreateDrmNodeError, DrmNode, NodeType};
|
||||
pub use node::{CreateDrmNodeError, DrmNode, NodeType};
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
pub use surface::gbm::{Error as GbmBufferedSurfaceError, GbmBufferedSurface};
|
||||
pub use surface::DrmSurface;
|
||||
|
|
|
@ -37,9 +37,3 @@ pub const RENDER_NAME: &str = "renderD";
|
|||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub const RENDER_NAME: &str = "drmR";
|
||||
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
pub const DIR_NAME: &str = "dev";
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
pub const DIR_NAME: &str = "dev/dri";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Module for abstractions on drm device nodes
|
||||
|
||||
pub(crate) mod constants;
|
||||
|
||||
use constants::*;
|
||||
|
@ -7,12 +9,11 @@ use std::{
|
|||
fmt::{self, Display, Formatter},
|
||||
fs, io,
|
||||
os::unix::prelude::{AsRawFd, IntoRawFd, RawFd},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use nix::{
|
||||
fcntl::{self, OFlag},
|
||||
sys::stat::{fstat, major, minor, Mode},
|
||||
sys::stat::{fstat, major, minor, stat},
|
||||
unistd::close,
|
||||
};
|
||||
|
||||
|
@ -62,15 +63,6 @@ impl DrmNode {
|
|||
})
|
||||
}
|
||||
|
||||
/// Creates a DRM node of the specified type using the same DRM device as the provided node.
|
||||
/// The provided node will be consumed if the new node is successfully created.
|
||||
///
|
||||
/// This function is useful for obtaining the render node of a DRM device when the provided
|
||||
/// node is a primary node.
|
||||
pub fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
|
||||
from_node_with_type(node, ty)
|
||||
}
|
||||
|
||||
/// Returns the type of the DRM node.
|
||||
pub fn ty(&self) -> NodeType {
|
||||
self.ty
|
||||
|
@ -83,7 +75,12 @@ impl DrmNode {
|
|||
|
||||
/// Returns the path of the open device if possible.
|
||||
pub fn dev_path(&self) -> Option<PathBuf> {
|
||||
fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
|
||||
node_path(self, self.ty).ok()
|
||||
}
|
||||
|
||||
/// Returns the path of the specified node type matching the open device if possible.
|
||||
pub fn dev_path_with_type(&self, ty: NodeType) -> Option<PathBuf> {
|
||||
node_path(self, ty).ok()
|
||||
}
|
||||
|
||||
/// Returns the major device number of the DRM device.
|
||||
|
@ -206,54 +203,14 @@ impl From<io::Error> for CreateDrmNodeError {
|
|||
}
|
||||
}
|
||||
|
||||
/// A DRM node could not be used to get an alternative type of node.
|
||||
///
|
||||
/// If this error is returned, the original DRM node may be retrieved using [`ConvertNodeError::node`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("{kind}")]
|
||||
pub struct ConvertNodeError {
|
||||
node: DrmNode,
|
||||
kind: ConvertErrorKind,
|
||||
}
|
||||
|
||||
impl ConvertNodeError {
|
||||
/// Returns the original DrmNode.
|
||||
pub fn node(self) -> DrmNode {
|
||||
self.node
|
||||
}
|
||||
|
||||
/// Returns the kind of error that occurred when obtaining a different type of DRM node.
|
||||
pub fn kind(&self) -> &ConvertErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that may occur when obtaining a different type of DRM node.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConvertErrorKind {
|
||||
/// The requested node type is not available.
|
||||
#[error("the requested node type, {0}, is not available.")]
|
||||
NodeTypeNotAvailable(NodeType),
|
||||
|
||||
/// An IO error occurred when when obtaining a different type of DRM node.
|
||||
#[error("{0}")]
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl From<io::Error> for ConvertErrorKind {
|
||||
fn from(err: io::Error) -> Self {
|
||||
ConvertErrorKind::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the given device by major:minor pair is a drm device
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_device_drm(major: u64, minor: u64) -> bool {
|
||||
use nix::sys::stat::stat;
|
||||
|
||||
pub fn is_device_drm(major: u64, minor: u64) -> bool {
|
||||
let path = format!("/sys/dev/char/{}:{}/device/drm", major, minor);
|
||||
stat(path.as_str()).is_ok()
|
||||
}
|
||||
|
||||
/// Returns if the given device by major:minor pair is a drm device
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub fn is_device_drm(major: u64, _minor: u64) -> bool {
|
||||
use nix::sys::stat::makedev;
|
||||
|
@ -287,71 +244,44 @@ pub fn is_device_drm(major: u64, _minor: u64) -> bool {
|
|||
|| dev_name.starts_with("dri/renderD")
|
||||
}
|
||||
|
||||
/// Returns if the given device by major:minor pair is a drm device
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
pub fn is_device_drm(major: u64, _minor: u64) -> bool {
|
||||
major == DRM_MAJOR
|
||||
}
|
||||
|
||||
/// Returns the path of a specific type of node from the same DRM device as another path of the same node.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
|
||||
match node_path(&node, ty) {
|
||||
Ok(path) => {
|
||||
let fd = match fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty())
|
||||
.map_err(Into::<io::Error>::into)
|
||||
.map_err(Into::<ConvertErrorKind>::into)
|
||||
{
|
||||
Ok(fd) => fd,
|
||||
Err(kind) => return Err(ConvertNodeError { node, kind }),
|
||||
};
|
||||
pub fn path_to_type<P: AsRef<Path>>(path: P, ty: NodeType) -> io::Result<PathBuf> {
|
||||
let stat = stat(path.as_ref()).map_err(Into::<io::Error>::into)?;
|
||||
let dev = stat.st_rdev;
|
||||
let major = major(dev);
|
||||
let minor = minor(dev);
|
||||
|
||||
let result = DrmNode::from_fd(fd);
|
||||
|
||||
match result {
|
||||
Ok(node) => Ok(node),
|
||||
|
||||
// Some error occurred while opening the new node
|
||||
Err(CreateDrmNodeError::Io(err)) => Err(ConvertNodeError {
|
||||
node,
|
||||
kind: err.into(),
|
||||
}),
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => Err(ConvertNodeError {
|
||||
node,
|
||||
kind: err.into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
|
||||
// TODO: Not implemented yet, so fail conversion
|
||||
Err(ConvertNodeError {
|
||||
node,
|
||||
kind: ConvertErrorKind::NodeTypeNotAvailable(ty),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
|
||||
// TODO: Not implemented yet, so fail conversion
|
||||
Err(ConvertNodeError {
|
||||
node,
|
||||
kind: ConvertErrorKind::NodeTypeNotAvailable(ty),
|
||||
})
|
||||
dev_path(major, minor, ty)
|
||||
}
|
||||
|
||||
/// Returns the path of a specific type of node from the same DRM device as an existing DrmNode.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn node_path(node: &DrmNode, ty: NodeType) -> io::Result<PathBuf> {
|
||||
use std::io::ErrorKind;
|
||||
|
||||
pub fn node_path(node: &DrmNode, ty: NodeType) -> io::Result<PathBuf> {
|
||||
let major = node.major();
|
||||
let minor = node.minor();
|
||||
|
||||
dev_path(major, minor, ty)
|
||||
}
|
||||
|
||||
/// Returns the path of a specific type of node from the DRM device described by major and minor device numbers.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn dev_path(major: u64, minor: u64, ty: NodeType) -> io::Result<PathBuf> {
|
||||
use std::io::ErrorKind;
|
||||
|
||||
if !is_device_drm(major, minor) {
|
||||
return Err(io::Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!("{}:{} is no DRM device", major, minor),
|
||||
));
|
||||
}
|
||||
|
||||
let read = fs::read_dir(format!("/sys/dev/char/{}:{}/device/drm", major, minor))?;
|
||||
|
||||
for entry in read.flatten() {
|
||||
|
@ -361,9 +291,7 @@ fn node_path(node: &DrmNode, ty: NodeType) -> io::Result<PathBuf> {
|
|||
// Only 1 primary, control and render node may exist simultaneously, so the
|
||||
// first occurrence is good enough.
|
||||
if name.starts_with(ty.minor_name_prefix()) {
|
||||
let mut path = entry.path();
|
||||
path.push(DIR_NAME);
|
||||
|
||||
let path = [r"/", "dev", "dri", &name].iter().collect::<PathBuf>();
|
||||
if path.exists() {
|
||||
return Ok(path);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
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<_>>())
|
||||
}
|
|
@ -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<u32> 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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -16,9 +16,6 @@ use winit::{platform::unix::WindowExtUnix, window::Window as WinitWindow};
|
|||
#[cfg(feature = "backend_gbm")]
|
||||
use gbm::{AsRaw, Device as GbmDevice};
|
||||
|
||||
#[cfg(feature = "backend_x11")]
|
||||
use crate::backend::x11::X11Surface;
|
||||
|
||||
/// Create a `EGLPlatform<'a>` for the provided platform.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -168,24 +165,35 @@ impl EGLNativeDisplay for WinitWindow {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_x11")]
|
||||
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![
|
||||
// todo: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_device.txt
|
||||
// see: https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_platform_gbm.txt
|
||||
egl_platform!(
|
||||
PLATFORM_GBM_KHR,
|
||||
self.device().as_raw(),
|
||||
&["EGL_KHR_platform_gbm"]
|
||||
),
|
||||
// see: https://www.khronos.org/registry/EGL/extensions/MESA/EGL_MESA_platform_gbm.txt
|
||||
egl_platform!(
|
||||
PLATFORM_GBM_MESA,
|
||||
self.device().as_raw(),
|
||||
&["EGL_MESA_platform_gbm"]
|
||||
),
|
||||
]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::io;
|
||||
|
||||
use gbm::DeviceDestroyedError;
|
||||
use nix::errno::Errno;
|
||||
use x11rb::rust_connection::{ConnectError, ConnectionError, ReplyError, ReplyOrIdError};
|
||||
|
||||
|
@ -24,6 +25,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,
|
||||
|
@ -116,7 +121,11 @@ impl From<CreateWindowError> for X11Error {
|
|||
pub enum AllocateBuffersError {
|
||||
/// Failed to open the DRM device to allocate buffers.
|
||||
#[error("Failed to open the DRM device to allocate buffers.")]
|
||||
OpenDevice(io::Error),
|
||||
OpenDevice(#[from] io::Error),
|
||||
|
||||
/// The gbm device was destroyed
|
||||
#[error("The gbm device was destroyed.")]
|
||||
DeviceDestroyed(#[from] DeviceDestroyedError),
|
||||
|
||||
/// The device used to allocate buffers is not the correct drm node type.
|
||||
#[error("The device used to allocate buffers is not the correct drm node type.")]
|
||||
|
@ -124,7 +133,7 @@ pub enum AllocateBuffersError {
|
|||
|
||||
/// Exporting a dmabuf failed.
|
||||
#[error("Exporting a dmabuf failed.")]
|
||||
ExportDmabuf(GbmConvertError),
|
||||
ExportDmabuf(#[from] GbmConvertError),
|
||||
}
|
||||
|
||||
impl From<Errno> for AllocateBuffersError {
|
||||
|
@ -133,18 +142,6 @@ impl From<Errno> for AllocateBuffersError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for AllocateBuffersError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::OpenDevice(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GbmConvertError> for AllocateBuffersError {
|
||||
fn from(err: GbmConvertError) -> Self {
|
||||
Self::ExportDmabuf(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CreateDrmNodeError> for AllocateBuffersError {
|
||||
fn from(err: CreateDrmNodeError) -> Self {
|
||||
match err {
|
||||
|
|
|
@ -13,17 +13,36 @@
|
|||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use std::error::Error;
|
||||
//! # use smithay::backend::x11::X11Backend;
|
||||
//! # use smithay::backend::x11::{X11Backend, X11Surface};
|
||||
//! use smithay::backend::egl::{EGLDisplay, EGLContext};
|
||||
//! use smithay::reexports::gbm;
|
||||
//! use std::collections::HashSet;
|
||||
//!
|
||||
//! # struct CompositorState;
|
||||
//! fn init_x11_backend(
|
||||
//! handle: calloop::LoopHandle<CompositorState>,
|
||||
//! logger: slog::Logger
|
||||
//! ) -> Result<(), Box<dyn Error>> {
|
||||
//! // 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.clone())?;
|
||||
//! // 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)?;
|
||||
//! // Initialize EGL to retrieve the support modifier list
|
||||
//! let egl = EGLDisplay::new(&device, logger.clone()).expect("Failed to create EGLDisplay");
|
||||
//! let context = EGLContext::new(&egl, logger).expect("Failed to create EGLContext");
|
||||
//! let modifiers = context.dmabuf_render_formats().iter().map(|format| format.modifier).collect::<HashSet<_>>();
|
||||
//!
|
||||
//! // Finally create the X11 surface, you will use this to obtain buffers that will be presented to the
|
||||
//! // window.
|
||||
//! let surface = X11Surface::new(&mut backend, device, modifiers.into_iter());
|
||||
//!
|
||||
//! // 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.
|
||||
|
@ -58,15 +77,19 @@ use self::{buffer::PixmapWrapperExt, window_inner::WindowInner};
|
|||
use crate::{
|
||||
backend::{
|
||||
allocator::dmabuf::{AsDmabuf, Dmabuf},
|
||||
drm::{DrmNode, NodeType},
|
||||
drm::{node::path_to_type, CreateDrmNodeError, DrmNode, NodeType},
|
||||
egl::{native::X11DefaultDisplay, EGLDevice, EGLDisplay, Error as EGLError},
|
||||
input::{Axis, ButtonState, InputEvent, KeyState},
|
||||
},
|
||||
utils::{x11rb::X11Source, Logical, Size},
|
||||
};
|
||||
use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
|
||||
use drm_fourcc::DrmFourcc;
|
||||
use gbm::BufferObjectFlags;
|
||||
use nix::fcntl;
|
||||
use drm_fourcc::{DrmFourcc, DrmModifier};
|
||||
use gbm::{BufferObject, BufferObjectFlags};
|
||||
use nix::{
|
||||
fcntl::{self, OFlag},
|
||||
sys::stat::Mode,
|
||||
};
|
||||
use slog::{error, info, o, Logger};
|
||||
use std::{
|
||||
io, mem,
|
||||
|
@ -122,8 +145,12 @@ pub struct X11Backend {
|
|||
source: X11Source,
|
||||
screen_number: usize,
|
||||
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>,
|
||||
window_format: DrmFourcc,
|
||||
}
|
||||
|
||||
atom_manager! {
|
||||
|
@ -140,7 +167,7 @@ impl X11Backend {
|
|||
/// Initializes the X11 backend.
|
||||
///
|
||||
/// 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
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
|
@ -151,7 +178,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<L>(title: &str, logger: L) -> Result<(X11Backend, X11Surface), X11Error>
|
||||
pub fn with_title<L>(title: &str, logger: L) -> Result<X11Backend, X11Error>
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
|
@ -162,7 +189,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<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
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
|
@ -176,7 +203,7 @@ impl X11Backend {
|
|||
size: Size<u16, Logical>,
|
||||
title: &str,
|
||||
logger: L,
|
||||
) -> Result<(X11Backend, X11Surface), X11Error>
|
||||
) -> Result<X11Backend, X11Error>
|
||||
where
|
||||
L: Into<Option<slog::Logger>>,
|
||||
{
|
||||
|
@ -244,21 +271,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.
|
||||
|
@ -275,66 +297,16 @@ impl X11Backend {
|
|||
pub fn window(&self) -> Window {
|
||||
self.window.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// An X11 surface which uses GBM to allocate and present buffers.
|
||||
#[derive(Debug)]
|
||||
pub struct X11Surface {
|
||||
connection: Weak<RustConnection>,
|
||||
window: Window,
|
||||
resize: Receiver<Size<u16, Logical>>,
|
||||
device: gbm::Device<DrmNode>,
|
||||
format: DrmFourcc,
|
||||
width: u16,
|
||||
height: u16,
|
||||
current: Dmabuf,
|
||||
next: Dmabuf,
|
||||
}
|
||||
|
||||
impl X11Surface {
|
||||
fn new(
|
||||
backend: &X11Backend,
|
||||
format: DrmFourcc,
|
||||
resize: Receiver<Size<u16, Logical>>,
|
||||
) -> Result<X11Surface, X11Error> {
|
||||
let connection = &backend.connection;
|
||||
let window = backend.window();
|
||||
|
||||
// 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)?;
|
||||
/// 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
|
||||
//
|
||||
|
@ -349,46 +321,165 @@ impl X11Surface {
|
|||
//
|
||||
// Of course if the DRM device does not support render nodes, no DRIVER_RENDER capability, then
|
||||
// fall back to the primary node.
|
||||
let drm_node = DrmNode::from_fd(drm_device_fd).map_err(Into::<AllocateBuffersError>::into)?;
|
||||
let drm_node = if drm_node.ty() != NodeType::Render {
|
||||
if drm_node.has_render() {
|
||||
// Try to get the render node.
|
||||
match DrmNode::from_node_with_type(drm_node, NodeType::Render) {
|
||||
Ok(node) => node,
|
||||
Err(err) => {
|
||||
slog::warn!(&backend.log, "Could not create render node from existing DRM node, falling back to primary node");
|
||||
err.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.
|
||||
egl_init(self).or_else(|err| {
|
||||
slog::warn!(
|
||||
&self.log,
|
||||
"Failed to init X11 surface via egl, falling back to dri3: {}",
|
||||
err
|
||||
);
|
||||
dri3_init(self)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An X11 surface which uses GBM to allocate and present buffers.
|
||||
#[derive(Debug)]
|
||||
pub struct X11Surface {
|
||||
connection: Weak<RustConnection>,
|
||||
window: Window,
|
||||
resize: Receiver<Size<u16, Logical>>,
|
||||
device: gbm::Device<DrmNode>,
|
||||
format: DrmFourcc,
|
||||
width: u16,
|
||||
height: u16,
|
||||
current: BufferObject<Dmabuf>,
|
||||
next: BufferObject<Dmabuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum EGLInitError {
|
||||
#[error(transparent)]
|
||||
EGL(#[from] EGLError),
|
||||
#[error(transparent)]
|
||||
IO(#[from] io::Error),
|
||||
}
|
||||
|
||||
fn egl_init(_backend: &X11Backend) -> Result<DrmNode, EGLInitError> {
|
||||
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::<io::Error>::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(backend: &X11Backend) -> Result<DrmNode, X11Error> {
|
||||
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 {
|
||||
slog::warn!(
|
||||
&backend.log,
|
||||
"DRM Device does not have a render node, falling back to primary node"
|
||||
);
|
||||
drm_node
|
||||
}
|
||||
} else {
|
||||
drm_node
|
||||
};
|
||||
err.into()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Finally create a GBMDevice to manage the buffers.
|
||||
let device = gbm::Device::new(drm_node).map_err(Into::<AllocateBuffersError>::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 size = backend.window().size();
|
||||
let current = device
|
||||
.create_buffer_object::<()>(size.w as u32, size.h as u32, format, BufferObjectFlags::empty())
|
||||
.map_err(Into::<AllocateBuffersError>::into)?
|
||||
.export()
|
||||
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::<AllocateBuffersError>::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::<std::io::Error>::into)
|
||||
.map_err(CreateDrmNodeError::Io)
|
||||
.and_then(DrmNode::from_fd)
|
||||
.unwrap_or_else(|err| {
|
||||
slog::warn!(&backend.log, "Could not create render node from existing DRM node ({}), falling back to primary node", err);
|
||||
drm_node
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
slog::warn!(
|
||||
&backend.log,
|
||||
"DRM Device does not have a render node, falling back to primary node"
|
||||
);
|
||||
Ok(drm_node)
|
||||
}
|
||||
|
||||
impl X11Surface {
|
||||
/// 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<DrmNode>,
|
||||
modifiers: impl Iterator<Item = DrmModifier>,
|
||||
) -> Result<X11Surface, X11Error> {
|
||||
if backend.resize.is_some() {
|
||||
return Err(X11Error::SurfaceExists);
|
||||
}
|
||||
|
||||
let modifiers = modifiers.collect::<Vec<_>>();
|
||||
|
||||
let window = backend.window();
|
||||
let format = window.format().unwrap();
|
||||
let size = window.size();
|
||||
let mut current = device
|
||||
.create_buffer_object_with_modifiers(
|
||||
size.w as u32,
|
||||
size.h as u32,
|
||||
format,
|
||||
modifiers.iter().cloned(),
|
||||
)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
current
|
||||
.set_userdata(current.export().map_err(Into::<AllocateBuffersError>::into)?)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
|
||||
let next = device
|
||||
.create_buffer_object::<()>(size.w as u32, size.h as u32, format, BufferObjectFlags::empty())
|
||||
.map_err(Into::<AllocateBuffersError>::into)?
|
||||
.export()
|
||||
let mut next = device
|
||||
.create_buffer_object_with_modifiers(
|
||||
size.w as u32,
|
||||
size.h as u32,
|
||||
format,
|
||||
modifiers.iter().cloned(),
|
||||
)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
next.set_userdata(next.export().map_err(Into::<AllocateBuffersError>::into)?)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
|
||||
let (sender, recv) = mpsc::channel();
|
||||
|
||||
backend.resize = Some(sender);
|
||||
|
||||
Ok(X11Surface {
|
||||
connection: Arc::downgrade(connection),
|
||||
connection: Arc::downgrade(&backend.connection),
|
||||
window,
|
||||
device,
|
||||
format,
|
||||
|
@ -396,7 +487,7 @@ impl X11Surface {
|
|||
height: size.h,
|
||||
current,
|
||||
next,
|
||||
resize,
|
||||
resize: recv,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -422,25 +513,30 @@ impl X11Surface {
|
|||
}
|
||||
|
||||
fn resize(&mut self, size: Size<u16, Logical>) -> Result<(), AllocateBuffersError> {
|
||||
let current = self
|
||||
let mut current = self
|
||||
.device
|
||||
.create_buffer_object::<()>(
|
||||
.create_buffer_object(
|
||||
size.w as u32,
|
||||
size.h as u32,
|
||||
self.format,
|
||||
BufferObjectFlags::empty(),
|
||||
)?
|
||||
.export()?;
|
||||
)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
current
|
||||
.set_userdata(current.export().map_err(Into::<AllocateBuffersError>::into)?)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
|
||||
let next = self
|
||||
let mut next = self
|
||||
.device
|
||||
.create_buffer_object::<()>(
|
||||
.create_buffer_object(
|
||||
size.w as u32,
|
||||
size.h as u32,
|
||||
self.format,
|
||||
BufferObjectFlags::empty(),
|
||||
)?
|
||||
.export()?;
|
||||
)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
next.set_userdata(next.export().map_err(Into::<AllocateBuffersError>::into)?)
|
||||
.map_err(Into::<AllocateBuffersError>::into)?;
|
||||
|
||||
self.width = size.w;
|
||||
self.height = size.h;
|
||||
|
@ -480,8 +576,13 @@ impl Present<'_> {
|
|||
/// Returns the next buffer that will be presented to the Window.
|
||||
///
|
||||
/// You may bind this buffer to a renderer to render.
|
||||
pub fn buffer(&self) -> Dmabuf {
|
||||
self.surface.next.clone()
|
||||
pub fn buffer(&self) -> Result<Dmabuf, AllocateBuffersError> {
|
||||
Ok(self
|
||||
.surface
|
||||
.next
|
||||
.userdata()
|
||||
.map(|dmabuf| dmabuf.cloned())
|
||||
.map(Option::unwrap)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,9 +594,16 @@ impl Drop for Present<'_> {
|
|||
// Swap the buffers
|
||||
mem::swap(&mut surface.next, &mut surface.current);
|
||||
|
||||
if let Ok(pixmap) = PixmapWrapper::with_dmabuf(&*connection, &surface.window, &surface.current) {
|
||||
// Now present the current buffer
|
||||
let _ = pixmap.present(&*connection, &surface.window);
|
||||
match surface.current.userdata().map(Option::unwrap) {
|
||||
Ok(dmabuf) => {
|
||||
if let Ok(pixmap) = PixmapWrapper::with_dmabuf(&*connection, &surface.window, dmabuf) {
|
||||
// Now present the current buffer
|
||||
let _ = pixmap.present(&*connection, &surface.window);
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
todo!("Log error")
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection.
|
||||
|
@ -771,7 +879,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue