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/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/surface.rs b/src/backend/drm/atomic/surface.rs index 4dd2710..481e3d7 100644 --- a/src/backend/drm/atomic/surface.rs +++ b/src/backend/drm/atomic/surface.rs @@ -40,7 +40,7 @@ pub struct Planes { 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(in crate::backend::drm) planes: Planes, pub(super) state: RwLock, 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/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/error.rs b/src/backend/egl/error.rs index 938fc19..6690e8f 100644 --- a/src/backend/egl/error.rs +++ b/src/backend/egl/error.rs @@ -91,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, @@ -114,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 ad1e7e8..3918d8f 100644 --- a/src/backend/egl/ffi.rs +++ b/src/backend/egl/ffi.rs @@ -37,6 +37,8 @@ pub fn make_sure_egl_is_loaded() { 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); }); } @@ -196,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", &[])) + } + } + } }