diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aea1f8a..3375855 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: - backend_drm - backend_drm_legacy - backend_drm_gbm + - backend_drm_eglstream - backend_drm_egl - backend_libinput - backend_udev diff --git a/Cargo.toml b/Cargo.toml index 6f6f4ad..830e792 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,12 +45,13 @@ slog-term = "2.3" gl_generator = { version = "0.14", optional = true } [features] -default = ["backend_winit", "backend_drm_legacy", "backend_drm_atomic", "backend_drm_gbm", "backend_drm_egl", "backend_libinput", "backend_udev", "backend_session_logind", "renderer_glium", "xwayland", "wayland_frontend"] +default = ["backend_winit", "backend_drm_legacy", "backend_drm_atomic", "backend_drm_gbm", "backend_drm_eglstream", "backend_drm_egl", "backend_libinput", "backend_udev", "backend_session_logind", "renderer_glium", "xwayland", "wayland_frontend"] backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl", "use_system_lib"] backend_drm = ["drm", "failure"] backend_drm_atomic = ["backend_drm"] backend_drm_legacy = ["backend_drm"] backend_drm_gbm = ["backend_drm", "gbm", "image"] +backend_drm_eglstream = ["backend_drm", "backend_egl"] backend_drm_egl = ["backend_drm", "backend_egl"] backend_egl = ["gl_generator"] backend_libinput = ["input"] diff --git a/anvil/Cargo.toml b/anvil/Cargo.toml index 263fff5..e8d5637 100644 --- a/anvil/Cargo.toml +++ b/anvil/Cargo.toml @@ -29,5 +29,5 @@ gl_generator = "0.14" default = [ "winit", "egl", "udev", "logind" ] egl = [ "smithay/use_system_lib" ] winit = [ "smithay/backend_winit" ] -udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm_atomic", "smithay/backend_drm_legacy", "smithay/backend_drm_gbm", "smithay/backend_drm_egl", "smithay/backend_session", "input" ] +udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm_atomic", "smithay/backend_drm_legacy", "smithay/backend_drm_gbm", "smithay/backend_drm_eglstream", "smithay/backend_drm_egl", "smithay/backend_session", "input" ] logind = [ "smithay/backend_session_logind" ] diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 6f54a68..d489e85 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -18,9 +18,10 @@ use smithay::{ backend::{ drm::{ atomic::AtomicDrmDevice, - common::fallback::FallbackDevice, + common::fallback::{FallbackDevice, FallbackSurface}, device_bind, egl::{EglDevice, EglSurface}, + eglstream::{egl::EglStreamDeviceBackend, EglStreamDevice, EglStreamSurface}, gbm::{egl::Gbm as EglGbmBackend, GbmDevice, GbmSurface}, legacy::LegacyDrmDevice, DevPath, Device, DeviceHandler, Surface, @@ -76,12 +77,20 @@ impl AsRawFd for SessionFd { } } -type RenderDevice = EglDevice< - EglGbmBackend, LegacyDrmDevice>>, - GbmDevice, LegacyDrmDevice>>, +type RenderDevice = FallbackDevice< + EglDevice< + EglGbmBackend, LegacyDrmDevice>>, + GbmDevice, LegacyDrmDevice>>, + >, + EglDevice< + EglStreamDeviceBackend, LegacyDrmDevice>>, + EglStreamDevice, LegacyDrmDevice>>, + >, +>; +type RenderSurface = FallbackSurface< + EglSurface, LegacyDrmDevice>>>, + EglSurface, LegacyDrmDevice>>>, >; -type RenderSurface = - EglSurface, LegacyDrmDevice>>>; pub fn run_udev( display: Rc>, @@ -301,7 +310,7 @@ impl UdevHandlerImpl { .filter_map(|e| *e) .flat_map(|encoder_handle| device.get_encoder_info(encoder_handle)) .collect::>(); - for encoder_info in encoder_infos { + 'outer: for encoder_info in encoder_infos { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { if let Entry::Vacant(entry) = backends.entry(crtc) { let renderer = GliumDrawer::init( @@ -313,7 +322,7 @@ impl UdevHandlerImpl { ); entry.insert(Rc::new(renderer)); - break; + break 'outer; } } } @@ -349,14 +358,14 @@ impl UdevHandlerImpl { .filter_map(|e| *e) .flat_map(|encoder_handle| device.get_encoder_info(encoder_handle)) .collect::>(); - for encoder_info in encoder_infos { + 'outer: for encoder_info in encoder_infos { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { if !backends.contains_key(&crtc) { let renderer = GliumDrawer::init(device.create_surface(crtc).unwrap(), logger.clone()); backends.insert(crtc, Rc::new(renderer)); - break; + break 'outer; } } } @@ -376,23 +385,29 @@ impl UdevHandlerImpl { OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, ) .ok() - .and_then( - |fd| match FallbackDevice::new(SessionFd(fd), true, self.logger.clone()) { + .and_then(|fd| { + match FallbackDevice::, LegacyDrmDevice<_>>::new( + SessionFd(fd), + true, + self.logger.clone(), + ) { Ok(drm) => Some(drm), Err(err) => { warn!(self.logger, "Skipping drm device, because of error: {}", err); None } - }, - ) - .and_then(|drm| match GbmDevice::new(drm, self.logger.clone()) { - Ok(gbm) => Some(gbm), - Err(err) => { - warn!(self.logger, "Skipping gbm device, because of error: {}", err); - None } }) - .and_then(|gbm| match EglDevice::new(gbm, self.logger.clone()) { + .and_then(|drm| { + match FallbackDevice::, EglStreamDevice<_>>::new(drm, self.logger.clone()) { + Ok(dev) => Some(dev), + Err(err) => { + warn!(self.logger, "Skipping device, because of error: {}", err); + None + } + } + }) + .and_then(|dev| match FallbackDevice::new_egl(dev, self.logger.clone()) { Ok(egl) => Some(egl), Err(err) => { warn!(self.logger, "Skipping egl device, because of error: {}", err); @@ -657,6 +672,7 @@ impl DrmRenderer { }; if reschedule { + debug!(self.logger, "Rescheduling"); match (timer, evt_handle) { (Some(handle), _) => { let _ = handle.add_timeout( diff --git a/build.rs b/build.rs index dbd1dec..1612d6e 100644 --- a/build.rs +++ b/build.rs @@ -32,6 +32,17 @@ fn main() { "EGL_EXT_platform_wayland", "EGL_EXT_platform_device", "EGL_KHR_image_base", + "EGL_EXT_output_base", + "EGL_EXT_output_drm", + "EGL_EXT_device_drm", + "EGL_EXT_device_enumeration", + "EGL_EXT_device_query", + "EGL_KHR_stream", + "EGL_KHR_stream_producer_eglsurface", + "EGL_EXT_stream_consumer_egloutput", + "EGL_KHR_stream_fifo", + "EGL_NV_output_drm_flip_event", + "EGL_NV_stream_attrib", ], ) .write_bindings(gl_generator::GlobalGenerator, &mut file) diff --git a/examples/raw_nvidia.rs b/examples/raw_nvidia.rs new file mode 100644 index 0000000..f8c7ce1 --- /dev/null +++ b/examples/raw_nvidia.rs @@ -0,0 +1,175 @@ +#![warn(rust_2018_idioms)] + +#[macro_use] +extern crate slog; + +use glium::Surface as GliumSurface; +use slog::Drain; +use smithay::{ + backend::{ + drm::{ + atomic::AtomicDrmDevice, + common::fallback::{EitherError, FallbackDevice}, + common::Error as DrmError, + device_bind, + egl::{EglDevice, EglSurface, Error as EglError}, + eglstream::{ + egl::EglStreamDeviceBackend, EglStreamDevice, EglStreamSurface, Error as EglStreamError, + }, + legacy::LegacyDrmDevice, + Device, DeviceHandler, + }, + graphics::glium::GliumGraphicsBackend, + }, + reexports::{ + calloop::EventLoop, + drm::control::{connector::State as ConnectorState, crtc}, + }, +}; +use std::{ + fs::{File, OpenOptions}, + io::Error as IoError, + os::unix::io::{AsRawFd, RawFd}, + rc::Rc, + sync::Mutex, +}; + +pub struct ClonableFile { + file: File, +} + +impl AsRawFd for ClonableFile { + fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() + } +} + +impl Clone for ClonableFile { + fn clone(&self) -> Self { + ClonableFile { + file: self.file.try_clone().expect("Unable to clone file"), + } + } +} + +fn main() { + let log = slog::Logger::root(Mutex::new(slog_term::term_full().fuse()).fuse(), o!()); + + /* + * Initialize the drm backend + */ + + // "Find" a suitable drm device + let mut options = OpenOptions::new(); + options.read(true); + options.write(true); + let file = ClonableFile { + file: options.open("/dev/dri/card1").expect("Failed to open card1"), + }; + let mut device = EglDevice::new( + EglStreamDevice::new( + FallbackDevice::, LegacyDrmDevice<_>>::new(file, true, log.clone()) + .expect("Failed to initialize drm device"), + log.clone(), + ) + .expect("Failed to initialize egl stream device"), + log.clone(), + ) + .expect("Failed to initialize egl device"); + + // Get a set of all modesetting resource handles (excluding planes): + let res_handles = Device::resource_handles(&device).unwrap(); + + // Use first connected connector + let connector_info = res_handles + .connectors() + .iter() + .map(|conn| Device::get_connector_info(&device, *conn).unwrap()) + .find(|conn| conn.state() == ConnectorState::Connected) + .unwrap(); + println!("Conn: {:?}", connector_info.interface()); + + // Use the first encoder + let encoder_info = + Device::get_encoder_info(&device, connector_info.encoders()[0].expect("expected encoder")).unwrap(); + + // use the connected crtc if any + let crtc = encoder_info + .crtc() + // or use the first one that is compatible with the encoder + .unwrap_or_else(|| { + *res_handles + .filter_crtcs(encoder_info.possible_crtcs()) + .iter() + .next() + .unwrap() + }); + println!("Crtc {:?}", crtc); + + // Assuming we found a good connector and loaded the info into `connector_info` + let mode = connector_info.modes()[0]; // Use first mode (usually highest resolution, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) + println!("Mode: {:?}", mode); + + // Initialize the hardware backend + let surface = device + .create_surface(crtc, mode, &[connector_info.handle()]) + .expect("Failed to create surface"); + + let backend: Rc> = Rc::new(surface.into()); + device.set_handler(DrmHandlerImpl { + surface: backend.clone(), + }); + + /* + * Register the DrmDevice on the EventLoop + */ + let mut event_loop = EventLoop::<()>::new().unwrap(); + let _source = device_bind(&event_loop.handle(), device) + .map_err(|err| -> IoError { err.into() }) + .unwrap(); + + // Start rendering + { + if let Err(smithay::backend::graphics::SwapBuffersError::ContextLost(err)) = backend.draw().finish() { + println!("{}", err); + return; + }; + } + + // Run + event_loop.run(None, &mut (), |_| {}).unwrap(); +} + +pub struct DrmHandlerImpl { + surface: Rc< + GliumGraphicsBackend< + EglSurface< + EglStreamSurface< + FallbackDevice, LegacyDrmDevice>, + >, + >, + >, + >, +} + +impl DeviceHandler for DrmHandlerImpl { + type Device = EglDevice< + EglStreamDeviceBackend, LegacyDrmDevice>>, + EglStreamDevice, LegacyDrmDevice>>, + >; + + fn vblank(&mut self, _crtc: crtc::Handle) { + { + println!("Vblank"); + let mut frame = self.surface.draw(); + frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, Some(1.0), None); + if let Err(smithay::backend::graphics::SwapBuffersError::ContextLost(err)) = frame.finish() { + panic!("Failed to swap: {}", err); + } + } + } + + fn error(&mut self, error: EglError>>) { + panic!("{:?}", error); + } +} diff --git a/src/backend/drm/atomic/mod.rs b/src/backend/drm/atomic/mod.rs index fa0911b..13d7c45 100644 --- a/src/backend/drm/atomic/mod.rs +++ b/src/backend/drm/atomic/mod.rs @@ -61,7 +61,7 @@ type Mapping = ( HashMap>, ); -struct Dev { +pub(in crate::backend::drm) struct Dev { fd: A, privileged: bool, active: Arc, diff --git a/src/backend/drm/atomic/surface.rs b/src/backend/drm/atomic/surface.rs index 347a0ea..481e3d7 100644 --- a/src/backend/drm/atomic/surface.rs +++ b/src/backend/drm/atomic/surface.rs @@ -38,11 +38,11 @@ pub struct Planes { pub cursor: plane::Handle, } -pub(super) struct AtomicDrmSurfaceInternal { +pub(in crate::backend::drm) struct AtomicDrmSurfaceInternal { pub(super) dev: Rc>, - pub(super) crtc: crtc::Handle, + pub(in crate::backend::drm) crtc: crtc::Handle, pub(super) cursor: CursorState, - pub(super) planes: Planes, + pub(in crate::backend::drm) planes: Planes, pub(super) state: RwLock, pub(super) pending: RwLock, pub(super) logger: ::slog::Logger, @@ -918,7 +918,7 @@ impl AtomicDrmSurfaceInternal { )) } - pub(crate) fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> { + pub(super) fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> { let mut req = AtomicModeReq::new(); req.add_property( @@ -948,7 +948,9 @@ impl AtomicDrmSurfaceInternal { } /// Open raw crtc utilizing atomic mode-setting -pub struct AtomicDrmSurface(pub(super) Rc>); +pub struct AtomicDrmSurface( + pub(in crate::backend::drm) Rc>, +); impl AsRawFd for AtomicDrmSurface { fn as_raw_fd(&self) -> RawFd { diff --git a/src/backend/drm/common/fallback.rs b/src/backend/drm/common/fallback.rs index 6a47b31..597864b 100644 --- a/src/backend/drm/common/fallback.rs +++ b/src/backend/drm/common/fallback.rs @@ -2,9 +2,20 @@ //! Types to make fallback device initialization easier //! +#[cfg(feature = "backend_drm_egl")] +use crate::backend::drm::egl::{Arguments as EglDeviceArguments, EglDevice, Error as EglDeviceError}; #[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] use crate::backend::drm::{atomic::AtomicDrmDevice, legacy::LegacyDrmDevice}; use crate::backend::drm::{common::Error, Device, DeviceHandler, RawDevice, RawSurface, Surface}; +#[cfg(all(feature = "backend_drm_gbm", feature = "backend_drm_eglstream"))] +use crate::backend::drm::{ + eglstream::{EglStreamDevice, Error as EglStreamError}, + gbm::{Error as GbmError, GbmDevice}, +}; +#[cfg(feature = "backend_drm_egl")] +use crate::backend::egl::context::{GlAttributes, PixelFormatRequirements}; +#[cfg(feature = "backend_drm_egl")] +use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; use crate::backend::egl::Error as EGLError; #[cfg(feature = "use_system_lib")] use crate::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend}; @@ -36,23 +47,25 @@ pub enum FallbackDevice { Fallback(D2), } -struct FallbackDeviceHandlerD1( +struct FallbackDeviceHandlerD1( Box> + 'static>, ) where - E: std::error::Error + Send + 'static, + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, + S1: Surface + 'static, + S2: Surface + 'static, D1: Device + 'static, D2: Device + 'static; -impl DeviceHandler for FallbackDeviceHandlerD1 +impl DeviceHandler for FallbackDeviceHandlerD1 where - E: std::error::Error + Send + 'static, + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, + S1: Surface + 'static, + S2: Surface + 'static, D1: Device + 'static, D2: Device + 'static, { @@ -61,28 +74,30 @@ where fn vblank(&mut self, crtc: crtc::Handle) { self.0.vblank(crtc) } - fn error(&mut self, error: E) { - self.0.error(error); + fn error(&mut self, error: E1) { + self.0.error(EitherError::Either(error)); } } -struct FallbackDeviceHandlerD2( +struct FallbackDeviceHandlerD2( Box> + 'static>, ) where - E: std::error::Error + Send + 'static, + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, + S1: Surface + 'static, + S2: Surface + 'static, D1: Device + 'static, D2: Device + 'static; -impl DeviceHandler for FallbackDeviceHandlerD2 +impl DeviceHandler for FallbackDeviceHandlerD2 where - E: std::error::Error + Send + 'static, + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, + S1: Surface + 'static, + S2: Surface + 'static, D1: Device + 'static, D2: Device + 'static, { @@ -91,8 +106,8 @@ where fn vblank(&mut self, crtc: crtc::Handle) { self.0.vblank(crtc) } - fn error(&mut self, error: E) { - self.0.error(error); + fn error(&mut self, error: E2) { + self.0.error(EitherError::Or(error)); } } @@ -147,9 +162,33 @@ pub enum FallbackSurface { Fallback(S2), } +/// Enum uniting two kinds of possible errors. +#[derive(Debug, thiserror::Error)] +pub enum EitherError { + /// Either this error + #[error("{0}")] + Either(#[source] E1), + /// Or this error + #[error("{0}")] + Or(#[source] E2), +} + +impl Into for EitherError +where + E1: std::error::Error + Into + 'static, + E2: std::error::Error + Into + 'static, +{ + fn into(self) -> SwapBuffersError { + match self { + EitherError::Either(err) => err.into(), + EitherError::Or(err) => err.into(), + } + } +} + #[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] impl FallbackDevice, LegacyDrmDevice> { - /// Try to initialize an [`AtomicDrmDevice`](::backend::drm:;atomic::AtomicDrmDevice) + /// Try to initialize an [`AtomicDrmDevice`](::backend::drm::atomic::AtomicDrmDevice) /// and fall back to a [`LegacyDrmDevice`] if atomic-modesetting is not supported. /// /// # Arguments @@ -203,6 +242,146 @@ impl FallbackDevice, LegacyDrmD } } +#[cfg(all( + feature = "backend_drm_gbm", + feature = "backend_drm_eglstream", + feature = "backend_udev" +))] +type GbmOrEglStreamError = EitherError< + GbmError<<::Surface as Surface>::Error>, + EglStreamError<<::Surface as Surface>::Error>, +>; +#[cfg(all( + feature = "backend_drm_gbm", + feature = "backend_drm_eglstream", + feature = "backend_udev" +))] +impl FallbackDevice, EglStreamDevice> +where + D: RawDevice + ControlDevice + 'static, +{ + /// Try to initialize a [`GbmDevice`](::backend::drm::gbm::GbmDevice) + /// or a [`EglStreamDevice`](::backend::drm::eglstream::EglStreamDevice) depending on the used driver. + /// + /// # Arguments + /// + /// - `dev` - Open drm device (needs implement [`RawDevice`](::backend::drm::RawDevice)) + /// - `logger` - Optional [`slog::Logger`] to be used by the resulting device. + /// + /// # Return + /// + /// Returns an error, if the choosen device fails to initialize. + pub fn new(dev: D, logger: L) -> Result> + where + L: Into>, + { + let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm_fallback")); + + let driver = crate::backend::udev::driver(dev.device_id()).expect("Failed to query device"); + info!(log, "Drm device driver: {:?}", driver); + if driver.as_ref().and_then(|x| x.to_str()) == Some("nvidia") { + Ok(FallbackDevice::Fallback( + EglStreamDevice::new(dev, log).map_err(EitherError::Or)?, + )) + } else { + Ok(FallbackDevice::Preference( + GbmDevice::new(dev, log.clone()).map_err(EitherError::Either)?, + )) + } + } +} + +#[cfg(feature = "backend_drm_egl")] +type EglUnderlying = EitherError< + EglDeviceError<<::Surface as Surface>::Error>, + EglDeviceError<<::Surface as Surface>::Error>, +>; + +#[cfg(feature = "backend_drm_egl")] +type FallbackEglDevice = FallbackDevice, EglDevice>; + +#[cfg(feature = "backend_drm_egl")] +impl FallbackDevice +where + D1: Device + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, + D2: Device + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, +{ + /// Try to create a new [`EglDevice`] from a [`FallbackDevice`] containing two compatible device types. + /// + /// This helper function is necessary as implementing [`NativeDevice`](::backend::egl::native::NativeDevice) for [`FallbackDevice`] is impossible + /// as selecting the appropriate [`Backend`](::backend::egl::native::Backend) would be impossible without knowing + /// the underlying device type, that was selected by [`FallbackDevice`]. + /// + /// Returns an error if the context creation was not successful. + pub fn new_egl( + dev: FallbackDevice, + logger: L, + ) -> Result, EglUnderlying> + where + B1: Backend::Surface, Error = <::Surface as Surface>::Error> + + 'static, + D1: NativeDisplay, + B2: Backend::Surface, Error = <::Surface as Surface>::Error> + + 'static, + D2: NativeDisplay, + L: Into>, + { + let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm_fallback")); + match dev { + FallbackDevice::Preference(gbm) => match EglDevice::new(gbm, log) { + Ok(dev) => Ok(FallbackDevice::Preference(dev)), + Err(err) => Err(EglUnderlying::::Either(err)), + }, + FallbackDevice::Fallback(eglstream) => match EglDevice::new(eglstream, log) { + Ok(dev) => Ok(FallbackDevice::Fallback(dev)), + Err(err) => Err(EglUnderlying::::Or(err)), + }, + } + } + + /// Try to create a new [`EglDevice`] from a [`FallbackDevice`] containing two compatible device types with + /// the given attributes and requirements as defaults for new surfaces. + /// + /// This helper function is necessary as implementing [`NativeDevice`](::backend::egl::native::NativeDevice) for [`FallbackDevice`] is impossible + /// as selecting the appropriate [`Backend`](::backend::egl::native::Backend) would be impossible without knowing + /// the underlying device type, that was selected by [`FallbackDevice`]. + /// + /// Returns an error if the context creation was not successful. + pub fn new_egl_with_defaults( + dev: FallbackDevice, + default_attributes: GlAttributes, + default_requirements: PixelFormatRequirements, + logger: L, + ) -> Result, EglUnderlying> + where + B1: Backend::Surface, Error = <::Surface as Surface>::Error> + + 'static, + D1: NativeDisplay, + B2: Backend::Surface, Error = <::Surface as Surface>::Error> + + 'static, + D2: NativeDisplay, + L: Into>, + { + let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm_fallback")); + match dev { + FallbackDevice::Preference(gbm) => { + match EglDevice::new_with_defaults(gbm, default_attributes, default_requirements, log) { + Ok(dev) => Ok(FallbackDevice::Preference(dev)), + Err(err) => Err(EglUnderlying::::Either(err)), + } + } + FallbackDevice::Fallback(eglstream) => { + match EglDevice::new_with_defaults(eglstream, default_attributes, default_requirements, log) { + Ok(dev) => Ok(FallbackDevice::Fallback(dev)), + Err(err) => Err(EglUnderlying::::Or(err)), + } + } + } + } +} + macro_rules! fallback_device_impl { ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { @@ -219,6 +398,19 @@ macro_rules! fallback_device_impl { fallback_device_impl!($func_name, $self, ()); }; } +macro_rules! fallback_device_err_impl { + ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { + fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { + match self { + FallbackDevice::Preference(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Either), + FallbackDevice::Fallback(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Or), + } + } + }; + ($func_name:ident, $self:ty, $return:ty) => { + fallback_device_err_impl!($func_name, $self, $return,); + }; +} macro_rules! fallback_surface_impl { ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { @@ -236,6 +428,16 @@ macro_rules! fallback_surface_impl { fallback_surface_impl!($func_name, $self, ()); }; } +macro_rules! fallback_surface_err_impl { + ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { + fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { + match self { + FallbackSurface::Preference(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Either), + FallbackSurface::Fallback(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Or), + } + } + }; +} impl AsRawFd for FallbackDevice { fallback_device_impl!(as_raw_fd, &Self, RawFd); @@ -243,13 +445,14 @@ impl AsRawFd for FallbackDevice { impl BasicDevice for FallbackDevice {} impl ControlDevice for FallbackDevice {} -impl Device for FallbackDevice +impl Device for FallbackDevice where - // Connectors and Error need to match for both Surfaces - E: std::error::Error + Send + 'static, + // Connectors need to match for both Surfaces + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, + S1: Surface + 'static, + S2: Surface + 'static, D1: Device + 'static, D2: Device + 'static, { @@ -268,18 +471,20 @@ where crtc: crtc::Handle, mode: Mode, connectors: &[connector::Handle], - ) -> Result { + ) -> Result> { match self { FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference( - dev.create_surface(crtc, mode, connectors)?, + dev.create_surface(crtc, mode, connectors) + .map_err(EitherError::Either)?, )), FallbackDevice::Fallback(dev) => Ok(FallbackSurface::Fallback( - dev.create_surface(crtc, mode, connectors)?, + dev.create_surface(crtc, mode, connectors) + .map_err(EitherError::Or)?, )), } } fallback_device_impl!(process_events, &mut Self); - fallback_device_impl!(resource_handles, &Self, Result); + fallback_device_err_impl!(resource_handles, &Self, Result>); fallback_device_impl!(get_connector_info, &Self, Result, conn: connector::Handle); fallback_device_impl!(get_crtc_info, &Self, Result, crtc: crtc::Handle); fallback_device_impl!(get_encoder_info, &Self, Result, enc: encoder::Handle); @@ -288,13 +493,14 @@ where } // Impl RawDevice where underlying types implement RawDevice -impl RawDevice for FallbackDevice +impl RawDevice for FallbackDevice where - // Connectors and Error need to match for both Surfaces - E: std::error::Error + Send + 'static, + // Connectors need to match for both Surfaces + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: RawSurface + Surface + 'static, - S2: RawSurface + Surface + 'static, + S1: RawSurface + Surface + 'static, + S2: RawSurface + Surface + 'static, D1: RawDevice + 'static, D2: RawDevice + 'static, { @@ -308,41 +514,47 @@ impl, display : &Display); } -impl Surface for FallbackSurface +impl Surface for FallbackSurface where - // Connectors and Error need to match for both Surfaces - E: std::error::Error + Send + 'static, + // Connectors need to match for both Surfaces + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, + S1: Surface + 'static, + S2: Surface + 'static, { - type Error = E; + type Error = EitherError; type Connectors = C; fallback_surface_impl!(crtc, &Self, crtc::Handle); fallback_surface_impl!(current_connectors, &Self, C); fallback_surface_impl!(pending_connectors, &Self, C); - fallback_surface_impl!(add_connector, &Self, Result<(), E>, conn: connector::Handle); - fallback_surface_impl!(remove_connector, &Self, Result<(), E>, conn: connector::Handle); - fallback_surface_impl!(set_connectors, &Self, Result<(), E>, conns: &[connector::Handle]); + fallback_surface_err_impl!(add_connector, &Self, Result<(), EitherError>, conn: connector::Handle); + fallback_surface_err_impl!(remove_connector, &Self, Result<(), EitherError>, conn: connector::Handle); + fallback_surface_err_impl!(set_connectors, &Self, Result<(), EitherError>, conns: &[connector::Handle]); fallback_surface_impl!(current_mode, &Self, Mode); fallback_surface_impl!(pending_mode, &Self, Mode); - fallback_surface_impl!(use_mode, &Self, Result<(), E>, mode: Mode); + fallback_surface_err_impl!(use_mode, &Self, Result<(), EitherError>, mode: Mode); } -impl RawSurface for FallbackSurface +impl RawSurface for FallbackSurface where - E: std::error::Error + Send + 'static, + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: RawSurface + Surface + 'static, - S2: RawSurface + Surface + 'static, + S1: RawSurface + Surface + 'static, + S2: RawSurface + Surface + 'static, { fallback_surface_impl!(commit_pending, &Self, bool); - fallback_surface_impl!(commit, &Self, Result<(), E>, fb: framebuffer::Handle); - fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), E> { + fallback_surface_err_impl!(commit, &Self, Result<(), EitherError>, fb: framebuffer::Handle); + fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), EitherError> { match self { - FallbackSurface::Preference(dev) => RawSurface::page_flip(dev, framebuffer), - FallbackSurface::Fallback(dev) => RawSurface::page_flip(dev, framebuffer), + FallbackSurface::Preference(dev) => { + RawSurface::page_flip(dev, framebuffer).map_err(EitherError::Either) + } + FallbackSurface::Fallback(dev) => { + RawSurface::page_flip(dev, framebuffer).map_err(EitherError::Or) + } } } } @@ -353,29 +565,32 @@ impl AsRawFd for FallbackSurface BasicDevice for FallbackSurface {} impl ControlDevice for FallbackSurface {} -impl CursorBackend for FallbackSurface +impl CursorBackend for FallbackSurface where E1: std::error::Error + Send + 'static, - E2: 'static, + E2: std::error::Error + Send + 'static, + E3: std::error::Error + 'static, + E4: std::error::Error + 'static, CF: ?Sized, C: IntoIterator + 'static, - S1: Surface + CursorBackend + 'static, - S2: Surface + CursorBackend + 'static, + S1: Surface + CursorBackend + 'static, + S2: Surface + CursorBackend + 'static, { type CursorFormat = CF; - type Error = E2; + type Error = EitherError; - fallback_surface_impl!(set_cursor_position, &Self, Result<(), E2>, x: u32, y: u32); - fallback_surface_impl!(set_cursor_representation, &Self, Result<(), E2>, buffer: &Self::CursorFormat, hotspot: (u32, u32)); + fallback_surface_err_impl!(set_cursor_position, &Self, Result<(), EitherError>, x: u32, y: u32); + fallback_surface_err_impl!(set_cursor_representation, &Self, Result<(), EitherError>, buffer: &Self::CursorFormat, hotspot: (u32, u32)); } #[cfg(feature = "renderer_gl")] -impl GLGraphicsBackend for FallbackSurface +impl GLGraphicsBackend for FallbackSurface where - E: std::error::Error + Send + 'static, + E1: std::error::Error + Send + 'static, + E2: std::error::Error + Send + 'static, C: IntoIterator + 'static, - S1: Surface + GLGraphicsBackend + 'static, - S2: Surface + GLGraphicsBackend + 'static, + S1: Surface + GLGraphicsBackend + 'static, + S2: Surface + GLGraphicsBackend + 'static, { fallback_surface_impl!(swap_buffers, &Self, Result<(), SwapBuffersError>); fallback_surface_impl!(get_proc_address, &Self, *const c_void, symbol: &str); diff --git a/src/backend/drm/egl/mod.rs b/src/backend/drm/egl/mod.rs index e17ead8..70f9934 100644 --- a/src/backend/drm/egl/mod.rs +++ b/src/backend/drm/egl/mod.rs @@ -46,17 +46,16 @@ pub enum Error); +pub(crate) type Arguments = (crtc::Handle, Mode, Vec); type BackendRef = Weak::Surface>>; /// Representation of an egl device to create egl rendering surfaces pub struct EglDevice where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { dev: EGLDisplay, logger: ::slog::Logger, @@ -67,11 +66,10 @@ where impl AsRawFd for EglDevice where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { fn as_raw_fd(&self) -> RawFd { self.dev.borrow().as_raw_fd() @@ -80,11 +78,10 @@ where impl EglDevice where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { /// Try to create a new [`EglDevice`] from an open device. /// @@ -138,22 +135,20 @@ where struct InternalDeviceHandler where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { handler: Box> + 'static>, } impl DeviceHandler for InternalDeviceHandler where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { type Device = D; @@ -167,11 +162,10 @@ where impl Device for EglDevice where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { type Surface = EglSurface<::Surface>; @@ -250,11 +244,10 @@ where #[cfg(feature = "use_system_lib")] impl EGLGraphicsBackend for EglDevice where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { fn bind_wl_display(&self, display: &Display) -> Result { self.dev.bind_wl_display(display) @@ -263,11 +256,10 @@ where impl Drop for EglDevice where - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay::Surface as Surface>::Error> + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + NativeDisplay + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { fn drop(&mut self) { self.clear_handler(); diff --git a/src/backend/drm/egl/session.rs b/src/backend/drm/egl/session.rs index 6a6f2e0..9a6c774 100644 --- a/src/backend/drm/egl/session.rs +++ b/src/backend/drm/egl/session.rs @@ -28,15 +28,13 @@ pub struct EglDeviceObserver AsSessionObserver::Surface>> for EglDevice where S: SessionObserver + 'static, - B: Backend::Surface> + 'static, - D: Device - + NativeDisplay< - B, - Arguments = (crtc::Handle, Mode, Vec), - Error = <::Surface as Surface>::Error, - > + AsSessionObserver + B: Backend::Surface, Error = <::Surface as Surface>::Error> + 'static, - ::Surface: NativeSurface, + D: Device + + NativeDisplay)> + + AsSessionObserver + + 'static, + ::Surface: NativeSurface::Surface as Surface>::Error>, { fn observer(&mut self) -> EglDeviceObserver::Surface> { EglDeviceObserver { diff --git a/src/backend/drm/eglstream/egl.rs b/src/backend/drm/eglstream/egl.rs new file mode 100644 index 0000000..538fb00 --- /dev/null +++ b/src/backend/drm/eglstream/egl.rs @@ -0,0 +1,238 @@ +//! +//! Egl [`NativeDisplay`](::backend::egl::native::NativeDisplay) and +//! [`NativeSurface`](::backend::egl::native::NativeSurface) support for +//! [`EglStreamDevice`](EglStreamDevice) and [`EglStreamSurface`](EglStreamSurface). +//! + +#[cfg(feature = "backend_drm_atomic")] +use crate::backend::drm::atomic::AtomicDrmDevice; +#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] +use crate::backend::drm::common::fallback::{EitherError, FallbackDevice, FallbackSurface}; +#[cfg(any(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] +use crate::backend::drm::common::Error as DrmError; +#[cfg(feature = "backend_drm_legacy")] +use crate::backend::drm::legacy::LegacyDrmDevice; +use crate::backend::drm::{Device, RawDevice, RawSurface, Surface}; +use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use crate::backend::egl::{ + display::EGLDisplayHandle, ffi, wrap_egl_call, EGLError, Error as EglBackendError, SurfaceCreationError, + SwapBuffersError, +}; + +use super::Error; +use super::{EglStreamDevice, EglStreamSurface}; + +use drm::control::{connector, crtc, Device as ControlDevice, Mode}; +use nix::libc::{c_int, c_void}; +use std::marker::PhantomData; +use std::os::unix::io::AsRawFd; +use std::sync::Arc; + +/// Egl Device backend type +/// +/// See [`Backend`](::backend::egl::native::Backend). +pub struct EglStreamDeviceBackend { + _userdata: PhantomData, +} + +impl Backend for EglStreamDeviceBackend +where + EglStreamSurface: NativeSurface::Surface as Surface>::Error>>, +{ + type Surface = EglStreamSurface; + type Error = Error<<::Surface as Surface>::Error>; + + unsafe fn get_display( + display: ffi::NativeDisplayType, + attribs: &[ffi::EGLint], + has_dp_extension: F, + log: ::slog::Logger, + ) -> Result + where + F: Fn(&str) -> bool, + { + if has_dp_extension("EGL_EXT_platform_device") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { + debug!( + log, + "EGL Display Initialization via EGL_EXT_platform_device with {:?}", display + ); + wrap_egl_call(|| { + ffi::egl::GetPlatformDisplayEXT( + ffi::egl::PLATFORM_DEVICE_EXT, + display as *mut _, + attribs.as_ptr() as *const _, + ) + }) + } else { + Ok(ffi::egl::NO_DISPLAY) + } + } +} + +unsafe impl NativeDisplay> + for EglStreamDevice +where + EglStreamSurface: NativeSurface::Surface as Surface>::Error>>, +{ + type Arguments = (crtc::Handle, Mode, Vec); + + fn is_backend(&self) -> bool { + true + } + + fn ptr(&self) -> Result { + Ok(self.dev as *const _) + } + + fn attributes(&self) -> Vec { + vec![ + ffi::egl::DRM_MASTER_FD_EXT as ffi::EGLint, + self.raw.as_raw_fd(), + ffi::egl::NONE as i32, + ] + } + + fn surface_type(&self) -> ffi::EGLint { + ffi::egl::STREAM_BIT_KHR as ffi::EGLint + } + + fn create_surface( + &mut self, + args: Self::Arguments, + ) -> Result, Error<<::Surface as Surface>::Error>> { + Device::create_surface(self, args.0, args.1, &args.2) + } +} + +#[cfg(feature = "backend_drm_atomic")] +unsafe impl NativeSurface for EglStreamSurface> { + type Error = Error; + + unsafe fn create( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + surface_attribs: &[c_int], + ) -> Result<*const c_void, SurfaceCreationError> { + let output_attributes = { + let mut out: Vec = Vec::with_capacity(3); + out.push(ffi::egl::DRM_PLANE_EXT as isize); + out.push(Into::::into(self.0.crtc.0.planes.primary) as isize); + out.push(ffi::egl::NONE as isize); + out + }; + + self.create_surface(display, config_id, surface_attribs, &output_attributes) + .map_err(SurfaceCreationError::NativeSurfaceCreationFailed) + } + + fn needs_recreation(&self) -> bool { + self.0.crtc.commit_pending() + } + + fn swap_buffers( + &self, + display: &Arc, + surface: ffi::egl::types::EGLSurface, + ) -> Result<(), SwapBuffersError>> { + if let Some((buffer, fb)) = self.0.commit_buffer.take() { + let _ = self.0.crtc.destroy_framebuffer(fb); + let _ = self.0.crtc.destroy_dumb_buffer(buffer); + } + + self.flip(self.0.crtc.0.crtc, display, surface) + } +} + +#[cfg(feature = "backend_drm_legacy")] +unsafe impl NativeSurface for EglStreamSurface> { + type Error = Error; + + unsafe fn create( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + surface_attribs: &[c_int], + ) -> Result<*const c_void, SurfaceCreationError> { + let output_attributes = { + let mut out: Vec = Vec::with_capacity(3); + out.push(ffi::egl::DRM_CRTC_EXT as isize); + out.push(Into::::into(self.0.crtc.0.crtc) as isize); + out.push(ffi::egl::NONE as isize); + out + }; + + self.create_surface(display, config_id, surface_attribs, &output_attributes) + .map_err(SurfaceCreationError::NativeSurfaceCreationFailed) + } + + fn needs_recreation(&self) -> bool { + self.0.crtc.commit_pending() + } + + fn swap_buffers( + &self, + display: &Arc, + surface: ffi::egl::types::EGLSurface, + ) -> Result<(), SwapBuffersError>> { + if let Some((buffer, fb)) = self.0.commit_buffer.take() { + let _ = self.0.crtc.destroy_framebuffer(fb); + let _ = self.0.crtc.destroy_dumb_buffer(buffer); + } + self.flip(self.0.crtc.0.crtc, display, surface) + } +} + +#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] +unsafe impl NativeSurface + for EglStreamSurface, LegacyDrmDevice>> +{ + type Error = Error>; + + unsafe fn create( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + surface_attribs: &[c_int], + ) -> Result<*const c_void, SurfaceCreationError> { + let output_attributes = { + let mut out: Vec = Vec::with_capacity(3); + match &self.0.crtc { + FallbackSurface::Preference(dev) => { + out.push(ffi::egl::DRM_PLANE_EXT as isize); + out.push(Into::::into(dev.0.planes.primary) as isize); + } //AtomicDrmSurface + FallbackSurface::Fallback(dev) => { + out.push(ffi::egl::DRM_CRTC_EXT as isize); + out.push(Into::::into(dev.0.crtc) as isize); + } // LegacyDrmSurface + } + out.push(ffi::egl::NONE as isize); + out + }; + + self.create_surface(display, config_id, surface_attribs, &output_attributes) + .map_err(SurfaceCreationError::NativeSurfaceCreationFailed) + } + + fn needs_recreation(&self) -> bool { + self.0.crtc.commit_pending() + } + + fn swap_buffers( + &self, + display: &Arc, + surface: ffi::egl::types::EGLSurface, + ) -> Result<(), SwapBuffersError> { + if let Some((buffer, fb)) = self.0.commit_buffer.take() { + let _ = self.0.crtc.destroy_framebuffer(fb); + let _ = self.0.crtc.destroy_dumb_buffer(buffer); + } + let crtc = match &self.0.crtc { + FallbackSurface::Preference(dev) => dev.0.crtc, + FallbackSurface::Fallback(dev) => dev.0.crtc, + }; + + self.flip(crtc, display, surface) + } +} diff --git a/src/backend/drm/eglstream/mod.rs b/src/backend/drm/eglstream/mod.rs new file mode 100644 index 0000000..24e2bfe --- /dev/null +++ b/src/backend/drm/eglstream/mod.rs @@ -0,0 +1,364 @@ +//! +//! [`Device`](Device) and [`Surface`](Surface) implementations using +//! the proposed EGLStream api for efficient rendering. +//! Currently this api is only implemented by the proprietary `nvidia` driver. +//! +//! Usually this implementation will be wrapped into a [`EglDevice`](::backend::drm::egl::EglDevice). +//! +//! To use these types standalone, you will need to render via egl yourself as page flips +//! are driven via `eglSwapBuffers`. +//! +//! To use this api in place of GBM for nvidia cards take a look at +//! [`FallbackDevice`](::backend::drm::common::fallback::FallbackDevice). +//! Take a look at `anvil`s source code for an example of this. +//! + +use super::{Device, DeviceHandler, RawDevice, Surface}; + +use drm::buffer::format::PixelFormat; +use drm::control::{ + connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles, +}; +use drm::SystemError as DrmError; +use failure::ResultExt; +use nix::libc::dev_t; + +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::ffi::CStr; +use std::os::unix::fs::MetadataExt; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::{Rc, Weak}; +use std::{fs, ptr}; + +mod surface; +pub use self::surface::EglStreamSurface; +use self::surface::EglStreamSurfaceInternal; + +pub mod egl; +#[cfg(feature = "backend_session")] +pub mod session; + +use crate::backend::egl::ffi::{self, egl::types::EGLDeviceEXT}; +use crate::backend::egl::{wrap_egl_call, EGLError as RawEGLError, Error as EglError}; +use crate::backend::graphics::SwapBuffersError; + +/// Errors thrown by the [`EglStreamDevice`](::backend::drm::eglstream::EglStreamDevice) +/// and [`EglStreamSurface`](::backend::drm::eglstream::EglStreamSurface). +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// `eglInitialize` returned an error + #[error("Failed to initialize EGL: {0:}")] + InitFailed(#[source] RawEGLError), + /// Failed to enumerate EGL_EXT_drm_device + #[error("Failed to enumerate EGL_EXT_drm_device: {0:}")] + FailedToEnumerateDevices(#[source] RawEGLError), + /// Device is not compatible with EGL_EXT_drm_device extension + #[error("Device is not compatible with EGL_EXT_drm_device extension")] + DeviceIsNoEGLStreamDevice, + /// Device has not suitable output layer + #[error("Device has no suitable output layer")] + DeviceNoOutputLayer, + /// Device was unable to create an EGLStream + #[error("EGLStream creation failed")] + DeviceStreamCreationFailed, + /// Underlying backend error + #[error("Underlying error: {0}")] + Underlying(#[source] U), + /// Buffer creation failed + #[error("Buffer creation failed")] + BufferCreationFailed(#[source] failure::Compat), + /// Buffer write failed + #[error("Buffer write failed")] + BufferWriteFailed(#[source] failure::Compat), + /// Stream flip failed + #[error("Stream flip failed ({0})")] + StreamFlipFailed(#[source] RawEGLError), +} + +/// Representation of an open egl stream device to create egl rendering surfaces +pub struct EglStreamDevice { + pub(self) dev: EGLDeviceEXT, + raw: D, + backends: Rc>>>>, + logger: ::slog::Logger, +} + +impl EglStreamDevice { + /// Try to create a new [`EglStreamDevice`] from an open device. + /// + /// Returns an error if the underlying device would not support the required EGL extensions. + pub fn new(mut raw: D, logger: L) -> Result::Surface as Surface>::Error>> + where + L: Into>, + { + let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_eglstream")); + + raw.clear_handler(); + + debug!(log, "Creating egl stream device"); + + ffi::make_sure_egl_is_loaded(); + + fn has_extensions(exts: &[String], check: &'static [&'static str]) -> Result<(), EglError> { + check + .iter() + .map(|ext| { + if exts.iter().any(|s| *s == *ext) { + Ok(()) + } else { + Err(EglError::EglExtensionNotSupported(check)) + } + }) + .collect() + } + + let device = unsafe { + // the first step is to query the list of extensions without any display, if supported + let dp_extensions = { + let p = wrap_egl_call(|| { + ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32) + }) + .map_err(Error::InitFailed)?; + + // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise + // `eglQueryString` returns an error + if p.is_null() { + vec![] + } else { + let p = CStr::from_ptr(p); + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + } + }; + debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions); + + // we need either EGL_EXT_device_base or EGL_EXT_device_enumeration &_query + if let Err(_err) = has_extensions(&dp_extensions, &["EGL_EXT_device_base"]) { + has_extensions( + &dp_extensions, + &["EGL_EXT_device_enumeration", "EGL_EXT_device_query"], + ) + .map_err(|_| Error::DeviceIsNoEGLStreamDevice)?; + } + + let mut num_devices = 0; + wrap_egl_call(|| ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut num_devices)) + .map_err(Error::FailedToEnumerateDevices)?; + if num_devices == 0 { + return Err(Error::DeviceIsNoEGLStreamDevice); + } + + let mut devices = Vec::with_capacity(num_devices as usize); + wrap_egl_call(|| ffi::egl::QueryDevicesEXT(num_devices, devices.as_mut_ptr(), &mut num_devices)) + .map_err(Error::FailedToEnumerateDevices)?; + devices.set_len(num_devices as usize); + debug!(log, "Devices: {:#?}, Count: {}", devices, num_devices); + + devices + .into_iter() + .find(|device| { + *device != ffi::egl::NO_DEVICE_EXT + && { + let device_extensions = { + let p = ffi::egl::QueryDeviceStringEXT(*device, ffi::egl::EXTENSIONS as i32); + if p.is_null() { + vec![] + } else { + let p = CStr::from_ptr(p); + let list = String::from_utf8(p.to_bytes().to_vec()) + .unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + } + }; + + device_extensions.iter().any(|s| *s == "EGL_EXT_device_drm") + } + && { + let path = { + let p = ffi::egl::QueryDeviceStringEXT( + *device, + ffi::egl::DRM_DEVICE_FILE_EXT as i32, + ); + if p.is_null() { + String::new() + } else { + let p = CStr::from_ptr(p); + String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()) + } + }; + + match fs::metadata(&path) { + Ok(metadata) => metadata.rdev() == raw.device_id(), + Err(_) => false, + } + } + }) + .ok_or(Error::DeviceIsNoEGLStreamDevice)? + }; + + Ok(EglStreamDevice { + dev: device, + raw, + backends: Rc::new(RefCell::new(HashMap::new())), + logger: log, + }) + } +} + +struct InternalDeviceHandler { + handler: Box> + 'static>, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl DeviceHandler for InternalDeviceHandler { + type Device = D; + + fn vblank(&mut self, crtc: crtc::Handle) { + if let Some(backends) = self.backends.upgrade() { + if let Some(surface) = backends.borrow().get(&crtc) { + if surface.upgrade().is_some() { + self.handler.vblank(crtc); + } + } else { + warn!( + self.logger, + "Surface ({:?}) not managed by egl stream, event not handled.", crtc + ); + } + } + } + fn error(&mut self, error: <::Surface as Surface>::Error) { + self.handler.error(Error::Underlying(error)) + } +} + +impl Device for EglStreamDevice { + type Surface = EglStreamSurface; + + fn device_id(&self) -> dev_t { + self.raw.device_id() + } + + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + self.raw.set_handler(InternalDeviceHandler { + handler: Box::new(handler), + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + }); + } + + fn clear_handler(&mut self) { + self.raw.clear_handler(); + } + + fn create_surface( + &mut self, + crtc: crtc::Handle, + mode: Mode, + connectors: &[connector::Handle], + ) -> Result, Error<<::Surface as Surface>::Error>> { + info!(self.logger, "Initializing EglStreamSurface"); + + let drm_surface = + Device::create_surface(&mut self.raw, crtc, mode, connectors).map_err(Error::Underlying)?; + + // initialize a buffer for the cursor image + let cursor = Cell::new(Some(( + self.raw + .create_dumb_buffer((1, 1), PixelFormat::ARGB8888) + .compat() + .map_err(Error::BufferCreationFailed)?, + (0, 0), + ))); + + let backend = Rc::new(EglStreamSurfaceInternal { + crtc: drm_surface, + cursor, + stream: RefCell::new(None), + commit_buffer: Cell::new(None), + logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))), + locked: std::sync::atomic::AtomicBool::new(false), + }); + self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); + Ok(EglStreamSurface(backend)) + } + + fn process_events(&mut self) { + self.raw.process_events() + } + + fn resource_handles(&self) -> Result::Surface as Surface>::Error>> { + Device::resource_handles(&self.raw).map_err(Error::Underlying) + } + + fn get_connector_info(&self, conn: connector::Handle) -> Result { + self.raw.get_connector_info(conn) + } + fn get_crtc_info(&self, crtc: crtc::Handle) -> Result { + self.raw.get_crtc_info(crtc) + } + fn get_encoder_info(&self, enc: encoder::Handle) -> Result { + self.raw.get_encoder_info(enc) + } + fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result { + self.raw.get_framebuffer_info(fb) + } + fn get_plane_info(&self, plane: plane::Handle) -> Result { + self.raw.get_plane_info(plane) + } +} + +impl AsRawFd for EglStreamDevice { + fn as_raw_fd(&self) -> RawFd { + self.raw.as_raw_fd() + } +} + +impl Drop for EglStreamDevice { + fn drop(&mut self) { + self.clear_handler(); + } +} + +impl Into for Error +where + E: std::error::Error + Into + 'static, +{ + fn into(self) -> SwapBuffersError { + match self { + Error::BufferCreationFailed(x) + if match x.get_ref() { + drm::SystemError::Unknown { + errno: nix::errno::Errno::EBUSY, + } => true, + drm::SystemError::Unknown { + errno: nix::errno::Errno::EINTR, + } => true, + _ => false, + } => + { + SwapBuffersError::TemporaryFailure(Box::new(Error::::BufferCreationFailed(x))) + } + Error::BufferWriteFailed(x) + if match x.get_ref() { + drm::SystemError::Unknown { + errno: nix::errno::Errno::EBUSY, + } => true, + drm::SystemError::Unknown { + errno: nix::errno::Errno::EINTR, + } => true, + _ => false, + } => + { + SwapBuffersError::TemporaryFailure(Box::new(Error::::BufferCreationFailed(x))) + } + Error::StreamFlipFailed(x @ RawEGLError::ResourceBusy) => { + SwapBuffersError::TemporaryFailure(Box::new(Error::::StreamFlipFailed(x))) + } + Error::Underlying(x) => x.into(), + x => SwapBuffersError::ContextLost(Box::new(x)), + } + } +} diff --git a/src/backend/drm/eglstream/session.rs b/src/backend/drm/eglstream/session.rs new file mode 100644 index 0000000..8b5d7fc --- /dev/null +++ b/src/backend/drm/eglstream/session.rs @@ -0,0 +1,91 @@ +//! +//! Support to register a [`EglStreamDevice`](EglStreamDevice) +//! to an open [`Session`](::backend::session::Session). +//! + +use super::{EglStreamDevice, EglStreamSurfaceInternal}; +use crate::backend::drm::{RawDevice, Surface}; +use crate::backend::egl::ffi; +use crate::backend::session::{AsSessionObserver, SessionObserver}; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::os::unix::io::RawFd; +use std::rc::{Rc, Weak}; + +use drm::control::{crtc, Device as ControlDevice}; + +/// [`SessionObserver`](SessionObserver) +/// linked to the [`EglStreamDevice`](EglStreamDevice) it was +/// created from. +pub struct EglStreamDeviceObserver< + O: SessionObserver + 'static, + D: RawDevice + AsSessionObserver + 'static, +> { + observer: O, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl + 'static> + AsSessionObserver> for EglStreamDevice +{ + fn observer(&mut self) -> EglStreamDeviceObserver { + EglStreamDeviceObserver { + observer: self.raw.observer(), + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + } + } +} + +impl + 'static> SessionObserver + for EglStreamDeviceObserver +{ + fn pause(&mut self, devnum: Option<(u32, u32)>) { + if let Some(backends) = self.backends.upgrade() { + for (_, backend) in backends.borrow().iter() { + if let Some(backend) = backend.upgrade() { + // destroy/disable the streams so it will not submit any pending frames + if let Some((display, stream)) = backend.stream.replace(None) { + unsafe { + ffi::egl::DestroyStreamKHR(display.handle, stream); + } + } + // framebuffers will be likely not valid anymore, lets just recreate those after activation. + if let Some((buffer, fb)) = backend.commit_buffer.take() { + let _ = backend.crtc.destroy_framebuffer(fb); + let _ = backend.crtc.destroy_dumb_buffer(buffer); + } + } + } + } + + self.observer.pause(devnum); + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + self.observer.activate(devnum); + if let Some(backends) = self.backends.upgrade() { + for (_, backend) in backends.borrow().iter() { + if let Some(backend) = backend.upgrade() { + if let Some((cursor, hotspot)) = backend.cursor.get() { + if backend + .crtc + .set_cursor2( + backend.crtc.crtc(), + Some(&cursor), + (hotspot.0 as i32, hotspot.1 as i32), + ) + .is_err() + { + if let Err(err) = backend.crtc.set_cursor(backend.crtc.crtc(), Some(&cursor)) { + warn!(self.logger, "Failed to reset cursor: {}", err) + } + } + } + } + } + } + } +} diff --git a/src/backend/drm/eglstream/surface.rs b/src/backend/drm/eglstream/surface.rs new file mode 100644 index 0000000..1423c05 --- /dev/null +++ b/src/backend/drm/eglstream/surface.rs @@ -0,0 +1,528 @@ +use super::super::{Device, RawDevice, RawSurface, Surface}; +use super::Error; + +use drm::buffer::format::PixelFormat; +use drm::control::{connector, crtc, dumbbuffer::DumbBuffer, framebuffer, Device as ControlDevice, Mode}; +#[cfg(feature = "backend_drm")] +use failure::ResultExt; +#[cfg(feature = "backend_drm")] +use image::{ImageBuffer, Rgba}; +use nix::libc::{c_int, c_void}; + +use std::cell::{Cell, RefCell}; +use std::ffi::CStr; +use std::ptr; +use std::rc::Rc; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +#[cfg(feature = "backend_drm")] +use crate::backend::drm::common::Error as DrmError; +#[cfg(feature = "backend_drm")] +use crate::backend::drm::DevPath; +use crate::backend::egl::ffi::{ + self, + egl::{self, types::EGLStreamKHR}, +}; +use crate::backend::egl::{display::EGLDisplayHandle, wrap_egl_call, EGLError, SwapBuffersError}; +#[cfg(feature = "backend_drm")] +use crate::backend::graphics::CursorBackend; + +pub(in crate::backend::drm) struct EglStreamSurfaceInternal { + pub(in crate::backend::drm) crtc: ::Surface, + pub(in crate::backend::drm) cursor: Cell>, + pub(in crate::backend::drm) stream: RefCell, EGLStreamKHR)>>, + pub(in crate::backend::drm) commit_buffer: Cell>, + pub(in crate::backend::drm) locked: AtomicBool, + pub(in crate::backend::drm) logger: ::slog::Logger, +} + +impl Surface for EglStreamSurfaceInternal { + type Connectors = <::Surface as Surface>::Connectors; + type Error = Error<<::Surface as Surface>::Error>; + + fn crtc(&self) -> crtc::Handle { + self.crtc.crtc() + } + + fn current_connectors(&self) -> Self::Connectors { + self.crtc.current_connectors() + } + + fn pending_connectors(&self) -> Self::Connectors { + self.crtc.pending_connectors() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { + self.crtc.add_connector(connector).map_err(Error::Underlying) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { + self.crtc.remove_connector(connector).map_err(Error::Underlying) + } + + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { + self.crtc.set_connectors(connectors).map_err(Error::Underlying) + } + + fn current_mode(&self) -> Mode { + self.crtc.current_mode() + } + + fn pending_mode(&self) -> Mode { + self.crtc.pending_mode() + } + + fn use_mode(&self, mode: Mode) -> Result<(), Error<<::Surface as Surface>::Error>> { + self.crtc.use_mode(mode).map_err(Error::Underlying) + } +} + +impl Drop for EglStreamSurfaceInternal { + fn drop(&mut self) { + if let Some((buffer, _)) = self.cursor.get() { + let _ = self.crtc.destroy_dumb_buffer(buffer); + } + if let Some((buffer, fb)) = self.commit_buffer.take() { + let _ = self.crtc.destroy_framebuffer(fb); + let _ = self.crtc.destroy_dumb_buffer(buffer); + } + if let Some((display, stream)) = self.stream.replace(None) { + unsafe { + egl::DestroyStreamKHR(display.handle, stream); + } + } + } +} + +// Conceptionally EglStream is a weird api. +// It does modesetting on its own, bypassing our `RawSurface::commit` function. +// As a result, we cannot easily sync any other planes to the commit without more +// experimental egl extensions to do this via the EglStream-API. +// +// So instead we leverage the fact, that all drm-drivers still support the legacy +// `drmModeSetCursor` and `drmModeMoveCursor` functions, that (mostly) implicitly sync to the primary plane. +// That way we can use hardware cursors at least on all drm-backends (including atomic), although +// this is a little hacky. Overlay planes however are completely out of question for now. +// +// Note that this might still appear a little chuppy, we should just use software cursors +// on eglstream devices by default and only use this, if the user really wants it. +#[cfg(feature = "backend_drm")] +impl CursorBackend for EglStreamSurfaceInternal { + type CursorFormat = ImageBuffer, Vec>; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { + trace!(self.logger, "Move the cursor to {},{}", x, y); + self.crtc + .move_cursor(self.crtc.crtc(), (x as i32, y as i32)) + .compat() + .map_err(|source| DrmError::Access { + errmsg: "Error moving cursor", + dev: self.crtc.dev_path(), + source, + }) + .map_err(Error::Underlying) + } + + fn set_cursor_representation( + &self, + buffer: &ImageBuffer, Vec>, + hotspot: (u32, u32), + ) -> Result<(), Self::Error> { + let (w, h) = buffer.dimensions(); + debug!(self.logger, "Importing cursor"); + + // import the cursor into a buffer we can render + let mut cursor = self + .crtc + .create_dumb_buffer((w, h), PixelFormat::ARGB8888) + .compat() + .map_err(Error::BufferCreationFailed)?; + + { + let mut mapping = self + .crtc + .map_dumb_buffer(&mut cursor) + .compat() + .map_err(Error::BufferWriteFailed)?; + mapping.as_mut().copy_from_slice(buffer); + } + + trace!(self.logger, "Setting the new imported cursor"); + + trace!(self.logger, "Setting the new imported cursor"); + + if self + .crtc + .set_cursor2( + self.crtc.crtc(), + Some(&cursor), + (hotspot.0 as i32, hotspot.1 as i32), + ) + .is_err() + { + self.crtc + .set_cursor(self.crtc.crtc(), Some(&cursor)) + .compat() + .map_err(|source| DrmError::Access { + errmsg: "Failed to set cursor", + dev: self.crtc.dev_path(), + source, + }) + .map_err(Error::Underlying)?; + } + + // and store it + if let Some((old, _)) = self.cursor.replace(Some((cursor, hotspot))) { + if self.crtc.destroy_dumb_buffer(old).is_err() { + warn!(self.logger, "Failed to free old cursor"); + } + } + + Ok(()) + } +} + +/// egl stream surface for rendering +pub struct EglStreamSurface( + pub(in crate::backend::drm) Rc>, +); + +impl EglStreamSurface { + /// Check if underlying gbm resources need to be recreated. + pub fn needs_recreation(&self) -> bool { + self.0.crtc.commit_pending() || self.0.stream.borrow().is_none() + } + + pub(super) fn create_stream( + &self, + display: &Arc, + output_attribs: &[isize], + ) -> Result::Surface as Surface>::Error>> { + // drop old steam, if it already exists + if let Some((display, stream)) = self.0.stream.replace(None) { + // ignore result + unsafe { + ffi::egl::DestroyStreamKHR(display.handle, stream); + } + } + + // because we are re-creating there might be a new mode. if there is? -> commit it + if self.0.crtc.commit_pending() { + let (w, h) = self.pending_mode().size(); + // but we need a buffer to commit... + // well lets create one and clean it up once the stream is running + if let Ok(buffer) = self + .0 + .crtc + .create_dumb_buffer((w as u32, h as u32), PixelFormat::ARGB8888) + { + if let Ok(fb) = self.0.crtc.add_framebuffer(&buffer) { + if let Some((buffer, fb)) = self.0.commit_buffer.replace(Some((buffer, fb))) { + let _ = self.0.crtc.destroy_framebuffer(fb); + let _ = self.0.crtc.destroy_dumb_buffer(buffer); + } + self.0.crtc.commit(fb).map_err(Error::Underlying)?; + } + } + } + + let extensions = { + let p = + unsafe { CStr::from_ptr(ffi::egl::QueryString(display.handle, ffi::egl::EXTENSIONS as i32)) }; + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + }; + + if !extensions.iter().any(|s| *s == "EGL_EXT_output_base") + || !extensions.iter().any(|s| *s == "EGL_EXT_output_drm") + || !extensions.iter().any(|s| *s == "EGL_KHR_stream") + || !extensions + .iter() + .any(|s| *s == "EGL_EXT_stream_consumer_egloutput") + || !extensions + .iter() + .any(|s| *s == "EGL_KHR_stream_producer_eglsurface") + { + error!(self.0.logger, "Extension for EGLStream surface creation missing"); + return Err(Error::DeviceIsNoEGLStreamDevice); + } + + if cfg!(debug_assertions) { + // TEST START + let mut num_layers = 0; + if unsafe { + ffi::egl::GetOutputLayersEXT( + display.handle, + ptr::null(), + ptr::null_mut(), + 10, + &mut num_layers, + ) + } == 0 + { + error!(self.0.logger, "Failed to get any! output layer"); + } + if num_layers == 0 { + error!(self.0.logger, "Failed to find any! output layer"); + } + let mut layers = Vec::with_capacity(num_layers as usize); + if unsafe { + ffi::egl::GetOutputLayersEXT( + display.handle, + ptr::null(), + layers.as_mut_ptr(), + num_layers, + &mut num_layers, + ) + } == 0 + { + error!(self.0.logger, "Failed to receive Output Layers"); + } + unsafe { + layers.set_len(num_layers as usize); + } + for layer in layers { + debug!(self.0.logger, "Found layer: {:?}", layer); + let mut val = 0; + if unsafe { + ffi::egl::QueryOutputLayerAttribEXT( + display.handle, + layer, + ffi::egl::DRM_CRTC_EXT as i32, + &mut val, + ) + } != 0 + { + info!(self.0.logger, "Possible crtc output layer: {}", val); + } + val = 0; + if unsafe { + ffi::egl::QueryOutputLayerAttribEXT( + display.handle, + layer, + ffi::egl::DRM_PLANE_EXT as i32, + &mut val, + ) + } != 0 + { + info!(self.0.logger, "Possible plane output layer: {}", val); + } + } + // TEST END + } + + let mut num_layers = 0; + if unsafe { + ffi::egl::GetOutputLayersEXT( + display.handle, + output_attribs.as_ptr(), + ptr::null_mut(), + 1, + &mut num_layers, + ) + } == 0 + { + error!( + self.0.logger, + "Failed to acquire Output Layer. Attributes {:?}", output_attribs + ); + return Err(Error::DeviceNoOutputLayer); + } + if num_layers == 0 { + error!(self.0.logger, "Failed to find Output Layer"); + return Err(Error::DeviceNoOutputLayer); + } + let mut layers = Vec::with_capacity(num_layers as usize); + if unsafe { + ffi::egl::GetOutputLayersEXT( + display.handle, + output_attribs.as_ptr(), + layers.as_mut_ptr(), + num_layers, + &mut num_layers, + ) + } == 0 + { + error!(self.0.logger, "Failed to get Output Layer"); + return Err(Error::DeviceNoOutputLayer); + } + unsafe { + layers.set_len(num_layers as usize); + } + + let layer = layers[0]; + unsafe { + ffi::egl::OutputLayerAttribEXT(display.handle, layer, ffi::egl::SWAP_INTERVAL_EXT as i32, 0); + } + + let stream_attributes = { + let mut out: Vec = Vec::with_capacity(7); + out.push(ffi::egl::STREAM_FIFO_LENGTH_KHR as i32); + out.push(0); + out.push(ffi::egl::CONSUMER_AUTO_ACQUIRE_EXT as i32); + out.push(ffi::egl::FALSE as i32); + out.push(ffi::egl::CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR as i32); + out.push(0); + out.push(ffi::egl::NONE as i32); + out + }; + + let stream = unsafe { ffi::egl::CreateStreamKHR(display.handle, stream_attributes.as_ptr()) }; + if stream == ffi::egl::NO_STREAM_KHR { + error!(self.0.logger, "Failed to create egl stream"); + return Err(Error::DeviceStreamCreationFailed); + } + + if unsafe { ffi::egl::StreamConsumerOutputEXT(display.handle, stream, layer) } == 0 { + error!(self.0.logger, "Failed to link Output Layer as Stream Consumer"); + return Err(Error::DeviceStreamCreationFailed); + } + + let _ = self.0.stream.replace(Some((display.clone(), stream))); + + Ok(stream) + } + + pub(super) fn create_surface( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + _surface_attribs: &[c_int], + output_attribs: &[isize], + ) -> Result<*const c_void, Error<<::Surface as Surface>::Error>> { + let stream = self.create_stream(display, output_attribs)?; + + let (w, h) = self.current_mode().size(); + info!(self.0.logger, "Creating stream surface with size: ({}:{})", w, h); + let surface_attributes = { + let mut out: Vec = Vec::with_capacity(5); + out.push(ffi::egl::WIDTH as i32); + out.push(w as i32); + out.push(ffi::egl::HEIGHT as i32); + out.push(h as i32); + out.push(ffi::egl::NONE as i32); + out + }; + + let surface = unsafe { + ffi::egl::CreateStreamProducerSurfaceKHR( + display.handle, + config_id, + stream, + surface_attributes.as_ptr(), + ) + }; + if surface == ffi::egl::NO_SURFACE { + error!(self.0.logger, "Failed to create surface: 0x{:X}", unsafe { + ffi::egl::GetError() + }); + } + Ok(surface) + } + + pub(super) fn flip( + &self, + crtc: crtc::Handle, + display: &Arc, + surface: ffi::egl::types::EGLSurface, + ) -> Result<(), SwapBuffersError::Surface as Surface>::Error>>> { + if self.0.locked.load(Ordering::SeqCst) { + let acquire_attributes = [ + ffi::egl::DRM_FLIP_EVENT_DATA_NV as isize, + Into::::into(crtc) as isize, + ffi::egl::NONE as isize, + ]; + + if let Ok(stream) = self.0.stream.try_borrow() { + let res = if let Some(&(ref display, ref stream)) = stream.as_ref() { + wrap_egl_call(|| unsafe { + ffi::egl::StreamConsumerAcquireAttribNV( + display.handle, + *stream, + acquire_attributes.as_ptr(), + ); + }) + .map_err(Error::StreamFlipFailed) + } else { + Err(Error::StreamFlipFailed(EGLError::NotInitialized)) + }; + if res.is_ok() { + self.0.locked.store(false, Ordering::SeqCst); + } else { + return res.map_err(SwapBuffersError::Underlying); + } + } + } + + if !self.0.locked.load(Ordering::SeqCst) { + wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) }) + .map_err(SwapBuffersError::EGLSwapBuffers)?; + self.0.locked.store(true, Ordering::SeqCst); + } + + Ok(()) + } +} + +impl Surface for EglStreamSurface { + type Connectors = <::Surface as Surface>::Connectors; + type Error = Error<<::Surface as Surface>::Error>; + + fn crtc(&self) -> crtc::Handle { + self.0.crtc() + } + + fn current_connectors(&self) -> Self::Connectors { + self.0.current_connectors() + } + + fn pending_connectors(&self) -> Self::Connectors { + self.0.pending_connectors() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { + self.0.add_connector(connector) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { + self.0.remove_connector(connector) + } + + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { + self.0.set_connectors(connectors) + } + + fn current_mode(&self) -> Mode { + self.0.current_mode() + } + + fn pending_mode(&self) -> Mode { + self.0.pending_mode() + } + + fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> { + self.0.use_mode(mode) + } +} + +#[cfg(feature = "backend_drm_legacy")] +impl CursorBackend for EglStreamSurface { + type CursorFormat = ImageBuffer, Vec>; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { + self.0.set_cursor_position(x, y) + } + + fn set_cursor_representation( + &self, + buffer: &ImageBuffer, Vec>, + hotspot: (u32, u32), + ) -> Result<(), Self::Error> { + self.0.set_cursor_representation(buffer, hotspot) + } +} diff --git a/src/backend/drm/gbm/egl.rs b/src/backend/drm/gbm/egl.rs index 556230c..6e8d830 100644 --- a/src/backend/drm/gbm/egl.rs +++ b/src/backend/drm/gbm/egl.rs @@ -5,16 +5,19 @@ //! use crate::backend::drm::{Device, RawDevice, Surface}; -use crate::backend::egl::ffi; use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; -use crate::backend::egl::{wrap_egl_call, EGLError, Error as EglBackendError}; +use crate::backend::egl::{display::EGLDisplayHandle, ffi}; +use crate::backend::egl::{ + wrap_egl_call, EGLError, Error as EglBackendError, SurfaceCreationError, SwapBuffersError, +}; use super::{Error, GbmDevice, GbmSurface}; use drm::control::{connector, crtc, Device as ControlDevice, Mode}; use gbm::AsRaw; +use nix::libc::{c_int, c_void}; use std::marker::PhantomData; -use std::ptr; +use std::sync::Arc; /// Egl Gbm backend type /// @@ -25,9 +28,11 @@ pub struct Gbm { impl Backend for Gbm { type Surface = GbmSurface; + type Error = Error<<::Surface as Surface>::Error>; unsafe fn get_display( display: ffi::NativeDisplayType, + attribs: &[ffi::EGLint], has_dp_extension: F, log: ::slog::Logger, ) -> Result @@ -36,29 +41,33 @@ impl Backend for Gbm { { if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); + let attribs = attribs.iter().map(|x| *x as isize).collect::>(); wrap_egl_call(|| { - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, attribs.as_ptr()) }) } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); wrap_egl_call(|| { - ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + ffi::egl::GetPlatformDisplayEXT( + ffi::egl::PLATFORM_GBM_MESA, + display as *mut _, + attribs.as_ptr(), + ) }) } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + let attribs = attribs.iter().map(|x| *x as isize).collect::>(); wrap_egl_call(|| { - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, attribs.as_ptr()) }) } else { - trace!(log, "Default EGL Display Initialization via GetDisplay"); - wrap_egl_call(|| ffi::egl::GetDisplay(display as *mut _)) + Ok(ffi::egl::NO_DISPLAY) } } } unsafe impl NativeDisplay> for GbmDevice { type Arguments = (crtc::Handle, Mode, Vec); - type Error = Error<<::Surface as Surface>::Error>; fn is_backend(&self) -> bool { true @@ -68,29 +77,49 @@ unsafe impl NativeDisplay> for Gb Ok(self.dev.borrow().as_raw() as *const _) } - fn create_surface(&mut self, args: Self::Arguments) -> Result, Self::Error> { + fn create_surface( + &mut self, + args: Self::Arguments, + ) -> Result, Error<<::Surface as Surface>::Error>> { Device::create_surface(self, args.0, args.1, &args.2) } } unsafe impl NativeSurface for GbmSurface { - type Error = Error<<::Surface as Surface>::Error>; + type Error = Error<<::Surface as Surface>::Error>; - fn ptr(&self) -> ffi::NativeWindowType { - self.0.surface.borrow().as_raw() as *const _ + unsafe fn create( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + surface_attributes: &[c_int], + ) -> Result<*const c_void, SurfaceCreationError> { + GbmSurface::recreate(self).map_err(SurfaceCreationError::NativeSurfaceCreationFailed)?; + + wrap_egl_call(|| { + ffi::egl::CreateWindowSurface( + display.handle, + config_id, + self.0.surface.borrow().as_raw() as *const _, + surface_attributes.as_ptr(), + ) + }) + .map_err(SurfaceCreationError::EGLSurfaceCreationFailed) } fn needs_recreation(&self) -> bool { self.needs_recreation() } - fn recreate(&self) -> Result<(), Self::Error> { - GbmSurface::recreate(self) - } - - fn swap_buffers(&self) -> Result<(), Self::Error> { + fn swap_buffers( + &self, + display: &Arc, + surface: ffi::egl::types::EGLSurface, + ) -> Result<(), SwapBuffersError> { + wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) }) + .map_err(SwapBuffersError::EGLSwapBuffers)?; // this is safe since `eglSwapBuffers` will have been called exactly once // if this is used by our egl module, which is why this trait is unsafe. - unsafe { self.page_flip() } + unsafe { self.page_flip() }.map_err(SwapBuffersError::Underlying) } } diff --git a/src/backend/drm/legacy/surface.rs b/src/backend/drm/legacy/surface.rs index be2f6f9..035a88c 100644 --- a/src/backend/drm/legacy/surface.rs +++ b/src/backend/drm/legacy/surface.rs @@ -23,9 +23,9 @@ pub struct State { pub connectors: HashSet, } -pub(super) struct LegacyDrmSurfaceInternal { +pub(in crate::backend::drm) struct LegacyDrmSurfaceInternal { pub(super) dev: Rc>, - pub(super) crtc: crtc::Handle, + pub(in crate::backend::drm) crtc: crtc::Handle, pub(super) state: RwLock, pub(super) pending: RwLock, pub(super) logger: ::slog::Logger, @@ -454,7 +454,9 @@ impl Drop for LegacyDrmSurfaceInternal { } /// Open raw crtc utilizing legacy mode-setting -pub struct LegacyDrmSurface(pub(super) Rc>); +pub struct LegacyDrmSurface( + pub(in crate::backend::drm) Rc>, +); impl AsRawFd for LegacyDrmSurface { fn as_raw_fd(&self) -> RawFd { diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index eacdb9e..e370554 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -54,6 +54,8 @@ pub mod atomic; pub mod common; #[cfg(feature = "backend_drm_egl")] pub mod egl; +#[cfg(feature = "backend_drm_eglstream")] +pub mod eglstream; #[cfg(feature = "backend_drm_gbm")] pub mod gbm; #[cfg(feature = "backend_drm_legacy")] diff --git a/src/backend/egl/display.rs b/src/backend/egl/display.rs index 4c8462c..03a9b55 100644 --- a/src/backend/egl/display.rs +++ b/src/backend/egl/display.rs @@ -8,9 +8,7 @@ use crate::backend::egl::{ }; use std::sync::Arc; -use std::ptr; - -use nix::libc::{c_int, c_void}; +use nix::libc::c_int; #[cfg(feature = "wayland_frontend")] use wayland_server::{protocol::wl_buffer::WlBuffer, Display}; @@ -22,7 +20,7 @@ use crate::backend::egl::context::{GlAttributes, PixelFormatRequirements}; use crate::backend::graphics::gl::ffi as gl_ffi; use crate::backend::graphics::PixelFormat; use std::cell::{Ref, RefCell, RefMut}; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::marker::PhantomData; use std::mem::MaybeUninit; @@ -30,8 +28,9 @@ use std::ops::Deref; /// Wrapper around [`ffi::EGLDisplay`](ffi::egl::types::EGLDisplay) to ensure display is only destroyed /// once all resources bound to it have been dropped. -pub(crate) struct EGLDisplayHandle { - handle: ffi::egl::types::EGLDisplay, +pub struct EGLDisplayHandle { + /// ffi EGLDisplay ptr + pub handle: ffi::egl::types::EGLDisplay, } impl Deref for EGLDisplayHandle { @@ -69,29 +68,9 @@ impl> EGLDisplay { { let log = crate::slog_or_stdlog(logger.into()).new(o!("smithay_module" => "renderer_egl")); let ptr = native.ptr()?; + let egl_attribs = native.attributes(); - ffi::egl::LOAD.call_once(|| unsafe { - fn constrain(f: F) -> F - where - F: for<'a> Fn(&'a str) -> *const ::std::os::raw::c_void, - { - f - }; - - ffi::egl::load_with(|sym| { - let name = CString::new(sym).unwrap(); - let symbol = ffi::egl::LIB.get::<*mut c_void>(name.as_bytes()); - match symbol { - Ok(x) => *x as *const _, - Err(_) => ptr::null(), - } - }); - let proc_address = constrain(|sym| get_proc_address(sym)); - ffi::egl::load_with(&proc_address); - ffi::egl::BindWaylandDisplayWL::load_with(&proc_address); - ffi::egl::UnbindWaylandDisplayWL::load_with(&proc_address); - ffi::egl::QueryWaylandBufferWL::load_with(&proc_address); - }); + ffi::make_sure_egl_is_loaded(); // the first step is to query the list of extensions without any display, if supported let dp_extensions = unsafe { @@ -112,9 +91,17 @@ impl> EGLDisplay { debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions); let display = unsafe { - B::get_display(ptr, |e: &str| dp_extensions.iter().any(|s| s == e), log.clone()) - .map_err(Error::DisplayNotSupported)? + B::get_display( + ptr, + &egl_attribs, + |e: &str| dp_extensions.iter().any(|s| s == e), + log.clone(), + ) + .map_err(Error::DisplayCreationError)? }; + if display == ffi::egl::NO_DISPLAY { + return Err(Error::DisplayNotSupported); + } let egl_version = { let mut major: MaybeUninit = MaybeUninit::uninit(); @@ -174,6 +161,7 @@ impl> EGLDisplay { ) -> Result<(PixelFormat, ffi::egl::types::EGLConfig), Error> { let descriptor = { let mut out: Vec = Vec::with_capacity(37); + let surface_type = self.native.borrow().surface_type(); if self.egl_version >= (1, 2) { trace!(self.logger, "Setting COLOR_BUFFER_TYPE to RGB_BUFFER"); @@ -181,12 +169,12 @@ impl> EGLDisplay { out.push(ffi::egl::RGB_BUFFER as c_int); } - trace!(self.logger, "Setting SURFACE_TYPE to WINDOW"); + trace!(self.logger, "Setting SURFACE_TYPE to {}", surface_type); out.push(ffi::egl::SURFACE_TYPE as c_int); // TODO: Some versions of Mesa report a BAD_ATTRIBUTE error // if we ask for PBUFFER_BIT as well as WINDOW_BIT - out.push((ffi::egl::WINDOW_BIT) as c_int); + out.push(surface_type); match attributes.version { Some((3, _)) => { @@ -265,11 +253,17 @@ impl> EGLDisplay { config_ids.set_len(num_configs as usize); } - // TODO: Deeper swap intervals might have some uses + if config_ids.is_empty() { + return Err(Error::NoAvailablePixelFormat); + } + let desired_swap_interval = if attributes.vsync { 1 } else { 0 }; - let config_ids = config_ids - .into_iter() + // try to select a config with the desired_swap_interval + // (but don't fail, as the margin might be very small on some cards and most configs are fine) + let config_id = config_ids + .iter() + .copied() .map(|config| unsafe { let mut min_swap_interval = 0; wrap_egl_call(|| { @@ -305,14 +299,8 @@ impl> EGLDisplay { .map_err(Error::ConfigFailed)? .into_iter() .flatten() - .collect::>(); - - if config_ids.is_empty() { - return Err(Error::NoAvailablePixelFormat); - } - - // TODO: Improve config selection - let config_id = config_ids[0]; + .next() + .unwrap_or_else(|| config_ids[0]); // analyzing each config macro_rules! attrib { @@ -371,7 +359,7 @@ impl> EGLDisplay { double_buffer: Option, config: ffi::egl::types::EGLConfig, args: N::Arguments, - ) -> Result, SurfaceCreationError> { + ) -> Result, SurfaceCreationError> { trace!(self.logger, "Creating EGL window surface."); let surface = self .native @@ -391,7 +379,6 @@ impl> EGLDisplay { debug!(self.logger, "EGL surface successfully created"); x }) - .map_err(SurfaceCreationError::EGLSurfaceCreationFailed) } /// Returns the runtime egl version of this display diff --git a/src/backend/egl/error.rs b/src/backend/egl/error.rs index cc4c24d..6690e8f 100644 --- a/src/backend/egl/error.rs +++ b/src/backend/egl/error.rs @@ -15,9 +15,12 @@ pub enum Error { /// Backend does not match the context type #[error("The expected backend '{0:?}' does not match the runtime")] NonMatchingBackend(&'static str), + /// Display creation failed + #[error("Display creation failed with error: {0:}")] + DisplayCreationError(#[source] EGLError), /// Unable to obtain a valid EGL Display - #[error("Unable to obtain a valid EGL Display. Err: {0:}")] - DisplayNotSupported(#[source] EGLError), + #[error("Unable to obtain a valid EGL Display.")] + DisplayNotSupported, /// `eglInitialize` returned an error #[error("Failed to initialize EGL. Err: {0:}")] InitFailed(#[source] EGLError), @@ -88,6 +91,10 @@ 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, /// A power management event has occurred. The application must destroy all contexts and reinitialise OpenGL ES state and objects to continue rendering. #[error("A power management event has occurred. The application must destroy all contexts and reinitialise OpenGL ES state and objects to continue rendering.")] ContextLost, @@ -111,6 +118,8 @@ impl From for EGLError { 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 042ef9f..3918d8f 100644 --- a/src/backend/egl/ffi.rs +++ b/src/backend/egl/ffi.rs @@ -13,6 +13,35 @@ pub type NativeDisplayType = *const c_void; pub type NativePixmapType = *const c_void; pub type NativeWindowType = *const c_void; +pub fn make_sure_egl_is_loaded() { + use std::{ffi::CString, ptr}; + + egl::LOAD.call_once(|| unsafe { + fn constrain(f: F) -> F + where + F: for<'a> Fn(&'a str) -> *const ::std::os::raw::c_void, + { + f + }; + + egl::load_with(|sym| { + let name = CString::new(sym).unwrap(); + let symbol = egl::LIB.get::<*mut c_void>(name.as_bytes()); + match symbol { + Ok(x) => *x as *const _, + Err(_) => ptr::null(), + } + }); + let proc_address = constrain(|sym| super::get_proc_address(sym)); + egl::load_with(&proc_address); + egl::BindWaylandDisplayWL::load_with(&proc_address); + egl::UnbindWaylandDisplayWL::load_with(&proc_address); + egl::QueryWaylandBufferWL::load_with(&proc_address); + #[cfg(feature = "backend_drm_eglstream")] + egl::StreamConsumerAcquireAttribNV::load_with(&proc_address); + }); +} + #[allow(clippy::all, rust_2018_idioms)] pub mod egl { use super::*; @@ -169,4 +198,66 @@ pub mod egl { // Accepted in the parameter of eglQueryWaylandBufferWL: pub const EGL_TEXTURE_FORMAT: i32 = 0x3080; pub const WAYLAND_Y_INVERTED_WL: i32 = 0x31DB; + + /// nVidia support needs some implemented but only proposed egl extensions... + /// Therefor gl_generator cannot generate them and we need some constants... + /// And a function... + #[cfg(feature = "backend_drm_eglstream")] + pub const CONSUMER_AUTO_ACQUIRE_EXT: i32 = 0x332B; + #[cfg(feature = "backend_drm_eglstream")] + pub const DRM_FLIP_EVENT_DATA_NV: i32 = 0x333E; + #[cfg(feature = "backend_drm_eglstream")] + pub const CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR: i32 = 0x321E; + #[cfg(feature = "backend_drm_eglstream")] + pub const RESOURCE_BUSY_EXT: u32 = 0x3353; + + #[cfg(feature = "backend_drm_eglstream")] + #[allow(non_snake_case, unused_variables, dead_code)] + #[inline] + pub unsafe fn StreamConsumerAcquireAttribNV( + dpy: types::EGLDisplay, + stream: types::EGLStreamKHR, + attrib_list: *const types::EGLAttrib, + ) -> types::EGLBoolean { + __gl_imports::mem::transmute::< + _, + extern "system" fn( + types::EGLDisplay, + types::EGLStreamKHR, + *const types::EGLAttrib, + ) -> types::EGLBoolean, + >(nvidia_storage::StreamConsumerAcquireAttribNV.f)(dpy, stream, attrib_list) + } + + #[cfg(feature = "backend_drm_eglstream")] + mod nvidia_storage { + use super::{FnPtr, __gl_imports::raw}; + pub static mut StreamConsumerAcquireAttribNV: FnPtr = FnPtr { + f: super::missing_fn_panic as *const raw::c_void, + is_loaded: false, + }; + } + + #[cfg(feature = "backend_drm_eglstream")] + #[allow(non_snake_case)] + pub mod StreamConsumerAcquireAttribNV { + use super::{FnPtr, __gl_imports::raw, metaloadfn, nvidia_storage}; + + #[inline] + #[allow(dead_code)] + pub fn is_loaded() -> bool { + unsafe { nvidia_storage::StreamConsumerAcquireAttribNV.is_loaded } + } + + #[allow(dead_code)] + pub fn load_with(mut loadfn: F) + where + F: FnMut(&str) -> *const raw::c_void, + { + unsafe { + nvidia_storage::StreamConsumerAcquireAttribNV = + FnPtr::new(metaloadfn(&mut loadfn, "eglStreamConsumerAcquireAttribNV", &[])) + } + } + } } diff --git a/src/backend/egl/native.rs b/src/backend/egl/native.rs index 7c4bd5d..b878cf3 100644 --- a/src/backend/egl/native.rs +++ b/src/backend/egl/native.rs @@ -1,9 +1,10 @@ //! Type safe native types for safe context/surface creation -use super::{ffi, wrap_egl_call, EGLError, Error}; - -#[cfg(feature = "backend_winit")] -use std::ptr; +use super::{ + display::EGLDisplayHandle, ffi, wrap_egl_call, EGLError, Error, SurfaceCreationError, SwapBuffersError, +}; +use nix::libc::{c_int, c_void}; +use std::sync::Arc; #[cfg(feature = "backend_winit")] use wayland_egl as wegl; @@ -15,7 +16,9 @@ use winit::window::Window as WinitWindow; /// Trait for typed backend variants (X11/Wayland/GBM) pub trait Backend { /// Surface type created by this backend - type Surface: NativeSurface; + type Surface: NativeSurface; + /// Error type thrown by the surface creation in case of failure. + type Error: ::std::error::Error + Send + 'static; /// Return an [`EGLDisplay`](ffi::egl::types::EGLDisplay) based on this backend /// @@ -25,6 +28,7 @@ pub trait Backend { /// but there is no way to test that. unsafe fn get_display bool>( display: ffi::NativeDisplayType, + attribs: &[ffi::EGLint], has_dp_extension: F, log: ::slog::Logger, ) -> Result; @@ -36,9 +40,11 @@ pub enum Wayland {} #[cfg(feature = "backend_winit")] impl Backend for Wayland { type Surface = wegl::WlEglSurface; + type Error = Error; unsafe fn get_display( display: ffi::NativeDisplayType, + attribs: &[ffi::EGLint], has_dp_extension: F, log: ::slog::Logger, ) -> Result @@ -47,8 +53,13 @@ impl Backend for Wayland { { if has_dp_extension("EGL_KHR_platform_wayland") && ffi::egl::GetPlatformDisplay::is_loaded() { trace!(log, "EGL Display Initialization via EGL_KHR_platform_wayland"); + let attribs = attribs.iter().map(|x| *x as isize).collect::>(); wrap_egl_call(|| { - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_WAYLAND_KHR, display as *mut _, ptr::null()) + ffi::egl::GetPlatformDisplay( + ffi::egl::PLATFORM_WAYLAND_KHR, + display as *mut _, + attribs.as_ptr(), + ) }) } else if has_dp_extension("EGL_EXT_platform_wayland") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { @@ -57,7 +68,7 @@ impl Backend for Wayland { ffi::egl::GetPlatformDisplayEXT( ffi::egl::PLATFORM_WAYLAND_EXT, display as *mut _, - ptr::null(), + attribs.as_ptr(), ) }) } else { @@ -76,9 +87,11 @@ pub enum X11 {} #[cfg(feature = "backend_winit")] impl Backend for X11 { type Surface = XlibWindow; + type Error = Error; unsafe fn get_display( display: ffi::NativeDisplayType, + attribs: &[ffi::EGLint], has_dp_extension: F, log: ::slog::Logger, ) -> Result @@ -87,13 +100,18 @@ impl Backend for X11 { { if has_dp_extension("EGL_KHR_platform_x11") && ffi::egl::GetPlatformDisplay::is_loaded() { trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11"); + let attribs = attribs.iter().map(|x| *x as isize).collect::>(); wrap_egl_call(|| { - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null()) + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, attribs.as_ptr()) }) } else if has_dp_extension("EGL_EXT_platform_x11") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11"); wrap_egl_call(|| { - ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null()) + ffi::egl::GetPlatformDisplayEXT( + ffi::egl::PLATFORM_X11_EXT, + display as *mut _, + attribs.as_ptr(), + ) }) } else { trace!(log, "Default EGL Display Initialization via GetDisplay"); @@ -110,21 +128,28 @@ impl Backend for X11 { pub unsafe trait NativeDisplay { /// Arguments used to surface creation. type Arguments; - /// Error type thrown by the surface creation in case of failure. - type Error: ::std::error::Error + Send + 'static; /// Because one type might implement multiple [`Backend`]s this function must be called to check /// if the expected [`Backend`] is used at runtime. fn is_backend(&self) -> bool; /// Return a raw pointer EGL will accept for context creation. fn ptr(&self) -> Result; + /// Return attributes that might be used by `B::get_display` + /// + /// Default implementation returns an empty list + fn attributes(&self) -> Vec { + vec![ffi::egl::NONE as ffi::EGLint] + } + /// Type of surfaces created + fn surface_type(&self) -> ffi::EGLint { + ffi::egl::WINDOW_BIT as ffi::EGLint + } /// Create a surface - fn create_surface(&mut self, args: Self::Arguments) -> Result; + fn create_surface(&mut self, args: Self::Arguments) -> Result; } #[cfg(feature = "backend_winit")] unsafe impl NativeDisplay for WinitWindow { type Arguments = (); - type Error = Error; fn is_backend(&self) -> bool { self.xlib_display().is_some() @@ -146,7 +171,6 @@ unsafe impl NativeDisplay for WinitWindow { #[cfg(feature = "backend_winit")] unsafe impl NativeDisplay for WinitWindow { type Arguments = (); - type Error = Error; fn is_backend(&self) -> bool { self.wayland_display().is_some() @@ -177,67 +201,88 @@ unsafe impl NativeDisplay for WinitWindow { /// The returned [`NativeWindowType`](ffi::NativeWindowType) must be valid for EGL /// and there is no way to test that. pub unsafe trait NativeSurface { - /// Error of the underlying surface - type Error: std::error::Error; - - /// Return a raw pointer egl will accept for surface creation. - fn ptr(&self) -> ffi::NativeWindowType; + /// Error type thrown by the surface creation in case of failure. + type Error: ::std::error::Error + Send + 'static; + /// Create an EGLSurface from the internal native type. + /// + /// Must be able to deal with re-creation of existing resources, + /// if `needs_recreation` can return `true`. + /// + /// # Safety + /// This is usually an unsafe operation returning a raw pointer. + unsafe fn create( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + surface_attributes: &[c_int], + ) -> Result<*const c_void, SurfaceCreationError>; /// Will be called to check if any internal resources will need - /// to be recreated. Old resources must be used until `recreate` - /// was called. + /// to be recreated. Old resources must be used until `create` + /// was called again and a new surface was optained. /// - /// Only needs to be recreated, if this shall sometimes return true. + /// Only needs to be recreated, if this may return true. /// The default implementation always returns false. fn needs_recreation(&self) -> bool { false } - /// Instructs the surface to recreate internal resources - /// - /// Must only be implemented if `needs_recreation` can return `true`. - /// Returns true on success. - /// If this call was successful `ptr()` *should* return something different. - fn recreate(&self) -> Result<(), Self::Error> { - Ok(()) - } - /// Adds additional semantics when calling /// [EGLSurface::swap_buffers](::backend::egl::surface::EGLSurface::swap_buffers) /// /// Only implement if required by the backend. - fn swap_buffers(&self) -> Result<(), Self::Error> { - Ok(()) + fn swap_buffers( + &self, + display: &Arc, + surface: ffi::egl::types::EGLSurface, + ) -> Result<(), SwapBuffersError> { + wrap_egl_call(|| unsafe { + ffi::egl::SwapBuffers(***display, surface as *const _); + }) + .map_err(SwapBuffersError::EGLSwapBuffers) } } -/// Hack until ! gets stablized -#[derive(Debug)] -pub enum Never {} -impl std::fmt::Display for Never { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - unreachable!() - } -} -impl std::error::Error for Never {} - #[cfg(feature = "backend_winit")] unsafe impl NativeSurface for XlibWindow { - // this would really be a case for this: - // type Error = !; (https://github.com/rust-lang/rust/issues/35121) - type Error = Never; + type Error = Error; - fn ptr(&self) -> ffi::NativeWindowType { - self.0 as *const _ + unsafe fn create( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + surface_attributes: &[c_int], + ) -> Result<*const c_void, SurfaceCreationError> { + wrap_egl_call(|| { + ffi::egl::CreateWindowSurface( + display.handle, + config_id, + self.0 as *const _, + surface_attributes.as_ptr(), + ) + }) + .map_err(SurfaceCreationError::EGLSurfaceCreationFailed) } } #[cfg(feature = "backend_winit")] unsafe impl NativeSurface for wegl::WlEglSurface { - // type Error = !; - type Error = Never; + type Error = Error; - fn ptr(&self) -> ffi::NativeWindowType { - self.ptr() as *const _ + unsafe fn create( + &self, + display: &Arc, + config_id: ffi::egl::types::EGLConfig, + surface_attributes: &[c_int], + ) -> Result<*const c_void, SurfaceCreationError> { + wrap_egl_call(|| { + ffi::egl::CreateWindowSurface( + display.handle, + config_id, + self.ptr() as *const _, + surface_attributes.as_ptr(), + ) + }) + .map_err(SurfaceCreationError::EGLSurfaceCreationFailed) } } diff --git a/src/backend/egl/surface.rs b/src/backend/egl/surface.rs index 3ba5f32..00d1d42 100644 --- a/src/backend/egl/surface.rs +++ b/src/backend/egl/surface.rs @@ -1,6 +1,6 @@ //! EGL surface related structs -use super::{ffi, native, wrap_egl_call, EGLError, SwapBuffersError}; +use super::{ffi, native, EGLError, SurfaceCreationError, SwapBuffersError}; use crate::backend::egl::display::EGLDisplayHandle; use crate::backend::graphics::PixelFormat; use nix::libc::c_int; @@ -42,7 +42,7 @@ impl EGLSurface { config: ffi::egl::types::EGLConfig, native: N, log: L, - ) -> Result, EGLError> + ) -> Result, SurfaceCreationError> where L: Into>, { @@ -69,12 +69,12 @@ impl EGLSurface { out }; - let surface = wrap_egl_call(|| unsafe { - ffi::egl::CreateWindowSurface(**display, config, native.ptr(), surface_attributes.as_ptr()) - })?; + let surface = unsafe { native.create(&display, config, &surface_attributes)? }; if surface == ffi::egl::NO_SURFACE { - return Err(EGLError::BadSurface); + return Err(SurfaceCreationError::EGLSurfaceCreationFailed( + EGLError::BadSurface, + )); } Ok(EGLSurface { @@ -93,9 +93,7 @@ impl EGLSurface { let surface = self.surface.get(); let result = if !surface.is_null() { - wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(**self.display, surface as *const _) }) - .map_err(SwapBuffersError::EGLSwapBuffers) - .and_then(|_| self.native.swap_buffers().map_err(SwapBuffersError::Underlying)) + self.native.swap_buffers(&self.display, surface) } else { Err(SwapBuffersError::EGLSwapBuffers(EGLError::BadSurface)) }; @@ -108,20 +106,20 @@ impl EGLSurface { }; if self.native.needs_recreation() || surface.is_null() || is_bad_surface { - self.native.recreate().map_err(SwapBuffersError::Underlying)?; if !surface.is_null() { let _ = unsafe { ffi::egl::DestroySurface(**self.display, surface as *const _) }; } self.surface.set(unsafe { - wrap_egl_call(|| { - ffi::egl::CreateWindowSurface( - **self.display, - self.config_id, - self.native.ptr(), - self.surface_attributes.as_ptr(), - ) - }) - .map_err(SwapBuffersError::EGLCreateWindowSurface)? + self.native + .create(&self.display, self.config_id, &self.surface_attributes) + .map_err(|err| match err { + SurfaceCreationError::EGLSurfaceCreationFailed(err) => { + SwapBuffersError::EGLCreateWindowSurface(err) + } + SurfaceCreationError::NativeSurfaceCreationFailed(err) => { + SwapBuffersError::Underlying(err) + } + })? }); result.map_err(|err| { diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index 8848163..3b5308d 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -32,7 +32,7 @@ pub enum SwapBuffersError { /// /// Operations will have no effect. Functions that read textures, buffers, etc. /// will return uninitialized data instead. - #[error("The context has been lost, it needs to be recreated")] + #[error("The context has been lost, it needs to be recreated: {0}")] ContextLost(Box), /// A temporary condition caused to rendering to fail. /// @@ -43,6 +43,6 @@ pub enum SwapBuffersError { /// Proceed after investigating the source to reschedule another full rendering step or just this page_flip at a later time. /// If the root cause cannot be discovered and subsequent renderings also fail, it is advised to fallback to /// recreation. - #[error("A temporary condition caused the page flip to fail.")] + #[error("A temporary condition caused the page flip to fail: {0}")] TemporaryFailure(Box), } diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 75d4b53..7615a86 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -256,3 +256,24 @@ pub fn all_gpus>(seat: S) -> IoResult> { .flat_map(|device| device.devnode().map(PathBuf::from)) .collect()) } + +/// Returns the loaded driver for a device named by it's [`dev_t`](::nix::sys::stat::dev_t). +pub fn driver(dev: dev_t) -> IoResult> { + let mut enumerator = Enumerator::new()?; + enumerator.match_subsystem("drm")?; + enumerator.match_sysname("card[0-9]*")?; + Ok(enumerator + .scan_devices()? + .filter(|device| device.devnum() == Some(dev)) + .flat_map(|dev| { + let mut device = Some(dev); + while let Some(dev) = device { + if dev.driver().is_some() { + return dev.driver().map(std::ffi::OsStr::to_os_string); + } + device = dev.parent(); + } + None + }) + .next()) +}